Skip to content

Commit

Permalink
fix: support jest snapshot matchers with snapshot name (#25)
Browse files Browse the repository at this point in the history
* fix: support jest snapshot matchers with snapshot name

* revert: assertion message

* fix: older nodejs versions

* test: fix test for hermes

* test: update fixture
  • Loading branch information
sparten11740 authored Feb 19, 2025
1 parent 015f4c4 commit ad63b5e
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 54 deletions.
20 changes: 13 additions & 7 deletions src/jest.snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<anonymous>', '')
const normalized = value.replaceAll(' > ', ' ').replaceAll('<anonymous>', '')
return snapshotName ? `${normalized}: ${snapshotName}` : normalized
},
})
try {
Expand Down Expand Up @@ -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
Expand All @@ -125,12 +130,13 @@ const snapOnDisk = (expect, orig, matcher) => {

if (!context?.assert?.snapshot) {
const namePath = getTestNamePath(context).map((x) => (x === '<anonymous>' ? '' : 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)
Expand Down
184 changes: 137 additions & 47 deletions tests/jest/__snapshots__/snapshot.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[` 1`] = `"empty names test"`;
exports[` 1`] = `
"empty names test"
`;

exports[`arrays 1`] = `
[
Expand Down Expand Up @@ -58,7 +58,9 @@ exports[`arrays 5`] = `
]
`;
exports[`async errors 1`] = `"slow but nah"`;
exports[`async errors 1`] = `
"slow but nah"
`;
exports[`complex 1`] = `
[
Expand Down Expand Up @@ -112,7 +114,9 @@ I
Failed"
`;
exports[`formateted objects 1`] = `Any<Number>`;
exports[`formateted objects 1`] = `
Any<Number>
`;
exports[`formateted objects 2`] = `
{
Expand All @@ -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`] = `
[
Expand All @@ -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`] = `
[
Expand All @@ -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`] = `
{
Expand Down Expand Up @@ -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`] = `
{
Expand Down Expand Up @@ -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 \\
\`
Expand All @@ -344,21 +422,33 @@ exports[`weird names \\
\\\`\`"
`;
exports[`weird names \\ 1`] = `"\\"`;
exports[`weird names \\ 1`] = `
"\\"
`;
exports[`weird names \` 1`] = `
"\`"
`;
exports[`weird names \` \`
\` 1`] = `
"\` \`
\`"
`;
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`] = `
"{}"
`;
25 changes: 25 additions & 0 deletions tests/jest/snapshot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})

0 comments on commit ad63b5e

Please sign in to comment.