@exodus/test 1.0.0-rc.113 → 1.0.0-rc.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -25
- package/bin/browsers.js +7 -1
- package/bin/find-binary.js +2 -0
- package/bin/index.js +8 -6
- package/package.json +13 -12
- package/src/engine.pure.cjs +36 -42
- package/src/engine.pure.mock.module.cjs +44 -0
- package/src/engine.pure.snapshot.cjs +12 -7
- package/src/expect.cjs +3 -0
- package/src/jest.mock.js +0 -1
package/README.md
CHANGED
|
@@ -12,27 +12,28 @@ A runner for `node:test`, `jest`, and `tape` test suites on top of `node:test` (
|
|
|
12
12
|
|
|
13
13
|
It can run your existing tests on [all runtimes and also browsers](#engines), with snapshots and module mocks:
|
|
14
14
|
|
|
15
|
-
[](https://nodejs.org/api/test.html)
|
|
16
|
+
[](https://deno.com/)
|
|
17
|
+
[](https://bun.sh/)
|
|
18
|
+
[](http://electronjs.org/)
|
|
19
|
+
[](https://github.com/cloudflare/workerd)\
|
|
20
|
+
[](https://www.chromium.org/Home/)
|
|
21
|
+
[](http://webkit.org/)
|
|
22
|
+
[](https://github.com/mozilla-firefox)
|
|
23
|
+
[](https://github.com/brave)
|
|
24
|
+
[](https://github.com/microsoftedge)
|
|
25
|
+
[](https://servo.org/)\
|
|
26
|
+
[](https://hermesengine.dev)
|
|
27
|
+
[](https://v8.dev/docs/d8)
|
|
28
|
+
[](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html)
|
|
29
|
+
[](https://spidermonkey.dev/)\
|
|
30
|
+
[](https://github.com/quickjs-ng/quickjs)
|
|
31
|
+
[](https://github.com/Moddable-OpenSource/moddable)
|
|
32
|
+
[](https://github.com/oracle/graaljs)
|
|
33
|
+
[](https://github.com/boa-dev/boa)
|
|
34
|
+
[](https://github.com/trynova/nova)
|
|
35
|
+
[](https://github.com/Samsung/escargot)
|
|
36
|
+
[](https://github.com/engine262/engine262)
|
|
36
37
|
|
|
37
38
|
Compatible with tests written in:
|
|
38
39
|
|
|
@@ -44,10 +45,11 @@ See [documentation](https://exodusoss.github.io/test/).
|
|
|
44
45
|
|
|
45
46
|
## Features
|
|
46
47
|
|
|
48
|
+
- Zero learning curve: runs your existing tests
|
|
49
|
+
- Runs anywhere (including Hermes, the [React Native](https://reactnative.dev/) JavaScript engine)
|
|
47
50
|
- Native ESM, including in Jest tests
|
|
48
51
|
- Esbuild on the fly for old faux-ESM interop (enable via `--esbuild`)
|
|
49
52
|
- TypeScript support
|
|
50
|
-
- Runs anywhere (including Hermes, the [React Native](https://reactnative.dev/) JavaScript engine)
|
|
51
53
|
- Use snapshots to cross-compare between runtimes, browsers and barebones (including Hermes)
|
|
52
54
|
- Testsuite-agnostic — can run any file as long as it sets exit code based on test results
|
|
53
55
|
- Built-in [Jest](https://jestjs.io) compatibility (with `--jest`), including `jest.*` global
|
|
@@ -65,8 +67,8 @@ See [documentation](https://exodusoss.github.io/test/).
|
|
|
65
67
|
- JSDOM env support
|
|
66
68
|
- Hanging tests error by default (unlike `jest`)
|
|
67
69
|
- Babel support, picks up your Babel config (enable via `--babel`)
|
|
68
|
-
- Unlike `bun:test`, it runs test files in isolated contexts \
|
|
69
|
-
Bun leaks globals / side effects between test files ([ref](https://github.com/oven-sh/bun/issues/6024)),
|
|
70
|
+
- Unlike `bun:test`, it runs test files in isolated contexts on Bun. \
|
|
71
|
+
Without this, Bun leaks globals / side effects between test files ([ref](https://github.com/oven-sh/bun/issues/6024)),
|
|
70
72
|
and has incompatible `test()` lifecycle / order
|
|
71
73
|
|
|
72
74
|
## Getting started
|
|
@@ -174,10 +176,11 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
|
|
|
174
176
|
- `hermes:bundle` — [Hermes](https://hermesengine.dev) (React Native JavaScript engine)
|
|
175
177
|
- `spidermonkey:bundle` — [SpiderMonkey](https://spidermonkey.dev/) (Firefox/Gecko JavaScript engine)
|
|
176
178
|
- `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs)
|
|
177
|
-
- `xs:bundle` — [XS](https://github.com/Moddable-OpenSource/moddable
|
|
179
|
+
- `xs:bundle` — [Moddable XS](https://github.com/Moddable-OpenSource/moddable)
|
|
178
180
|
- `graaljs:bundle` — [GraalJS](https://github.com/oracle/graaljs)
|
|
179
181
|
- `escargot:bundle` — [Escargot](https://github.com/Samsung/escargot)
|
|
180
182
|
- `boa:bundle` — [Boa](https://github.com/boa-dev/boa)
|
|
183
|
+
- `nova:bundle` — [Nova](https://github.com/trynova/nova) (note that Nova itself is a Work In Progress)
|
|
181
184
|
- `engine262:bundle` - [engine262](https://github.com/engine262/engine262), the per-spec implementation of ECMA-262
|
|
182
185
|
(install with [esvu](https://npmjs.com/package/esvu))
|
|
183
186
|
|
package/bin/browsers.js
CHANGED
|
@@ -75,7 +75,13 @@ export async function run(runner, args, { binary, devtools, dropNetwork, timeout
|
|
|
75
75
|
const [stdout, stderr] = [[], []]
|
|
76
76
|
|
|
77
77
|
assert(Object.hasOwn(launchers, runner), 'Unexpected runner')
|
|
78
|
-
|
|
78
|
+
try {
|
|
79
|
+
if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools: !!devtools })
|
|
80
|
+
} catch {
|
|
81
|
+
// Try a second time, this sometime times out
|
|
82
|
+
if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools: !!devtools })
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
const { page, context } = await newPage(runner, await launched[runner], { binary, dropNetwork })
|
|
80
86
|
|
|
81
87
|
if (throttle) {
|
package/bin/find-binary.js
CHANGED
|
@@ -69,6 +69,8 @@ function findBinaryOnce(name) {
|
|
|
69
69
|
return require('electron')
|
|
70
70
|
case 'workerd':
|
|
71
71
|
return require.resolve('workerd/bin/workerd')
|
|
72
|
+
case 'porffor':
|
|
73
|
+
return require.resolve('porffor/porf')
|
|
72
74
|
case 'jerryscript':
|
|
73
75
|
name = 'jerry' // look under this name, including in global
|
|
74
76
|
return findFile([jsvu, esvu])
|
package/bin/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const ENGINES = new Map(
|
|
|
39
39
|
'deno:test': { binary: 'deno', binaryArgs: denoT, loader: '--preload', ts: 'auto' },
|
|
40
40
|
'deno:pure': { binary: 'deno', binaryArgs: denoA, pure: true, loader: '--preload', ts: 'auto' },
|
|
41
41
|
'deno:bundle': { binary: 'deno', binaryArgs: ['run'], target: 'deno1', ...bundleOpts },
|
|
42
|
+
'workerd:bundle': { binary: 'workerd', binaryArgs: ['test'], ...bundleOpts },
|
|
42
43
|
// Barebone engines
|
|
43
44
|
'v8:bundle': { binary: 'd8', binaryArgs: ['--expose-gc'], ...bareboneOpts },
|
|
44
45
|
'jsc:bundle': { binary: 'jsc', target: 'safari13', ...bareboneOpts },
|
|
@@ -55,9 +56,9 @@ const ENGINES = new Map(
|
|
|
55
56
|
'boa:bundle': { binary: 'boa', binaryArgs: ['-m'], ...bareboneOpts },
|
|
56
57
|
'nova:bundle': { binary: 'nova', binaryArgs: ['eval'], ...bareboneOpts },
|
|
57
58
|
'jerryscript:bundle': { binary: 'jerryscript', ...bareboneOpts },
|
|
59
|
+
'porffor:bundle': { binary: 'porffor', ...bareboneOpts }, // blocked on https://github.com/CanadaHonk/porffor/issues/176
|
|
58
60
|
// Special case: running a browser from CLI like a bundle
|
|
59
61
|
'servo:bundle': { binary: 'servo', binaryArgs: ['--headless'], ...bundleOpts, html: true },
|
|
60
|
-
'workerd:bundle': { binary: 'workerd', binaryArgs: ['test'], ...bundleOpts, workerd: true },
|
|
61
62
|
// Browser engines
|
|
62
63
|
'chrome:puppeteer': { binary: 'chrome', browsers: 'puppeteer', ...bundleOpts },
|
|
63
64
|
'firefox:puppeteer': { binary: 'firefox', browsers: 'puppeteer', ...bundleOpts },
|
|
@@ -71,8 +72,8 @@ const ENGINES = new Map(
|
|
|
71
72
|
})
|
|
72
73
|
)
|
|
73
74
|
const bareOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes', 'shermes']
|
|
74
|
-
const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', '
|
|
75
|
-
const bareIncomplete = ['ladybird-js', 'nova', '
|
|
75
|
+
const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', 'jerryscript', 'engine262']
|
|
76
|
+
const bareIncomplete = ['ladybird-js', 'nova', 'duktape']
|
|
76
77
|
|
|
77
78
|
const getEnvFlag = (name) => {
|
|
78
79
|
if (!Object.hasOwn(process.env, name)) return
|
|
@@ -662,7 +663,8 @@ async function launch(binary, args, opts = {}, buffering = false) {
|
|
|
662
663
|
}
|
|
663
664
|
|
|
664
665
|
const barebones = [...bareOk, ...bareNotrack, ...bareIncomplete]
|
|
665
|
-
|
|
666
|
+
const bins = ['node', 'bun', 'deno', 'electron', 'workerd', 'servo', 'jerry', 'duk', ...barebones]
|
|
667
|
+
assertBinary(binary, bins)
|
|
666
668
|
if (binary === c8 && process.platform === 'win32') {
|
|
667
669
|
;[binary, args] = ['node', [binary, ...args]]
|
|
668
670
|
}
|
|
@@ -693,7 +695,7 @@ if (options.pure) {
|
|
|
693
695
|
|
|
694
696
|
setEnv('EXODUS_TEST_CONTEXT', 'pure')
|
|
695
697
|
const missUnhandled = bareNotrack.includes(options.platform) || isBrowserLike
|
|
696
|
-
const isIncomplete = bareIncomplete.includes(options.platform)
|
|
698
|
+
const isIncomplete = bareIncomplete.includes(options.platform) || options.platform === 'workerd'
|
|
697
699
|
if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
|
|
698
700
|
if (isIncomplete) warnHuman(`Warning: ${engineName} support is incomplete`)
|
|
699
701
|
|
|
@@ -707,7 +709,7 @@ if (options.pure) {
|
|
|
707
709
|
await writeFile(bundled.fileHtml, `<script src="${bundled.file}"></script>`)
|
|
708
710
|
}
|
|
709
711
|
|
|
710
|
-
if (bundled && options.workerd) {
|
|
712
|
+
if (bundled && options.platform === 'workerd') {
|
|
711
713
|
bundled.fileWrapper = `${bundled.file}.wrapper.js`
|
|
712
714
|
bundled.fileConfig = `${bundled.file}.capnp`
|
|
713
715
|
assert(/^[a-z0-9/_.-]+\.js$/iu.test(bundled.file), bundled.file)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/test",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.115",
|
|
4
4
|
"author": "Exodus Movement, Inc.",
|
|
5
5
|
"description": "A test suite runner",
|
|
6
6
|
"homepage": "https://github.com/ExodusOSS/test",
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"src/engine.js",
|
|
89
89
|
"src/engine.node.cjs",
|
|
90
90
|
"src/engine.pure.cjs",
|
|
91
|
+
"src/engine.pure.mock.module.cjs",
|
|
91
92
|
"src/engine.pure.snapshot.cjs",
|
|
92
93
|
"src/engine.select.cjs",
|
|
93
94
|
"src/exodus.js",
|
|
@@ -125,22 +126,22 @@
|
|
|
125
126
|
"tape.js"
|
|
126
127
|
],
|
|
127
128
|
"scripts": {
|
|
128
|
-
"test:_bundle": "EXODUS_TEST_IGNORE='tests/{{jest-extended,inband}/**,jest-when/when.test.*,jest/jest.resetModules.*,jest/mock/jest.mock.mocks-dir.test.js}' npm run test --",
|
|
129
|
+
"test:_bundle": "EXODUS_TEST_IGNORE='tests/{{vendor/jest-extended,inband}/**,vendor/jest-when/when.test.*,jest/jest.resetModules.*,jest/mock/jest.mock.mocks-dir.test.js}' npm run test --",
|
|
129
130
|
"test": "npm run test:jest --",
|
|
130
131
|
"test:all": "npm run test:simple && npm run test:jest && npm run test:tape && npm run test:native && npm run test:esbuild && npm run test:pure && npm run test:fetch && npm run test:jsdom && npm run test:bundle",
|
|
131
|
-
"test:native": "EXODUS_TEST_IGNORE='
|
|
132
|
+
"test:native": "EXODUS_TEST_IGNORE='**/jest.transformed/**/*' ./bin/index.js --jest 'tests/**/*.test.{js,cjs,mjs}'",
|
|
132
133
|
"test:typescript": "node ./bin/index.js --jest --typescript tests/typescript.test.ts",
|
|
133
|
-
"test:jest": "node ./bin/index.js --jest --esbuild=ts,
|
|
134
|
+
"test:jest": "node ./bin/index.js --jest --esbuild=ts,sum.test.js",
|
|
134
135
|
"test:esbuild": "node ./bin/index.js --jest --esbuild",
|
|
135
|
-
"test:tape": "node ./bin/index.js
|
|
136
|
+
"test:tape": "node ./bin/index.js tests/vendor/tape/test/*.js tests/tape/*.test.*js",
|
|
136
137
|
"test:simple": "node ./bin/index.js 'tests/*.test.js'",
|
|
137
138
|
"test:pure": "EXODUS_TEST_ENGINE=node:pure npm run test --",
|
|
138
139
|
"test:bundle": "EXODUS_TEST_ENGINE=node:bundle npm run test:_bundle --",
|
|
139
140
|
"test:bun:test": "EXODUS_TEST_ENGINE=bun:test npm run test --",
|
|
140
141
|
"test:bun:pure": "EXODUS_TEST_ENGINE=bun:pure npm run test --",
|
|
141
142
|
"test:bun:bundle": "EXODUS_TEST_ENGINE=bun:bundle npm run test:_bundle",
|
|
142
|
-
"test:deno:test": "EXODUS_TEST_ENGINE=deno:test node ./bin/index.js tests/tape
|
|
143
|
-
"test:deno:pure": "EXODUS_TEST_IGNORE='**/jest
|
|
143
|
+
"test:deno:test": "EXODUS_TEST_ENGINE=deno:test node ./bin/index.js tests/tape/*.test.*js tests/simple.test.js tests/env.test.js 'tests/engines/**.test.js' tests/node/simple.test.js tests/node/order.test.js tests/node/snapshot.test.js tests/node/mock/mock.import.test.js",
|
|
144
|
+
"test:deno:pure": "EXODUS_TEST_IGNORE='**/jest/examples/timer/**' EXODUS_TEST_ENGINE=deno:pure npm run test --",
|
|
144
145
|
"test:deno:bundle": "EXODUS_TEST_ENGINE=deno:bundle npm run test:_bundle --",
|
|
145
146
|
"test:electron:node": "EXODUS_TEST_ENGINE=electron-as-node:test npm run test",
|
|
146
147
|
"test:electron:node:pure": "EXODUS_TEST_ENGINE=electron-as-node:pure npm run test --",
|
|
@@ -175,20 +176,20 @@
|
|
|
175
176
|
"playwright": "node ./bin/index.js --playwright",
|
|
176
177
|
"esvu": "esvu",
|
|
177
178
|
"jsvu": "jsvu",
|
|
178
|
-
"jest": "NODE_OPTIONS=--experimental-vm-modules jest tests/jest/ tests/jest-when/",
|
|
179
|
+
"jest": "NODE_OPTIONS=--experimental-vm-modules jest tests/jest/ tests/vendor/jest/ tests/vendor/jest-when/",
|
|
179
180
|
"lint": "prettier --list-different . && eslint .",
|
|
180
181
|
"lint:fix": "prettier --write . && eslint --fix ."
|
|
181
182
|
},
|
|
182
183
|
"optionalDependencies": {
|
|
183
184
|
"@chalker/queue": "^1.0.1",
|
|
184
|
-
"@exodus/replay": "^1.0.0-rc.
|
|
185
|
-
"@exodus/test-bundler": "1.0.0-rc.
|
|
185
|
+
"@exodus/replay": "^1.0.0-rc.11",
|
|
186
|
+
"@exodus/test-bundler": "1.0.0-rc.16",
|
|
186
187
|
"c8": "^9.1.0",
|
|
187
188
|
"expect": "^30.2.0",
|
|
188
189
|
"fast-glob": "^3.2.11",
|
|
189
|
-
"playwright-core": "^1.
|
|
190
|
+
"playwright-core": "^1.58.2",
|
|
190
191
|
"pretty-format": "^30.2.0",
|
|
191
|
-
"puppeteer-core": "^24.
|
|
192
|
+
"puppeteer-core": "^24.37.2",
|
|
192
193
|
"tsx": "^4.21.0"
|
|
193
194
|
},
|
|
194
195
|
"devDependencies": {
|
package/src/engine.pure.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const assert = require('node:assert/strict')
|
|
2
2
|
const assertLoose = require('node:assert')
|
|
3
3
|
const { matchSnapshot } = require('./engine.pure.snapshot.cjs')
|
|
4
|
+
const { mockModule, baseFile, ...mockModuleMethods } = require('./engine.pure.mock.module.cjs')
|
|
4
5
|
|
|
5
6
|
const { setTimeout, setInterval, setImmediate, Date } = globalThis
|
|
6
7
|
const { clearTimeout, clearInterval, clearImmediate } = globalThis
|
|
@@ -30,7 +31,7 @@ const check = (condition, message) => {
|
|
|
30
31
|
|
|
31
32
|
function parseArgs(args) {
|
|
32
33
|
check(args.length <= 3)
|
|
33
|
-
const name = typeof args[0] === 'string' ? args.shift() :
|
|
34
|
+
const name = typeof args[0] === 'string' ? args.shift() : undefined
|
|
34
35
|
const fn = args.pop()
|
|
35
36
|
const options = args.pop() || {}
|
|
36
37
|
return { name, options, fn }
|
|
@@ -44,8 +45,10 @@ class Context {
|
|
|
44
45
|
#fullName
|
|
45
46
|
#assert
|
|
46
47
|
#hooks
|
|
48
|
+
#mock
|
|
47
49
|
|
|
48
50
|
constructor(parent, name, options = {}) {
|
|
51
|
+
if (!name || typeof name !== 'string') name = '<anonymous>'
|
|
49
52
|
Object.assign(this, { root: parent?.root, parent, name, options })
|
|
50
53
|
this.#fullName = parent && parent !== parent.root ? `${parent.fullName} > ${name}` : name
|
|
51
54
|
if (this.#fullName === name) this.#fullName = this.#fullName.replace(INBAND_PREFIX_REGEX, '')
|
|
@@ -79,6 +82,11 @@ class Context {
|
|
|
79
82
|
return this.#assert
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
get mock() {
|
|
86
|
+
if (!this.#mock) this.#mock = new MockTracker()
|
|
87
|
+
return this.#mock
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
async addHook(type, fn) {
|
|
83
91
|
if (!this.#hooks) this.#hooks = Object.create(null)
|
|
84
92
|
if (!this.#hooks[type]) this.#hooks[type] = []
|
|
@@ -251,13 +259,18 @@ test.todo = (...args) => {
|
|
|
251
259
|
return test(name, { ...options, todo: true }, fn)
|
|
252
260
|
}
|
|
253
261
|
|
|
262
|
+
const codeError = (msg, code) => Object.assign(new Error(msg), { code })
|
|
263
|
+
|
|
264
|
+
let mockTimersEnabled = false
|
|
265
|
+
|
|
254
266
|
class MockTimers {
|
|
255
267
|
#enabled = false
|
|
256
268
|
#base = 0
|
|
257
269
|
#elapsed = 0
|
|
258
270
|
#queue = []
|
|
259
271
|
enable({ now = 0, apis = ['setInterval', 'setTimeout', 'setImmediate', 'Date'] } = {}) {
|
|
260
|
-
|
|
272
|
+
if (mockTimersEnabled) throw codeError('MockTimers is already enabled!', 'ERR_INVALID_STATE')
|
|
273
|
+
mockTimersEnabled = this.#enabled = true
|
|
261
274
|
this.#base = +now
|
|
262
275
|
this.#elapsed = 0
|
|
263
276
|
if (apis.includes('setInterval')) {
|
|
@@ -300,7 +313,8 @@ class MockTimers {
|
|
|
300
313
|
}
|
|
301
314
|
|
|
302
315
|
reset() {
|
|
303
|
-
this.#enabled
|
|
316
|
+
if (!this.#enabled) return
|
|
317
|
+
mockTimersEnabled = this.#enabled = false
|
|
304
318
|
Object.assign(globalThis, { setTimeout, setInterval, setImmediate, Date })
|
|
305
319
|
Object.assign(globalThis, { clearTimeout, clearInterval, clearImmediate })
|
|
306
320
|
}
|
|
@@ -380,10 +394,18 @@ class MockTimers {
|
|
|
380
394
|
}
|
|
381
395
|
}
|
|
382
396
|
|
|
383
|
-
|
|
384
|
-
module
|
|
385
|
-
|
|
386
|
-
|
|
397
|
+
class MockTracker {
|
|
398
|
+
get module() {
|
|
399
|
+
return mockModule
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#timers
|
|
403
|
+
get timers() {
|
|
404
|
+
if (!this.#timers) this.#timers = new MockTimers()
|
|
405
|
+
return this.#timers
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
fn(original = () => {}, implementation = original) {
|
|
387
409
|
let impl = implementation
|
|
388
410
|
const _mock = {
|
|
389
411
|
calls: [],
|
|
@@ -459,20 +481,10 @@ const mock = {
|
|
|
459
481
|
return Object.getOwnPropertyDescriptor(target, key)
|
|
460
482
|
},
|
|
461
483
|
})
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
process.env.EXODUS_TEST_ENGINE === 'node:pure' ||
|
|
467
|
-
process.env.EXODUS_TEST_ENGINE === 'electron-as-node:pure'
|
|
468
|
-
) {
|
|
469
|
-
// Try load module mocks from node:test, if present
|
|
470
|
-
try {
|
|
471
|
-
const nodeTest = require('node:test')
|
|
472
|
-
mock.module = nodeTest.mock.module.bind(nodeTest.mock)
|
|
473
|
-
} catch {}
|
|
484
|
+
}
|
|
474
485
|
}
|
|
475
486
|
|
|
487
|
+
const mock = new MockTracker()
|
|
476
488
|
const beforeEach = (fn) => context.addHook('beforeEach', fn)
|
|
477
489
|
const afterEach = (fn) => context.addHook('afterEach', fn)
|
|
478
490
|
const before = (fn) => context.addHook('before', fn)
|
|
@@ -529,41 +541,24 @@ const awaitForMicrotaskQueue = async () => {
|
|
|
529
541
|
for (let i = 0; i < tickPromiseRounds; i++) await promise
|
|
530
542
|
}
|
|
531
543
|
|
|
532
|
-
let
|
|
533
|
-
let requireIsRelative = false
|
|
534
|
-
let relativeRequire, baseFile, isTopLevelESM, syncBuiltinESMExports, readSnapshot, utilFormat
|
|
544
|
+
let readSnapshot, utilFormat
|
|
535
545
|
if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
|
|
536
|
-
// eslint-disable-next-line no-undef
|
|
537
|
-
const files = EXODUS_TEST_FILES
|
|
538
|
-
baseFile = files.length === 1 ? files[0] : undefined
|
|
539
|
-
isTopLevelESM = () => false
|
|
540
546
|
// eslint-disable-next-line no-undef
|
|
541
547
|
const bundleSnaps = typeof EXODUS_TEST_SNAPSHOTS !== 'undefined' && new Map(EXODUS_TEST_SNAPSHOTS)
|
|
542
548
|
const resolveSnapshot = (f) => snapshotResolver(f[0], f[1]).join('/')
|
|
543
549
|
readSnapshot = (f = baseFile) => (f && bundleSnaps?.get(resolveSnapshot(f))) || null
|
|
544
550
|
utilFormat = require('exodus-test:util-format')
|
|
545
551
|
} else {
|
|
546
|
-
const {
|
|
547
|
-
const { dirname, basename,
|
|
548
|
-
const nodeModule = require('node:module')
|
|
549
|
-
const files = process.argv.slice(1)
|
|
550
|
-
baseFile = files.length === 1 && existsSync(files[0]) ? normalize(files[0]) : undefined
|
|
551
|
-
requireIsRelative = Boolean(baseFile)
|
|
552
|
-
relativeRequire = baseFile ? nodeModule.createRequire(baseFile) : require
|
|
553
|
-
isTopLevelESM = () =>
|
|
554
|
-
!baseFile || // assume ESM otherwise
|
|
555
|
-
!Object.hasOwn(relativeRequire.cache, baseFile) || // node esm
|
|
556
|
-
relativeRequire.cache[baseFile].exports[Symbol.toStringTag] === 'Module' // bun esm
|
|
552
|
+
const { readFileSync } = require('node:fs')
|
|
553
|
+
const { dirname, basename, join } = require('node:path')
|
|
557
554
|
const resolveSnapshot = (f) => join(...snapshotResolver(dirname(f), basename(f)))
|
|
558
555
|
readSnapshot = (f = baseFile) => (f ? readFileSync(resolveSnapshot(f), 'utf8') : null)
|
|
559
|
-
builtinModules = nodeModule.builtinModules
|
|
560
|
-
syncBuiltinESMExports = nodeModule.syncBuiltinESMExports || nodeModule.syncBuiltinExports // bun has it under a different name (also a no-op and always synced atm)
|
|
561
556
|
utilFormat = require('node:util').format
|
|
562
557
|
}
|
|
563
558
|
|
|
564
559
|
// eslint-disable-next-line no-undef
|
|
565
560
|
let snapshotResolver = (dir, name) => [dir, `${name}.snapshot`] // default per Node.js docs
|
|
566
|
-
let snapshotSerializers = [(obj) => JSON.stringify(obj, null, 2)]
|
|
561
|
+
let snapshotSerializers = [(obj) => (obj === undefined ? `${obj}` : JSON.stringify(obj, null, 2))]
|
|
567
562
|
const serializeSnapshot = (obj) => {
|
|
568
563
|
let val = obj
|
|
569
564
|
for (const fn of snapshotSerializers) val = fn(val)
|
|
@@ -585,9 +580,8 @@ module.exports = {
|
|
|
585
580
|
engine: 'pure',
|
|
586
581
|
...{ assert, assertLoose },
|
|
587
582
|
...{ mock, describe, test, beforeEach, afterEach, before, after },
|
|
588
|
-
...{ builtinModules, syncBuiltinESMExports },
|
|
589
583
|
...{ utilFormat, isPromise, nodeVersion, awaitForMicrotaskQueue },
|
|
590
|
-
...{
|
|
584
|
+
...{ mockModule, baseFile, ...mockModuleMethods },
|
|
591
585
|
...{ readSnapshot, setSnapshotSerializers, setSnapshotResolver },
|
|
592
586
|
}
|
|
593
587
|
/* eslint-enable unicorn/no-useless-spread */
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
let mockModule
|
|
2
|
+
let builtinModules = []
|
|
3
|
+
let requireIsRelative = false
|
|
4
|
+
let relativeRequire, baseFile, isTopLevelESM, syncBuiltinESMExports
|
|
5
|
+
if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
|
|
6
|
+
// eslint-disable-next-line no-undef
|
|
7
|
+
const files = EXODUS_TEST_FILES
|
|
8
|
+
baseFile = files.length === 1 ? files[0] : undefined
|
|
9
|
+
isTopLevelESM = () => false
|
|
10
|
+
} else {
|
|
11
|
+
const { existsSync } = require('node:fs')
|
|
12
|
+
const { normalize } = require('node:path')
|
|
13
|
+
const nodeModule = require('node:module')
|
|
14
|
+
const files = process.argv.slice(1)
|
|
15
|
+
baseFile = files.length === 1 && existsSync(files[0]) ? normalize(files[0]) : undefined
|
|
16
|
+
requireIsRelative = Boolean(baseFile)
|
|
17
|
+
relativeRequire = baseFile ? nodeModule.createRequire(baseFile) : require
|
|
18
|
+
isTopLevelESM = () =>
|
|
19
|
+
!baseFile || // assume ESM otherwise
|
|
20
|
+
!Object.hasOwn(relativeRequire.cache, baseFile) || // node esm
|
|
21
|
+
relativeRequire.cache[baseFile].exports[Symbol.toStringTag] === 'Module' // bun esm
|
|
22
|
+
builtinModules = nodeModule.builtinModules
|
|
23
|
+
syncBuiltinESMExports = nodeModule.syncBuiltinESMExports || nodeModule.syncBuiltinExports // bun has it under a different name (also a no-op and always synced atm)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
process.env.EXODUS_TEST_ENGINE === 'node:pure' ||
|
|
28
|
+
process.env.EXODUS_TEST_ENGINE === 'electron-as-node:pure'
|
|
29
|
+
) {
|
|
30
|
+
// Try load module mocks from node:test, if present
|
|
31
|
+
try {
|
|
32
|
+
const nodeTest = require('node:test')
|
|
33
|
+
mockModule = nodeTest.mock.module.bind(nodeTest.mock)
|
|
34
|
+
} catch {}
|
|
35
|
+
} else if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
|
|
36
|
+
globalThis.EXODUS_TEST_MOCK_BUILTINS = new Map()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* eslint-disable unicorn/no-useless-spread */
|
|
40
|
+
module.exports = {
|
|
41
|
+
...{ mockModule, builtinModules, syncBuiltinESMExports },
|
|
42
|
+
...{ requireIsRelative, relativeRequire, baseFile, isTopLevelESM },
|
|
43
|
+
}
|
|
44
|
+
/* eslint-enable unicorn/no-useless-spread */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const nameCounts = new Map()
|
|
2
2
|
let snapshotText, snapshotTextClean
|
|
3
3
|
|
|
4
|
-
const escapeSnapshot = (str) => str.replaceAll(/([\\`]|\$\{)/gu,
|
|
4
|
+
const escapeSnapshot = (str) => str.replaceAll(/([\\`]|\$\{)/gu, (x) => `\\${x}`)
|
|
5
|
+
const escapeSnapshotKey = (s) => escapeSnapshot(s).replaceAll('\n', '\\n').replaceAll('"', '\\"')
|
|
5
6
|
|
|
6
7
|
function matchSnapshot(readSnapshot, assert, name, serialized) {
|
|
7
8
|
// We don't have native snapshots, polyfill reading
|
|
@@ -25,12 +26,16 @@ function matchSnapshot(readSnapshot, assert, name, serialized) {
|
|
|
25
26
|
nameCounts.set(name, count)
|
|
26
27
|
const escaped = escapeSnapshot(serialized)
|
|
27
28
|
const key = `${name} ${count}`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Node.js and jest escape keys differently, both result to same strings, accept both
|
|
30
|
+
for (const keyfun of [escapeSnapshot, escapeSnapshotKey]) {
|
|
31
|
+
const makeEntry = (x) => `\nexports[\`${keyfun(key)}\`] = \`${x}\`;\n`
|
|
32
|
+
const fixedText = escaped.includes('\r') ? snapshotText : snapshotTextClean // well, if we expect \r let's preserve them
|
|
33
|
+
const final = escaped.includes('\n') ? `\n${escaped}\n` : escaped
|
|
34
|
+
if (fixedText.includes(makeEntry(final))) return
|
|
35
|
+
// Perhaps wrapped with newlines from Node.js snapshots?
|
|
36
|
+
if (!final.includes('\n') && fixedText.includes(makeEntry(`\n${final}\n`))) return
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
return assert.fail(`Could not match "${key}" in snapshot. ${addFail}`)
|
|
35
40
|
}
|
|
36
41
|
|
package/src/expect.cjs
CHANGED
package/src/jest.mock.js
CHANGED
|
@@ -47,7 +47,6 @@ export const jestModuleMocks = {
|
|
|
47
47
|
jestModuleMocks.dontMock = jestModuleMocks.unmock
|
|
48
48
|
|
|
49
49
|
if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
|
|
50
|
-
globalThis.EXODUS_TEST_MOCK_BUILTINS = new Map()
|
|
51
50
|
Object.assign(jestModuleMocks, {
|
|
52
51
|
__mockBundle(name, builtin, actual, mock) {
|
|
53
52
|
jestmock(name, mock, { actual, builtin, override: true })
|