Skip to content

Commit

Permalink
clean process.cwd() out of snapshots by default
Browse files Browse the repository at this point in the history
Also, make the test-built package.json a bit closer to it's eventual
state, just to avoid cases where the build fails, and it leaves it in a
weird state.

Fix: #885
  • Loading branch information
isaacs committed Sep 14, 2023
1 parent 4c0dc72 commit d7e7e4f
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 17 deletions.
37 changes: 37 additions & 0 deletions src/snapshot/src/clean-cwd.ts
@@ -0,0 +1,37 @@
import { cwd } from '@tapjs/core'

// escape a string to json, then unwrap the "
// used for cwd detection
const esc = (s: string) => {
const j = JSON.stringify(s)
return j.substring(1, j.length - 1)
}
const cwdPosix = cwd.replace(/\\/g, '/')
const cwdLcase = cwd.toLowerCase()
const cwdPosixLcase = cwdPosix.toLowerCase()
const cwds = new Set([cwd, cwdPosix, cwdLcase, cwdPosixLcase])
for (const c of [...cwds]) {
cwds.add(esc(c))
cwds.add(esc(esc(c)))
cwds.add(esc(esc(esc(c))))
cwds.add(esc(esc(esc(esc(c)))))
}

export const cleanCWD = (snap: string) => {
const tag = '{CWD}'
for (const c of cwds) {
let i = -1
// pad it out so that the length matches through the walk
const replace = tag + '\u0001'.repeat(c.length - tag.length)
while (
-1 !== (i = snap.toLowerCase().indexOf(c.toLowerCase(), i))
) {
snap =
snap.substring(0, i) +
replace +
snap.substring(i + replace.length)
}
snap = snap.split(replace).join(tag)
}
return snap
}
69 changes: 59 additions & 10 deletions src/snapshot/src/index.ts
Expand Up @@ -12,6 +12,7 @@ import { relative, resolve } from 'path'
import { CompareOptions, format, strict } from 'tcompare'
import { Deferred } from 'trivial-deferred'
import { fileURLToPath } from 'url'
import { cleanCWD } from './clean-cwd.js'
import { SnapshotProviderDefault } from './provider.js'

const defaultFormatSnapshot =
Expand Down Expand Up @@ -269,6 +270,12 @@ export class SnapshotPlugin {
})(Buffer.from(found))
}

// for the cwd, probably the most common error in snapshot
// testing, see https://github.com/tapjs/tapjs/issues/885
if (process.env.TAP_SNAPSHOT_CLEAN_CWD !== '0') {
found = cleanCWD(found)
}

if (this.writeSnapshot) {
this.#snapshot.snap(found, m)
return this.#t.pass(...me)
Expand Down Expand Up @@ -351,17 +358,17 @@ export class SnapshotPlugin {
}
}

/**
* Generate snapshot files for `t.matchSnapshot()` assertions.
*
* Defaults to true if the `TAP_SNAPSHOT` environment variable is set to
* `1`, or if the `npm_lifecycle_event` environment variable is set to
* either `snap` or `snapshot`.
*
* That is, if you put `"scripts": { "snap": "tap" }` in your package.json
* file, then `npm run snap` will generate snapshots.
*/
export const config = {
/**
* Generate snapshot files for `t.matchSnapshot()` assertions.
*
* Defaults to true if the `TAP_SNAPSHOT` environment variable is set to
* `1`, or if the `npm_lifecycle_event` environment variable is set to
* either `snap` or `snapshot`.
*
* That is, if you put `"scripts": { "snap": "tap" }` in your package.json
* file, then `npm run snap` will generate snapshots.
*/
snapshot: {
type: 'boolean',
short: 'S',
Expand All @@ -377,4 +384,46 @@ export const config = {
snapshots.
`,
},

/**
* Automatically clean the current working directory out of snapshot data,
* replacing it with a token.
*
* This helps prevent frustrating "works on my machine" when tests capture an
* error message or file path, but then fail when run on any other system,
* and so is enabled by default.`,
*/
'snapshot-clean-cwd': {
type: 'boolean',
default: true,
description: `Automatically clean the current working directory out of
snapshot data, replacing it with a token.
This helps prevent frustrating "works on my machine" when
tests capture an error message or file path, but then fail
when run on any other system, and so is enabled by default.`,
},

/**
* Do not automatically clean the current working directory out of snapshot
* data, replacing it with a token.
*
* May be required when using fixtures or other snapshot data sources that
* intentionally include strings which happen to match the current working
* directory.
*
* Not recommended! It's better to leave this protection on, and edit your
* fixtures so that they do not include the cwd.
*/
'no-snapshot-clean-cwd': {
type: 'boolean',
description: `Do not clean the current working directory out of snapshots
May be required when using fixtures or other snapshot data
sources that intentionally include strings which happen to
match the current working directory.
Not recommended! It's better to leave this protection on, and
edit your fixtures so that they do not include the cwd.`,
},
}
24 changes: 24 additions & 0 deletions src/snapshot/tap-snapshots/test/index.ts.test.cjs
Expand Up @@ -7,6 +7,18 @@
'use strict'
exports[`test/index.ts > TAP > config 1`] = `
Object {
"no-snapshot-clean-cwd": Object {
"description": String(
Do not clean the current working directory out of snapshots
May be required when using fixtures or other snapshot data
sources that intentionally include strings which happen to
match the current working directory.
Not recommended! It's better to leave this protection on, and
edit your fixtures so that they do not include the cwd.
),
},
"snapshot": Object {
"description": String(
Generate snapshot files for \`t.matchSnapshot()\`
Expand All @@ -24,6 +36,18 @@ Object {
"short": "S",
"type": "boolean",
},
"snapshot-clean-cwd": Object {
"default": true,
"description": String(
Automatically clean the current working directory out of
snapshot data, replacing it with a token.
This helps prevent frustrating "works on my machine" when
tests capture an error message or file path, but then fail
when run on any other system, and so is enabled by default.
),
"type": "boolean",
},
}
`

Expand Down
40 changes: 40 additions & 0 deletions src/snapshot/test/clean-cwd.ts
@@ -0,0 +1,40 @@
import t from 'tap'

t.capture(process, 'cwd', () => 'D:\\some\\TEST-Path')
const { cleanCWD } = (await t.mockImport('../dist/esm/clean-cwd.js', {
'@tapjs/core': {
cwd: 'D:\\some\\TEST-Path',
},
})) as typeof import('../dist/esm/clean-cwd.js')

const s = process.cwd()
const p = s.replace(/\\/g, '/')

const obj = { s: [s, p, s, p] }
const j1 = JSON.stringify(obj)
const j2 = JSON.stringify({ json: j1 })
const j3 = JSON.stringify({ json: j2 })
const j4 = JSON.stringify({ json: j3 })
const tests = new Set([s, p, j1, j2, j3, j4])
for (const s of [...tests]) {
tests.add(s.toUpperCase())
tests.add(s.toLowerCase())
}

for (const c of tests) {
t.equal(
cleanCWD(c).toLowerCase().indexOf('test-path'),
-1,
'escaped',
{
string: c,
}
)
}

// just capture the limit. only 4 levels of escaping supported
t.not(
cleanCWD(JSON.stringify(j4)).toLowerCase().indexOf('test-path'),
-1,
'does not escape beyond 4 levels'
)
2 changes: 1 addition & 1 deletion src/snapshot/test/index.ts
Expand Up @@ -27,7 +27,7 @@ class TestSnapshotProvider implements SnapshotProvider {
testProviders.add(this)
}
read(msg: string) {
return this.data[msg]
return this.data[msg] as string
}
snap(data: string, msg: string) {
this.data[msg] = data
Expand Down
14 changes: 12 additions & 2 deletions src/test/scripts/package-template.json
Expand Up @@ -5,6 +5,7 @@
"description": "the plugged in Test class for node-tap",
"author": "Isaac Z. Schlueter <i@izs.me> (https://blog.izs.me)",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
Expand All @@ -16,10 +17,19 @@
}
}
},
"files": ["dist"],
"files": [
"dist"
],
"scripts": {
"prepare": "tshy",
"typedoc": "typedoc --tsconfig .tshy/esm.json ./src/*.ts"
},
"license": "BlueOak-1.0.0"
"license": "BlueOak-1.0.0",
"type": "module",
"tshy": {
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
}
}
31 changes: 31 additions & 0 deletions src/test/test-built/dist/commonjs/index.d.ts
Expand Up @@ -174,6 +174,37 @@ export declare const config: <C extends ConfigSet>(jack: Jack<C>) => Jack<C & im
multiple?: false | undefined;
delim?: undefined;
};
}> & import("jackspeak").ConfigSetFromMetaSet<"boolean", false, {
"snapshot-clean-cwd": {
type: string;
default: boolean;
description: string;
} & {
type: "boolean";
short?: string | undefined;
default?: boolean | undefined;
description?: string | undefined;
hint?: undefined;
validate?: ((v: any) => v is boolean) | undefined;
} & {
multiple?: false | undefined;
delim?: undefined;
};
}> & import("jackspeak").ConfigSetFromMetaSet<"boolean", false, {
"no-snapshot-clean-cwd": {
type: string;
description: string;
} & {
type: "boolean";
short?: string | undefined;
default?: boolean | undefined;
description?: string | undefined;
hint?: undefined;
validate?: ((v: any) => v is boolean) | undefined;
} & {
multiple?: false | undefined;
delim?: undefined;
};
}> & import("jackspeak").ConfigSetFromMetaSet<"boolean", false, {
typecheck: {
type: string;
Expand Down

0 comments on commit d7e7e4f

Please sign in to comment.