@exodus/test 1.0.0-rc.33 → 1.0.0-rc.35
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/bin/bundle.js +18 -8
- package/bin/index.js +44 -11
- package/bin/typescript.loader.js +2 -6
- package/package.json +7 -3
- package/src/engine.pure.cjs +51 -21
- package/src/jest.js +10 -5
- package/src/jest.snapshot.js +2 -1
- package/src/version.js +1 -0
package/bin/bundle.js
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
|
-
import { basename, dirname, resolve, join } from 'node:path'
|
|
5
|
+
import { basename, dirname, extname, resolve, join } from 'node:path'
|
|
6
6
|
import { createRequire } from 'node:module'
|
|
7
7
|
import { randomUUID as uuid, randomBytes } from 'node:crypto'
|
|
8
8
|
import * as esbuild from 'esbuild'
|
|
@@ -56,8 +56,14 @@ const loadPipeline = [
|
|
|
56
56
|
|
|
57
57
|
const options = {}
|
|
58
58
|
|
|
59
|
-
export const init = async ({ platform, jest, target, jestConfig, outdir }) => {
|
|
60
|
-
Object.assign(options, { platform, jest, target, jestConfig, outdir })
|
|
59
|
+
export const init = async ({ platform, jest, flow, target, jestConfig, outdir }) => {
|
|
60
|
+
Object.assign(options, { platform, jest, flow, target, jestConfig, outdir })
|
|
61
|
+
|
|
62
|
+
if (options.flow) {
|
|
63
|
+
const { default: flowRemoveTypes } = await import('flow-remove-types')
|
|
64
|
+
loadPipeline.unshift((source) => flowRemoveTypes(source, { pretty: true }).toString())
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
if (options.platform === 'hermes') {
|
|
62
68
|
const babel = await import('./babel-worker.cjs')
|
|
63
69
|
loadPipeline.push(async (source) => {
|
|
@@ -182,10 +188,12 @@ export const build = async (...files) => {
|
|
|
182
188
|
'process.env.NO_COLOR': stringify('1'),
|
|
183
189
|
'process.env.NODE_ENV': stringify(process.env.NODE_ENV),
|
|
184
190
|
'process.env.EXODUS_TEST_CONTEXT': stringify('pure'),
|
|
185
|
-
'process.env.EXODUS_TEST_ENVIRONMENT': stringify('bundle'),
|
|
186
|
-
'process.env.EXODUS_TEST_PLATFORM': stringify(process.env.EXODUS_TEST_PLATFORM),
|
|
191
|
+
'process.env.EXODUS_TEST_ENVIRONMENT': stringify('bundle'), // always 'bundle'
|
|
192
|
+
'process.env.EXODUS_TEST_PLATFORM': stringify(process.env.EXODUS_TEST_PLATFORM), // e.g. 'hermes', 'node'
|
|
193
|
+
'process.env.EXODUS_TEST_ENGINE': stringify(process.env.EXODUS_TEST_ENGINE), // e.g. 'hermes:bundle', 'node:bundle'
|
|
187
194
|
'process.env.EXODUS_TEST_JEST_CONFIG': stringify(JSON.stringify(options.jestConfig)),
|
|
188
195
|
'process.env.EXODUS_TEST_EXECARGV': stringify(process.env.EXODUS_TEST_EXECARGV),
|
|
196
|
+
'process.env.EXODUS_TEST_ONLY': stringify(process.env.EXODUS_TEST_ONLY),
|
|
189
197
|
'process.env.NODE_DEBUG': stringify(),
|
|
190
198
|
'process.env.DEBUG': stringify(),
|
|
191
199
|
'process.env.READABLE_STREAM': stringify(),
|
|
@@ -254,17 +262,19 @@ export const build = async (...files) => {
|
|
|
254
262
|
{
|
|
255
263
|
name: 'exodus-test.bundle',
|
|
256
264
|
setup({ onLoad }) {
|
|
257
|
-
onLoad({ filter: /\.[cm]?[jt]
|
|
265
|
+
onLoad({ filter: /\.[cm]?[jt]sx?$/, namespace: 'file' }, async (args) => {
|
|
258
266
|
let filepath = args.path
|
|
259
267
|
// Resolve .native versions
|
|
260
268
|
// TODO: move flag to engine options
|
|
261
269
|
// TODO: maybe follow package.json for this
|
|
262
270
|
if (['jsc', 'hermes'].includes(options.platform)) {
|
|
263
|
-
const maybeNative = filepath.replace(/(\.[cm]?[jt]
|
|
271
|
+
const maybeNative = filepath.replace(/(\.[cm]?[jt]sx?)$/u, '.native$1')
|
|
264
272
|
if (existsSync(maybeNative)) filepath = maybeNative
|
|
265
273
|
}
|
|
266
274
|
|
|
267
|
-
const loader =
|
|
275
|
+
const loader = extname(filepath).replace(/^\.[cm]?/, '') // TODO: a flag to force jsx/tsx perhaps
|
|
276
|
+
assert(['js', 'ts', 'jsx', 'tx'].includes(loader))
|
|
277
|
+
|
|
268
278
|
return { contents: await loadSourceFile(filepath), loader }
|
|
269
279
|
})
|
|
270
280
|
},
|
package/bin/index.js
CHANGED
|
@@ -38,6 +38,7 @@ function parseOptions() {
|
|
|
38
38
|
const options = {
|
|
39
39
|
jest: false,
|
|
40
40
|
typescript: false,
|
|
41
|
+
flow: false,
|
|
41
42
|
esbuild: false,
|
|
42
43
|
babel: false,
|
|
43
44
|
coverage: false,
|
|
@@ -47,6 +48,7 @@ function parseOptions() {
|
|
|
47
48
|
passWithNoTests: false,
|
|
48
49
|
writeSnapshots: false,
|
|
49
50
|
debug: { files: false },
|
|
51
|
+
dropNetwork: ![undefined, '', '0'].includes(process.env.EXODUS_TEST_DROP_NETWORK),
|
|
50
52
|
ideaCompat: false,
|
|
51
53
|
engine: process.env.EXODUS_TEST_ENGINE ?? 'node:test',
|
|
52
54
|
}
|
|
@@ -85,6 +87,9 @@ function parseOptions() {
|
|
|
85
87
|
case '--typescript':
|
|
86
88
|
options.typescript = true
|
|
87
89
|
break
|
|
90
|
+
case '--flow':
|
|
91
|
+
options.flow = true
|
|
92
|
+
break
|
|
88
93
|
case '--esbuild':
|
|
89
94
|
options.esbuild = true
|
|
90
95
|
break
|
|
@@ -100,6 +105,7 @@ function parseOptions() {
|
|
|
100
105
|
case '--watch':
|
|
101
106
|
options.watch = true
|
|
102
107
|
break
|
|
108
|
+
case '--test-only':
|
|
103
109
|
case '--only':
|
|
104
110
|
options.only = true
|
|
105
111
|
break
|
|
@@ -128,6 +134,9 @@ function parseOptions() {
|
|
|
128
134
|
process.env.NO_COLOR = '1'
|
|
129
135
|
process.env.NODE_DISABLE_COLORS = '1'
|
|
130
136
|
break
|
|
137
|
+
case '--drop-network':
|
|
138
|
+
options.dropNetwork = true
|
|
139
|
+
break
|
|
131
140
|
case '--idea-compat':
|
|
132
141
|
options.ideaCompat = true
|
|
133
142
|
break
|
|
@@ -169,7 +178,6 @@ if (options.pure) {
|
|
|
169
178
|
assert(!options.writeSnapshots, `Can not use write snapshots with ${options.engine} engine`)
|
|
170
179
|
assert(!options.forceExit, `Can not use --force-exit with ${options.engine} engine yet`) // TODO
|
|
171
180
|
assert(!options.watch, `Can not use --watch with with ${options.engine} engine`)
|
|
172
|
-
assert(!options.only, `Can not use --only with with ${options.engine} engine yet`) // TODO
|
|
173
181
|
} else if (options.engine === 'node:test') {
|
|
174
182
|
args.push('--test', '--no-warnings=ExperimentalWarning', '--test-reporter=spec')
|
|
175
183
|
|
|
@@ -340,7 +348,9 @@ if (tsTests.length > 0 && !options.esbuild && !options.typescript) {
|
|
|
340
348
|
}
|
|
341
349
|
|
|
342
350
|
if (!Object.hasOwn(process.env, 'NODE_ENV')) process.env.NODE_ENV = 'test'
|
|
343
|
-
process.env.EXODUS_TEST_PLATFORM = options.binary
|
|
351
|
+
process.env.EXODUS_TEST_PLATFORM = options.binary // e.g. 'hermes', 'node'
|
|
352
|
+
process.env.EXODUS_TEST_ENGINE = options.engine // e.g. 'hermes:bundle', 'node:bundle', 'node:test', 'node:pure'
|
|
353
|
+
process.env.EXODUS_TEST_ONLY = options.only ? '1' : ''
|
|
344
354
|
|
|
345
355
|
const c8 = resolveRequire('c8/bin/c8.js')
|
|
346
356
|
if (resolveImport) assert.equal(c8, resolveImport('c8/bin/c8.js'))
|
|
@@ -353,7 +363,7 @@ if (options.coverage) {
|
|
|
353
363
|
args.unshift(options.binary)
|
|
354
364
|
options.binary = c8
|
|
355
365
|
// perhaps use text-summary ?
|
|
356
|
-
args.unshift('-r', 'text', '-r', 'html', '-r', 'lcov')
|
|
366
|
+
args.unshift('-r', 'text', '-r', 'html', '-r', 'lcov', '-r', 'json-summary')
|
|
357
367
|
} else {
|
|
358
368
|
throw new Error(`Unknown coverage engine: ${JSON.stringify(options.coverageEngine)}`)
|
|
359
369
|
}
|
|
@@ -374,7 +384,32 @@ if (options.bundle) {
|
|
|
374
384
|
buildFile = (file) => bundle.build(file)
|
|
375
385
|
}
|
|
376
386
|
|
|
377
|
-
|
|
387
|
+
if (options.dropNetwork) {
|
|
388
|
+
console.warn(`--drop-network is experimental and is a test helper, not a security mechanism`)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const execFile = promisify(execFileCallback)
|
|
392
|
+
|
|
393
|
+
async function launch(binary, args, opts = {}, buffering = false) {
|
|
394
|
+
assert(binary && ['node', 'bun', 'deno', 'jsc', 'hermes', c8].includes(binary))
|
|
395
|
+
if (options.dropNetwork) {
|
|
396
|
+
switch (process.platform) {
|
|
397
|
+
case 'darwin':
|
|
398
|
+
;[binary, args] = ['sandbox-exec', ['-n', 'no-network', binary, ...args]]
|
|
399
|
+
break
|
|
400
|
+
case 'linux':
|
|
401
|
+
;[binary, args] = ['unshare', ['-n', '-r', binary, ...args]]
|
|
402
|
+
break
|
|
403
|
+
default:
|
|
404
|
+
assert.fail(`--drop-network is not implemented on platform: ${process.platform}`)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (buffering) return execFile(binary, args, { maxBuffer: 5 * 1024 * 1024, ...opts }) // 5 MiB just in case
|
|
409
|
+
const child = spawn(binary, args, { stdio: 'inherit', ...opts })
|
|
410
|
+
const [code] = await once(child, 'close')
|
|
411
|
+
return { code }
|
|
412
|
+
}
|
|
378
413
|
|
|
379
414
|
if (options.pure) {
|
|
380
415
|
if (options.binary === 'hermes') {
|
|
@@ -405,8 +440,6 @@ if (options.pure) {
|
|
|
405
440
|
process.env.EXODUS_TEST_CONTEXT = 'pure'
|
|
406
441
|
console.warn(`\n${options.engine} engine is experimental and may not work an expected\n`)
|
|
407
442
|
|
|
408
|
-
const execFile = promisify(execFileCallback)
|
|
409
|
-
|
|
410
443
|
const runOne = async (inputFile) => {
|
|
411
444
|
const bundled = buildFile ? await buildFile(inputFile) : undefined
|
|
412
445
|
if (buildFile) assert(bundled.file)
|
|
@@ -414,12 +447,12 @@ if (options.pure) {
|
|
|
414
447
|
if (bundled?.errors.length > 0) return { ok: false, output: bundled.errors }
|
|
415
448
|
|
|
416
449
|
const { binaryArgs = [] } = options
|
|
417
|
-
//
|
|
450
|
+
// Timeout is fallback if timeout in script hangs, 50x as it can be adjusted per-script inside them
|
|
418
451
|
// Do we want to extract timeouts from script code instead? Also, hermes might be slower, so makes sense to increase
|
|
419
|
-
const
|
|
452
|
+
const timeout = (jestConfig?.testTimeout || 5000) * 50
|
|
420
453
|
try {
|
|
421
454
|
const fullArgs = [...binaryArgs, ...args, file]
|
|
422
|
-
const { code = 0, stdout, stderr } = await
|
|
455
|
+
const { code = 0, stdout, stderr } = await launch(options.binary, fullArgs, { timeout }, true)
|
|
423
456
|
return { ok: code === 0, output: [stdout, stderr] }
|
|
424
457
|
} catch (err) {
|
|
425
458
|
const { code, stdout = '', stderr = '', signal, killed } = err
|
|
@@ -463,6 +496,7 @@ if (options.pure) {
|
|
|
463
496
|
.replaceAll(/^✔ PASS /gmu, color('✔ PASS ', 'green'))
|
|
464
497
|
.replaceAll(/^⏭ SKIP /gmu, color('⏭ SKIP ', 'dim'))
|
|
465
498
|
.replaceAll(/^✖ FAIL /gmu, color('✖ FAIL ', 'red'))
|
|
499
|
+
.replaceAll(/^⚠ WARN /gmu, color('⚠ WARN ', 'blue'))
|
|
466
500
|
.replaceAll(/^‼ FATAL /gmu, `${color('‼', 'red')} ${color(' FATAL ', 'bgRed')} `)
|
|
467
501
|
}
|
|
468
502
|
|
|
@@ -498,7 +532,6 @@ if (options.pure) {
|
|
|
498
532
|
process.env.EXODUS_TEST_CONTEXT = options.engine
|
|
499
533
|
assert(files.length > 0) // otherwise we can run recursively
|
|
500
534
|
assert(!options.binaryArgs)
|
|
501
|
-
const
|
|
502
|
-
const [code] = await once(node, 'close')
|
|
535
|
+
const { code } = await launch(options.binary, [...args, ...files])
|
|
503
536
|
process.exitCode = code
|
|
504
537
|
}
|
package/bin/typescript.loader.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { dirname, resolve as pathResolve } from 'node:path'
|
|
2
|
-
import { createRequire } from 'node:module'
|
|
3
1
|
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { transformSync } from 'amaro'
|
|
4
3
|
|
|
5
|
-
const require = createRequire(import.meta.url)
|
|
6
|
-
const amaroDir = dirname(require.resolve('amaro/package.json'))
|
|
7
|
-
const amaro = await import(pathResolve(amaroDir, 'dist/index.js'))
|
|
8
4
|
const extensionsRegex = /\.ts$|\.mts$/
|
|
9
5
|
|
|
10
6
|
export async function load(url, context, nextLoad) {
|
|
11
7
|
if (extensionsRegex.test(url) && !url.includes('/node_modules/')) {
|
|
12
8
|
const sourceBuf = await readFile(new URL(url))
|
|
13
9
|
const source = sourceBuf.toString('utf8')
|
|
14
|
-
const transformed =
|
|
10
|
+
const { code: transformed } = transformSync(source, { isModule: true })
|
|
15
11
|
const transformedBuf = Buffer.from(transformed)
|
|
16
12
|
if (sourceBuf.length !== transformed.length) throw new Error('length mismatch')
|
|
17
13
|
// eslint-disable-next-line unicorn/no-for-loop
|
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.35",
|
|
4
4
|
"author": "Exodus Movement, Inc.",
|
|
5
5
|
"description": "A test suite runner",
|
|
6
6
|
"homepage": "https://github.com/ExodusMovement/test",
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
"exports": {
|
|
32
32
|
"./jest": "./src/jest.js",
|
|
33
33
|
"./node": "./src/node.js",
|
|
34
|
-
"./tape":
|
|
34
|
+
"./tape": {
|
|
35
|
+
"import": "./src/tape.js",
|
|
36
|
+
"require": "./src/tape.cjs"
|
|
37
|
+
}
|
|
35
38
|
},
|
|
36
39
|
"prettier": "@exodus/prettier",
|
|
37
40
|
"files": [
|
|
@@ -100,7 +103,7 @@
|
|
|
100
103
|
"@babel/register": "^7.0.0",
|
|
101
104
|
"@chalker/queue": "^1.0.0",
|
|
102
105
|
"@ungap/url-search-params": "^0.2.2",
|
|
103
|
-
"amaro": "^0.0.
|
|
106
|
+
"amaro": "^0.0.5",
|
|
104
107
|
"assert": "^2.1.0",
|
|
105
108
|
"browserify-zlib": "^0.2.0",
|
|
106
109
|
"buffer": "^6.0.3",
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
"events": "^3.3.0",
|
|
112
115
|
"expect": "^29.7.0",
|
|
113
116
|
"fast-glob": "^3.2.11",
|
|
117
|
+
"flow-remove-types": "^2.242.0",
|
|
114
118
|
"jest-extended": "^4.0.2",
|
|
115
119
|
"jsdom": "^24.1.0",
|
|
116
120
|
"os-browserify": "^0.3.0",
|
package/src/engine.pure.cjs
CHANGED
|
@@ -20,28 +20,37 @@ function parseArgs(args) {
|
|
|
20
20
|
return { name, options, fn }
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
class Context {
|
|
24
|
+
test = test // todo: bind to context
|
|
25
|
+
describe = describe // todo: bind to context
|
|
26
|
+
children = []
|
|
27
|
+
assert = { ...assertLoose, snapshot: undefined }
|
|
28
|
+
hooks = { __proto__: null, before: [], after: [], beforeEach: [], afterEach: [] }
|
|
29
|
+
|
|
30
|
+
constructor(parent, name, options = {}) {
|
|
31
|
+
Object.assign(this, { root: parent?.root, parent, name, options })
|
|
32
|
+
this.fullName = parent && parent !== parent.root ? `${parent.fullName} > ${name}` : name
|
|
33
|
+
if (this.root) {
|
|
34
|
+
this.parent.children.push(this)
|
|
35
|
+
} else {
|
|
36
|
+
assert(this.name === '<root>' && !this.parent)
|
|
37
|
+
this.root = this
|
|
38
|
+
}
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
assert((context.name = '<root>'))
|
|
42
|
-
assert(!context.parent)
|
|
43
|
-
context.root = context
|
|
40
|
+
|
|
41
|
+
get onlySomewhere() {
|
|
42
|
+
return this.options.only || this.children.some((x) => x.onlySomewhere)
|
|
44
43
|
}
|
|
44
|
+
|
|
45
|
+
get only() {
|
|
46
|
+
return (this.options.only && !this.children.some((x) => x.onlySomewhere)) || this.parent?.only
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function enterContext(name, options) {
|
|
51
|
+
assert(!running)
|
|
52
|
+
if (willstart) clearTimeout(willstart) // have to he accurate for engines like Hermes
|
|
53
|
+
context = new Context(context, name, options)
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
function exitContext() {
|
|
@@ -55,6 +64,8 @@ async function runFunction(fn, context) {
|
|
|
55
64
|
return new Promise((resolve, reject) => fn(context, (err) => (err ? reject(err) : resolve())))
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
const runOnly = process.env.EXODUS_TEST_ONLY === '1'
|
|
68
|
+
|
|
58
69
|
async function runContext(context) {
|
|
59
70
|
const { options, children, hooks, fn } = context
|
|
60
71
|
assert(!context.running, 'Can not run twice')
|
|
@@ -63,6 +74,12 @@ async function runContext(context) {
|
|
|
63
74
|
assert(children.length === 0 || !fn)
|
|
64
75
|
if (options.skip) return console.log('⏭ SKIP', context.fullName)
|
|
65
76
|
if (context.fn) {
|
|
77
|
+
if (runOnly) {
|
|
78
|
+
if (!context.only) return console.log('⏭ SKIP', context.fullName)
|
|
79
|
+
} else if (options.only) {
|
|
80
|
+
console.log(`⚠ WARN test.only requires the --only command-line option`)
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
let error
|
|
67
84
|
const stack = [context]
|
|
68
85
|
while (stack[0].parent) stack.unshift(stack[0].parent)
|
|
@@ -93,6 +110,10 @@ async function runContext(context) {
|
|
|
93
110
|
abstractProcess.exitCode = 1
|
|
94
111
|
}
|
|
95
112
|
} else {
|
|
113
|
+
if (options.only && !runOnly) {
|
|
114
|
+
console.log(`⚠ WARN describe.only requires the --only command-line option`)
|
|
115
|
+
}
|
|
116
|
+
|
|
96
117
|
// if (context !== context.root) console.log(`▶ ${context.fullName}`)
|
|
97
118
|
// TODO: try/catch for hooks?
|
|
98
119
|
// TODO: flatten recursion before running?
|
|
@@ -118,7 +139,6 @@ async function run() {
|
|
|
118
139
|
async function describe(...args) {
|
|
119
140
|
const { name, options, fn } = parseArgs(args)
|
|
120
141
|
enterContext(name, options)
|
|
121
|
-
context.options = options
|
|
122
142
|
// todo: callback support?
|
|
123
143
|
if (!options.skip) {
|
|
124
144
|
try {
|
|
@@ -140,6 +160,11 @@ describe.skip = (...args) => {
|
|
|
140
160
|
return describe(name, { ...options, skip: true }, fn)
|
|
141
161
|
}
|
|
142
162
|
|
|
163
|
+
describe.only = (...args) => {
|
|
164
|
+
const { name, options, fn } = parseArgs(args)
|
|
165
|
+
return describe(name, { ...options, only: true }, fn)
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
function test(...args) {
|
|
144
169
|
const { name, options, fn } = parseArgs(args)
|
|
145
170
|
enterContext(name, options)
|
|
@@ -152,6 +177,11 @@ test.skip = (...args) => {
|
|
|
152
177
|
return test(name, { ...options, skip: true }, fn)
|
|
153
178
|
}
|
|
154
179
|
|
|
180
|
+
test.only = (...args) => {
|
|
181
|
+
const { name, options, fn } = parseArgs(args)
|
|
182
|
+
return test(name, { ...options, only: true }, fn)
|
|
183
|
+
}
|
|
184
|
+
|
|
155
185
|
class MockTimers {
|
|
156
186
|
#enabled = false
|
|
157
187
|
#base = 0
|
package/src/jest.js
CHANGED
|
@@ -89,12 +89,13 @@ const forceExit = execArgv.map((x) => x.replaceAll('_', '-')).includes('--test-f
|
|
|
89
89
|
const inConcurrent = []
|
|
90
90
|
const inDescribe = []
|
|
91
91
|
const concurrent = []
|
|
92
|
-
|
|
92
|
+
|
|
93
|
+
const describeRaw = (nodeDescribe, ...args) => {
|
|
93
94
|
const fn = args.pop()
|
|
94
95
|
inDescribe.push(fn)
|
|
95
96
|
const optionsConcurrent = args?.at(-1)?.concurrency > 1
|
|
96
97
|
if (optionsConcurrent) inConcurrent.push(fn)
|
|
97
|
-
const result =
|
|
98
|
+
const result = nodeDescribe(...args, () => {
|
|
98
99
|
const res = fn()
|
|
99
100
|
|
|
100
101
|
// We do only block-level concurrency, not file-level
|
|
@@ -104,7 +105,7 @@ const describe = (...args) => {
|
|
|
104
105
|
} else if (concurrent.length > 0) {
|
|
105
106
|
const queue = [...concurrent]
|
|
106
107
|
concurrent.length = 0
|
|
107
|
-
|
|
108
|
+
nodeDescribe('concurrent', { concurrency: defaultConcurrency }, () => {
|
|
108
109
|
for (const args of queue) testRaw(...args)
|
|
109
110
|
})
|
|
110
111
|
}
|
|
@@ -133,6 +134,9 @@ Also, using expect.assertions() to ensure the planned number of assertions is be
|
|
|
133
134
|
})
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
const describe = (...args) => describeRaw(node.describe, ...args)
|
|
138
|
+
describe.only = (...args) => describeRaw(node.describe.only, ...args)
|
|
139
|
+
|
|
136
140
|
const test = (...args) => testRaw(getCallerLocation(), node.test, ...args)
|
|
137
141
|
test.only = (...args) => testRaw(getCallerLocation(), node.test.only, ...args)
|
|
138
142
|
|
|
@@ -178,10 +182,11 @@ const isBundle = process.env.EXODUS_TEST_ENVIRONMENT === 'bundle' // TODO: impro
|
|
|
178
182
|
export const jest = {
|
|
179
183
|
exodus: {
|
|
180
184
|
__proto__: null,
|
|
185
|
+
platform: String(process.env.EXODUS_TEST_PLATFORM), // e.g. 'hermes', 'node'
|
|
186
|
+
engine: String(process.env.EXODUS_TEST_ENGINE), // e.g. 'hermes:bundle', 'node:bundle', 'node:test', 'node:pure'
|
|
187
|
+
implementation: String(node.engine), // aka process.env.EXODUS_TEST_CONTEXT, e.g. 'node:test' or 'pure'
|
|
181
188
|
features: {
|
|
182
189
|
__proto__: null,
|
|
183
|
-
platform: String(process.env.EXODUS_TEST_PLATFORM),
|
|
184
|
-
engine: String(node.engine),
|
|
185
190
|
timers: Boolean(mock.timers && haveValidTimers),
|
|
186
191
|
esmMocks: Boolean(mock.module && !isBundle), // full support for ESM mocks
|
|
187
192
|
esmInterop: Boolean(insideEsbuild && !isBundle), // loading/using ESM as CJS, ESM mocks creation without a mocker function
|
package/src/jest.snapshot.js
CHANGED
|
@@ -10,6 +10,7 @@ import { expect } from 'expect'
|
|
|
10
10
|
import { format, plugins as builtinPlugins } from 'pretty-format'
|
|
11
11
|
import { jestConfig } from './jest.config.js'
|
|
12
12
|
import { getTestNamePath } from './dark.cjs'
|
|
13
|
+
import { haveSnapshotsReportUnescaped } from './version.js'
|
|
13
14
|
|
|
14
15
|
const { snapshotFormat, snapshotSerializers } = jestConfig()
|
|
15
16
|
const plugins = Object.values(builtinPlugins)
|
|
@@ -160,7 +161,7 @@ const snapOnDisk = (orig, matcher) => {
|
|
|
160
161
|
wrapContextName(() => getAssert().snapshot(obj))
|
|
161
162
|
} catch (e) {
|
|
162
163
|
if (typeof e.expected === 'string') {
|
|
163
|
-
const escaped = escape(e.expected)
|
|
164
|
+
const escaped = haveSnapshotsReportUnescaped ? e.expected : escape(e.expected)
|
|
164
165
|
const final = escaped.includes('\n') ? escaped : `\n${escaped}\n`
|
|
165
166
|
if (final === e.actual) return
|
|
166
167
|
}
|
package/src/version.js
CHANGED
|
@@ -12,6 +12,7 @@ export { major, minor, patch }
|
|
|
12
12
|
|
|
13
13
|
export const haveModuleMocks = (major === 22 && minor >= 3) || major > 22
|
|
14
14
|
export const haveSnapshots = (major === 22 && minor >= 3) || major > 22
|
|
15
|
+
export const haveSnapshotsReportUnescaped = (major === 22 && minor >= 5) || major > 22
|
|
15
16
|
export const haveForceExit = (major === 20 && minor > 13) || major >= 22
|
|
16
17
|
export const haveValidTimers = (major === 20 && minor >= 11) || major >= 22 // older glitch in various ways / stop executing
|
|
17
18
|
export const haveNoTimerInfiniteLoopBug = (major === 20 && minor >= 11) || major >= 22 // mock.timers.runAll() can get into infinite recursion
|