Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support jest snapshot matchers with snapshot name #25

Merged
merged 5 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
})