Skip to content

Commit 7a04a43

Browse files
committedSep 24, 2023
feat: Attach actual/expected args to error instance to enable IDE diffs
1 parent ba4f6b5 commit 7a04a43

File tree

4 files changed

+79
-10
lines changed

4 files changed

+79
-10
lines changed
 

‎src/errors.spec.ts

+35
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,41 @@ foobar`
158158
// Yeah, funky way to do a negated ansiless contains.
159159
expect(() => expectAnsilessContain(error.message, `bar`)).toThrow();
160160
});
161+
162+
it("should contain actual and expected values when there's a single expectation remaining", () => {
163+
const matcher = It.matches(() => false, {
164+
getDiff: () => ({ actual: 'actual', expected: 'expected' }),
165+
});
166+
167+
const expectation = new StrongExpectation('foo', [matcher, matcher], {
168+
value: ':irrelevant:',
169+
});
170+
171+
const error = new UnexpectedCall(
172+
'foo',
173+
['any arg', 'any arg'],
174+
[expectation]
175+
);
176+
177+
expect(error.matcherResult).toEqual({
178+
actual: ['actual', 'actual'],
179+
expected: ['expected', 'expected'],
180+
});
181+
});
182+
183+
it('should not contain actual and expected values when the expectation has no expected args', () => {
184+
const expectation = new StrongExpectation('foo', [], {
185+
value: ':irrelevant:',
186+
});
187+
188+
const error = new UnexpectedCall(
189+
'foo',
190+
['any arg', 'any arg'],
191+
[expectation]
192+
);
193+
194+
expect(error.matcherResult).toBeUndefined();
195+
});
161196
});
162197

163198
describe('UnexpectedCalls', () => {

‎src/errors.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { EXPECTED_COLOR } from 'jest-matcher-utils';
22
import type { Expectation } from './expectation/expectation';
3+
import { getMatcherDiffs } from './expectation/matcher';
34
import type { CallMap } from './expectation/repository/expectation-repository';
45
import {
56
printCall,
@@ -42,7 +43,16 @@ ${printRemainingExpectations(expectations)}`);
4243
}
4344
}
4445

45-
export class UnexpectedCall extends Error {
46+
type MatcherResult = { expected: unknown; actual: unknown };
47+
48+
// This is taken from jest.
49+
interface MatcherError {
50+
matcherResult?: MatcherResult;
51+
}
52+
53+
export class UnexpectedCall extends Error implements MatcherError {
54+
public matcherResult?: MatcherResult;
55+
4656
constructor(
4757
property: Property,
4858
args: unknown[],
@@ -62,8 +72,26 @@ export class UnexpectedCall extends Error {
6272
6373
Remaining expectations:
6474
${printDiffForAllExpectations(propertyExpectations, args)}`);
75+
76+
// If we have a single expectation we can attach the actual/expected args
77+
// to the error instance, so that an IDE may show its own diff for them.
78+
if (
79+
propertyExpectations.length === 1 &&
80+
propertyExpectations[0].args?.length
81+
) {
82+
const { actual, expected } = getMatcherDiffs(
83+
propertyExpectations[0].args,
84+
args
85+
);
86+
this.matcherResult = {
87+
actual,
88+
expected,
89+
};
90+
}
6591
} else {
66-
super(header);
92+
super(`${header}
93+
94+
No remaining expectations.`);
6795
}
6896
}
6997
}

‎src/expectation/matcher.ts

+11
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,14 @@ export type TypeMatcher<T> = T & Matcher;
5555
export function isMatcher(f: unknown): f is Matcher {
5656
return !!(f && (<Matcher>f)[MATCHER_SYMBOL]);
5757
}
58+
59+
export const getMatcherDiffs = (
60+
matchers: Matcher[],
61+
args: any[]
62+
): { actual?: unknown[]; expected?: unknown[] } => {
63+
const matcherDiffs = matchers.map((matcher, i) => matcher.getDiff(args[i]));
64+
const actual = matcherDiffs?.map((d) => d.actual);
65+
const expected = matcherDiffs?.map((d) => d.expected);
66+
67+
return { actual, expected };
68+
};

‎src/print.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from 'jest-matcher-utils';
88
import type { Expectation } from './expectation/expectation';
99
import { ApplyProp } from './expectation/expectation';
10-
import { isMatcher } from './expectation/matcher';
10+
import { getMatcherDiffs, isMatcher } from './expectation/matcher';
1111
import type { ReturnValue } from './expectation/repository/return-value';
1212
import type { Property } from './proxy';
1313

@@ -83,13 +83,8 @@ export const printExpectationDiff = (e: Expectation, args: any[]) => {
8383
return '';
8484
}
8585

86-
const matcherDiffs = e.args?.map((matcher, j) => matcher.getDiff(args[j]));
87-
88-
const diff = printDiff(
89-
matcherDiffs?.map((d) => d.expected),
90-
matcherDiffs?.map((d) => d.actual),
91-
{ omitAnnotationLines: true }
92-
);
86+
const { actual, expected } = getMatcherDiffs(e.args, args);
87+
const diff = printDiff(expected, actual, { omitAnnotationLines: true });
9388

9489
if (!diff) {
9590
return '';

0 commit comments

Comments
 (0)
Please sign in to comment.