@exodus/test 1.0.0-rc.114 → 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 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
- [![Node.js](https://img.shields.io/badge/Node.js-338750?style=flat-square&logo=Node.js&logoColor=FFF)](https://nodejs.org/api/test.html)
16
- [![Deno](https://img.shields.io/badge/Deno-121417?style=flat-square&logo=Deno&logoColor=FFF)](https://deno.com/)
17
- [![Bun](https://img.shields.io/badge/Bun-F472B6?style=flat-square&logo=Bun&logoColor=FFF)](https://bun.sh/)
18
- [![Electron](https://img.shields.io/badge/Electron-2F3242?style=flat-square&logo=Electron&logoColor=A2ECFB)](http://electronjs.org/)
19
- [![workerd](https://img.shields.io/badge/workerd-F38020?style=flat-square&logo=cloudflareworkers&logoColor=FFF)](https://github.com/cloudflare/workerd)\
20
- [![Chrome](https://img.shields.io/badge/Chrome-4285F4?style=flat-square&logo=GoogleChrome&logoColor=FFF)](https://www.chromium.org/Home/)
21
- [![WebKit](https://img.shields.io/badge/WebKit-006CFF?style=flat-square&logo=Safari&logoColor=FFF)](http://webkit.org/)
22
- [![Firefox](https://img.shields.io/badge/Firefox-FF7139?style=flat-square&logo=Firefox&logoColor=FFF)](https://github.com/mozilla-firefox)
23
- [![Brave](https://img.shields.io/badge/Brave-F0F0F0?style=flat-square&logo=Brave)](https://github.com/brave)
24
- [![Microsoft Edge](https://img.shields.io/badge/Edge-0078D7?style=flat-square)](https://github.com/microsoftedge)
25
- [![Servo](https://img.shields.io/badge/Servo-009D9A?style=flat-square)](https://servo.org/)\
26
- [![Hermes](https://img.shields.io/badge/Hermes-282C34?style=flat-square&logo=React)](https://hermesengine.dev)
27
- [![V8](https://img.shields.io/badge/V8-4285F4?style=flat-square&logo=V8&logoColor=white)](https://v8.dev/docs/d8)
28
- [![JavaScriptCore](https://img.shields.io/badge/JavaScriptCore-006CFF?style=flat-square)](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html)
29
- [![SpiderMonkey](https://img.shields.io/badge/SpiderMonkey-FFD681?style=flat-square)](https://spidermonkey.dev/)
30
- [![QuickJS](https://img.shields.io/badge/QuickJS-E58200?style=flat-square)](https://github.com/quickjs-ng/quickjs)
31
- [![XS](https://img.shields.io/badge/XS-0B307A?style=flat-square)](https://github.com/Moddable-OpenSource/moddable)
32
- [![GraalJS](https://img.shields.io/badge/GraalJS-C74634?style=flat-square)](https://github.com/oracle/graaljs)
33
- [![Boa](https://img.shields.io/badge/Boa-F3FF00?style=flat-square)](https://github.com/boa-dev/boa)
34
- [![Escargot](https://img.shields.io/badge/Escargot-1428A0?style=flat-square)](https://github.com/Samsung/escargot)
35
- [![engine262](https://img.shields.io/badge/engine262-f0db4f?style=flat-square&logo=javascript&logoColor=000)](https://github.com/engine262/engine262)
15
+ [![Node.js](https://img.shields.io/badge/Node.js-338750?style=for-the-badge&logo=Node.js&logoColor=FFF)](https://nodejs.org/api/test.html)
16
+ [![Deno](https://img.shields.io/badge/Deno-121417?style=for-the-badge&logo=Deno&logoColor=FFF)](https://deno.com/)
17
+ [![Bun](https://img.shields.io/badge/Bun-F472B6?style=for-the-badge&logo=Bun&logoColor=FFF)](https://bun.sh/)
18
+ [![Electron](https://img.shields.io/badge/Electron-2F3242?style=for-the-badge&logo=Electron&logoColor=A2ECFB)](http://electronjs.org/)
19
+ [![workerd](https://img.shields.io/badge/workerd-F38020?style=for-the-badge&logo=cloudflareworkers&logoColor=FFF)](https://github.com/cloudflare/workerd)\
20
+ [![Chrome](https://img.shields.io/badge/Chrome-4285F4?style=for-the-badge&logo=GoogleChrome&logoColor=FFF)](https://www.chromium.org/Home/)
21
+ [![WebKit](https://img.shields.io/badge/WebKit-006CFF?style=for-the-badge&logo=Safari&logoColor=FFF)](http://webkit.org/)
22
+ [![Firefox](https://img.shields.io/badge/Firefox-FF7139?style=for-the-badge&logo=Firefox&logoColor=FFF)](https://github.com/mozilla-firefox)
23
+ [![Brave](https://img.shields.io/badge/Brave-F0F0F0?style=for-the-badge&logo=Brave)](https://github.com/brave)
24
+ [![Microsoft Edge](https://img.shields.io/badge/Edge-0078D7?style=for-the-badge)](https://github.com/microsoftedge)
25
+ [![Servo](https://img.shields.io/badge/Servo-009D9A?style=for-the-badge)](https://servo.org/)\
26
+ [![Hermes](https://img.shields.io/badge/Hermes-282C34?style=for-the-badge&logo=React)](https://hermesengine.dev)
27
+ [![V8](https://img.shields.io/badge/V8-4285F4?style=for-the-badge&logo=V8&logoColor=white)](https://v8.dev/docs/d8)
28
+ [![JavaScriptCore](https://img.shields.io/badge/JavaScriptCore-006CFF?style=for-the-badge)](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html)
29
+ [![SpiderMonkey](https://img.shields.io/badge/SpiderMonkey-FFD681?style=for-the-badge)](https://spidermonkey.dev/)\
30
+ [![QuickJS](https://img.shields.io/badge/QuickJS-E58200?style=for-the-badge)](https://github.com/quickjs-ng/quickjs)
31
+ [![XS](https://img.shields.io/badge/XS-0B307A?style=for-the-badge)](https://github.com/Moddable-OpenSource/moddable)
32
+ [![GraalJS](https://img.shields.io/badge/GraalJS-C74634?style=for-the-badge)](https://github.com/oracle/graaljs)
33
+ [![Boa](https://img.shields.io/badge/Boa-F3FF00?style=for-the-badge)](https://github.com/boa-dev/boa)
34
+ [![Nova](https://img.shields.io/badge/Nova-FF810A?style=for-the-badge)](https://github.com/trynova/nova)
35
+ [![Escargot](https://img.shields.io/badge/Escargot-1428A0?style=for-the-badge&logo=Samsung)](https://github.com/Samsung/escargot)
36
+ [![engine262](https://img.shields.io/badge/engine262-f0db4f?style=for-the-badge&logo=javascript&logoColor=000)](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
@@ -178,6 +180,7 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
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
- if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools: !!devtools })
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/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 },
@@ -58,7 +59,6 @@ const ENGINES = new Map(
58
59
  'porffor:bundle': { binary: 'porffor', ...bareboneOpts }, // blocked on https://github.com/CanadaHonk/porffor/issues/176
59
60
  // Special case: running a browser from CLI like a bundle
60
61
  'servo:bundle': { binary: 'servo', binaryArgs: ['--headless'], ...bundleOpts, html: true },
61
- 'workerd:bundle': { binary: 'workerd', binaryArgs: ['test'], ...bundleOpts, workerd: true },
62
62
  // Browser engines
63
63
  'chrome:puppeteer': { binary: 'chrome', browsers: 'puppeteer', ...bundleOpts },
64
64
  'firefox:puppeteer': { binary: 'firefox', browsers: 'puppeteer', ...bundleOpts },
@@ -72,7 +72,7 @@ const ENGINES = new Map(
72
72
  })
73
73
  )
74
74
  const bareOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes', 'shermes']
75
- const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', 'jerryscript', 'engine262', 'servo']
75
+ const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', 'jerryscript', 'engine262']
76
76
  const bareIncomplete = ['ladybird-js', 'nova', 'duktape']
77
77
 
78
78
  const getEnvFlag = (name) => {
@@ -663,7 +663,8 @@ async function launch(binary, args, opts = {}, buffering = false) {
663
663
  }
664
664
 
665
665
  const barebones = [...bareOk, ...bareNotrack, ...bareIncomplete]
666
- assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', 'jerry', 'duk', ...barebones])
666
+ const bins = ['node', 'bun', 'deno', 'electron', 'workerd', 'servo', 'jerry', 'duk', ...barebones]
667
+ assertBinary(binary, bins)
667
668
  if (binary === c8 && process.platform === 'win32') {
668
669
  ;[binary, args] = ['node', [binary, ...args]]
669
670
  }
@@ -708,7 +709,7 @@ if (options.pure) {
708
709
  await writeFile(bundled.fileHtml, `<script src="${bundled.file}"></script>`)
709
710
  }
710
711
 
711
- if (bundled && options.workerd) {
712
+ if (bundled && options.platform === 'workerd') {
712
713
  bundled.fileWrapper = `${bundled.file}.wrapper.js`
713
714
  bundled.fileConfig = `${bundled.file}.capnp`
714
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.114",
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='{**/typescript/**,**/jest-repo/**/user.test.js}' ./bin/index.js --jest 'tests/**/*.test.{js,cjs,mjs}'",
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,user.test.js,sum.test.js",
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 'tests/tape/tests/*.js' tests/tape.test.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.test.js tests/simple.test.js tests/env.test.js 'tests/engines/**.test.js' tests/node/simple.test.js tests/node/order.test.js",
143
- "test:deno:pure": "EXODUS_TEST_IGNORE='**/jest-repo/examples/timer/**' EXODUS_TEST_ENGINE=deno:pure npm run test --",
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
185
  "@exodus/replay": "^1.0.0-rc.11",
185
- "@exodus/test-bundler": "1.0.0-rc.15",
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.52.0",
190
+ "playwright-core": "^1.58.2",
190
191
  "pretty-format": "^30.2.0",
191
- "puppeteer-core": "^24.14.0",
192
+ "puppeteer-core": "^24.37.2",
192
193
  "tsx": "^4.21.0"
193
194
  },
194
195
  "devDependencies": {
@@ -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
@@ -44,6 +45,7 @@ class Context {
44
45
  #fullName
45
46
  #assert
46
47
  #hooks
48
+ #mock
47
49
 
48
50
  constructor(parent, name, options = {}) {
49
51
  if (!name || typeof name !== 'string') name = '<anonymous>'
@@ -80,6 +82,11 @@ class Context {
80
82
  return this.#assert
81
83
  }
82
84
 
85
+ get mock() {
86
+ if (!this.#mock) this.#mock = new MockTracker()
87
+ return this.#mock
88
+ }
89
+
83
90
  async addHook(type, fn) {
84
91
  if (!this.#hooks) this.#hooks = Object.create(null)
85
92
  if (!this.#hooks[type]) this.#hooks[type] = []
@@ -252,13 +259,18 @@ test.todo = (...args) => {
252
259
  return test(name, { ...options, todo: true }, fn)
253
260
  }
254
261
 
262
+ const codeError = (msg, code) => Object.assign(new Error(msg), { code })
263
+
264
+ let mockTimersEnabled = false
265
+
255
266
  class MockTimers {
256
267
  #enabled = false
257
268
  #base = 0
258
269
  #elapsed = 0
259
270
  #queue = []
260
271
  enable({ now = 0, apis = ['setInterval', 'setTimeout', 'setImmediate', 'Date'] } = {}) {
261
- check(!this.#enabled, 'MockTimers is already enabled!')
272
+ if (mockTimersEnabled) throw codeError('MockTimers is already enabled!', 'ERR_INVALID_STATE')
273
+ mockTimersEnabled = this.#enabled = true
262
274
  this.#base = +now
263
275
  this.#elapsed = 0
264
276
  if (apis.includes('setInterval')) {
@@ -301,7 +313,8 @@ class MockTimers {
301
313
  }
302
314
 
303
315
  reset() {
304
- this.#enabled = false
316
+ if (!this.#enabled) return
317
+ mockTimersEnabled = this.#enabled = false
305
318
  Object.assign(globalThis, { setTimeout, setInterval, setImmediate, Date })
306
319
  Object.assign(globalThis, { clearTimeout, clearInterval, clearImmediate })
307
320
  }
@@ -381,10 +394,18 @@ class MockTimers {
381
394
  }
382
395
  }
383
396
 
384
- const mock = {
385
- module: undefined,
386
- timers: new MockTimers(),
387
- fn: (original = () => {}, implementation = original) => {
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) {
388
409
  let impl = implementation
389
410
  const _mock = {
390
411
  calls: [],
@@ -460,20 +481,10 @@ const mock = {
460
481
  return Object.getOwnPropertyDescriptor(target, key)
461
482
  },
462
483
  })
463
- },
464
- }
465
-
466
- if (
467
- process.env.EXODUS_TEST_ENGINE === 'node:pure' ||
468
- process.env.EXODUS_TEST_ENGINE === 'electron-as-node:pure'
469
- ) {
470
- // Try load module mocks from node:test, if present
471
- try {
472
- const nodeTest = require('node:test')
473
- mock.module = nodeTest.mock.module.bind(nodeTest.mock)
474
- } catch {}
484
+ }
475
485
  }
476
486
 
487
+ const mock = new MockTracker()
477
488
  const beforeEach = (fn) => context.addHook('beforeEach', fn)
478
489
  const afterEach = (fn) => context.addHook('afterEach', fn)
479
490
  const before = (fn) => context.addHook('before', fn)
@@ -530,35 +541,18 @@ const awaitForMicrotaskQueue = async () => {
530
541
  for (let i = 0; i < tickPromiseRounds; i++) await promise
531
542
  }
532
543
 
533
- let builtinModules = []
534
- let requireIsRelative = false
535
- let relativeRequire, baseFile, isTopLevelESM, syncBuiltinESMExports, readSnapshot, utilFormat
544
+ let readSnapshot, utilFormat
536
545
  if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
537
- // eslint-disable-next-line no-undef
538
- const files = EXODUS_TEST_FILES
539
- baseFile = files.length === 1 ? files[0] : undefined
540
- isTopLevelESM = () => false
541
546
  // eslint-disable-next-line no-undef
542
547
  const bundleSnaps = typeof EXODUS_TEST_SNAPSHOTS !== 'undefined' && new Map(EXODUS_TEST_SNAPSHOTS)
543
548
  const resolveSnapshot = (f) => snapshotResolver(f[0], f[1]).join('/')
544
549
  readSnapshot = (f = baseFile) => (f && bundleSnaps?.get(resolveSnapshot(f))) || null
545
550
  utilFormat = require('exodus-test:util-format')
546
551
  } else {
547
- const { existsSync, readFileSync } = require('node:fs')
548
- const { dirname, basename, normalize, join } = require('node:path')
549
- const nodeModule = require('node:module')
550
- const files = process.argv.slice(1)
551
- baseFile = files.length === 1 && existsSync(files[0]) ? normalize(files[0]) : undefined
552
- requireIsRelative = Boolean(baseFile)
553
- relativeRequire = baseFile ? nodeModule.createRequire(baseFile) : require
554
- isTopLevelESM = () =>
555
- !baseFile || // assume ESM otherwise
556
- !Object.hasOwn(relativeRequire.cache, baseFile) || // node esm
557
- relativeRequire.cache[baseFile].exports[Symbol.toStringTag] === 'Module' // bun esm
552
+ const { readFileSync } = require('node:fs')
553
+ const { dirname, basename, join } = require('node:path')
558
554
  const resolveSnapshot = (f) => join(...snapshotResolver(dirname(f), basename(f)))
559
555
  readSnapshot = (f = baseFile) => (f ? readFileSync(resolveSnapshot(f), 'utf8') : null)
560
- builtinModules = nodeModule.builtinModules
561
- syncBuiltinESMExports = nodeModule.syncBuiltinESMExports || nodeModule.syncBuiltinExports // bun has it under a different name (also a no-op and always synced atm)
562
556
  utilFormat = require('node:util').format
563
557
  }
564
558
 
@@ -586,9 +580,8 @@ module.exports = {
586
580
  engine: 'pure',
587
581
  ...{ assert, assertLoose },
588
582
  ...{ mock, describe, test, beforeEach, afterEach, before, after },
589
- ...{ builtinModules, syncBuiltinESMExports },
590
583
  ...{ utilFormat, isPromise, nodeVersion, awaitForMicrotaskQueue },
591
- ...{ requireIsRelative, relativeRequire, baseFile, isTopLevelESM, mockModule: mock.module },
584
+ ...{ mockModule, baseFile, ...mockModuleMethods },
592
585
  ...{ readSnapshot, setSnapshotSerializers, setSnapshotResolver },
593
586
  }
594
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,7 @@
1
1
  const nameCounts = new Map()
2
2
  let snapshotText, snapshotTextClean
3
3
 
4
- const escapeSnapshot = (str) => str.replaceAll(/([\\`]|\$\{)/gu, '\\$1')
4
+ const escapeSnapshot = (str) => str.replaceAll(/([\\`]|\$\{)/gu, (x) => `\\${x}`)
5
5
  const escapeSnapshotKey = (s) => escapeSnapshot(s).replaceAll('\n', '\\n').replaceAll('"', '\\"')
6
6
 
7
7
  function matchSnapshot(readSnapshot, assert, name, serialized) {
package/src/expect.cjs CHANGED
@@ -180,3 +180,6 @@ function createExpect() {
180
180
 
181
181
  exports.expect = createExpect()
182
182
  exports.loadExpect = loadExpect
183
+
184
+ // https://github.com/trynova/nova/issues/935
185
+ if (process.env.EXODUS_TEST_PLATFORM === 'nova') exports.expect = require('expect').expect
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 })