Skip to content

Commit

Permalink
fix(messages): Improve prettify(..) helper and assertion messages (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseLion authored Jan 3, 2024
1 parent 9a18f36 commit df8a260
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 172 deletions.
21 changes: 10 additions & 11 deletions packages/core/src/lib/ArrayAssertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,14 @@ export class ArrayAssertion<T> extends Assertion<T[]> {
* @returns the assertion instance
*/
public toHaveSameMembers(expected: T[]): this {
const prettyValues = `[${expected.map(value => JSON.stringify(value)).join(", ")}]`;
const error = new AssertionError({
actual: this.actual,
expected,
message: `Expected array to have the same members as: ${prettyValues}`,
message: `Expected array to have the same members as <${prettify(expected)}>`,
});
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected array NOT to have the same members as: ${prettyValues}`,
message: `Expected array NOT to have the same members as <${prettify(expected)}>`,
});

return this.execute({
Expand All @@ -252,14 +251,14 @@ export class ArrayAssertion<T> extends Assertion<T[]> {
* @returns the assertion instance
*/
public toContainAll(...values: T[]): this {
const prettyValues = `[${values.map(value => JSON.stringify(value)).join(", ")}]`;
const allValues = values.map(prettify).join(", ");
const error = new AssertionError({
actual: this.actual,
message: `Expected array to contain the following values: ${prettyValues}`,
message: `Expected array to contain all the values <${allValues}>`,
});
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected array NOT to contain the following values, but it does: ${prettyValues}`,
message: `Expected array NOT to contain all the values <${allValues}>`,
});

return this.execute({
Expand All @@ -281,14 +280,14 @@ export class ArrayAssertion<T> extends Assertion<T[]> {
* @returns the assertion instance
*/
public toContainAny(...values: T[]): this {
const prettyValues = `[${values.map(value => JSON.stringify(value)).join(", ")}]`;
const allValues = values.map(prettify).join(", ");
const error = new AssertionError({
actual: this.actual,
message: `Expected array to contain at least one of the following values: ${prettyValues}`,
message: `Expected array to contain any of the values <${allValues}>`,
});
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected array NOT to contain one of the following values, but it does: ${prettyValues}`,
message: `Expected array NOT to contain any of the values <${allValues}>`,
});

return this.execute({
Expand All @@ -309,11 +308,11 @@ export class ArrayAssertion<T> extends Assertion<T[]> {
const error = new AssertionError({
actual: this.actual[index],
expected: value,
message: `Expected value at index ${index} of the array to be <${prettify(value)}>`,
message: `Expected value at index <${index}> to be <${prettify(value)}>`,
});
const invertedError = new AssertionError({
actual: this.actual[index],
message: `Expected value at index ${index} of the array NOT to be <${prettify(value)}>`,
message: `Expected value at index <${index}> NOT to be <${prettify(value)}>`,
});

return this.execute({
Expand Down
40 changes: 20 additions & 20 deletions packages/core/src/lib/NumberAssertion.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Assertion } from "./Assertion";
import { isHighInclusiveOptions, isInclusiveOptions, isLowInclusiveOptions } from "./helpers/guards";
import { prettify } from "./helpers/messages";

import { AssertionError } from "assert";

Expand Down Expand Up @@ -277,62 +276,64 @@ export class NumberAssertion extends Assertion<number> {
* @returns the assertion instance
*/
public toBeBetween(options: BetweenOptions): this {
const [min, max] = options.range;

if (isInclusiveOptions(options)) {
const between = options.inclusive ? "strictly between" : "between";
const rangeText = options.inclusive ? `[${min}, ${max}]` : `(${min}, ${max})`;
const inclusiveError = new AssertionError({
actual: this.actual,
expected: options,
message: `Expected <${this.actual}> to be ${between} <${prettify(options.range)}>` });
message: `Expected <${this.actual}> to be between ${rangeText} range` });
const inclusiveInvertedError = new AssertionError({
actual: this.actual,
message: `Expected <${this.actual}> NOT to be ${between} <${prettify(options.range)}>` });
message: `Expected <${this.actual}> NOT to be between ${rangeText} range` });

return this.execute({
assertWhen: options.inclusive
? this.actual >= options.range[0] && this.actual <= options.range[1]
: this.actual > options.range[0] && this.actual < options.range[1],
? this.actual >= min && this.actual <= max
: this.actual > min && this.actual < max,
error: inclusiveError,
invertedError: inclusiveInvertedError,
});
}

if (isLowInclusiveOptions(options)) {
const withInclusion = options.lowInclusive ? ` with <${options.range[0]}> inclusion` : "";
const rangeText = options.lowInclusive ? `[${min}, ${max})` : `(${min}, ${max})`;
const lowInclusiveError = new AssertionError({
actual: this.actual,
expected: options,
message: `Expected <${this.actual}> to be between <${prettify(options.range)}>${withInclusion}`,
message: `Expected <${this.actual}> to be between ${rangeText} range`,
});
const lowInclusiveErrorInvertedError = new AssertionError({
actual: this.actual,
message: `Expected <${this.actual}> NOT to be between <${prettify(options.range)}>${withInclusion}`,
message: `Expected <${this.actual}> NOT to be between ${rangeText} range`,
});

return this.execute({
assertWhen: options.lowInclusive
? this.actual >= options.range[0] && this.actual < options.range[1]
: this.actual > options.range[0] && this.actual < options.range[1],
? this.actual >= min && this.actual < max
: this.actual > min && this.actual < max,
error: lowInclusiveError,
invertedError: lowInclusiveErrorInvertedError,
});
}

if (isHighInclusiveOptions(options)) {
const withInclusion = options.highInclusive ? ` with <${options.range[1]}> inclusion` : "";
const rangeText = options.highInclusive ? `(${min}, ${max}]` : `(${min}, ${max})`;
const highInclusiveError = new AssertionError({
actual: this.actual,
expected: options,
message: `Expected <${this.actual}> to be between <${prettify(options.range)}>${withInclusion}`,
message: `Expected <${this.actual}> to be between ${rangeText} range`,
});
const highInclusiveErrorInvertedError = new AssertionError({
actual: this.actual,
message: `Expected <${this.actual}> NOT to be between <${prettify(options.range)}>${withInclusion}`,
message: `Expected <${this.actual}> NOT to be between ${rangeText} range`,
});

return this.execute({
assertWhen: options.highInclusive
? this.actual > options.range[0] && this.actual <= options.range[1]
: this.actual > options.range[0] && this.actual < options.range[1],
? this.actual > min && this.actual <= max
: this.actual > min && this.actual < max,
error: highInclusiveError,
invertedError: highInclusiveErrorInvertedError,
});
Expand All @@ -341,16 +342,15 @@ public toBeBetween(options: BetweenOptions): this {
const error = new AssertionError({
actual: this.actual,
expected: options,
message: `Expected <${this.actual}> to be between <${prettify(options.range)}>`,
message: `Expected <${this.actual}> to be between (${min}, ${max}) range`,
});
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected <${this.actual}> NOT to be between <${prettify(options.range)}>`,
message: `Expected <${this.actual}> NOT to be between (${min}, ${max}) range`,
});

return this.execute({
assertWhen:
this.actual > options.range[0] && this.actual < options.range[1],
assertWhen: this.actual > min && this.actual < max,
error,
invertedError,
});
Expand Down
55 changes: 31 additions & 24 deletions packages/core/src/lib/ObjectAssertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
public toContainKey(key: keyof T): this {
const error = new AssertionError({
actual: this.actual,
message: `Expected the object to contain the provided key <${String(key)}>`,
message: `Expected the object to contain the provided key <${prettify(key)}>`,
});
const invertedError = new AssertionError({
actual: this.actual,
message: `Expected the object NOT to contain the provided key <${String(key)}>`,
message: `Expected the object NOT to contain the provided key <${prettify(key)}>`,
});

return this.execute({
Expand All @@ -86,14 +86,15 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @returns the assertion instance
*/
public toContainAllKeys(...keys: Array<keyof T>): this {
const allKeys = keys.map(prettify).join(", ");
const error = new AssertionError({
actual: Object.keys(this.actual),
expected: keys,
message: `Expected the object to contain all the provided keys <${prettify(keys)}>`,
message: `Expected the object to contain all the provided keys <${allKeys}>`,
});
const invertedError = new AssertionError({
actual: Object.keys(this.actual),
message: `Expected the object NOT to contain all the provided keys <${prettify(keys)}>`,
message: `Expected the object NOT to contain all the provided keys <${allKeys}>`,
});

return this.execute({
Expand All @@ -115,14 +116,15 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @returns the assertion instance
*/
public toContainAnyKeys(...keys: Array<keyof T>): this {
const allKeys = keys.map(prettify).join(", ");
const error = new AssertionError({
actual: Object.keys(this.actual),
expected: keys,
message: `Expected the object to contain at least one of the provided keys <${prettify(keys)}>`,
message: `Expected the object to contain at least one of the provided keys <${allKeys}>`,
});
const invertedError = new AssertionError({
actual: Object.keys(this.actual),
message: `Expected the object NOT to contain any of the provided keys <${prettify(keys)}>`,
message: `Expected the object NOT to contain any of the provided keys <${allKeys}>`,
});

return this.execute({
Expand All @@ -146,15 +148,16 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
public toHaveKeys(...keys: Array<keyof T>): this {
const sortedActual = Object.keys(this.actual).sort();
const sortedKeys = [...keys].sort();
const allKeys = sortedKeys.map(prettify).join(", ");

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

return this.execute({
Expand Down Expand Up @@ -205,14 +208,15 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @returns the assertion instance
*/
public toContainAllValues(...values: Array<T[keyof T]>): this {
const allValues = values.map(prettify).join(", ");
const error = new AssertionError({
actual: Object.values(this.actual),
expected: values,
message: `Expected the object to contain all the provided values <${prettify(values)}>`,
message: `Expected the object to contain all the provided values <${allValues}>`,
});
const invertedError = new AssertionError({
actual: Object.values(this.actual),
message: `Expected the object NOT to contain all the provided values <${prettify(values)}>`,
message: `Expected the object NOT to contain all the provided values <${allValues}>`,
});

return this.execute({
Expand All @@ -237,14 +241,15 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @returns the assertion instance
*/
public toContainAnyValues(...values: Array<T[keyof T]>): this {
const allValues = values.map(prettify).join(", ");
const error = new AssertionError({
actual: Object.values(this.actual),
expected: values,
message: `Expected the object to contain at least one of the provided values <${prettify(values)}>`,
message: `Expected the object to contain at least one of the provided values <${allValues}>`,
});
const invertedError = new AssertionError({
actual: Object.values(this.actual),
message: `Expected the object NOT to contain any of the provided values <${prettify(values)}>`,
message: `Expected the object NOT to contain any of the provided values <${allValues}>`,
});

return this.execute({
Expand All @@ -271,15 +276,16 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
public toHaveValues(...values: Array<T[keyof T]>): this {
const sortedActual = Object.values(this.actual).sort();
const sorterdValues = [...values].sort();
const allValues = sorterdValues.map(prettify).join(", ");

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

return this.execute({
Expand Down Expand Up @@ -331,15 +337,16 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @returns the assertion instance
*/
public toContainAllEntries(...entries: Entry<T>[]): this {
const allEntries = entries.map(prettify).join(", ");
const error = new AssertionError({
actual: Object.entries(this.actual),
expected: entries,
message: `Expected the object to contain all the provided entries <${prettify(entries)}>`,
message: `Expected the object to contain all the provided entries <${allEntries}>`,
});

const invertedError = new AssertionError({
actual: Object.entries(this.actual),
message: `Expected the object NOT to contain all the provided entries <${prettify(entries)}>`,
message: `Expected the object NOT to contain all the provided entries <${allEntries}>`,
});
return this.execute({
assertWhen: entries
Expand All @@ -365,15 +372,16 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
* @returns the assertion instance
*/
public toContainAnyEntries(...entries: Entry<T>[]): this {
const allEntries = entries.map(prettify).join(", ");
const error = new AssertionError({
actual: Object.entries(this.actual),
expected: entries,
message: `Expected the object to contain at least one of the provided entries <${prettify(entries)}>`,
message: `Expected the object to contain at least one of the provided entries <${allEntries}>`,
});

const invertedError = new AssertionError({
actual: Object.entries(this.actual),
message: `Expected the object NOT to contain any of the provided entries <${prettify(entries)}>`,
message: `Expected the object NOT to contain any of the provided entries <${allEntries}>`,
});
return this.execute({
assertWhen: entries
Expand Down Expand Up @@ -401,15 +409,15 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
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 allEntries = sortedEntries.map(prettify).join(", ");
const error = new AssertionError({
actual: sortedActual,
expected: sortedEntries,
message: `Expected the object to have exactly the entries <${prettyEntries}>`,
message: `Expected the object to have exactly the entries <${allEntries}>`,
});
const invertedError = new AssertionError({
actual: Object.entries(this.actual),
message: `Expected the object NOT to have the entries <${prettyEntries}>`,
message: `Expected the object NOT to have the entries <${allEntries}>`,
});

return this.execute({
Expand All @@ -433,13 +441,12 @@ export class ObjectAssertion<T extends Struct> extends Assertion<T> {
public toPartiallyMatch(other: Partial<T>): this {
const error = new AssertionError({
actual: this.actual,
expected: other,
message: "Expected the object to be a partial match",
message: `Expected the object to partially match <${prettify(other)}>`,
});

const invertedError = new AssertionError({
actual: this.actual,
message: "Expected the object NOT to be a partial match",
message: `Expected the object NOT to partially match <${prettify(other)}>`,
});
return this.execute({
assertWhen: Object.keys(other)
Expand Down
20 changes: 18 additions & 2 deletions packages/core/src/lib/helpers/messages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
export function prettify<T>(value: T): string {
return typeof value === "object" && value !== null
const isClassObject = typeof value === "object"
&& value !== null
&& value.toString() !== "[object Object]";
const nonJsonValue = value === undefined
|| Number.isNaN(value)
|| typeof value === "symbol"
|| typeof value === "bigint";

if (Array.isArray(value)) {
return `[${value.map(prettify).join(",")}]`;
}

if (isClassObject || nonJsonValue) {
return String(value);
}

return typeof value === "function"
? value.toString()
: String(value);
: JSON.stringify(value);
}
Loading

0 comments on commit df8a260

Please sign in to comment.