Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(expect): implement chai inspect for AsymmetricMatcher #4942

Merged
6 changes: 6 additions & 0 deletions packages/expect/src/jest-asymmetric-matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export abstract class AsymmetricMatcher<
abstract toString(): string
getExpectedType?(): string
toAsymmetricMatcher?(): string

// implement loupe-based serialization for AssertionError.message
// https://github.com/chaijs/loupe/blob/9b8a6deabcd50adc056a64fb705896194710c5c6/src/index.ts#L29
[Symbol.for('chai/inspect')]() {
return stringify(this)
}
}

export class StringContaining extends AsymmetricMatcher<string> {
Expand Down
139 changes: 139 additions & 0 deletions test/core/test/__snapshots__/jest-expect.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`asymmetric matcher error 1`] = `
{
"actual": "hello",
"diff": null,
"expected": "StringContaining "xx"",
"message": "expected 'hello' to deeply equal StringContaining "xx"",
}
`;

exports[`asymmetric matcher error 2`] = `
{
"actual": "hello",
"diff": null,
"expected": "StringNotContaining "ll"",
"message": "expected 'hello' to deeply equal StringNotContaining "ll"",
}
`;

exports[`asymmetric matcher error 3`] = `
{
"actual": "Object {
"foo": "hello",
}",
"diff": "- Expected
+ Received

Object {
- "foo": StringContaining "xx",
+ "foo": "hello",
}",
"expected": "Object {
"foo": StringContaining "xx",
}",
"message": "expected { foo: 'hello' } to deeply equal { foo: StringContaining "xx" }",
}
`;

exports[`asymmetric matcher error 4`] = `
{
"actual": "Object {
"foo": "hello",
}",
"diff": "- Expected
+ Received

Object {
- "foo": StringNotContaining "ll",
+ "foo": "hello",
}",
"expected": "Object {
"foo": StringNotContaining "ll",
}",
"message": "expected { foo: 'hello' } to deeply equal { foo: StringNotContaining "ll" }",
}
`;

exports[`asymmetric matcher error 5`] = `
{
"actual": "hello",
"diff": "- Expected:
stringContainingCustom<xx>

+ Received:
"hello"",
"expected": "stringContainingCustom<xx>",
"message": "expected 'hello' to deeply equal stringContainingCustom<xx>",
}
`;

exports[`asymmetric matcher error 6`] = `
{
"actual": "hello",
"diff": "- Expected:
not.stringContainingCustom<ll>

+ Received:
"hello"",
"expected": "not.stringContainingCustom<ll>",
"message": "expected 'hello' to deeply equal not.stringContainingCustom<ll>",
}
`;

exports[`asymmetric matcher error 7`] = `
{
"actual": "Object {
"foo": "hello",
}",
"diff": "- Expected
+ Received

Object {
- "foo": stringContainingCustom<xx>,
+ "foo": "hello",
}",
"expected": "Object {
"foo": stringContainingCustom<xx>,
}",
"message": "expected { foo: 'hello' } to deeply equal { foo: stringContainingCustom<xx> }",
}
`;

exports[`asymmetric matcher error 8`] = `
{
"actual": "Object {
"foo": "hello",
}",
"diff": "- Expected
+ Received

Object {
- "foo": not.stringContainingCustom<ll>,
+ "foo": "hello",
}",
"expected": "Object {
"foo": not.stringContainingCustom<ll>,
}",
"message": "expected { foo: 'hello' } to deeply equal { foo: not.stringContainingCustom<ll> }",
}
`;

exports[`asymmetric matcher error 9`] = `
{
"actual": "undefined",
"diff": undefined,
"expected": "undefined",
"message": "expected "hello" to contain "xx"",
}
`;

exports[`asymmetric matcher error 10`] = `
{
"actual": "undefined",
"diff": undefined,
"expected": "undefined",
"message": "expected "hello" not to contain "ll"",
}
`;
51 changes: 48 additions & 3 deletions test/core/test/jest-expect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ describe('jest-expect', () => {
}).toEqual({
sum: expect.closeTo(0.4),
})
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { sum: 0.30000000000000004 } to deeply equal { sum: CloseTo{ …(4) } }]`)
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { sum: 0.30000000000000004 } to deeply equal { sum: NumberCloseTo 0.4 (2 digits) }]`)

// TODO: support set
// expect(new Set(['bar'])).not.toEqual(new Set([expect.stringContaining('zoo')]))
Expand Down Expand Up @@ -949,7 +949,7 @@ it('toHaveProperty error diff', () => {
// non match value (with asymmetric matcher)
expect(getError(() => expect({ name: 'foo' }).toHaveProperty('name', expect.any(Number)))).toMatchInlineSnapshot(`
[
"expected { name: 'foo' } to have property "name" with value Any{ …(3) }",
"expected { name: 'foo' } to have property "name" with value Any<Number>",
"- Expected:
Any<Number>

Expand All @@ -961,7 +961,7 @@ it('toHaveProperty error diff', () => {
// non match key (with asymmetric matcher)
expect(getError(() => expect({ noName: 'foo' }).toHaveProperty('name', expect.any(Number)))).toMatchInlineSnapshot(`
[
"expected { noName: 'foo' } to have property "name" with value Any{ …(3) }",
"expected { noName: 'foo' } to have property "name" with value Any<Number>",
"- Expected:
Any<Number>

Expand Down Expand Up @@ -995,4 +995,49 @@ it('toHaveProperty error diff', () => {
`)
})

it('asymmetric matcher error', () => {
setupColors(getDefaultColors())

expect.extend({
stringContainingCustom(received: unknown, other: string) {
return {
pass: typeof received === 'string' && received.includes(other),
message: () => `expected ${this.utils.printReceived(received)} ${this.isNot ? 'not ' : ''}to contain ${this.utils.printExpected(other)}`,
}
},
})

function getError(f: () => unknown) {
try {
f()
return expect.unreachable()
}
catch (error) {
const e = processError(error)
return {
message: e.message,
diff: e.diff,
expected: e.expected,
actual: e.actual,
}
}
}

// builtin
expect(getError(() => expect('hello').toEqual((expect as any).stringContaining('xx')))).toMatchSnapshot()
expect(getError(() => expect('hello').toEqual((expect as any).not.stringContaining('ll')))).toMatchSnapshot()
expect(getError(() => expect({ foo: 'hello' }).toEqual({ foo: (expect as any).stringContaining('xx') }))).toMatchSnapshot()
expect(getError(() => expect({ foo: 'hello' }).toEqual({ foo: (expect as any).not.stringContaining('ll') }))).toMatchSnapshot()

// custom
expect(getError(() => expect('hello').toEqual((expect as any).stringContainingCustom('xx')))).toMatchSnapshot()
expect(getError(() => expect('hello').toEqual((expect as any).not.stringContainingCustom('ll')))).toMatchSnapshot()
expect(getError(() => expect({ foo: 'hello' }).toEqual({ foo: (expect as any).stringContainingCustom('xx') }))).toMatchSnapshot()
expect(getError(() => expect({ foo: 'hello' }).toEqual({ foo: (expect as any).not.stringContainingCustom('ll') }))).toMatchSnapshot()

// assertion form
expect(getError(() => (expect('hello') as any).stringContainingCustom('xx'))).toMatchSnapshot()
expect(getError(() => (expect('hello') as any).not.stringContainingCustom('ll'))).toMatchSnapshot()
})

it('timeout', () => new Promise(resolve => setTimeout(resolve, 500)))