From ec544110a871d505f4e4229d4a5868c05f6eacf0 Mon Sep 17 00:00:00 2001 From: Aymeric Bouzy Date: Fri, 9 Feb 2018 10:17:38 +0100 Subject: [PATCH 1/6] test --- packages/expect/src/__tests__/extend.test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/expect/src/__tests__/extend.test.js b/packages/expect/src/__tests__/extend.test.js index 59b36aa5969e..b0b8525241b0 100644 --- a/packages/expect/src/__tests__/extend.test.js +++ b/packages/expect/src/__tests__/extend.test.js @@ -70,3 +70,11 @@ it('exposes an equality function to custom matchers', () => { expect(() => jestExpect().toBeOne()).not.toThrow(); }); + +it('defines asymmetric matchers', () => { + jestExpect({value: 2}).toEqual({value: expect.toBeDivisibleBy(2)}); + jestExpect({value: 3}).toEqual({value: expect.not.toBeDivisibleBy(2)}); + expect(() => + jestExpect({value: 3}).toEqual({value: expect.toBeDivisibleBy(2)}), + ).toThrowErrorMatchingSnapshot(); +}); From e6e008fde357af9db1dcce54467328778907ccc5 Mon Sep 17 00:00:00 2001 From: Aymeric Bouzy Date: Fri, 9 Feb 2018 10:59:05 +0100 Subject: [PATCH 2/6] define asymmetric matchers --- .../__snapshots__/extend.test.js.snap | 2 + packages/expect/src/__tests__/extend.test.js | 9 ++-- packages/expect/src/asymmetric_matchers.js | 2 +- packages/expect/src/index.js | 8 ++-- packages/expect/src/jest_matchers_object.js | 41 ++++++++++++++++++- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap b/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap index 7565cb732261..7425eb7ea76e 100644 --- a/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap @@ -1,5 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`defines asymmetric matchers 1`] = `"val.toAsymmetricMatcher is not a function"`; + exports[`is available globally 1`] = `"expected 15 to be divisible by 2"`; exports[`is ok if there is no message specified 1`] = `"No message was specified for this matcher."`; diff --git a/packages/expect/src/__tests__/extend.test.js b/packages/expect/src/__tests__/extend.test.js index b0b8525241b0..4b919fed2d03 100644 --- a/packages/expect/src/__tests__/extend.test.js +++ b/packages/expect/src/__tests__/extend.test.js @@ -72,9 +72,12 @@ it('exposes an equality function to custom matchers', () => { }); it('defines asymmetric matchers', () => { - jestExpect({value: 2}).toEqual({value: expect.toBeDivisibleBy(2)}); - jestExpect({value: 3}).toEqual({value: expect.not.toBeDivisibleBy(2)}); + jestExpect({value: 2}).toEqual({value: jestExpect.toBeDivisibleBy(2)}); expect(() => - jestExpect({value: 3}).toEqual({value: expect.toBeDivisibleBy(2)}), + jestExpect({value: 3}).toEqual({value: jestExpect.toBeDivisibleBy(2)}), ).toThrowErrorMatchingSnapshot(); }); + +it('defines asymmetric matchers that can be prefixed by not', () => { + jestExpect({value: 3}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}); +}); diff --git a/packages/expect/src/asymmetric_matchers.js b/packages/expect/src/asymmetric_matchers.js index 717d5f552c05..e59545e29a30 100644 --- a/packages/expect/src/asymmetric_matchers.js +++ b/packages/expect/src/asymmetric_matchers.js @@ -15,7 +15,7 @@ import { isUndefined, } from './jasmine_utils'; -class AsymmetricMatcher { +export class AsymmetricMatcher { $$typeof: Symbol; constructor() { diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index 50a495f50332..287395f4a3b7 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -254,7 +254,7 @@ const makeThrowingMatcher = ( }; expect.extend = (matchers: MatchersObject): void => - setMatchers(matchers, false); + setMatchers(matchers, false, expect); expect.anything = anything; expect.any = any; @@ -282,9 +282,9 @@ const _validateResult = result => { }; // add default jest matchers -setMatchers(matchers, true); -setMatchers(spyMatchers, true); -setMatchers(toThrowMatchers, true); +setMatchers(matchers, true, expect); +setMatchers(spyMatchers, true, expect); +setMatchers(toThrowMatchers, true, expect); expect.addSnapshotSerializer = () => void 0; expect.assertions = (expected: number) => { diff --git a/packages/expect/src/jest_matchers_object.js b/packages/expect/src/jest_matchers_object.js index 29c374279436..5b0bba2eb406 100644 --- a/packages/expect/src/jest_matchers_object.js +++ b/packages/expect/src/jest_matchers_object.js @@ -7,7 +7,8 @@ * @flow */ -import type {MatchersObject} from 'types/Matchers'; +import {AsymmetricMatcher} from './asymmetric_matchers'; +import type {Expect, MatchersObject} from 'types/Matchers'; // Global matchers object holds the list of available matchers and // the state, that can hold matcher specific values that change over time. @@ -39,12 +40,48 @@ export const setState = (state: Object) => { export const getMatchers = () => global[JEST_MATCHERS_OBJECT].matchers; -export const setMatchers = (matchers: MatchersObject, isInternal: boolean) => { +export const setMatchers = ( + matchers: MatchersObject, + isInternal: boolean, + expect: Expect, +) => { Object.keys(matchers).forEach(key => { const matcher = matchers[key]; Object.defineProperty(matcher, INTERNAL_MATCHER_FLAG, { value: isInternal, }); + + if (!isInternal) { + // expect is defined + + class CustomMatcher extends AsymmetricMatcher { + sample: any; + + constructor(sample: any) { + super(); + this.sample = sample; + } + + asymmetricMatch(other: any) { + const {pass}: {message: () => string, pass: boolean} = matcher( + (other: any), + (this.sample: any), + ); + + return pass; + } + + toString() { + return key; + } + + getExpectedType() { + return 'any'; + } + } + + expect[key] = (sample: any) => new CustomMatcher(sample); + } }); Object.assign(global[JEST_MATCHERS_OBJECT].matchers, matchers); From 23d316b3196fd94e090ce0c1327a002fef71b52a Mon Sep 17 00:00:00 2001 From: Aymeric Bouzy Date: Mon, 5 Mar 2018 11:58:10 +0100 Subject: [PATCH 3/6] more --- .../__snapshots__/extend.test.js.snap | 38 ++++++++++++++++++- packages/expect/src/__tests__/extend.test.js | 11 +++++- packages/expect/src/jest_matchers_object.js | 12 ++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap b/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap index 7425eb7ea76e..381b08c9f64f 100644 --- a/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/extend.test.js.snap @@ -1,6 +1,42 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`defines asymmetric matchers 1`] = `"val.toAsymmetricMatcher is not a function"`; +exports[`defines asymmetric matchers 1`] = ` +"expect(received).toEqual(expected) + +Expected value to equal: + {\\"value\\": toBeDivisibleBy<2>} +Received: + {\\"value\\": 3} + +Difference: + +- Expected ++ Received + + Object { +- \\"value\\": toBeDivisibleBy<2>, ++ \\"value\\": 3, + }" +`; + +exports[`defines asymmetric matchers that can be prefixed by not 1`] = ` +"expect(received).toEqual(expected) + +Expected value to equal: + {\\"value\\": not.toBeDivisibleBy<2>} +Received: + {\\"value\\": 2} + +Difference: + +- Expected ++ Received + + Object { +- \\"value\\": not.toBeDivisibleBy<2>, ++ \\"value\\": 2, + }" +`; exports[`is available globally 1`] = `"expected 15 to be divisible by 2"`; diff --git a/packages/expect/src/__tests__/extend.test.js b/packages/expect/src/__tests__/extend.test.js index 4b919fed2d03..64511d6eb243 100644 --- a/packages/expect/src/__tests__/extend.test.js +++ b/packages/expect/src/__tests__/extend.test.js @@ -72,12 +72,19 @@ it('exposes an equality function to custom matchers', () => { }); it('defines asymmetric matchers', () => { - jestExpect({value: 2}).toEqual({value: jestExpect.toBeDivisibleBy(2)}); + expect(() => + jestExpect({value: 2}).toEqual({value: jestExpect.toBeDivisibleBy(2)}), + ).not.toThrow(); expect(() => jestExpect({value: 3}).toEqual({value: jestExpect.toBeDivisibleBy(2)}), ).toThrowErrorMatchingSnapshot(); }); it('defines asymmetric matchers that can be prefixed by not', () => { - jestExpect({value: 3}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}); + expect(() => + jestExpect({value: 2}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}), + ).toThrowErrorMatchingSnapshot(); + expect(() => + jestExpect({value: 3}).toEqual({value: jestExpect.not.toBeDivisibleBy(2)}), + ).not.toThrow(); }); diff --git a/packages/expect/src/jest_matchers_object.js b/packages/expect/src/jest_matchers_object.js index 5b0bba2eb406..500b2f3b73ac 100644 --- a/packages/expect/src/jest_matchers_object.js +++ b/packages/expect/src/jest_matchers_object.js @@ -57,9 +57,10 @@ export const setMatchers = ( class CustomMatcher extends AsymmetricMatcher { sample: any; - constructor(sample: any) { + constructor(sample: any, inverse: boolean = false) { super(); this.sample = sample; + this.inverse = inverse; } asymmetricMatch(other: any) { @@ -68,19 +69,24 @@ export const setMatchers = ( (this.sample: any), ); - return pass; + return this.inverse ? !pass : pass; } toString() { - return key; + return `${this.inverse ? 'not.' : ''}${key}`; } getExpectedType() { return 'any'; } + + toAsymmetricMatcher() { + return `${this.toString()}<${this.sample}>`; + } } expect[key] = (sample: any) => new CustomMatcher(sample); + expect.not[key] = (sample: any) => new CustomMatcher(sample, true); } }); From eaf6b04c4710b11b0b531e03b11acd3d38eca2eb Mon Sep 17 00:00:00 2001 From: Aymeric Bouzy Date: Mon, 5 Mar 2018 12:07:45 +0100 Subject: [PATCH 4/6] correction --- packages/expect/src/jest_matchers_object.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/expect/src/jest_matchers_object.js b/packages/expect/src/jest_matchers_object.js index 500b2f3b73ac..12cae657475d 100644 --- a/packages/expect/src/jest_matchers_object.js +++ b/packages/expect/src/jest_matchers_object.js @@ -86,6 +86,9 @@ export const setMatchers = ( } expect[key] = (sample: any) => new CustomMatcher(sample); + if (!expect.not) { + expect.not = {}; + } expect.not[key] = (sample: any) => new CustomMatcher(sample, true); } }); From 50d1e161590f3fcbb4f5dd652c53817a0e390b84 Mon Sep 17 00:00:00 2001 From: Aymeric Bouzy Date: Mon, 5 Mar 2018 12:14:59 +0100 Subject: [PATCH 5/6] trying to fix flow --- types/Matchers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/types/Matchers.js b/types/Matchers.js index 04e11e5b0939..3b21458aa6f0 100644 --- a/types/Matchers.js +++ b/types/Matchers.js @@ -59,6 +59,7 @@ export type Expect = { objectContaining(sample: Object): AsymmetricMatcher, stringContaining(expected: string): AsymmetricMatcher, stringMatching(expected: string | RegExp): AsymmetricMatcher, + not: {[id: string]: ThrowingMatcherFn}, }; export type ExpectationObject = { From 3ed393fb0d8f51970838b2e0cedb27e186dc8ff8 Mon Sep 17 00:00:00 2001 From: Aymeric Bouzy Date: Mon, 5 Mar 2018 12:32:02 +0100 Subject: [PATCH 6/6] flow fix --- packages/expect/src/index.js | 2 +- types/Matchers.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index 449040e6d4dc..4d766833e593 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -302,7 +302,7 @@ expect.addSnapshotSerializer = () => void 0; expect.assertions = (expected: number) => { getState().expectedAssertionsNumber = expected; }; -expect.hasAssertions = expected => { +expect.hasAssertions = (expected: any) => { utils.ensureNoExpected(expected, '.hasAssertions'); getState().isExpectingAssertions = true; }; diff --git a/types/Matchers.js b/types/Matchers.js index 3b21458aa6f0..b8ce53f024ca 100644 --- a/types/Matchers.js +++ b/types/Matchers.js @@ -59,7 +59,8 @@ export type Expect = { objectContaining(sample: Object): AsymmetricMatcher, stringContaining(expected: string): AsymmetricMatcher, stringMatching(expected: string | RegExp): AsymmetricMatcher, - not: {[id: string]: ThrowingMatcherFn}, + [id: string]: AsymmetricMatcher, + not: {[id: string]: AsymmetricMatcher}, }; export type ExpectationObject = {