From f326954d4284a65ed87794f567accc4875561988 Mon Sep 17 00:00:00 2001 From: Andrei Picus Date: Tue, 21 Feb 2023 18:03:44 +0100 Subject: [PATCH] feat: Improve colors in UnexpectedCall error message --- src/errors.ts | 17 +++++++++-------- src/expectation/it.ts | 3 +-- src/print.spec.ts | 8 ++++---- src/print.ts | 25 +++++++++++++++++-------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 891d1f5..a11ce8c 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,4 +1,4 @@ -import { EXPECTED_COLOR } from 'jest-matcher-utils'; +import { DIM_COLOR, EXPECTED_COLOR, RECEIVED_COLOR } from 'jest-matcher-utils'; import type { Expectation } from './expectation/expectation'; import { getMatcherDiffs } from './expectation/matcher'; import type { CallMap } from './expectation/repository/expectation-repository'; @@ -58,9 +58,8 @@ export class UnexpectedCall extends Error implements MatcherError { args: unknown[], expectations: Expectation[] ) { - const header = `Didn't expect mock${printCall( - property, - args + const header = `Didn't expect mock${RECEIVED_COLOR( + printCall(property, args, true) )} to be called.`; const propertyExpectations = expectations.filter( @@ -68,10 +67,12 @@ export class UnexpectedCall extends Error implements MatcherError { ); if (propertyExpectations.length) { - super(`${header} + super( + DIM_COLOR(`${header} Remaining expectations: -${printDiffForAllExpectations(propertyExpectations, args)}`); +${printDiffForAllExpectations(propertyExpectations, args)}`) + ); // If we have a single expectation we can attach the actual/expected args // to the error instance, so that an IDE may show its own diff for them. @@ -117,8 +118,8 @@ export class UnmetExpectations extends Error { * into a single call. * * @example - * mergeCalls({ foo: [{ arguments: undefined }, { arguments: [1, 2, 3] }] } - * // returns { foo: [{ arguments: [1, 2, 3] } } + * mergeCalls({ getData: [{ arguments: undefined }, { arguments: [1, 2, 3] }] } + * // returns { getData: [{ arguments: [1, 2, 3] } } */ const mergeCalls = (callMap: CallMap): CallMap => new Map( diff --git a/src/expectation/it.ts b/src/expectation/it.ts index 75c8b1d..c2f3c09 100644 --- a/src/expectation/it.ts +++ b/src/expectation/it.ts @@ -6,7 +6,6 @@ import { isUndefined, omitBy, } from 'lodash'; -import { printArg } from '../print'; import type { Matcher, TypeMatcher } from './matcher'; import { isMatcher, MATCHER_SYMBOL } from './matcher'; @@ -83,7 +82,7 @@ const deepEquals = ( return isEqual(removeUndefined(actual), removeUndefined(expected)); }, { - toJSON: () => printArg(expected), + toJSON: () => printExpected(expected), getDiff: (actual) => ({ actual, expected }), } ); diff --git a/src/print.spec.ts b/src/print.spec.ts index 39c4389..b11b462 100644 --- a/src/print.spec.ts +++ b/src/print.spec.ts @@ -162,22 +162,22 @@ describe('print', () => { getDiff: (actual) => ({ actual, expected: 'foo' }), }); - const expectation = new StrongExpectation(':irrelevant:', [matcher], { - value: ':irrelevant:', + const expectation = new StrongExpectation('prop', [matcher], { + value: 'return', }); const args = ['bar']; expectAnsilessEqual( printDiffForAllExpectations([expectation, expectation], args), - `when(() => mock.:irrelevant:(matches(() => false))).thenReturn(":irrelevant:").between(1, 1) + `when(() => mock.prop(matches(() => false))).thenReturn("return").between(1, 1) - Expected + Received - "foo", + "bar" -when(() => mock.:irrelevant:(matches(() => false))).thenReturn(":irrelevant:").between(1, 1) +when(() => mock.prop(matches(() => false))).thenReturn("return").between(1, 1) - Expected + Received diff --git a/src/print.ts b/src/print.ts index 26c2781..c86291a 100644 --- a/src/print.ts +++ b/src/print.ts @@ -23,12 +23,21 @@ export const printProperty = (property: Property) => { return `.${property}`; }; -export const printArg = (arg: unknown): string => - // Call toJSON on matchers directly to avoid wrapping them in quotes. - isMatcher(arg) ? arg.toJSON() : printReceived(arg); +const printArg = (arg: unknown, received = false): string => { + // Call toJSON on matchers directly to avoid wrapping strings returned by them in quotes. + if (isMatcher(arg)) { + return arg.toJSON(); + } + + return received ? printReceived(arg) : printExpected(arg); +}; -export const printCall = (property: Property, args: any[]) => { - const prettyArgs = args.map((arg) => printArg(arg)).join(', '); +export const printCall = ( + property: Property, + args: any[], + received = false // TODO: fix boolean trap +) => { + const prettyArgs = args.map((arg) => printArg(arg, received)).join(', '); const prettyProperty = printProperty(property); return `${prettyProperty}(${prettyArgs})`; @@ -53,15 +62,15 @@ export const printReturns = ( thenPrefix += 'thenReturn'; } - return `.${thenPrefix}(${printExpected(value)}).between(${min}, ${max})`; + return `.${thenPrefix}(${printReceived(value)}).between(${min}, ${max})`; }; export const printWhen = (property: Property, args: any[] | undefined) => { if (args) { - return `when(() => mock${printCall(property, args)})`; + return `when(() => mock${EXPECTED_COLOR(`${printCall(property, args)}`)})`; } - return `when(() => mock${printProperty(property)})`; + return `when(() => mock${EXPECTED_COLOR(`${printProperty(property)}`)})`; }; export const printExpectation = (