Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 105 additions & 8 deletions package/src/lib/ObjectAssertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
actual: this.actual,
message: `Expected the object to contain the provided key <${String(key)}>`,
});

const invertedError = new AssertionError({
actual: this.actual,
message: `Expected the object NOT to contain the provided key <${String(key)}>`,
});

return this.execute({
assertWhen: this.hasOwnProp(key),
error,
Expand All @@ -97,11 +97,11 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
expected: keys,
message: `Expected the object to contain all the provided keys <${prettify(keys)}>`,
});

const invertedError = new AssertionError({
actual: Object.keys(this.actual),
message: `Expected the object NOT to contain all the provided keys <${prettify(keys)}>`,
});

return this.execute({
assertWhen: keys.every(key => this.hasOwnProp(key)),
error,
Expand All @@ -126,18 +126,50 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
expected: keys,
message: `Expected the object to contain at least one of the provided keys <${prettify(keys)}>`,
});

const invertedError = new AssertionError({
actual: Object.keys(this.actual),
message: `Expected the object NOT to contain any of the provided keys <${prettify(keys)}>`,
});

return this.execute({
assertWhen: keys.some(key => this.hasOwnProp(key)),
error,
invertedError,
});
}

/**
* Check if the object has exactly the provided keys.
*
* @example
* ```
* expect({ x: 1, y: 2, z: 3 }).toHaveKeys("x", "y", "z");
* ```
*
* @param keys the keys the object should have
* @returns the assertion instance
*/
public toHaveKeys(...keys: Array<keyof T>): this {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider naming this something like "toExactlyHaveKeys" or similar to denote that it is an exact comparation?. I'm not sure about this though. Let me know what you think. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought about something similar, but it'd be a bit repetitive because we already have .toContainKeys, which are not exact. Also, I'd prefer keeping the names small and straightforward so they are easier to use and remember 🙂

Let's keep it this way for now, and we can add an alias later if we think it's necessary.

const sortedActual = Object.keys(this.actual).sort();
const sortedKeys = [...keys].sort();

const error = new AssertionError({
actual: sortedActual,
expected: sortedKeys,
message: `Expected the object to have exactly the keys <${prettify(sortedKeys)}>`,
});
const invertedError = new AssertionError({
actual: sortedActual,
message: `Expected the object NOT to have the keys <${prettify(sortedKeys)}>`,
});

return this.execute({
assertWhen: isDeepEqual(sortedActual, sortedKeys),
error,
invertedError,
});
}

/**
* Check if the object contains the provided value.
*
Expand All @@ -155,11 +187,11 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
actual: this.actual,
message: `Expected the object to contain the provided value <${prettify(value)}>`,
});

const invertedError = new AssertionError({
actual: this.actual,
message: `Expected the object NOT to contain the provided value <${prettify(value)}>`,
});

return this.execute({
assertWhen: Object.values(this.actual).some(actualValue => isDeepEqual(actualValue, value)),
error,
Expand All @@ -184,11 +216,11 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
expected: values,
message: `Expected the object to contain all the provided values <${prettify(values)}>`,
});

const invertedError = new AssertionError({
actual: Object.values(this.actual),
message: `Expected the object NOT to contain all the provided values <${prettify(values)}>`,
});

return this.execute({
assertWhen: values
.every(value =>
Expand Down Expand Up @@ -216,11 +248,11 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
expected: values,
message: `Expected the object to contain at least one of the provided values <${prettify(values)}>`,
});

const invertedError = new AssertionError({
actual: Object.values(this.actual),
message: `Expected the object NOT to contain any of the provided values <${prettify(values)}>`,
});

return this.execute({
assertWhen: values
.some(value =>
Expand All @@ -231,6 +263,38 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
});
}

/**
* Check if the object has exactly the provided values.
*
* @example
* ```
* expect({ x: 1, y: "a", z: true }).toHaveValues(1, "a", true);
* ```
*
* @param values the values the object should have
* @returns the assertion instance
*/
public toHaveValues(...values: Array<T[keyof T]>): this {
const sortedActual = Object.values(this.actual).sort();
const sorterdValues = [...values].sort();

const error = new AssertionError({
actual: sortedActual,
expected: sorterdValues,
message: `Expected the object to have exactly the values <${prettify(sorterdValues)}>`,
});
const invertedError = new AssertionError({
actual: sortedActual,
message: `Expected the object NOT to have the values <${prettify(sorterdValues)}>`,
});

return this.execute({
assertWhen: isDeepEqual(sortedActual, sorterdValues),
error,
invertedError,
});
}

/**
* Check if the object contains the provided entry.
*
Expand Down Expand Up @@ -272,7 +336,7 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @param entries the entries that the object should contain
* @returns the assertion instance
*/
public toContainAllEntries(...entries: Array<Entry<T>>): this {
public toContainAllEntries(...entries: Entry<T>[]): this {
const error = new AssertionError({
actual: Object.entries(this.actual),
expected: entries,
Expand Down Expand Up @@ -306,7 +370,7 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @param entries the entries that the object should contain
* @returns the assertion instance
*/
public toContainAnyEntries(...entries: Array<Entry<T>>): this {
public toContainAnyEntries(...entries: Entry<T>[]): this {
const error = new AssertionError({
actual: Object.entries(this.actual),
expected: entries,
Expand All @@ -328,6 +392,39 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
});
}

/**
* Check if the object has exactly the provided entries.
*
* @example
* ```
* expect({ a: 1, b: 2, c: 3 })
* .toHaveEntries(["a", 1], ["b", 2], ["c", 3]);
* ```
*
* @param entries the entries the object should have
* @returns the assertion instance
*/
public toHaveEntries(...entries: Entry<T>[]): this {
const sortedActual = Object.entries(this.actual).sort();
const sortedEntries = [...entries].sort();
const prettyEntries = sortedEntries.map(entry => `[${prettify(entry)}]`).join(",");
const error = new AssertionError({
actual: sortedActual,
expected: sortedEntries,
message: `Expected the object to have exactly the entries <${prettyEntries}>`,
});
const invertedError = new AssertionError({
actual: Object.entries(this.actual),
message: `Expected the object NOT to have the entries <${prettyEntries}>`,
});

return this.execute({
assertWhen: isDeepEqual(sortedActual, sortedEntries),
error,
invertedError,
});
}

/**
* Check if the object match the provided object.
*
Expand Down
78 changes: 78 additions & 0 deletions package/test/lib/ObjectAssertion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@ describe("[Unit] ObjectAssertion.test.ts", () => {
});
});

describe(".toHaveKeys", () => {
context("when the object has exactly provided keys", () => {
it("returns the assertion instance", () => {
const test = new ObjectAssertion({ x: 1, y: 2, z: 3 });

assert.deepStrictEqual(test.toHaveKeys("x", "y", "z"), test);
assert.throws(() => test.not.toHaveKeys("x", "y", "z"), {
message: "Expected the object NOT to have the keys <x,y,z>",
name: AssertionError.name,
});
});
});

context("when the object does NOT have exactly the provided keys", () => {
it("throws an assertion error", () => {
const test = new ObjectAssertion({ x: 1, y: 2, z: 3 });

assert.throws(() => test.toHaveKeys("x", "y"), {
message: "Expected the object to have exactly the keys <x,y>",
name: AssertionError.name,
});
assert.deepStrictEqual(test.not.toHaveKeys("x", "z"), test);
});
});
});

describe(".toContainValue", () => {
context("when the object contains the provided value", () => {
it("returns the assertion instance", () => {
Expand Down Expand Up @@ -215,6 +241,32 @@ describe("[Unit] ObjectAssertion.test.ts", () => {
});
});

describe(".toHaveValues", () => {
context("when the object has exactly the provided values", () => {
it("returns the assertion instance", () => {
const test = new ObjectAssertion({ x: 1, y: "a", z: true });

assert.deepStrictEqual(test.toHaveValues(1, "a", true), test);
assert.throws(() => test.not.toHaveValues(1, "a", true), {
message: "Expected the object NOT to have the values <1,a,true>",
name: AssertionError.name,
});
});
});

context("when the object does NOT have exactly the provided values", () => {
it("throws an assertion error", () => {
const test = new ObjectAssertion({ x: 1, y: "a", z: true });

assert.throws(() => test.toHaveValues(1, "a"), {
message: "Expected the object to have exactly the values <1,a>",
name: AssertionError.name,
});
assert.deepStrictEqual(test.not.toHaveValues(1, "a"), test);
});
});
});

describe(".toContainEntry", () => {
context("when the object contains the provided entry", () => {
it("returns the assertion instance", () => {
Expand Down Expand Up @@ -309,6 +361,32 @@ describe("[Unit] ObjectAssertion.test.ts", () => {
});
});

describe(".toHaveEntries", () => {
context("when the object has exactly the provided entries", () => {
it("returns the assertion instance", () => {
const test = new ObjectAssertion({ a: 1, b: 2, c: 3 });

assert.deepStrictEqual(test.toHaveEntries(["a", 1], ["b", 2], ["c", 3]), test);
assert.throws(() => test.not.toHaveEntries(["a", 1], ["b", 2], ["c", 3]), {
message: "Expected the object NOT to have the entries <[a,1],[b,2],[c,3]>",
name: AssertionError.name,
});
});
});

context("when the object doe NOT have exactly the provided entries", () => {
it("throws an assertion error", () => {
const test = new ObjectAssertion({ a: 1, b: 2, c: 3 });

assert.throws(() => test.toHaveEntries(["a", 1], ["c", 3]), {
message: "Expected the object to have exactly the entries <[a,1],[c,3]>",
name: AssertionError.name,
});
assert.deepStrictEqual(test.not.toHaveEntries(["a", 1], ["c", 3]), test);
});
});
});

describe(".toPartiallyMatch", () => {
context("when the object matches the provided object", () => {
it("returns the assertion instance", () => {
Expand Down