From 353154fc6ecfa1ba87d812f9277bb50ede38c3b4 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 17 Feb 2025 17:27:25 +0100 Subject: [PATCH 1/5] fix: support jest snapshot matchers with snapshot name --- src/jest.snapshot.js | 19 +- .../jest/__snapshots__/snapshot.test.js.snap | 184 +++++++++++++----- tests/jest/snapshot.test.js | 19 ++ 3 files changed, 168 insertions(+), 54 deletions(-) diff --git a/src/jest.snapshot.js b/src/jest.snapshot.js index 69b34bd..ebc2d7a 100644 --- a/src/jest.snapshot.js +++ b/src/jest.snapshot.js @@ -50,24 +50,26 @@ beforeEach((t) => (context = t)) const getAssert = () => context?.assert ?? assert // do not use non-strict comparisons on this! // Wrap reported context.fullName so that snapshots are placed/looked for under jest-compatible keys -function wrapContextName(fn) { - if (context.fullName === context.name) return fn() // fast path +function wrapContextName(fn, snapshotName) { + if (context.fullName === context.name && !snapshotName) return fn() // fast path const value = context.fullName - assert(typeof value === 'string' && value.endsWith(` > ${context.name}`)) + assert(snapshotName || (typeof value === 'string' && value.endsWith(` > ${context.name}`))) const SuiteContext = Object.getPrototypeOf(context) const fullNameDescriptor = Object.getOwnPropertyDescriptor(SuiteContext, 'fullName') assert(fullNameDescriptor && fullNameDescriptor.configurable) + Object.defineProperty(context, 'fullName', { configurable: true, get() { assert.equal(this, context) - return value.replaceAll(' > ', ' ').replaceAll('', '') + const normalized = value.replaceAll(' > ', ' ').replaceAll('', '') + return snapshotName ? `${normalized}: ${snapshotName}` : normalized }, }) try { return fn() } finally { - assert.notEqual(context.fullName, value) + assert.notEqual(context.fullName, value, 'fullName should be different after test') delete context.fullName assert.equal(context.fullName, value) } @@ -110,7 +112,10 @@ const deepMerge = (obj, matcher) => { return res } -const snapOnDisk = (expect, orig, matcher) => { +const snapOnDisk = (expect, orig, matcherOrSnapshotName, snapshotName) => { + const matcher = typeof matcherOrSnapshotName === 'object' ? matcherOrSnapshotName : undefined + const name = typeof matcherOrSnapshotName === 'string' ? matcherOrSnapshotName : snapshotName + if (matcher) { expect(orig).toMatchObject(matcher) // If we passed, make appear that the above call never happened @@ -130,7 +135,7 @@ const snapOnDisk = (expect, orig, matcher) => { // Node.js always wraps with newlines, while jest wraps only those that are already multiline try { - wrapContextName(() => context.assert.snapshot(obj)) + wrapContextName(() => context.assert.snapshot(obj), name) } catch (e) { if (typeof e.expected === 'string') { const escaped = haveSnapshotsReportUnescaped ? e.expected : escapeSnapshot(e.expected) diff --git a/tests/jest/__snapshots__/snapshot.test.js.snap b/tests/jest/__snapshots__/snapshot.test.js.snap index 9b18cce..2451544 100644 --- a/tests/jest/__snapshots__/snapshot.test.js.snap +++ b/tests/jest/__snapshots__/snapshot.test.js.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` 1`] = `"empty names test"`; +exports[` 1`] = ` +"empty names test" +`; exports[`arrays 1`] = ` [ @@ -58,7 +58,9 @@ exports[`arrays 5`] = ` ] `; -exports[`async errors 1`] = `"slow but nah"`; +exports[`async errors 1`] = ` +"slow but nah" +`; exports[`complex 1`] = ` [ @@ -112,7 +114,9 @@ I Failed" `; -exports[`formateted objects 1`] = `Any`; +exports[`formateted objects 1`] = ` +Any +`; exports[`formateted objects 2`] = ` { @@ -121,7 +125,23 @@ exports[`formateted objects 2`] = ` } `; -exports[`mixed 1`] = `true`; +exports[`mixed 1`] = ` +true +`; + +exports[`mixed 10`] = ` +41 +`; + +exports[`mixed 11`] = ` +{ + "bar": "buz", +} +`; + +exports[`mixed 12`] = ` +{} +`; exports[`mixed 2`] = ` [ @@ -137,11 +157,17 @@ exports[`mixed 3`] = ` } `; -exports[`mixed 4`] = `43`; +exports[`mixed 4`] = ` +43 +`; -exports[`mixed 5`] = `{}`; +exports[`mixed 5`] = ` +{} +`; -exports[`mixed 6`] = `[]`; +exports[`mixed 6`] = ` +[] +`; exports[`mixed 7`] = ` [ @@ -151,19 +177,13 @@ exports[`mixed 7`] = ` ] `; -exports[`mixed 8`] = `[]`; - -exports[`mixed 9`] = `false`; - -exports[`mixed 10`] = `41`; - -exports[`mixed 11`] = ` -{ - "bar": "buz", -} +exports[`mixed 8`] = ` +[] `; -exports[`mixed 12`] = `{}`; +exports[`mixed 9`] = ` +false +`; exports[`nested test nested test one 1`] = ` { @@ -233,37 +253,89 @@ exports[`property matchers will check the values and pass 1`] = ` } `; -exports[`simple 1`] = `10`; +exports[`simple 1`] = ` +10 +`; -exports[`simple 2`] = `null`; +exports[`simple 10`] = ` +/hello/ +`; -exports[`simple 3`] = `undefined`; +exports[`simple 11`] = ` +true +`; -exports[`simple 4`] = `[]`; +exports[`simple 12`] = ` +NaN +`; -exports[`simple 5`] = `/xx/`; +exports[`simple 13`] = ` +{} +`; -exports[`simple 6`] = `Infinity`; +exports[`simple 14`] = ` +42 +`; -exports[`simple 7`] = `false`; +exports[`simple 15`] = ` +[] +`; -exports[`simple 8`] = `true`; +exports[`simple 16`] = ` +-Infinity +`; -exports[`simple 9`] = `{}`; +exports[`simple 2`] = ` +null +`; -exports[`simple 10`] = `/hello/`; +exports[`simple 3`] = ` +undefined +`; -exports[`simple 11`] = `true`; +exports[`simple 4`] = ` +[] +`; -exports[`simple 12`] = `NaN`; +exports[`simple 5`] = ` +/xx/ +`; -exports[`simple 13`] = `{}`; +exports[`simple 6`] = ` +Infinity +`; -exports[`simple 14`] = `42`; +exports[`simple 7`] = ` +false +`; -exports[`simple 15`] = `[]`; +exports[`simple 8`] = ` +true +`; -exports[`simple 16`] = `-Infinity`; +exports[`simple 9`] = ` +{} +`; + +exports[`supports named snapshots: not so public knowledge 1`] = ` +{ + "identity": "Batman", + "name": "Bruce Wayne", +} +`; + +exports[`supports named snapshots: public knowledge 1`] = ` +{ + "name": "Bruce Wayne", +} +`; + +exports[`supports named snapshots: public knowledge 2`] = ` +{ + "address": "Arkham Asylum", + "name": "Joker", +} +`; exports[`test A 1`] = ` { @@ -330,11 +402,17 @@ exports[`weird names " `; -exports[`weird names !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~ 1`] = `" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~"`; +exports[`weird names !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~ 1`] = ` +" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~" +`; -exports[`weird names $ 1`] = `"$"`; +exports[`weird names $ 1`] = ` +"$" +`; -exports[`weird names > 1`] = `">"`; +exports[`weird names > 1`] = ` +">" +`; exports[`weird names \\ \` @@ -344,7 +422,13 @@ exports[`weird names \\ \\\`\`" `; -exports[`weird names \\ 1`] = `"\\"`; +exports[`weird names \\ 1`] = ` +"\\" +`; + +exports[`weird names \` 1`] = ` +"\`" +`; exports[`weird names \` \` \` 1`] = ` @@ -352,13 +436,19 @@ exports[`weird names \` \` \`" `; -exports[`weird names \` \` \` 1`] = `"\` \` \`"`; - -exports[`weird names \` 1`] = `"\`"`; - -exports[`weird names {} 1`] = `"{}"`; +exports[`weird names \` \` \` 1`] = ` +"\` \` \`" +`; exports[`weird names multi -line 1`] = `0`; +line 1`] = ` +0 +`; + +exports[`weird names with \` 1`] = ` +42 +`; -exports[`weird names with \` 1`] = `42`; +exports[`weird names {} 1`] = ` +"{}" +`; diff --git a/tests/jest/snapshot.test.js b/tests/jest/snapshot.test.js index 48c3bc6..883a7e6 100644 --- a/tests/jest/snapshot.test.js +++ b/tests/jest/snapshot.test.js @@ -1,3 +1,5 @@ +import { createRequire } from 'node:module' + it('simple', () => { expect(10).toMatchSnapshot() expect(null).toMatchSnapshot() @@ -248,3 +250,20 @@ test('inline snapshots, prefixed', () => { } `) // end padding is ignored! }) + +it('supports named snapshots', () => { + expect({ name: 'Bruce Wayne' }).toMatchSnapshot('public knowledge') + expect({ identity: 'Batman', name: 'Bruce Wayne' }).toMatchSnapshot('not so public knowledge') + expect({ name: 'Joker', address: 'Arkham Asylum' }).toMatchSnapshot('public knowledge') + + const require = createRequire(import.meta.url) + const snapshots = require('./__snapshots__/snapshot.test.js.snap') + + ;[ + 'supports named snapshots: public knowledge 1', + 'supports named snapshots: public knowledge 2', + 'supports named snapshots: not so public knowledge 1', + ].forEach((name) => { + expect(Object.hasOwn(snapshots, name)).toBe(true) + }) +}) From 0b57cc92e73d84f42a84ee665103c51c436d02dc Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 17 Feb 2025 17:29:40 +0100 Subject: [PATCH 2/5] revert: assertion message --- src/jest.snapshot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jest.snapshot.js b/src/jest.snapshot.js index ebc2d7a..376637a 100644 --- a/src/jest.snapshot.js +++ b/src/jest.snapshot.js @@ -69,7 +69,7 @@ function wrapContextName(fn, snapshotName) { try { return fn() } finally { - assert.notEqual(context.fullName, value, 'fullName should be different after test') + assert.notEqual(context.fullName, value) delete context.fullName assert.equal(context.fullName, value) } From e9ae4cddc4fb2eadd7a7e34383afd12ff218cf0f Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 17 Feb 2025 18:42:55 +0100 Subject: [PATCH 3/5] fix: older nodejs versions --- src/jest.snapshot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jest.snapshot.js b/src/jest.snapshot.js index 376637a..ee0652d 100644 --- a/src/jest.snapshot.js +++ b/src/jest.snapshot.js @@ -130,7 +130,8 @@ const snapOnDisk = (expect, orig, matcherOrSnapshotName, snapshotName) => { if (!context?.assert?.snapshot) { const namePath = getTestNamePath(context).map((x) => (x === '' ? '' : x)) - return matchSnapshot(readSnapshot, getAssert(), namePath.join(' '), serialize(obj)) + const qualified = name ? [...namePath.slice(0, -1), `${namePath.at(-1)}: ${name}`] : namePath + return matchSnapshot(readSnapshot, getAssert(), qualified.join(' '), serialize(obj)) } // Node.js always wraps with newlines, while jest wraps only those that are already multiline From ef804ced615b50ac9a6d616c922edae947baed96 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 17 Feb 2025 18:48:15 +0100 Subject: [PATCH 4/5] test: fix test for hermes --- tests/jest/snapshot.test.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/jest/snapshot.test.js b/tests/jest/snapshot.test.js index 883a7e6..c4c6595 100644 --- a/tests/jest/snapshot.test.js +++ b/tests/jest/snapshot.test.js @@ -1,5 +1,3 @@ -import { createRequire } from 'node:module' - it('simple', () => { expect(10).toMatchSnapshot() expect(null).toMatchSnapshot() @@ -251,11 +249,19 @@ test('inline snapshots, prefixed', () => { `) // end padding is ignored! }) -it('supports named snapshots', () => { +it('supports named snapshots', async () => { expect({ name: 'Bruce Wayne' }).toMatchSnapshot('public knowledge') expect({ identity: 'Batman', name: 'Bruce Wayne' }).toMatchSnapshot('not so public knowledge') expect({ name: 'Joker', address: 'Arkham Asylum' }).toMatchSnapshot('public knowledge') + let createRequire + try { + ;({ createRequire } = await import('node:module')) + } catch { + // skip the rest of this test for environments without node:module + return + } + const require = createRequire(import.meta.url) const snapshots = require('./__snapshots__/snapshot.test.js.snap') From d821c2ae9e22a9911ce5480f33c9d98489b6ded5 Mon Sep 17 00:00:00 2001 From: Jan W Date: Tue, 18 Feb 2025 08:38:03 +0100 Subject: [PATCH 5/5] test: update fixture --- tests/jest/__snapshots__/snapshot.test.js.snap | 2 +- tests/jest/snapshot.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/jest/__snapshots__/snapshot.test.js.snap b/tests/jest/__snapshots__/snapshot.test.js.snap index 2451544..b720f1e 100644 --- a/tests/jest/__snapshots__/snapshot.test.js.snap +++ b/tests/jest/__snapshots__/snapshot.test.js.snap @@ -319,7 +319,7 @@ exports[`simple 9`] = ` exports[`supports named snapshots: not so public knowledge 1`] = ` { - "identity": "Batman", + "alterEgo": "Batman", "name": "Bruce Wayne", } `; diff --git a/tests/jest/snapshot.test.js b/tests/jest/snapshot.test.js index c4c6595..be143fe 100644 --- a/tests/jest/snapshot.test.js +++ b/tests/jest/snapshot.test.js @@ -251,7 +251,7 @@ test('inline snapshots, prefixed', () => { it('supports named snapshots', async () => { expect({ name: 'Bruce Wayne' }).toMatchSnapshot('public knowledge') - expect({ identity: 'Batman', name: 'Bruce Wayne' }).toMatchSnapshot('not so public knowledge') + expect({ alterEgo: 'Batman', name: 'Bruce Wayne' }).toMatchSnapshot('not so public knowledge') expect({ name: 'Joker', address: 'Arkham Asylum' }).toMatchSnapshot('public knowledge') let createRequire