@exodus/test 1.0.0-rc.109 → 1.0.0-rc.110

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
@@ -1,21 +1,53 @@
1
1
  # @exodus/test
2
2
 
3
+ [![](https://flat.badgen.net/npm/v/@exodus/test)](https://npmjs.org/package/@exodus/test)
4
+ [![](https://flat.badgen.net/github/release/ExodusOSS/test?icon=github)](https://github.com/ExodusOSS/test/releases)
5
+ [![](https://flat.badgen.net/npm/license/@exodus/test)](https://github.com/ExodusOSS/test/blob/HEAD/LICENSE)
6
+ [![](https://flat.badgen.net/github/checks/ExodusOSS/test/main?icon=github)](https://github.com/ExodusOSS/test/actions/workflows/checks.yml?query=branch%3Amain)
7
+ [![](https://img.shields.io/github/stars/ExodusOSS/test)](https://github.com/ExodusOSS/test/stargazers)
8
+
3
9
  A runner for `node:test`, `jest`, and `tape` test suites on top of `node:test` (and any runtime).
4
10
 
5
- It can run your existing tests on [all runtimes and also browsers](#engines), with snapshots and module mocks.
11
+ ---
12
+
13
+ It can run your existing tests on [all runtimes and also browsers](#engines), with snapshots and module mocks:
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
+ [![Chrome](https://img.shields.io/badge/Chrome-4285F4?style=flat-square&logo=GoogleChrome&logoColor=FFF)](https://www.chromium.org/Home/)
20
+ [![WebKit](https://img.shields.io/badge/WebKit-006CFF?style=flat-square&logo=Safari&logoColor=FFF)](http://webkit.org/)
21
+ [![Firefox](https://img.shields.io/badge/Firefox-FF7139?style=flat-square&logo=Firefox&logoColor=FFF)](https://github.com/mozilla-firefox)
22
+ [![Brave](https://img.shields.io/badge/Brave-F0F0F0?style=flat-square&logo=Brave)](https://github.com/brave)
23
+ [![Microsoft Edge](https://img.shields.io/badge/Edge-0078D7?style=flat-square)](https://github.com/microsoftedge)
24
+ [![Servo](https://img.shields.io/badge/Servo-009D9A?style=flat-square)](https://servo.org/)\
25
+ [![Hermes](https://img.shields.io/badge/Hermes-282C34?style=flat-square&logo=React)](https://hermesengine.dev)
26
+ [![V8](https://img.shields.io/badge/V8-4285F4?style=flat-square&logo=V8&logoColor=white)](https://v8.dev/docs/d8)
27
+ [![JavaScriptCore](https://img.shields.io/badge/JavaScriptCore-006CFF?style=flat-square)](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html)
28
+ [![SpiderMonkey](https://img.shields.io/badge/SpiderMonkey-FFD681?style=flat-square)](https://spidermonkey.dev/)
29
+ [![QuickJS](https://img.shields.io/badge/QuickJS-E58200?style=flat-square)](https://github.com/quickjs-ng/quickjs)
30
+ [![XS](https://img.shields.io/badge/XS-0B307A?style=flat-square)](https://github.com/Moddable-OpenSource/moddable-xst)
31
+ [![GraalJS](https://img.shields.io/badge/GraalJS-C74634?style=flat-square)](https://github.com/oracle/graaljs)
32
+ [![Boa](https://img.shields.io/badge/Boa-F3FF00?style=flat-square)](https://github.com/boa-dev/boa)
33
+ [![Escargot](https://img.shields.io/badge/Escargot-1428A0?style=flat-square)](https://github.com/Samsung/escargot)
34
+ [![engine262](https://img.shields.io/badge/engine262-f0db4f?style=flat-square&logo=javascript&logoColor=000)](https://github.com/engine262/engine262)
35
+
36
+ Compatible with tests written in:
37
+
38
+ [![node:test](https://img.shields.io/badge/node:test-338750?style=for-the-badge&logo=Node.js&logoColor=FFF)](#using-with-nodetest-tests)
39
+ [![Jest](https://img.shields.io/badge/Jest-C21325?style=for-the-badge&logo=jest&logoColor=fff)](#migrating-from-jest)
40
+ [![tape](https://img.shields.io/badge/tape-6c5353?style=for-the-badge)](#migrating-from-tape)
41
+
42
+ See [documentation](https://exodusoss.github.io/test/).
6
43
 
7
44
  ## Features
8
45
 
9
46
  - Native ESM, including in Jest tests
10
- - Esbuild on the fly for babelified ESM interop (enable via `--esbuild`)
11
- - TypeScript support in both transform (through [tsx](https://tsx.is/), enable via `--esbuild`)
12
- and typestrip (via `--typescript`) modes
13
- - Runs on Node.js [node:test](https://nodejs.org/api/test.html), Bun, Deno, Electron,
14
- [v8 CLI](https://v8.dev/docs/d8), JSC, [Hermes](https://hermesengine.dev), [SpiderMonkey](https://spidermonkey.dev/),
15
- Chrome, Firefox, WebKit, Brave, Microsoft Edge, [Servo](https://servo.org/),
16
- [QuickJS](https://github.com/quickjs-ng/quickjs), [XS](https://github.com/Moddable-OpenSource/moddable-xst),
17
- [GraalJS](https://github.com/oracle/graaljs), [Escargot](https://github.com/Samsung/escargot),
18
- [Boa](https://github.com/boa-dev/boa), and even [engine262](https://github.com/engine262/engine262).
47
+ - Esbuild on the fly for old faux-ESM interop (enable via `--esbuild`)
48
+ - TypeScript support
49
+ - Runs anywhere (including Hermes, the [React Native](https://reactnative.dev/) JavaScript engine)
50
+ - Use snapshots to cross-compare between runtimes, browsers and barebones (including Hermes)
19
51
  - Testsuite-agnostic — can run any file as long as it sets exit code based on test results
20
52
  - Built-in [Jest](https://jestjs.io) compatibility (with `--jest`), including `jest.*` global
21
53
  - Up to ~10x faster depending on the original setup
@@ -35,7 +67,73 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
35
67
  - Unlike `bun:test`, it runs test files in isolated contexts \
36
68
  Bun leaks globals / side effects between test files ([ref](https://github.com/oven-sh/bun/issues/6024)),
37
69
  and has incompatible `test()` lifecycle / order
38
- - Also features a tape API for drop-in replacement
70
+
71
+ ## Getting started
72
+
73
+ First install with `npm install --save-dev @exodus/test` (or with your favorite package manager)
74
+
75
+ Then, add this to `package.json` scripts (or see [engines example](https://github.com/ExodusOSS/bytes/blob/v1.11.0/package.json#L25-L42)):
76
+
77
+ ```json
78
+ "test": "exodus-test"
79
+ ```
80
+
81
+ That's it. It works zero-config.
82
+
83
+ See [Options](#options) to change defaults, e.g. to enable Jest globals with `--jest`.
84
+
85
+ To use [Engines](#engines) on CI, see e.g. GitHub CI config of [@exodus/bytes](https://github.com/ExodusOSS/bytes/blob/main/.github/workflows/test.yml).
86
+
87
+ ### Using with node:test tests
88
+
89
+ You don't need to change the tests or any extra configuration on top of [Getting started](#getting-started).
90
+
91
+ It just works out of the box, and on Node.js native `node:test` is used under the hood.
92
+
93
+ Using this script is similar to `node --test`:
94
+
95
+ ```json
96
+ "test": "exodus-test"
97
+ ```
98
+
99
+ But, unlike bare `node --test` it supports [engines](#engines) and the [GitHub CI reporter](#github-actions).
100
+
101
+ ### Migrating from Jest
102
+
103
+ Use this in `package.json` scripts:
104
+
105
+ ```json
106
+ "test": "exodus-test --jest"
107
+ ```
108
+
109
+ If that doesn't work (e.g. some deps are faux-ESM), add `--esbuild` to transpile those:
110
+
111
+ ```json
112
+ "test": "exodus-test --jest --esbuild"
113
+ ```
114
+
115
+ To adjust module mocks for native ESM support, see [Module mocking in ESM](#module-mocking-in-esm).
116
+
117
+ Some complex setups with React Native don't work yet.
118
+
119
+ ### Migrating from tape
120
+
121
+ Use this in `package.json` scripts:
122
+
123
+ ```json
124
+ "test": "exodus-test"
125
+ ```
126
+
127
+ Great! Now your tape tests run on top of `node:test`, and are also runnable in browsers / barebone [engines](#engines).
128
+
129
+ > [!TIP]
130
+ > You can optionally replace `tape` imports with `@exodus/test/tape`
131
+ > to be able to be individually run them with just `node ./path-to-file.js`:
132
+ >
133
+ > ```js
134
+ > import test from '@exodus/test/tape' // ESM
135
+ > const test = require('@exodus/test/tape') // CJS
136
+ > ```
39
137
 
40
138
  ## Engines
41
139
 
@@ -67,7 +165,7 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
67
165
  - `brave:puppeteer` — Brave
68
166
  - `msedge:puppeteer` — Microsoft Edge
69
167
  - Bundle
70
- - `servo:bundle` — Servo (expects it to be installed in the system or in PATH)
168
+ - `servo:bundle` — Servo (expects it to be installed in the system)
71
169
  - Barebone engines (system-provided or installed with `npx jsvu` / `npx esvu`):
72
170
  - `v8:bundle` — [v8 CLI](https://v8.dev/docs/d8) (Chrome/Blink/Node.js JavaScript engine)
73
171
  - `jsc:bundle` — [JavaScriptCore](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html) (Safari/WebKit JavaScript engine)
@@ -81,9 +179,39 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
81
179
  - `engine262:bundle` - [engine262](https://github.com/engine262/engine262), the per-spec implementation of ECMA-262
82
180
  (install with [esvu](https://npmjs.com/package/esvu))
83
181
 
182
+ ## Options
183
+
184
+ - `--jest` — register jest test helpers as global variables, also load `jest.config.*` configuration options
185
+
186
+ - `--esbuild` — use esbuild loader, also enables Typescript support on old Node.js
187
+
188
+ - `--typescript` — enable Typescript type stripping (only needed on older Node.js versions which don't have it natively)
189
+
190
+ - `--babel` — use babel loader (slower than `--esbuild`, makes sense if you have a special config)
191
+
192
+ - `--coverage` — enable coverage, prints coverage output (varies by coverage engine)
193
+
194
+ - `--coverage-engine c8` — use c8 coverage engine (default), also generates `./coverage/` dirs
195
+
196
+ - `--coverage-engine node` — use Node.js builtint coverage engine
197
+
198
+ - `--watch` — operate in watch mode and re-run tests on file changes
199
+
200
+ - `--only` — only run the tests marked with `test.only`
201
+
202
+ - `--passWithNoTests` — do not error when no test files were found
203
+
204
+ - `--write-snapshots` — write snapshots instead of verifying them (has `--test-update-snapshots` alias)
205
+
206
+ - `--test-force-exit` — force exit after tests are done
207
+
208
+ - `--engine` — specify one of [Engines](#engines) to run on
209
+
84
210
  ## Reporter samples
85
211
 
86
- #### CLI (but uses colors when output supports them, e.g. in terminal):
212
+ ### CLI
213
+
214
+ Uses colors when output supports them, e.g. in terminal.
87
215
 
88
216
  ```console
89
217
  # tests/jest/expect.mock.test.js
@@ -100,7 +228,9 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
100
228
  ✔ PASS mock.invocationCallOrder (4.221042ms)
101
229
  ```
102
230
 
103
- #### GitHub Actions collapses test results per-file, like this:
231
+ ### GitHub Actions
232
+
233
+ Collapses test results per-file, like this:
104
234
 
105
235
  <details>
106
236
  <summary>✅ <strong>tests/jest/lifecycle.test.js</strong></summary>
@@ -132,44 +262,6 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
132
262
 
133
263
  See live output in [CI](https://github.com/ExodusOSS/test/actions/workflows/checks.yaml)
134
264
 
135
- ## Library
136
-
137
- ### List of exports
138
-
139
- - `@exodus/test/node` — `node:test` API, working under non-Node.js platforms
140
-
141
- - `@exodus/test/jest` — `jest` implementation
142
-
143
- - `@exodus/test/tape` — `tape` mock (can also be helpful when moving from `tap`)
144
-
145
- ## Binary
146
-
147
- Just use `"test": "exodus-test"`
148
-
149
- ### Options
150
-
151
- - `--jest` — register jest test helpers as global variables, also load `jest.config.*` configuration options
152
-
153
- - `--esbuild` — use esbuild loader, also enables Typescript support
154
-
155
- - `--babel` — use babel loader (slower than `--esbuild`, makes sense if you have a special config)
156
-
157
- - `--coverage` — enable coverage, prints coverage output (varies by coverage engine)
158
-
159
- - `--coverage-engine c8` — use c8 coverage engine (default), also generates `./coverage/` dirs
160
-
161
- - `--coverage-engine node` — use Node.js builtint coverage engine
162
-
163
- - `--watch` — operate in watch mode and re-run tests on file changes
164
-
165
- - `--only` — only run the tests marked with `test.only`
166
-
167
- - `--passWithNoTests` — do not error when no test files were found
168
-
169
- - `--write-snapshots` — write snapshots instead of verifying them (has `--test-update-snapshots` alias)
170
-
171
- - `--test-force-exit` — force exit after tests are done
172
-
173
265
  ## Module mocking in ESM
174
266
 
175
267
  Module mocks in ESM is a common source of confusion, as Jest in most old setups does not run real ESM,
package/bin/index.js CHANGED
@@ -19,7 +19,7 @@ import { glob as globImplementation } from '../src/glob.cjs'
19
19
  const DEFAULT_PATTERNS = [`**/?(*.)+(spec|test).?([cm])[jt]s?(x)`] // do not trust magic dirs by default
20
20
  const bundleOpts = { pure: true, bundle: true, esbuild: true, ts: 'auto' }
21
21
  const bareboneOpts = { ...bundleOpts, barebone: true }
22
- const hermesA = ['-w', '-Xmicrotask-queue'] // -Xes6-class fails with -O0 / -Og, --block-scoping fails in default, any of that is bad
22
+ const hermesA = ['-w', '-Xmicrotask-queue', '-eager'] // -Xes6-class fails with -O0 / -Og, --block-scoping fails in default, any of that is bad
23
23
  const hermesS = [...hermesA, '-Xes6-block-scoping']
24
24
  const denoA = ['run', '--allow-all'] // also will set DENO_COMPAT=1 env flag below
25
25
  const denoT = ['test', '--allow-all']
@@ -45,6 +45,7 @@ const ENGINES = new Map(
45
45
  'hermes:bundle': { binary: 'hermes', binaryArgs: hermesA, target: 'es2018', ...bareboneOpts },
46
46
  'shermes:bundle': { binary: 'shermes', binaryArgs: hermesS, target: 'es2018', ...bareboneOpts },
47
47
  'spidermonkey:bundle': { binary: 'spidermonkey', ...bareboneOpts },
48
+ 'ladybird-js:bundle': { binary: 'ladybird-js', binaryArgs: ['-i'], ...bareboneOpts },
48
49
  'engine262:bundle': { binary: 'engine262', ...bareboneOpts },
49
50
  'quickjs:bundle': { binary: 'quickjs', binaryArgs: ['--std'], ...bareboneOpts },
50
51
  'xs:bundle': { binary: 'xs', ...bareboneOpts },
@@ -66,8 +67,9 @@ const ENGINES = new Map(
66
67
  'msedge:playwright': { binary: 'msedge', browsers: 'playwright', ...bundleOpts },
67
68
  })
68
69
  )
69
- const barebonesOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes', 'shermes']
70
- const barebonesUnhandled = ['jsc', 'escargot', 'boa', 'graaljs', 'jerry', 'engine262', 'servo']
70
+ const bareOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes', 'shermes']
71
+ const bareUnhandled = ['jsc', 'escargot', 'boa', 'graaljs', 'jerry', 'engine262', 'servo']
72
+ const bareIncomplete = ['ladybird-js']
71
73
 
72
74
  const getEnvFlag = (name) => {
73
75
  if (!Object.hasOwn(process.env, name)) return
@@ -353,10 +355,13 @@ if (process.env.EXODUS_TEST_IGNORE) {
353
355
 
354
356
  // This might be used in presets, so has to be loaded before jest
355
357
  if (options.flow && !options.bundle) args.push('--import', import.meta.resolve('../loader/flow.js'))
356
- if (['node:test', 'electron-as-node:test', 'deno:test'].includes(options.engine)) {
357
- // Do not need node:test override
358
- } else if (options.engine === 'deno:pure') {
359
- args.push('--import-map', import.meta.resolve('../loader/deno-import-map.json'))
358
+ if (['node:test', 'electron-as-node:test', 'deno:test', 'deno:pure'].includes(options.engine)) {
359
+ args.push('--import', import.meta.resolve('../loader/remap.cjs'))
360
+ if (options.engine === 'deno:test') {
361
+ args.push('--import-map', import.meta.resolve('../loader/deno-import-map.json'))
362
+ } else if (options.engine === 'deno:pure') {
363
+ args.push('--import-map', import.meta.resolve('../loader/deno-import-map.pure.json'))
364
+ }
360
365
  } else if (!options.bundle) {
361
366
  args.push(options.loader ?? '-r', import.meta.resolve('../loader/node-test.js'))
362
367
  }
@@ -489,7 +494,7 @@ async function glob(patterns, { ignore, cwd }) {
489
494
 
490
495
  if (patterns.length === 0) patterns.push(...DEFAULT_PATTERNS) // defaults
491
496
  const globbed = await glob(patterns, { ignore })
492
- const allfiles = filter ? globbed.filter(filter) : globbed
497
+ const allfiles = (filter ? globbed.filter(filter) : globbed).sort()
493
498
 
494
499
  if (allfiles.length === 0) {
495
500
  if (options.passWithNoTests) {
@@ -653,7 +658,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
653
658
  return browsers.run(runner, args, { binary, devtools, dropNetwork, timeout, throttle })
654
659
  }
655
660
 
656
- const barebones = [...barebonesOk, ...barebonesUnhandled]
661
+ const barebones = [...bareOk, ...bareUnhandled, ...bareIncomplete]
657
662
  assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', ...barebones])
658
663
  if (binary === c8 && process.platform === 'win32') {
659
664
  ;[binary, args] = ['node', [binary, ...args]]
@@ -684,9 +689,10 @@ if (options.pure) {
684
689
  }
685
690
 
686
691
  setEnv('EXODUS_TEST_CONTEXT', 'pure')
687
- warnHuman(`${engineName} is experimental and may not work an expected`)
688
- const missUnhandled = barebonesUnhandled.includes(options.platform) || isBrowserLike
692
+ const missUnhandled = bareUnhandled.includes(options.platform) || isBrowserLike
693
+ const isIncomplete = bareIncomplete.includes(options.platform)
689
694
  if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
695
+ if (isIncomplete) warnHuman(`Warning: ${engineName} support is incomplete`)
690
696
 
691
697
  const runOne = async (inputFile) => {
692
698
  const bundled = buildFile ? await buildFile(inputFile) : undefined
@@ -700,7 +706,19 @@ if (options.pure) {
700
706
 
701
707
  const file = buildFile ? bundled.fileHtml ?? bundled.file : inputFile
702
708
  const failedBare = 'EXODUS_TEST_FAILED_EXIT_CODE_1'
703
- const cleanOut = (out) => out.replaceAll(`\n${failedBare}\n`, '\n').replaceAll(failedBare, '')
709
+ const cleanOut = (out, ok) => {
710
+ if (options.engine === 'ladybird-js:bundle') {
711
+ // It wrapps all prints/console.log in double quotes for strings, and escapes \b \n \v \f \r \, but does not wrap "
712
+ const unmap = { __proto__: null, b: '\b', n: '\n', v: '\v', f: '\f', r: '\r' }
713
+ const fix = (x) => x.slice(1, -1).replaceAll(/\\([bnvfr\\])/gu, (s) => unmap[s[1]] ?? s[1])
714
+ const lineMapper = (x) => (x.startsWith('"') && x.endsWith('"') ? fix(x) : x)
715
+ out = out.split('\n').map(lineMapper).join('\n')
716
+ }
717
+
718
+ out = out.replaceAll(`\n${failedBare}\n`, '\n').replaceAll(failedBare, '')
719
+ return out || (ok ? `✔ PASS ${file}` : `✖ FAIL ${file}`)
720
+ }
721
+
704
722
  // Timeout is fallback if timeout in script hangs, 50x as it can be adjusted per-script inside them
705
723
  // Do we want to extract timeouts from script code instead? Also, hermes might be slower, so makes sense to increase
706
724
  const timeout = (options.testTimeout || jestConfig?.testTimeout || 5000) * 50
@@ -711,11 +729,11 @@ if (options.pure) {
711
729
  const ms = Number(process.hrtime.bigint() - start) / 1e6
712
730
  if (stdout.includes(failedBare)) return { ok: false, output: [cleanOut(stdout), stderr], ms }
713
731
  const ok = code === 0 && !/^(✖ FAIL|‼ FATAL) /mu.test(stdout)
714
- return { ok, output: [stdout, stderr], ms }
732
+ return { ok, output: [cleanOut(stdout, ok), stderr], ms }
715
733
  } catch (err) {
716
734
  const ms = Number(process.hrtime.bigint() - start) / 1e6
717
735
  const { code, stderr = '', signal, killed } = err
718
- const stdout = cleanOut(err.stdout || '')
736
+ const stdout = cleanOut(err.stdout || '', false)
719
737
  if (code === null) {
720
738
  assert(signal)
721
739
  const message = ` ${signal}${killed ? ' (killed)' : ''}`
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "imports": {
3
- "node:test": "../src/node.js",
4
3
  "@jest/globals": "../src/jest.js",
5
4
  "tape": "../src/tape.js",
6
5
  "tape-promise/tape": "../src/tape.js",
@@ -0,0 +1,9 @@
1
+ {
2
+ "imports": {
3
+ "node:test": "../src/node.js",
4
+ "@jest/globals": "../src/jest.js",
5
+ "tape": "../src/tape.js",
6
+ "tape-promise/tape": "../src/tape.js",
7
+ "micro-should": "../src/jest.js"
8
+ }
9
+ }
@@ -12,3 +12,5 @@ Object.assign(testActual, test)
12
12
  const nodeModule = require('node:module')
13
13
  const syncBuiltinESMExports = nodeModule.syncBuiltinESMExports || nodeModule.syncBuiltinExports // old bun has it under a different name
14
14
  if (syncBuiltinESMExports) syncBuiltinESMExports()
15
+
16
+ require('./remap.cjs')
@@ -0,0 +1,26 @@
1
+ const nodeModule = require('node:module')
2
+ const { pathToFileURL } = require('node:url')
3
+
4
+ const remap = new Map(Object.entries(require('./deno-import-map.json').imports))
5
+ const resolveFile = (f) => require.resolve(f.endsWith('tape.js') ? `${f.slice(0, -2)}cjs` : f)
6
+
7
+ function resolve(specifier, context, nextResolve) {
8
+ if (!remap.has(specifier)) return nextResolve(specifier)
9
+ return { url: pathToFileURL(resolveFile(remap.get(specifier))).toString(), shortCircuit: true }
10
+ }
11
+
12
+ if (nodeModule.registerHooks) {
13
+ nodeModule.registerHooks({ resolve })
14
+ } else if (globalThis.Bun) {
15
+ const { mock } = require('bun:test')
16
+ for (const [k, v] of remap) mock.module(k, () => (v.endsWith('jest.js') ? import(v) : require(v)))
17
+ } else {
18
+ if (nodeModule.register) nodeModule.register('./remap.loader.js', pathToFileURL(__filename))
19
+ if (nodeModule._resolveFilename) {
20
+ const { _resolveFilename } = nodeModule
21
+ nodeModule._resolveFilename = function (request, parent, isMain, options) {
22
+ if (remap.has(request)) return resolveFile(remap.get(request))
23
+ return _resolveFilename.call(this, request, parent, isMain, options)
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,9 @@
1
+ // ESM version is the same as for Deno
2
+ import deno from './deno-import-map.json' with { type: 'json' }
3
+
4
+ const remap = new Map(Object.entries(deno.imports))
5
+
6
+ export function resolve(specifier, context, nextResolve) {
7
+ if (!remap.has(specifier)) return nextResolve(specifier)
8
+ return { url: new URL(remap.get(specifier), import.meta.url).toString(), shortCircuit: true }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/test",
3
- "version": "1.0.0-rc.109",
3
+ "version": "1.0.0-rc.110",
4
4
  "author": "Exodus Movement, Inc.",
5
5
  "description": "A test suite runner",
6
6
  "homepage": "https://github.com/ExodusOSS/test",
@@ -19,21 +19,41 @@
19
19
  ],
20
20
  "license": "MIT",
21
21
  "engines": {
22
- "node": "^20.18.0 || >=22.6.0"
22
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
23
23
  },
24
24
  "type": "module",
25
25
  "bin": {
26
26
  "exodus-test": "bin/index.js"
27
27
  },
28
28
  "exports": {
29
+ ".": {
30
+ "types": "./src/index.d.ts",
31
+ "default": "./src/index.js"
32
+ },
29
33
  "./node-test-reporter": "./bin/reporter.js",
30
34
  "./loader/jest": "./loader/jest.js",
31
- "./benchmark": "./src/benchmark.js",
32
- "./expect": "./src/expect.cjs",
33
- "./jest": "./src/jest.js",
34
- "./mock": "./src/mock.js",
35
- "./node": "./src/node.js",
35
+ "./benchmark": {
36
+ "types": "./src/benchmark.d.ts",
37
+ "default": "./src/benchmark.js"
38
+ },
39
+ "./expect": {
40
+ "types": "./src/expect.d.ts",
41
+ "default": "./src/expect.cjs"
42
+ },
43
+ "./jest": {
44
+ "types": "./src/jest.d.ts",
45
+ "default": "./src/jest.js"
46
+ },
47
+ "./mock": {
48
+ "types": "./src/mock.d.ts",
49
+ "default": "./src/mock.js"
50
+ },
51
+ "./node": {
52
+ "types": "./src/node.d.ts",
53
+ "default": "./src/node.js"
54
+ },
36
55
  "./tape": {
56
+ "types": "./src/tape.d.ts",
37
57
  "import": "./src/tape.js",
38
58
  "require": "./src/tape.cjs"
39
59
  }
@@ -49,14 +69,18 @@
49
69
  "bin/reporter.js",
50
70
  "loader/babel.cjs",
51
71
  "loader/deno-import-map.json",
72
+ "loader/deno-import-map.pure.json",
52
73
  "loader/esbuild.js",
53
74
  "loader/esbuild.optional.js",
54
75
  "loader/flow.js",
55
76
  "loader/jest.js",
77
+ "loader/remap.cjs",
78
+ "loader/remap.loader.js",
56
79
  "loader/node-test.js",
57
80
  "loader/typescript.js",
58
81
  "loader/typescript.loader.js",
59
82
  "src/benchmark.js",
83
+ "src/benchmark.d.ts",
60
84
  "src/dark.cjs",
61
85
  "src/engine.js",
62
86
  "src/engine.node.cjs",
@@ -65,8 +89,12 @@
65
89
  "src/engine.select.cjs",
66
90
  "src/exodus.js",
67
91
  "src/expect.cjs",
92
+ "src/expect.d.ts",
68
93
  "src/glob.cjs",
94
+ "src/index.js",
95
+ "src/index.d.ts",
69
96
  "src/jest.js",
97
+ "src/jest.d.ts",
70
98
  "src/jest.config.js",
71
99
  "src/jest.config.fs.js",
72
100
  "src/jest.environment.js",
@@ -76,10 +104,13 @@
76
104
  "src/jest.snapshot.js",
77
105
  "src/jest.timers.js",
78
106
  "src/mock.js",
107
+ "src/mock.d.ts",
79
108
  "src/node.js",
109
+ "src/node.d.ts",
80
110
  "src/pretty-format.cjs",
81
111
  "src/replay.js",
82
112
  "src/tape.js",
113
+ "src/tape.d.ts",
83
114
  "src/tape.cjs",
84
115
  "src/timers-track.js",
85
116
  "src/version.js",
@@ -88,8 +119,7 @@
88
119
  "jest.js",
89
120
  "mock.js",
90
121
  "node.js",
91
- "tape.js",
92
- "CHANGELOG.md"
122
+ "tape.js"
93
123
  ],
94
124
  "scripts": {
95
125
  "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 --",
@@ -124,6 +154,7 @@
124
154
  "test:hermes": "EXODUS_TEST_ENGINE=hermes:bundle npm run test:_bundle --",
125
155
  "test:shermes": "EXODUS_TEST_ENGINE=shermes:bundle npm run test:_bundle --",
126
156
  "test:spidermonkey": "EXODUS_TEST_ENGINE=spidermonkey:bundle npm run test:_bundle --",
157
+ "test:ladybird-js": "EXODUS_TEST_ENGINE=ladybird-js:bundle npm run test:_bundle --",
127
158
  "test:servo": "EXODUS_TEST_ENGINE=servo:bundle npm run test:_bundle --",
128
159
  "test:engine262": "EXODUS_TEST_ENGINE=engine262:bundle npm run test:_bundle --",
129
160
  "test:quickjs": "EXODUS_TEST_ENGINE=quickjs:bundle npm run test:_bundle --",
@@ -135,6 +166,7 @@
135
166
  "test:fetch": "node ./bin/index.js --jest --drop-network --engine node:pure 'tests/replay/*.test.js'",
136
167
  "test:jsdom": "EXODUS_TEST_JEST_CONFIG='{\"testMatch\":[\"**/*.jsdom-test.js\"],\"testEnvironment\":\"jsdom\", \"rootDir\": \".\"}' ./bin/index.js --jest",
137
168
  "coverage": "node ./bin/index.js --jest --esbuild --coverage",
169
+ "typedoc": "typedoc && mkdir -p doc/assets && cp -r theme/styles doc/assets/",
138
170
  "playwright": "node ./bin/index.js --playwright",
139
171
  "esvu": "esvu",
140
172
  "jsvu": "jsvu",
@@ -145,14 +177,14 @@
145
177
  "optionalDependencies": {
146
178
  "@chalker/queue": "^1.0.1",
147
179
  "@exodus/replay": "^1.0.0-rc.9",
148
- "@exodus/test-bundler": "1.0.0-rc.10",
180
+ "@exodus/test-bundler": "1.0.0-rc.11",
149
181
  "c8": "^9.1.0",
150
182
  "expect": "^30.2.0",
151
183
  "fast-glob": "^3.2.11",
152
184
  "playwright-core": "^1.52.0",
153
185
  "pretty-format": "^30.2.0",
154
186
  "puppeteer-core": "^24.14.0",
155
- "tsx": "^4.20.6"
187
+ "tsx": "^4.21.0"
156
188
  },
157
189
  "devDependencies": {
158
190
  "@exodus/eslint-config": "^5.24.0",
@@ -161,7 +193,7 @@
161
193
  "@types/jest-when": "^3.5.2",
162
194
  "@types/node": "^24.0.11",
163
195
  "@typescript-eslint/eslint-plugin": "^7.15.0",
164
- "electron": "^37.3.1",
196
+ "electron": "^38.7.0",
165
197
  "eslint": "^8.44.0",
166
198
  "esvu": "^1.2.16",
167
199
  "jest": "^29.7.0",
@@ -170,9 +202,10 @@
170
202
  "jest-serializer-ansi-escapes": "^3.0.0",
171
203
  "jest-when": "^3.6.0",
172
204
  "jsdom": "^26.1.0",
173
- "jsvu": "^3.0.0",
205
+ "jsvu": "^3.0.3",
174
206
  "prettier": "^3.0.3",
175
- "workerd": "^1.20250826.0"
207
+ "typedoc": "^0.28.16",
208
+ "workerd": "^1.20251217.0"
176
209
  },
177
210
  "peerDependencies": {
178
211
  "@babel/register": "^7.0.0",
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Benchmark utilities for performance testing
3
+ *
4
+ * ```js
5
+ * import { benchmark } from '@exodus/test/benchmark'
6
+ * ```
7
+ *
8
+ * Can be used with or without `@exodus/test` test runner.
9
+ * @module @exodus/test/benchmark
10
+ */
11
+
12
+ /**
13
+ * Benchmark options
14
+ * @inline
15
+ */
16
+ export interface BenchmarkOptions {
17
+ /** Array of arguments to pass to the benchmark function */
18
+ args?: any[]
19
+ /** Timeout in milliseconds (default: 1000) */
20
+ timeout?: number
21
+ /** Skip this benchmark */
22
+ skip?: boolean
23
+ }
24
+
25
+ /**
26
+ * Runs a benchmark
27
+ * @param name - Name of the benchmark
28
+ * @param options - Benchmark options
29
+ * @param fn - Function to benchmark
30
+ */
31
+ export declare function benchmark<A>(
32
+ name: string,
33
+ options: BenchmarkOptions & { args: A[] },
34
+ fn: (arg: A) => any | Promise<any>
35
+ ): Promise<void>
36
+ export declare function benchmark(
37
+ name: string,
38
+ options: BenchmarkOptions & { args?: undefined },
39
+ fn: (arg: number) => any | Promise<any>
40
+ ): Promise<void>
@@ -137,9 +137,10 @@ async function runContext(context) {
137
137
  const guard = { id: null, failed: false }
138
138
  const timeout = options.timeout || Number(process.env.EXODUS_TEST_TIMEOUT) || 5000
139
139
  guard.promise = new Promise((resolve) => {
140
- if (process.env.EXODUS_TEST_PLATFORM === 'engine262') {
140
+ if (['engine262', 'ladybird-js'].includes(process.env.EXODUS_TEST_PLATFORM)) {
141
141
  // parallel timeouts are slowing down everything on engine262
142
142
  // so we let only the host timeout to catch us, not individual test timeout
143
+ // ladybird-js has no timers but has a maximium promise chain length, so it breaks
143
144
  return
144
145
  }
145
146
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * [Jest Expect](https://jestjs.io/docs/expect)-compatible API, but much faster
3
+ *
4
+ * If you are using Jest globals, you don't have to import this manually.
5
+ *
6
+ * Just run `@exodus/test` with `--jest` flag and use global `expect`.
7
+ *
8
+ * See [Jest Expect documentation](https://jestjs.io/docs/expect).
9
+ *
10
+ * If you want to import it directly:
11
+ * ```js
12
+ * import { expect } from '@exodus/test/expect'
13
+ * ```
14
+ *
15
+ * This short-cuts most common cases and tries to avoid loading `expect` altogether,
16
+ * but for everything that can't be bypassed (e.g. test failures) it uses real `expect`.
17
+ *
18
+ * Everything including output formatting matches Jest `expect`.
19
+ * @module @exodus/test/expect
20
+ */
21
+
22
+ export type { Expect } from 'expect'
23
+ export declare const expect: import('expect').Expect
24
+
25
+ /**
26
+ * Load the expect library (for internal use)
27
+ * @internal
28
+ */
29
+ export declare function loadExpect(reason?: string): import('expect').Expect
package/src/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ### The `@exodus/test` package consists of submodules, there is no single export.
3
+ * Import specific submodules instead.
4
+ *
5
+ * Straightforward setups:
6
+ * 1. Import `node:test` and run with `@exodus/test`
7
+ * 2. Use jest globals and run with `@exodus/test --jest`
8
+ * 3. Import `@exodus/test/tape` instead of tape or tap and run with `@exodus/test` (or `node`)
9
+ *
10
+ * See [README](https://github.com/ExodusOSS/test/blob/main/README.md).
11
+ */
12
+ declare module '@exodus/test' {}
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ throw new Error(
2
+ `This package consists of submodules, there is no single export. Import specific submodules instead.
3
+
4
+ Straightforward setups:
5
+ 1. Import node:test and run with @exodus/test
6
+ 2. Use jest globals and run with @exodus/test --jest
7
+ 3. Import @exodus/test/tape instead of tape or tap and run with @exodus/test (or node)
8
+
9
+ See README: https://github.com/ExodusOSS/test/blob/main/README.md
10
+ `
11
+ )
package/src/jest.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * [Jest](https://jestjs.io/)-compatible API, but much faster, ESM friendly, and based on `node:test`
3
+ *
4
+ * You likely don't have to import this manually.
5
+ *
6
+ * Just run `@exodus/test` with `--jest` flag.
7
+ *
8
+ * See [Jest API documentation](https://jestjs.io/docs/api).
9
+ *
10
+ * If you want to run Jest test files with just `node` (or without `--jest` flag), do e.g.:
11
+ * ```js
12
+ * import { describe, test, expect, jest } from '@exodus/test/jest'
13
+ * import { beforeEach, afterEach, beforeAll, afterAll } from '@exodus/test/jest'
14
+ * ```
15
+ * @module @exodus/test/jest
16
+ */
17
+
18
+ // Re-export Jest types from @jest/globals
19
+ export { describe, test, it, jest, beforeEach, afterEach, beforeAll, afterAll } from '@jest/globals'
20
+ export type { expect, Expect } from './expect.d.ts'
21
+
22
+ /**
23
+ * Alias for test()
24
+ *
25
+ * For using the resolution as a drop-in replacement for libs expecting `should()`
26
+ * @hidden
27
+ */
28
+ export declare function should(name: string, fn: () => void | Promise<void>, timeout?: number): void
@@ -13,7 +13,8 @@ export const specialEnvironments = {
13
13
  runScripts: 'dangerously',
14
14
  virtualConsole,
15
15
  })
16
- virtualConsole.sendTo(console, { omitJSDOMErrors: true })
16
+ const forwardTo = virtualConsole.forwardTo ? 'forwardTo' : 'sendTo'
17
+ virtualConsole[forwardTo](console, { omitJSDOMErrors: true })
17
18
  virtualConsole.on('jsdomError', (error) => {
18
19
  throw error
19
20
  })
package/src/mock.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Extra mocking and testing utilities
3
+ *
4
+ * - Record / replay / inspect `fetch()` sessions
5
+ * - Record / replay / inspect `WebSocket` sessions
6
+ * - Speed up timers
7
+ * - Debug (or assert no) active timers
8
+ * @module @exodus/test/mock
9
+ */
10
+
11
+ /// <reference types="node" />
12
+
13
+ /**
14
+ * Network replay utilities
15
+ */
16
+
17
+ /** Records fetch calls and returns a fetch function */
18
+ export declare function fetchRecord(options?: any): typeof fetch
19
+
20
+ /** Replays fetch calls from recording and returns a fetch function */
21
+ export declare function fetchReplay(): typeof fetch
22
+
23
+ /** Records WebSocket calls and returns a WebSocket constructor */
24
+ export declare function websocketRecord(options?: any): typeof WebSocket
25
+
26
+ /** Replays WebSocket calls from recording and returns a WebSocket constructor */
27
+ export declare function websocketReplay(options?: any): typeof WebSocket
28
+
29
+ /**
30
+ * Timer tracking and debugging utilities
31
+ */
32
+
33
+ /** Enables timer tracking */
34
+ export declare function timersTrack(): void
35
+
36
+ /** Outputs debug information about active timers */
37
+ export declare function timersDebug(): void
38
+
39
+ /** Lists all active timers */
40
+ export declare function timersList(): any[]
41
+
42
+ /** Asserts no timers are active */
43
+ export declare function timersAssert(): void
44
+
45
+ /**
46
+ * Speeds up timers by the given rate
47
+ * @param rate - Speed multiplier (e.g., 2 means 2x faster)
48
+ * @param options - Configuration options
49
+ * @param options.apis - Array of APIs to speed up (default: ['setTimeout', 'setInterval', 'Date'])
50
+ */
51
+ export declare function timersSpeedup(
52
+ rate: number,
53
+ options?: { apis?: ('setTimeout' | 'setInterval' | 'Date')[] }
54
+ ): void
package/src/node.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * [node:test](https://nodejs.org/api/test.html)-compatible API
3
+ *
4
+ * You most likely don't have to import this manually, just use `node:test`.
5
+ *
6
+ * Example:
7
+ * ```js
8
+ * import { test, describe } from 'node:test'
9
+ * ```
10
+ *
11
+ * This import is what `@exodus/test` will resolve that to on non-Node.js engines,
12
+ * e.g. browsers and barebones.
13
+ *
14
+ * See [node:test documentation](https://nodejs.org/api/test.html).
15
+ * @module @exodus/test/node
16
+ */
17
+
18
+ /// <reference types="node" />
19
+
20
+ // Re-export from node:test module
21
+ export { describe, test, it, beforeEach, afterEach, before, after, mock, snapshot } from 'node:test'
package/src/node.js CHANGED
@@ -7,4 +7,4 @@ function setResolveSnapshotPath() {
7
7
 
8
8
  export const snapshot = { setDefaultSnapshotSerializers, setResolveSnapshotPath }
9
9
 
10
- export { mock, describe, test, beforeEach, afterEach, before, after } from './engine.js'
10
+ export { mock, describe, test, beforeEach, afterEach, before, after, test as it } from './engine.js'
package/src/tape.cjs CHANGED
@@ -9,6 +9,7 @@ const loadLib = async () => {
9
9
  const test = async (...args) => (await loadLib()).test(...args)
10
10
  test.skip = async (...args) => (await loadLib()).test.skip(...args)
11
11
  test.only = async (...args) => (await loadLib()).test.only(...args)
12
+ test.onFinish = async (...args) => (await loadLib()).test.onFinish(...args)
12
13
  test.test = test
13
14
  /* eslint-enable unicorn/no-await-expression-member */
14
15
 
package/src/tape.d.ts ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * [tape](https://npmjs.com/package/tape)-compatible API
3
+ *
4
+ * Replace your `tape` imports with `@exodus/test/tape` and it will run on top of `node:test`.
5
+ *
6
+ * And will be runnable in browsers / barebone engines with `@exodus/test`.
7
+ *
8
+ * Example:
9
+ * ```js
10
+ * import test from '@exodus/test/tape' // ESM
11
+ * const test = require('@exodus/test/tape') // CJS
12
+ * ```
13
+ * @module @exodus/test/tape
14
+ */
15
+
16
+ /// <reference types="node" />
17
+
18
+ /**
19
+ * Tape test context/assertion object
20
+ */
21
+ export interface Test {
22
+ /** Plans the number of assertions */
23
+ plan(count: number): void
24
+
25
+ /** Ends the test */
26
+ end(): void
27
+
28
+ /** Skips the test */
29
+ skip(msg?: string): void
30
+
31
+ /** Marks test as todo */
32
+ todo(msg?: string): void
33
+
34
+ /** Adds a comment */
35
+ comment(msg: string): void
36
+
37
+ /** Nested test */
38
+ test(name: string, cb: (t: Test) => void): void
39
+ test(name: string, opts: TestOptions, cb: (t: Test) => void): void
40
+
41
+ /** Assertions */
42
+
43
+ /** Assert that value is truthy */
44
+ ok(value: any, msg?: string): void
45
+ true(value: any, msg?: string): void
46
+ assert(value: any, msg?: string): void
47
+
48
+ /** Assert that value is falsy */
49
+ notOk(value: any, msg?: string): void
50
+ false(value: any, msg?: string): void
51
+ notok(value: any, msg?: string): void
52
+
53
+ /** Assert strict equality */
54
+ equal(actual: any, expected: any, msg?: string): void
55
+ equals(actual: any, expected: any, msg?: string): void
56
+ isEqual(actual: any, expected: any, msg?: string): void
57
+ is(actual: any, expected: any, msg?: string): void
58
+ strictEqual(actual: any, expected: any, msg?: string): void
59
+ strictEquals(actual: any, expected: any, msg?: string): void
60
+
61
+ /** Assert strict inequality */
62
+ notEqual(actual: any, expected: any, msg?: string): void
63
+ notEquals(actual: any, expected: any, msg?: string): void
64
+ notStrictEqual(actual: any, expected: any, msg?: string): void
65
+ notStrictEquals(actual: any, expected: any, msg?: string): void
66
+ doesNotEqual(actual: any, expected: any, msg?: string): void
67
+ isNotEqual(actual: any, expected: any, msg?: string): void
68
+ isNot(actual: any, expected: any, msg?: string): void
69
+ not(actual: any, expected: any, msg?: string): void
70
+ isInequal(actual: any, expected: any, msg?: string): void
71
+
72
+ /** Assert loose equality */
73
+ looseEqual(actual: any, expected: any, msg?: string): void
74
+ looseEquals(actual: any, expected: any, msg?: string): void
75
+
76
+ /** Assert loose inequality */
77
+ notLooseEqual(actual: any, expected: any, msg?: string): void
78
+ notLooseEquals(actual: any, expected: any, msg?: string): void
79
+
80
+ /** Assert deep strict equality */
81
+ deepEqual(actual: any, expected: any, msg?: string): void
82
+ deepEquals(actual: any, expected: any, msg?: string): void
83
+ isEquivalent(actual: any, expected: any, msg?: string): void
84
+ same(actual: any, expected: any, msg?: string): void
85
+
86
+ /** Assert deep strict inequality */
87
+ notDeepEqual(actual: any, expected: any, msg?: string): void
88
+ notDeepEquals(actual: any, expected: any, msg?: string): void
89
+ notEquivalent(actual: any, expected: any, msg?: string): void
90
+ notDeeply(actual: any, expected: any, msg?: string): void
91
+ notSame(actual: any, expected: any, msg?: string): void
92
+ isNotDeepEqual(actual: any, expected: any, msg?: string): void
93
+ isNotDeeply(actual: any, expected: any, msg?: string): void
94
+ isNotEquivalent(actual: any, expected: any, msg?: string): void
95
+ isInequivalent(actual: any, expected: any, msg?: string): void
96
+
97
+ /** Assert deep loose equality */
98
+ deepLooseEqual(actual: any, expected: any, msg?: string): void
99
+
100
+ /** Assert deep loose inequality */
101
+ notDeepLooseEqual(actual: any, expected: any, msg?: string): void
102
+
103
+ /** Assert function throws */
104
+ throws(fn: () => any, expected?: RegExp | Function, msg?: string): void
105
+
106
+ /** Assert function does not throw */
107
+ doesNotThrow(fn: () => any, expected?: RegExp | Function, msg?: string): void
108
+
109
+ /** Assert promise rejects */
110
+ rejects(promise: Promise<any>, expected?: RegExp | Function, msg?: string): Promise<void>
111
+
112
+ /** Assert promise resolves */
113
+ resolves(promise: Promise<any>, msg?: string): Promise<void>
114
+ doesNotReject(promise: Promise<any>, expected?: RegExp | Function, msg?: string): Promise<void>
115
+
116
+ /** Force a passing assertion */
117
+ pass(msg?: string): void
118
+
119
+ /** Force a failing assertion */
120
+ fail(msg?: string): void
121
+
122
+ /** Assert no error */
123
+ error(err: any, msg?: string): void
124
+ ifError(err: any, msg?: string): void
125
+ ifErr(err: any, msg?: string): void
126
+ iferror(err: any, msg?: string): void
127
+
128
+ /** Custom assertion function */
129
+ assertion(fn: Function, ...args: any[]): void
130
+ }
131
+
132
+ /**
133
+ * Test options
134
+ */
135
+ export interface TestOptions {
136
+ skip?: boolean
137
+ todo?: boolean
138
+ timeout?: number
139
+ concurrency?: number
140
+ }
141
+
142
+ /**
143
+ * Test function
144
+ */
145
+ export interface TestFunction {
146
+ /** Run a test */
147
+ (name: string, cb: (t: Test) => void): void
148
+ (name: string, opts: TestOptions, cb: (t: Test) => void): void
149
+ (cb: (t: Test) => void): void
150
+ (opts: TestOptions, cb: (t: Test) => void): void
151
+
152
+ /** Only run this test */
153
+ only: TestFunction
154
+
155
+ /** Skip this test */
156
+ skip: TestFunction
157
+
158
+ /** Register a callback to run after all tests finish */
159
+ onFinish(fn: () => void): void
160
+ }
161
+
162
+ declare const test: TestFunction
163
+
164
+ export { test }
165
+
166
+ export default test
package/src/tape.js CHANGED
@@ -155,6 +155,7 @@ function tapeWrap(test) {
155
155
  if (test.skip) tap.skip = tapeWrap(test.skip)
156
156
  if (test.only) tap.only = tapeWrap(test.only)
157
157
  tap.onFinish = (fn) => after(fn)
158
+ tap.test = tap // circular
158
159
  return tap
159
160
  }
160
161
 
package/tape.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from './src/tape.js'
2
+ export { default } from './src/tape.js'
package/CHANGELOG.md DELETED
@@ -1,3 +0,0 @@
1
- ## 1.0.0
2
-
3
- Initial release