Skip to content

Commit

Permalink
fix(snapshot): bring back support for array property matchers (#14025)
Browse files Browse the repository at this point in the history
Co-authored-by: Tom Mrazauskas <tom@mrazauskas.de>
Co-authored-by: Marius Bakas <marius.bakas@barbora.lt>
  • Loading branch information
3 people committed Apr 9, 2023
1 parent c40ef21 commit 93c1967
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 69 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

- `[jest-snapshot]` Support arrays as property matchers ([#14025](https://github.com/facebook/jest/pull/14025))

### Fixes

- `[jest-config]` Handle frozen config object ([#14054](https://github.com/facebook/jest/pull/14054))
Expand Down
@@ -1,14 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`matcher error toMatchInlineSnapshot Expected properties must be an object (array) with snapshot 1`] = `
<d>expect(</><r>received</><d>).</>toMatchInlineSnapshot<d>(</><g>properties</><d>, </>snapshot<d>)</>

<b>Matcher error</>: Expected <g>properties</> must be an object

Expected properties has type: array
Expected properties has value: <g>[]</>
`;

exports[`matcher error toMatchInlineSnapshot Expected properties must be an object (non-null) without snapshot 1`] = `
<d>expect(</><r>received</><d>).</>toMatchInlineSnapshot<d>(</><g>properties</><d>)</>

Expand Down Expand Up @@ -41,15 +32,6 @@ exports[`matcher error toMatchInlineSnapshot Snapshot matchers cannot be used wi
<b>Matcher error</>: Snapshot matchers cannot be used with <b>not</>
`;

exports[`matcher error toMatchSnapshot Expected properties must be an object (array) 1`] = `
<d>expect(</><r>received</><d>).</>toMatchSnapshot<d>(</><g>properties</><d>)</>

<b>Matcher error</>: Expected <g>properties</> must be an object

Expected properties has type: array
Expected properties has value: <g>[]</>
`;

exports[`matcher error toMatchSnapshot Expected properties must be an object (non-null) 1`] = `
<d>expect(</><r>received</><d>).</>toMatchSnapshot<d>(</><g>properties</><d>)</>

Expand Down
85 changes: 60 additions & 25 deletions packages/jest-snapshot/src/__tests__/printSnapshot.test.ts
Expand Up @@ -246,19 +246,6 @@ describe('matcher error', () => {
}).toThrowErrorMatchingSnapshot();
});

test('Expected properties must be an object (array) with snapshot', () => {
const context = {
isNot: false,
promise: '',
} as Context;
const properties: Array<unknown> = [];
const snapshot = '';

expect(() => {
toMatchInlineSnapshot.call(context, received, properties, snapshot);
}).toThrowErrorMatchingSnapshot();
});

test('Inline snapshot must be a string', () => {
const context = {
isNot: false,
Expand Down Expand Up @@ -333,18 +320,6 @@ describe('matcher error', () => {
}).toThrowErrorMatchingSnapshot();
});

test('Expected properties must be an object (array)', () => {
const context = {
isNot: false,
promise: '',
} as Context;
const properties: Array<unknown> = [];

expect(() => {
toMatchSnapshot.call(context, received, properties);
}).toThrowErrorMatchingSnapshot();
});

describe('received value must be an object', () => {
const context = {
currentTestName: '',
Expand Down Expand Up @@ -785,6 +760,66 @@ describe('pass true', () => {
) as SyncExpectationResult;
expect(pass).toBe(true);
});

test('array', () => {
const context = {
equals: () => true,
isNot: false,
promise: '',
snapshotState: {
match() {
return {
expected: [],
pass: true,
};
},
},
utils: {
iterableEquality: () => [],
subsetEquality: () => [],
},
} as unknown as Context;
const received: Array<unknown> = [];
const properties: Array<unknown> = [];

const {pass} = toMatchSnapshot.call(
context,
received,
properties,
) as SyncExpectationResult;
expect(pass).toBe(true);
});
});

describe('toMatchInlineSnapshot', () => {
test('array', () => {
const context = {
equals: () => true,
isNot: false,
promise: '',
snapshotState: {
match() {
return {
expected: [],
pass: true,
};
},
},
utils: {
iterableEquality: () => [],
subsetEquality: () => [],
},
} as unknown as Context;
const received: Array<unknown> = [];
const properties: Array<unknown> = [];

const {pass} = toMatchInlineSnapshot.call(
context,
received,
properties,
) as SyncExpectationResult;
expect(pass).toBe(true);
});
});
});

Expand Down
90 changes: 72 additions & 18 deletions packages/jest-snapshot/src/__tests__/utils.test.ts
Expand Up @@ -303,7 +303,12 @@ describe('removeLinesBeforeExternalMatcherTrap', () => {
});

describe('DeepMerge with property matchers', () => {
const matcher = expect.any(String);
const matcherString = expect.any(String);
const matcherNumber = expect.any(Number);
const matcherObject = expect.any(Object);
const matcherArray = expect.any(Array);
const matcherBoolean = expect.any(Boolean);
const matcherAnything = expect.anything();

it.each(
/* eslint-disable sort-keys */
Expand All @@ -321,14 +326,14 @@ describe('DeepMerge with property matchers', () => {
// Matchers
{
data: {
two: matcher,
two: matcherString,
},
},
// Expected
{
data: {
one: 'one',
two: matcher,
two: matcherString,
},
},
],
Expand Down Expand Up @@ -358,32 +363,32 @@ describe('DeepMerge with property matchers', () => {
data: {
one: [
{
two: matcher,
two: matcherString,
},
],
six: [
{seven: matcher},
{seven: matcherString},
// Include an array element not present in the target
{eight: matcher},
{eight: matcherString},
],
nine: [[{ten: matcher}]],
nine: [[{ten: matcherString}]],
},
},
// Expected
{
data: {
one: [
{
two: matcher,
two: matcherString,
three: 'three',
},
{
four: 'four',
five: 'five',
},
],
six: [{seven: matcher}, {eight: matcher}],
nine: [[{ten: matcher}]],
six: [{seven: matcherString}, {eight: matcherString}],
nine: [[{ten: matcherString}]],
},
},
],
Expand All @@ -402,18 +407,18 @@ describe('DeepMerge with property matchers', () => {
// Matchers
{
data: {
one: [matcher],
one: [matcherString],
two: ['two'],
three: [matcher],
three: [matcherString],
five: 'five',
},
},
// Expected
{
data: {
one: [matcher],
one: [matcherString],
two: ['two'],
three: [matcher, 'four'],
three: [matcherString, 'four'],
five: 'five',
},
},
Expand All @@ -424,19 +429,68 @@ describe('DeepMerge with property matchers', () => {
// Target
[{name: 'one'}, {name: 'two'}, {name: 'three'}],
// Matchers
[{name: 'one'}, {name: matcher}, {name: matcher}],
[{name: 'one'}, {name: matcherString}, {name: matcherString}],
// Expected
[{name: 'one'}, {name: matcher}, {name: matcher}],
[{name: 'one'}, {name: matcherString}, {name: matcherString}],
],

[
'an array of different types',
// Target
[
5,
'some words',
[],
{},
true,
false,
5,
'some words',
[],
{},
true,
false,
],
// Matchers
[
matcherNumber,
matcherString,
matcherArray,
matcherObject,
matcherBoolean,
matcherBoolean,
matcherAnything,
matcherAnything,
matcherAnything,
matcherAnything,
matcherAnything,
matcherAnything,
],
// Expected
[
matcherNumber,
matcherString,
matcherArray,
matcherObject,
matcherBoolean,
matcherBoolean,
matcherAnything,
matcherAnything,
matcherAnything,
matcherAnything,
matcherAnything,
matcherAnything,
],
],

[
'an array of arrays',
// Target
[['one'], ['two'], ['three']],
// Matchers
[['one'], [matcher], [matcher]],
[['one'], [matcherString], [matcherString]],
// Expected
[['one'], [matcher], [matcher]],
[['one'], [matcherString], [matcherString]],
],
],
/* eslint-enable sort-keys */
Expand Down
7 changes: 1 addition & 6 deletions packages/jest-snapshot/src/index.ts
Expand Up @@ -163,11 +163,7 @@ export const toMatchSnapshot: MatcherFunctionWithContext<
if (length === 2 && typeof propertiesOrHint === 'string') {
hint = propertiesOrHint;
} else if (length >= 2) {
if (
Array.isArray(propertiesOrHint) ||
typeof propertiesOrHint !== 'object' ||
propertiesOrHint === null
) {
if (typeof propertiesOrHint !== 'object' || propertiesOrHint === null) {
const options: MatcherHintOptions = {
isNot: this.isNot,
promise: this.promise,
Expand Down Expand Up @@ -234,7 +230,6 @@ export const toMatchInlineSnapshot: MatcherFunctionWithContext<
}

if (
Array.isArray(propertiesOrSnapshot) ||
typeof propertiesOrSnapshot !== 'object' ||
propertiesOrSnapshot === null
) {
Expand Down
9 changes: 7 additions & 2 deletions packages/jest-snapshot/src/utils.ts
Expand Up @@ -220,15 +220,20 @@ export const saveSnapshotFile = (
);
};

const isAnyOrAnything = (input: object) =>
'$$typeof' in input &&
input.$$typeof === Symbol.for('jest.asymmetricMatcher') &&
['Any', 'Anything'].includes(input.constructor.name);

const deepMergeArray = (target: Array<any>, source: Array<any>) => {
const mergedOutput = Array.from(target);

source.forEach((sourceElement, index) => {
const targetElement = mergedOutput[index];

if (Array.isArray(target[index])) {
if (Array.isArray(target[index]) && Array.isArray(sourceElement)) {
mergedOutput[index] = deepMergeArray(target[index], sourceElement);
} else if (isObject(targetElement)) {
} else if (isObject(targetElement) && !isAnyOrAnything(sourceElement)) {
mergedOutput[index] = deepMerge(target[index], sourceElement);
} else {
// Source does not exist in target or target is primitive and cannot be deep merged
Expand Down

0 comments on commit 93c1967

Please sign in to comment.