diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index dbf93932f56b..e29d60d9f8e4 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -223,14 +223,6 @@ describe('Beware of a misunderstanding! A sequence of dice rolls', () => { }); ``` -### `expect.arrayNotContaining(array)` - -`expect.arrayNotContaining(array)` matches a received array which contains none of -the elements in the expected array. That is, the expected array **is not a subset** -of the received array. - -It is the inverse of `expect.arrayContaining`. - ### `expect.assertions(number)` `expect.assertions(number)` verifies that a certain number of assertions are @@ -281,6 +273,36 @@ test('prepareState prepares a valid state', () => { The `expect.hasAssertions()` call ensures that the `prepareState` callback actually gets called. +### `expect.not.arrayContaining(array)` + +`expect.not.arrayContaining(array)` matches a received array which contains none of +the elements in the expected array. That is, the expected array **is not a subset** +of the received array. + +It is the inverse of `expect.arrayContaining`. + +### `expect.not.objecttContaining(object)` + +`expect.not.objectContaining(object)` matches any received object that does not recursively +match the expected properties. That is, the expected object **is not a subset** of +the received object. Therefore, it matches a received object which contains +properties that are **not** in the expected object. + +It is the inverse of `expect.objectContaining`. + +### `expect.not.stringContaining(string)` + +`expect.not.stringContaining(string)` matches any received string that does not contain the +exact expected string. + +It is the inverse of `expect.stringContaining`. + +### `expect.not.stringMatching(regexp)` + +`expect.not.stringMatching(regexp)` matches any received string that does not match the +expected regexp. + +It is the inverse of `expect.stringMatching`. ### `expect.objectContaining(object)` @@ -309,27 +331,11 @@ test('onPress gets called with the right thing', () => { }); ``` -### `expect.objectNotContaining(object)` - -`expect.objectNotContaining(object)` matches any received object that does not recursively -match the expected properties. That is, the expected object **is not a subset** of -the received object. Therefore, it matches a received object which contains -properties that are **not** in the expected object. - -It is the inverse of `expect.objectContaining`. - ### `expect.stringContaining(string)` `expect.stringContaining(string)` matches any received string that contains the exact expected string. -### `expect.stringNotContaining(string)` - -`expect.stringNotContaining(string)` matches any received string that does not contain the -exact expected string. - -It is the inverse of `expect.stringContaining`. - ### `expect.stringMatching(regexp)` `expect.stringMatching(regexp)` matches any received string that matches the @@ -363,13 +369,6 @@ describe('stringMatching in arrayContaining', () => { }); ``` -### `expect.stringNotMatching(regexp)` - -`expect.stringNotMatching(regexp)` matches any received string that does not match the -expected regexp. - -It is the inverse of `expect.stringMatching`. - ### `expect.addSnapshotSerializer(serializer)` You can call `expect.addSnapshotSerializer` to add a module that formats diff --git a/integration-tests/toThrowErrorMatchingSnapshot/__tests__/accept-custom-snapshot-name.test.js b/integration-tests/toThrowErrorMatchingSnapshot/__tests__/accept-custom-snapshot-name.test.js deleted file mode 100644 index 4f955d35bb88..000000000000 --- a/integration-tests/toThrowErrorMatchingSnapshot/__tests__/accept-custom-snapshot-name.test.js +++ /dev/null @@ -1,5 +0,0 @@ -test('accepts custom snapshot name', () => { - expect(() => { - throw new Error('apple'); - }).toThrowErrorMatchingSnapshot('custom-name'); -}); diff --git a/packages/expect/src/__tests__/asymmetric_matchers.test.js b/packages/expect/src/__tests__/asymmetric_matchers.test.js index 7c233732185f..c1601480e46d 100644 --- a/packages/expect/src/__tests__/asymmetric_matchers.test.js +++ b/packages/expect/src/__tests__/asymmetric_matchers.test.js @@ -188,37 +188,41 @@ test('ObjectNotContaining throws for non-objects', () => { test('StringContaining matches string against string', () => { jestExpect(stringContaining('en*').asymmetricMatch('queen*')).toBe(true); jestExpect(stringContaining('en').asymmetricMatch('queue')).toBe(false); - jestExpect(stringContaining('en').asymmetricMatch({})).toBe(false); }); test('StringContaining throws for non-strings', () => { jestExpect(() => { stringContaining([1]).asymmetricMatch('queen'); }).toThrow(); + + jestExpect(() => { + stringContaining('en*').asymmetricMatch(1); + }).toThrow(); }); test('StringNotContaining matches string against string', () => { jestExpect(stringNotContaining('en*').asymmetricMatch('queen*')).toBe(false); jestExpect(stringNotContaining('en').asymmetricMatch('queue')).toBe(true); - jestExpect(stringNotContaining('en').asymmetricMatch({})).toBe(true); }); test('StringNotContaining throws for non-strings', () => { jestExpect(() => { stringNotContaining([1]).asymmetricMatch('queen'); }).toThrow(); + + jestExpect(() => { + stringNotContaining('en*').asymmetricMatch(1); + }).toThrow(); }); test('StringMatching matches string against regexp', () => { jestExpect(stringMatching(/en/).asymmetricMatch('queen')).toBe(true); jestExpect(stringMatching(/en/).asymmetricMatch('queue')).toBe(false); - jestExpect(stringMatching(/en/).asymmetricMatch({})).toBe(false); }); test('StringMatching matches string against string', () => { jestExpect(stringMatching('en').asymmetricMatch('queen')).toBe(true); jestExpect(stringMatching('en').asymmetricMatch('queue')).toBe(false); - jestExpect(stringMatching('en').asymmetricMatch({})).toBe(false); }); test('StringMatching throws for non-strings and non-regexps', () => { @@ -227,16 +231,20 @@ test('StringMatching throws for non-strings and non-regexps', () => { }).toThrow(); }); +test('StringMatching throws for non-string actual values', () => { + jestExpect(() => { + stringMatching('en').asymmetricMatch(1); + }).toThrow(); +}); + test('StringNotMatching matches string against regexp', () => { jestExpect(stringNotMatching(/en/).asymmetricMatch('queen')).toBe(false); jestExpect(stringNotMatching(/en/).asymmetricMatch('queue')).toBe(true); - jestExpect(stringNotMatching(/en/).asymmetricMatch({})).toBe(true); }); test('StringNotMatching matches string against string', () => { jestExpect(stringNotMatching('en').asymmetricMatch('queen')).toBe(false); jestExpect(stringNotMatching('en').asymmetricMatch('queue')).toBe(true); - jestExpect(stringNotMatching('en').asymmetricMatch({})).toBe(true); }); test('StringNotMatching throws for non-strings and non-regexps', () => { @@ -244,3 +252,9 @@ test('StringNotMatching throws for non-strings and non-regexps', () => { stringNotMatching([1]).asymmetricMatch('queen'); }).toThrow(); }); + +test('StringNotMatching throws for non-string actual values', () => { + jestExpect(() => { + stringNotMatching('en').asymmetricMatch(1); + }).toThrow(); +}); diff --git a/packages/expect/src/asymmetric_matchers.js b/packages/expect/src/asymmetric_matchers.js index 4945ef6668aa..b8e7a9992264 100644 --- a/packages/expect/src/asymmetric_matchers.js +++ b/packages/expect/src/asymmetric_matchers.js @@ -19,6 +19,7 @@ import {emptyObject} from './utils'; class AsymmetricMatcher { $$typeof: Symbol; + inverse: boolean; constructor() { this.$$typeof = Symbol.for('jest.asymmetricMatcher'); @@ -115,9 +116,10 @@ class Anything extends AsymmetricMatcher { class ArrayContaining extends AsymmetricMatcher { sample: Array; - constructor(sample: Array) { + constructor(sample: Array, inverse: boolean = false) { super(); this.sample = sample; + this.inverse = inverse; } asymmetricMatch(other: Array) { @@ -129,15 +131,18 @@ class ArrayContaining extends AsymmetricMatcher { ); } - return ( + const result = this.sample.length === 0 || (Array.isArray(other) && - this.sample.every(item => other.some(another => equals(item, another)))) - ); + this.sample.every(item => + other.some(another => equals(item, another)), + )); + + return this.inverse ? !result : result; } toString() { - return 'ArrayContaining'; + return `Array${this.inverse ? 'Not' : ''}Containing`; } getExpectedType() { @@ -145,22 +150,13 @@ class ArrayContaining extends AsymmetricMatcher { } } -class ArrayNotContaining extends ArrayContaining { - asymmetricMatch(other: Array) { - return !super.asymmetricMatch(other); - } - - toString() { - return 'ArrayNotContaining'; - } -} - class ObjectContaining extends AsymmetricMatcher { sample: Object; - constructor(sample: Object) { + constructor(sample: Object, inverse: boolean = false) { super(); this.sample = sample; + this.inverse = inverse; } asymmetricMatch(other: Object) { @@ -172,20 +168,35 @@ class ObjectContaining extends AsymmetricMatcher { ); } - for (const property in this.sample) { - if ( - !hasProperty(other, property) || - !equals(this.sample[property], other[property]) - ) { - return false; + if (this.inverse) { + for (const property in this.sample) { + if ( + hasProperty(other, property) && + equals(this.sample[property], other[property]) && + !emptyObject(this.sample[property]) && + !emptyObject(other[property]) + ) { + return false; + } + } + + return true; + } else { + for (const property in this.sample) { + if ( + !hasProperty(other, property) || + !equals(this.sample[property], other[property]) + ) { + return false; + } } - } - return true; + return true; + } } toString() { - return 'ObjectContaining'; + return `Object${this.inverse ? 'Not' : ''}Containing`; } getExpectedType() { @@ -193,56 +204,30 @@ class ObjectContaining extends AsymmetricMatcher { } } -class ObjectNotContaining extends ObjectContaining { - asymmetricMatch(other: Object) { - if (typeof this.sample !== 'object') { - throw new Error( - `You must provide an object to ${this.toString()}, not '` + - typeof this.sample + - "'.", - ); - } - - for (const property in this.sample) { - if ( - hasProperty(other, property) && - equals(this.sample[property], other[property]) && - !emptyObject(this.sample[property]) && - !emptyObject(other[property]) - ) { - return false; - } - } - - return true; - } - - toString() { - return 'ObjectNotContaining'; - } -} - class StringContaining extends AsymmetricMatcher { sample: string; - constructor(sample: string) { + constructor(sample: string, inverse: boolean = false) { super(); if (!isA('String', sample)) { throw new Error('Expected is not a string'); } this.sample = sample; + this.inverse = inverse; } asymmetricMatch(other: string) { if (!isA('String', other)) { - return false; + throw new Error('Actual is not a string'); } - return other.includes(this.sample); + const result = other.includes(this.sample); + + return this.inverse ? !result : result; } toString() { - return 'StringContaining'; + return `String${this.inverse ? 'Not' : ''}Containing`; } getExpectedType() { @@ -250,38 +235,31 @@ class StringContaining extends AsymmetricMatcher { } } -class StringNotContaining extends StringContaining { - asymmetricMatch(other: string) { - return !super.asymmetricMatch(other); - } - - toString() { - return 'StringNotContaining'; - } -} - class StringMatching extends AsymmetricMatcher { sample: RegExp; - constructor(sample: string | RegExp) { + constructor(sample: string | RegExp, inverse: boolean = false) { super(); if (!isA('String', sample) && !isA('RegExp', sample)) { throw new Error('Expected is not a String or a RegExp'); } this.sample = new RegExp(sample); + this.inverse = inverse; } asymmetricMatch(other: string) { if (!isA('String', other)) { - return false; + throw new Error('Actual is not a string'); } - return this.sample.test(other); + const result = this.sample.test(other); + + return this.inverse ? !result : result; } toString() { - return 'StringMatching'; + return `String${this.inverse ? 'Not' : ''}Matching`; } getExpectedType() { @@ -289,27 +267,21 @@ class StringMatching extends AsymmetricMatcher { } } -class StringNotMatching extends StringMatching { - asymmetricMatch(other: string) { - return !super.asymmetricMatch(other); - } -} - export const any = (expectedObject: any) => new Any(expectedObject); export const anything = () => new Anything(); export const arrayContaining = (sample: Array) => new ArrayContaining(sample); export const arrayNotContaining = (sample: Array) => - new ArrayNotContaining(sample); + new ArrayContaining(sample, true); export const objectContaining = (sample: Object) => new ObjectContaining(sample); export const objectNotContaining = (sample: Object) => - new ObjectNotContaining(sample); + new ObjectContaining(sample, true); export const stringContaining = (expected: string) => new StringContaining(expected); export const stringNotContaining = (expected: string) => - new StringNotContaining(expected); + new StringContaining(expected, true); export const stringMatching = (expected: string | RegExp) => new StringMatching(expected); export const stringNotMatching = (expected: string | RegExp) => - new StringNotMatching(expected); + new StringMatching(expected, true); diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index 271b43237ad0..c4da96f53352 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -262,14 +262,18 @@ expect.extend = (matchers: MatchersObject): void => expect.anything = anything; expect.any = any; + +expect.not = { + arrayContaining: arrayNotContaining, + objectContaining: objectNotContaining, + stringContaining: stringNotContaining, + stringMatching: stringNotMatching, +}; + expect.objectContaining = objectContaining; -expect.objectNotContaining = objectNotContaining; expect.arrayContaining = arrayContaining; -expect.arrayNotContaining = arrayNotContaining; expect.stringContaining = stringContaining; -expect.stringNotContaining = stringNotContaining; expect.stringMatching = stringMatching; -expect.stringNotMatching = stringNotMatching; const _validateResult = result => { if (