From ad63b5eb34cadd32557f59ebab8badd6fe0d5988 Mon Sep 17 00:00:00 2001 From: Jan W Date: Wed, 19 Feb 2025 08:30:45 +0100 Subject: [PATCH] fix: support jest snapshot matchers with snapshot name (#25) * fix: support jest snapshot matchers with snapshot name * revert: assertion message * fix: older nodejs versions * test: fix test for hermes * test: update fixture --- src/jest.snapshot.js | 20 +- .../jest/__snapshots__/snapshot.test.js.snap | 184 +++++++++++++----- tests/jest/snapshot.test.js | 25 +++ 3 files changed, 175 insertions(+), 54 deletions(-) diff --git a/src/jest.snapshot.js b/src/jest.snapshot.js index 69b34bd..ee0652d 100644 --- a/src/jest.snapshot.js +++ b/src/jest.snapshot.js @@ -50,18 +50,20 @@ 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 { @@ -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 @@ -125,12 +130,13 @@ const snapOnDisk = (expect, orig, matcher) => { 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 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..b720f1e 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`] = ` +{ + "alterEgo": "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..be143fe 100644 --- a/tests/jest/snapshot.test.js +++ b/tests/jest/snapshot.test.js @@ -248,3 +248,28 @@ test('inline snapshots, prefixed', () => { } `) // end padding is ignored! }) + +it('supports named snapshots', async () => { + expect({ name: 'Bruce Wayne' }).toMatchSnapshot('public knowledge') + expect({ alterEgo: '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') + + ;[ + '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) + }) +})