Skip to content

Commit

Permalink
feat(expect): implement chai inspect for AsymmetricMatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa committed Jan 12, 2024
1 parent 9ec3f74 commit 8ed285e
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 3 deletions.
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)))

0 comments on commit 8ed285e

Please sign in to comment.