sxy-test-runner 2.0.5 → 2.1.0
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/AGENTS.md +286 -0
- package/dist/cli/lib/defaultConfig.d.ts.map +1 -1
- package/dist/cli/lib/defaultConfig.js +4 -3
- package/dist/cli/lib/defaultConfig.js.map +1 -1
- package/dist/cli/lib/loadTestFile.d.ts +1 -1
- package/dist/cli/lib/loadTestFile.d.ts.map +1 -1
- package/dist/cli/lib/loadTestFile.js +1 -1
- package/dist/cli/lib/loadTestFile.js.map +1 -1
- package/dist/cli/lib/loadTestFile.unitTest.js +13 -0
- package/dist/cli/lib/loadTestFile.unitTest.js.map +1 -1
- package/dist/packages/esmReload.additions.unitTest.d.ts +2 -0
- package/dist/packages/esmReload.additions.unitTest.d.ts.map +1 -0
- package/dist/packages/esmReload.additions.unitTest.js +50 -0
- package/dist/packages/esmReload.additions.unitTest.js.map +1 -0
- package/dist/packages/esmReload.d.ts +11 -0
- package/dist/packages/esmReload.d.ts.map +1 -0
- package/dist/packages/esmReload.js +43 -0
- package/dist/packages/esmReload.js.map +1 -0
- package/dist/packages/esmReload.unitTest.d.ts +2 -0
- package/dist/packages/esmReload.unitTest.d.ts.map +1 -0
- package/dist/packages/esmReload.unitTest.js +50 -0
- package/dist/packages/esmReload.unitTest.js.map +1 -0
- package/package.json +3 -1
- package/readme.md +28 -11
package/AGENTS.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# sxy-test-runner guide for coding agents
|
|
2
|
+
|
|
3
|
+
This document is the practical source of truth for agents that need to add, run, or
|
|
4
|
+
maintain tests using `sxy-test-runner`. It describes the current TypeScript implementation.
|
|
5
|
+
For introductory human-facing usage, also see `readme.md`.
|
|
6
|
+
|
|
7
|
+
## Safe default workflow
|
|
8
|
+
|
|
9
|
+
1. Confirm the project uses Node.js 24 or newer and ES modules.
|
|
10
|
+
2. Find the active `sxyt.config.*` or `sxy-test-runner.config.*` file. It may be in the
|
|
11
|
+
project root or `config/`.
|
|
12
|
+
3. Inspect `testsBase`, `tests`, and `testsIgnore` before creating a test so the file is
|
|
13
|
+
actually discoverable.
|
|
14
|
+
4. Import only `describe` from `sxy-test-runner`. Obtain `it`, `test`, and local hooks from
|
|
15
|
+
the `describe` callback.
|
|
16
|
+
5. Run `npx sxy-test-runner once` for finite agent or CI work.
|
|
17
|
+
6. Treat exit code `3` as test failure. Exit code `0` can also mean that no tests were
|
|
18
|
+
found, so check the output when discovery is in doubt.
|
|
19
|
+
|
|
20
|
+
Do not run a test file directly with `node`. `describe()` requires runner-owned global
|
|
21
|
+
state and deliberately throws if the framework has not initialized it.
|
|
22
|
+
|
|
23
|
+
## Minimal test
|
|
24
|
+
|
|
25
|
+
The runner supplies test organization and execution, but not an assertion library. Use
|
|
26
|
+
Node's assertion API, Chai, or another ESM-compatible assertion library.
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import assert from 'node:assert/strict'
|
|
30
|
+
import { describe } from 'sxy-test-runner'
|
|
31
|
+
import { add } from '../src/add.js'
|
|
32
|
+
|
|
33
|
+
describe('add()', ({ it }) => {
|
|
34
|
+
it('adds two numbers', () => {
|
|
35
|
+
assert.equal(add(1, 2), 3)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Both describe callbacks and test callbacks may be async:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
describe('records', ({ test }) => {
|
|
44
|
+
test('loads a record', async () => {
|
|
45
|
+
const record = await loadRecord('example')
|
|
46
|
+
assert.equal(record.name, 'example')
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`test` is an alias of `it`. There are no built-in mocks, spies, snapshots, fake timers, or
|
|
52
|
+
assertions.
|
|
53
|
+
|
|
54
|
+
## CLI and discovery
|
|
55
|
+
|
|
56
|
+
Available executable names are `sxyt`, `sxy-test`, and `sxy-test-runner`.
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
# Default: watch mode
|
|
60
|
+
npx sxy-test-runner
|
|
61
|
+
|
|
62
|
+
# One finite run, suitable for agents and CI
|
|
63
|
+
npx sxy-test-runner once
|
|
64
|
+
|
|
65
|
+
# Use a specific config
|
|
66
|
+
npx sxy-test-runner once --config config/sxyt.config.js
|
|
67
|
+
|
|
68
|
+
# Override the test glob in watch mode
|
|
69
|
+
npx sxy-test-runner watch --files "**/*.focused.test.js"
|
|
70
|
+
|
|
71
|
+
# Generate an initial config
|
|
72
|
+
npx sxy-test-runner init
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `--files` option is currently applied by watch mode, not by `once`. For a focused
|
|
76
|
+
one-off run, use a temporary/configured test pattern rather than assuming
|
|
77
|
+
`once --files` filters the suite.
|
|
78
|
+
|
|
79
|
+
Test discovery uses Node's built-in glob implementation. Patterns are resolved relative to
|
|
80
|
+
`testsBase`, and ignore patterns are resolved from that same base.
|
|
81
|
+
|
|
82
|
+
## Test lifecycle and data flow
|
|
83
|
+
|
|
84
|
+
All test files run in one Node.js process. A test file is imported with a fresh
|
|
85
|
+
`?instance=N` query, and the runner's ESM resolver propagates that instance through its
|
|
86
|
+
static dependency graph. Importing the file registers its describe blocks in
|
|
87
|
+
`global.__sxy_test_runner__`; execution happens afterward.
|
|
88
|
+
|
|
89
|
+
A module whose filename contains the explicit `._cached_.js` marker uses Node's ordinary
|
|
90
|
+
process-wide module cache instead. Its static dependencies are cached with it, allowing
|
|
91
|
+
explicitly shared state across test files:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import { database } from './fixtures/database._cached_.js'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The equivalent source suffixes are supported for JavaScript and TypeScript module
|
|
98
|
+
extensions, including `._cached_.ts`, `._cached_.mjs`, `._cached_.mts`,
|
|
99
|
+
`._cached_.cjs`, and `._cached_.cts`. Under TypeScript `NodeNext`, source imports
|
|
100
|
+
normally use the emitted `._cached_.js` extension.
|
|
101
|
+
|
|
102
|
+
The lifecycle is:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
setup
|
|
106
|
+
each test file:
|
|
107
|
+
beforeEachFile
|
|
108
|
+
each describe:
|
|
109
|
+
beforeEachDescribe
|
|
110
|
+
describe callback (register tests and local hooks)
|
|
111
|
+
each test:
|
|
112
|
+
config beforeEachTest
|
|
113
|
+
local beforeEachTest
|
|
114
|
+
test callback
|
|
115
|
+
local afterEachTest
|
|
116
|
+
config afterEachTest
|
|
117
|
+
local afterDescribe
|
|
118
|
+
afterEachDescribe
|
|
119
|
+
afterEachFile
|
|
120
|
+
teardown
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The global config hooks accept a function, a project-relative module path, or an array of
|
|
124
|
+
either. Arrays run in order. Functions may return an object, and task modules may provide
|
|
125
|
+
named exports.
|
|
126
|
+
|
|
127
|
+
Hook values are deliberately scoped:
|
|
128
|
+
|
|
129
|
+
- `beforeEachFile` values are passed only to the matching `afterEachFile`.
|
|
130
|
+
- `beforeEachDescribe` values are added to the `describe` callback argument and passed to
|
|
131
|
+
the matching `afterEachDescribe`.
|
|
132
|
+
- `beforeEachTest` values are added to the individual test callback argument and passed to
|
|
133
|
+
the matching `afterEachTest`.
|
|
134
|
+
- Reusing the same exported key within one scope is an error.
|
|
135
|
+
|
|
136
|
+
Example config-driven fixture:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
// sxyt.config.js
|
|
140
|
+
export default {
|
|
141
|
+
testsBase: 'dist',
|
|
142
|
+
tests: '**/*.test.js',
|
|
143
|
+
|
|
144
|
+
beforeEachTest: async () => {
|
|
145
|
+
const database = await createTestDatabase()
|
|
146
|
+
return { database }
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
afterEachTest: async ({ database }) => {
|
|
150
|
+
await database.close()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
describe('repository', ({ it }) => {
|
|
157
|
+
it('writes a record', async ({ database }) => {
|
|
158
|
+
await database.insert({ id: 1 })
|
|
159
|
+
assert.equal(await database.count(), 1)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Describe-local hooks are useful when the fixture belongs to one describe block:
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
describe('server', ({ it, beforeEachTest, afterEachTest, afterDescribe }) => {
|
|
168
|
+
const server = createServer()
|
|
169
|
+
|
|
170
|
+
beforeEachTest(async () => {
|
|
171
|
+
const client = await server.createClient()
|
|
172
|
+
return { client }
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
afterEachTest(async () => {
|
|
176
|
+
await server.reset()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('responds', async ({ client }) => {
|
|
180
|
+
assert.equal((await client.get('/health')).status, 200)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Inline cleanup at the bottom of the describe callback is too early.
|
|
184
|
+
afterDescribe(async () => {
|
|
185
|
+
await server.close()
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Execution and isolation
|
|
191
|
+
|
|
192
|
+
Configuration has `execution.files`, `execution.describes`, and `execution.tests`, each
|
|
193
|
+
accepting `sequential` or `parallel`.
|
|
194
|
+
|
|
195
|
+
Prefer sequential execution unless the tested code and fixtures are isolated. The runner
|
|
196
|
+
uses one process, so globals, environment variables, singleton modules, open ports, and
|
|
197
|
+
shared databases can collide. Per-test `console.log` association also depends on sequential
|
|
198
|
+
execution because the runner temporarily replaces the global `console.log`.
|
|
199
|
+
|
|
200
|
+
Current implementation detail: individual tests inside a describe are awaited one at a
|
|
201
|
+
time even when `execution.tests` is set to `parallel`. Do not design a suite that depends
|
|
202
|
+
on parallel `it` execution.
|
|
203
|
+
|
|
204
|
+
## Watch mode
|
|
205
|
+
|
|
206
|
+
Watch mode builds a static dependency tree for every test. When a file changes, it reruns
|
|
207
|
+
the changed test itself and tests whose static import graph includes that file. If
|
|
208
|
+
`watch.reRunFailingTests` is true, previously failing files are included until they pass.
|
|
209
|
+
|
|
210
|
+
Important limitations:
|
|
211
|
+
|
|
212
|
+
- Dynamic imports are not found by dependency analysis.
|
|
213
|
+
- All reruns still share one long-lived process.
|
|
214
|
+
- A fresh ESM instance reloads the test's static module graph, but unrelated global state
|
|
215
|
+
and external resources remain the test author's responsibility.
|
|
216
|
+
- Modules imported through a `._cached_.*` boundary also remain loaded for the life of the
|
|
217
|
+
process. Keep mutable access sequential or provide application-level synchronization,
|
|
218
|
+
and arrange explicit cleanup for resources such as servers and database connections.
|
|
219
|
+
- Use `once` instead of watch mode for autonomous agent verification so the command
|
|
220
|
+
terminates and returns a meaningful status.
|
|
221
|
+
|
|
222
|
+
## Configuration reference
|
|
223
|
+
|
|
224
|
+
The exported config type is available for JavaScript type checking:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
// @ts-check
|
|
228
|
+
|
|
229
|
+
/** @type {import('sxy-test-runner').Config} */
|
|
230
|
+
export default {
|
|
231
|
+
testsBase: 'dist',
|
|
232
|
+
tests: '**/*.test.js',
|
|
233
|
+
testsIgnore: '**/node_modules/**',
|
|
234
|
+
showErrorTrace: true,
|
|
235
|
+
|
|
236
|
+
setup: undefined,
|
|
237
|
+
beforeEachFile: undefined,
|
|
238
|
+
afterEachFile: undefined,
|
|
239
|
+
beforeEachDescribe: undefined,
|
|
240
|
+
afterEachDescribe: undefined,
|
|
241
|
+
beforeEachTest: undefined,
|
|
242
|
+
afterEachTest: undefined,
|
|
243
|
+
teardown: undefined,
|
|
244
|
+
|
|
245
|
+
execution: {
|
|
246
|
+
files: 'sequential',
|
|
247
|
+
describes: 'sequential',
|
|
248
|
+
tests: 'sequential'
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
watch: {
|
|
252
|
+
watchFilesBase: 'dist',
|
|
253
|
+
watchFiles: '**/*.js',
|
|
254
|
+
watchFilesIgnore: '**/node_modules/**',
|
|
255
|
+
runAllOnStartup: true,
|
|
256
|
+
reRunFailingTests: true
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Set `showErrorTrace: true` when an agent needs stack traces for test failures. With the
|
|
262
|
+
default `false`, failure output is intentionally shorter.
|
|
263
|
+
|
|
264
|
+
## Maintaining this repository
|
|
265
|
+
|
|
266
|
+
Source files live in `src/`; `dist/` is generated. Do not make a source fix only in
|
|
267
|
+
`dist/`.
|
|
268
|
+
|
|
269
|
+
The repository's unit tests are also TypeScript source files under `src/` and are compiled
|
|
270
|
+
into `dist/**/*.unitTest.js`. The normal finite verification sequence is:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
npm run build-once
|
|
274
|
+
npm run unit-tests-once
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
For a targeted area, build first and then use the matching `unit-test-*` package script.
|
|
278
|
+
Many targeted scripts are watch commands, so prefer `unit-tests-once` for final,
|
|
279
|
+
terminating verification.
|
|
280
|
+
|
|
281
|
+
When changing public behavior, check these sources together:
|
|
282
|
+
|
|
283
|
+
- `src/types.ts` for the public config and callback shapes.
|
|
284
|
+
- `src/cli/init.ts` for the generated config template.
|
|
285
|
+
- `src/cli/lib/defaultConfig.ts` for runtime defaults.
|
|
286
|
+
- `readme.md` and this guide for published behavior.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultConfig.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"defaultConfig.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,eAAO,MAAM,aAAa,EAAE,WAuB3B,CAAA"}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
export const defaultConfig = {
|
|
2
2
|
testsBase: '.',
|
|
3
3
|
tests: [
|
|
4
|
+
'**/*.sxyt.{js,mjs,jsx}',
|
|
4
5
|
'**/*.test.{js,mjs,jsx}',
|
|
5
6
|
'**/*.spec.{js,mjs,jsx}',
|
|
6
7
|
'**/tests?/*.{js,mjs,jsx}',
|
|
7
8
|
'**/__tests?__/*.{js,mjs,jsx}'
|
|
8
9
|
],
|
|
9
|
-
testsIgnore: ['node_modules'],
|
|
10
|
+
testsIgnore: ['**/node_modules/**/*'],
|
|
10
11
|
showErrorTrace: false,
|
|
11
12
|
execution: {
|
|
12
|
-
files: '
|
|
13
|
+
files: 'sequential',
|
|
13
14
|
describes: 'sequential',
|
|
14
15
|
tests: 'sequential'
|
|
15
16
|
},
|
|
16
17
|
watch: {
|
|
17
18
|
watchFilesBase: '.',
|
|
18
|
-
watchFiles: '
|
|
19
|
+
watchFiles: '**/*.js',
|
|
19
20
|
watchFilesIgnore: '**/node_modules/**/*',
|
|
20
21
|
runAllOnStartup: true,
|
|
21
22
|
reRunFailingTests: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultConfig.js","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACtC,SAAS,EAAE,GAAG;IACd,KAAK,EAAE;QACH,wBAAwB;QACxB,wBAAwB;QACxB,0BAA0B;QAC1B,8BAA8B;KACjC;IACD,WAAW,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"defaultConfig.js","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACtC,SAAS,EAAE,GAAG;IACd,KAAK,EAAE;QACH,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,0BAA0B;QAC1B,8BAA8B;KACjC;IACD,WAAW,EAAE,CAAC,sBAAsB,CAAC;IACrC,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,YAAY;KACtB;IACD,KAAK,EAAE;QACH,cAAc,EAAE,GAAG;QACnB,UAAU,EAAE,SAAS;QACrB,gBAAgB,EAAE,sBAAsB;QACxC,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,IAAI;KAC1B;CACJ,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadTestFile.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"loadTestFile.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"AAGA,OAAO,6BAA6B,CAAA;AAIpC,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAW3D,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoD3F"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import chalk from '../../packages/chalk.js';
|
|
3
3
|
import { createTimeProfiler } from '../../packages/timeProfiler.js';
|
|
4
|
-
import '
|
|
4
|
+
import '../../packages/esmReload.js'; // registers the test module reload hook
|
|
5
5
|
import { showTimeProfiling } from '../../config.js';
|
|
6
6
|
import { debug } from '../../output.js';
|
|
7
7
|
const { timeProfileAsync } = createTimeProfiler(showTimeProfiling);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadTestFile.js","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,yBAAyB,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,
|
|
1
|
+
{"version":3,"file":"loadTestFile.js","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,yBAAyB,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,6BAA6B,CAAA,CAAC,wCAAwC;AAE7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAGvC,MAAM,EAAE,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAAA;AAElE,IAAI,OAAO,GAA6B,IAAI,CAAA;AAE5C,IAAI,eAAe,GAAG,CAAC,CAAA;AACvB,SAAS,WAAW,CAAC,MAAc;IAC/B,OAAO,MAAM,CAAC,MAAM,GAAG,aAAa,eAAe,EAAE,EAAE,CAAC,CAAA;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAmB,EAAE,QAAgB;IACpE,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACnB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA,mEAAmE,CAAC,CAAA;QACtF,MAAM,OAAO,CAAA;QACb,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA,iDAAiD,CAAC,CAAA;IACxE,CAAC;IAED,MAAM,WAAW,GAAG,gBAAgB,CAAC,aAAa,QAAQ,EAAE,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;QAEhC,qCAAqC;QACrC,MAAM,CAAC,mBAAmB,GAAG;YACzB,SAAS,EAAE,EAAE;YACb,eAAe,EAAE,CAAC;SACrB,CAAA;QAED,MAAM,eAAe,GAAG,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QAE9D,MAAM,IAAI,GAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;QAEzC,MAAM,mBAAmB,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,KAAK,YAAY,CAAA;QAEnE,+BAA+B;QAC/B,IAAI,kBAAkD,CAAA;QACtD,MAAM,IAAI,GAAgB,EAAE,CAAA;QAC5B,IAAI,mBAAmB,EAAE,CAAC;YACtB,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAA,CAAC,iCAAiC;YAClE,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,KAAgB,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC,CAAA,CAAC,iCAAiC;QACjG,CAAC;QACD,IAAI;QAEJ,IAAI,CAAC;YACD,MAAM,WAAW,CAAC,eAAe,CAAC,CAAA;YAClC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,mBAAmB,EAAE,SAAS,IAAI,EAAE,CAAA;YAC5D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QACtB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,KAAK,GAAG,uBAAuB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;QACnD,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;YAChB,0CAA0C;YAC1C,IAAI,mBAAmB,IAAI,kBAAkB,EAAE,CAAC;gBAC5C,OAAO,CAAC,GAAG,GAAG,kBAAkB,CAAA,CAAC,iCAAiC;YACtE,CAAC;YACD,IAAI;QACR,CAAC;QAED,OAAO,IAAI,CAAA;IACf,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,WAAW,CAAA;IAErB,OAAO,MAAM,WAAW,CAAA;AAC5B,CAAC"}
|
|
@@ -9,5 +9,18 @@ mochaDescribe('loadTestFile() function', function () {
|
|
|
9
9
|
describes.at(-1)?.counter.should.equal(2);
|
|
10
10
|
describes[0]?.tests.should.be.a('function');
|
|
11
11
|
});
|
|
12
|
+
mochaIt('shares cached module graphs while isolating ordinary module graphs', async function () {
|
|
13
|
+
const testGlobal = global;
|
|
14
|
+
testGlobal.cacheBoundarySharedInstances = [];
|
|
15
|
+
testGlobal.cacheBoundaryIsolatedInstances = [];
|
|
16
|
+
await loadTestFile(defaultConfig, 'unitTesting/sampleTests/cacheBoundaryOne.test.js');
|
|
17
|
+
await loadTestFile(defaultConfig, 'unitTesting/sampleTests/cacheBoundaryTwo.test.js');
|
|
18
|
+
testGlobal.cacheBoundarySharedInstances.length.should.equal(2);
|
|
19
|
+
testGlobal.cacheBoundaryIsolatedInstances.length.should.equal(2);
|
|
20
|
+
chaiExpect(testGlobal.cacheBoundarySharedInstances[0])
|
|
21
|
+
.to.equal(testGlobal.cacheBoundarySharedInstances[1]);
|
|
22
|
+
chaiExpect(testGlobal.cacheBoundaryIsolatedInstances[0])
|
|
23
|
+
.not.to.equal(testGlobal.cacheBoundaryIsolatedInstances[1]);
|
|
24
|
+
});
|
|
12
25
|
});
|
|
13
26
|
//# sourceMappingURL=loadTestFile.unitTest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadTestFile.unitTest.js","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.unitTest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,aAAa,CAAC,yBAAyB,EAAE;IACrC,OAAO,CAAC,mEAAmE,EAAE,KAAK;QAC9E,MAAM,cAAc,GAAG,uDAAuD,CAAA;QAE9E,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;QAE9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAA;QACtC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACzC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"loadTestFile.unitTest.js","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.unitTest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,aAAa,CAAC,yBAAyB,EAAE;IACrC,OAAO,CAAC,mEAAmE,EAAE,KAAK;QAC9E,MAAM,cAAc,GAAG,uDAAuD,CAAA;QAE9E,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;QAE9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAA;QACtC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACzC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,oEAAoE,EAAE,KAAK;QAC/E,MAAM,UAAU,GAAG,MAGlB,CAAA;QACD,UAAU,CAAC,4BAA4B,GAAG,EAAE,CAAA;QAC5C,UAAU,CAAC,8BAA8B,GAAG,EAAE,CAAA;QAE9C,MAAM,YAAY,CAAC,aAAa,EAAE,kDAAkD,CAAC,CAAA;QACrF,MAAM,YAAY,CAAC,aAAa,EAAE,kDAAkD,CAAC,CAAA;QAErF,UAAU,CAAC,4BAA4B,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC9D,UAAU,CAAC,8BAA8B,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChE,UAAU,CAAC,UAAU,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC;aACjD,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,CAAA;QACzD,UAAU,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC;aACnD,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;AAEN,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmReload.additions.unitTest.d.ts","sourceRoot":"","sources":["../../src/packages/esmReload.additions.unitTest.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { cwd, execPath } from 'node:process';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
function spawnPromisified(command, args) {
|
|
6
|
+
const child = spawn(command, args);
|
|
7
|
+
let stderr = '';
|
|
8
|
+
let stdout = '';
|
|
9
|
+
child.stderr.setEncoding('utf8');
|
|
10
|
+
child.stderr.on('data', (data) => { stderr += data; });
|
|
11
|
+
child.stdout.setEncoding('utf8');
|
|
12
|
+
child.stdout.on('data', (data) => { stdout += data; });
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
child.on('close', (code, signal) => {
|
|
15
|
+
resolve({ code, signal, stderr, stdout });
|
|
16
|
+
});
|
|
17
|
+
child.on('error', reject);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
mochaDescribe('esmReload', function () {
|
|
21
|
+
mochaIt('reloads an ES module and its transitive dependencies', async function () {
|
|
22
|
+
const fixtureDirectory = join(cwd(), 'unitTesting/esmReload/fixtures/simple');
|
|
23
|
+
const fixture = join(fixtureDirectory, 'index.js');
|
|
24
|
+
const transitiveUrl = pathToFileURL(join(fixtureDirectory, 'transitive.js')).href;
|
|
25
|
+
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, [fixture]);
|
|
26
|
+
chaiExpect(code).to.equal(0);
|
|
27
|
+
chaiExpect(signal).to.equal(null);
|
|
28
|
+
chaiExpect(stderr).to.equal('');
|
|
29
|
+
chaiExpect(stdout.trim().split(/\r?\n/)).to.deep.equal([
|
|
30
|
+
transitiveUrl,
|
|
31
|
+
`${transitiveUrl}?instance=sxy-test-runner-reload-0`,
|
|
32
|
+
`${transitiveUrl}?instance=1`,
|
|
33
|
+
`${transitiveUrl}?instance=sxy-test-runner-reload-1`,
|
|
34
|
+
`${transitiveUrl}?instance=2`
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
mochaIt('reloads a file and ordinary modules while sharing _cached_ exports', async function () {
|
|
38
|
+
const fixture = join(cwd(), 'unitTesting/esmReload/fixtures/repeated/index.js');
|
|
39
|
+
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, [fixture]);
|
|
40
|
+
chaiExpect(code).to.equal(0);
|
|
41
|
+
chaiExpect(signal).to.equal(null);
|
|
42
|
+
chaiExpect(stderr).to.equal('');
|
|
43
|
+
chaiExpect(JSON.parse(stdout)).to.deep.equal({
|
|
44
|
+
loadedFileIsFresh: true,
|
|
45
|
+
ordinaryModuleIsFresh: true,
|
|
46
|
+
cachedExportIsShared: true
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=esmReload.additions.unitTest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmReload.additions.unitTest.js","sourceRoot":"","sources":["../../src/packages/esmReload.additions.unitTest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface ResolveContext {
|
|
2
|
+
parentURL?: string;
|
|
3
|
+
}
|
|
4
|
+
interface ResolveResult {
|
|
5
|
+
url: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
type NextResolve = (specifier: string, context: ResolveContext) => ResolveResult | Promise<ResolveResult>;
|
|
9
|
+
export declare function resolve(specifier: string, context: ResolveContext, nextResolve: NextResolve): Promise<ResolveResult>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=esmReload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmReload.d.ts","sourceRoot":"","sources":["../../src/packages/esmReload.ts"],"names":[],"mappings":"AAsBA,UAAU,cAAc;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,UAAU,aAAa;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACzB;AAED,KAAK,WAAW,GAAG,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,KACtB,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;AAsB3C,wBAAsB,OAAO,CACzB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,WAAW,GACzB,OAAO,CAAC,aAAa,CAAC,CA2BxB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isBuiltin, register } from 'node:module';
|
|
2
|
+
const cachedModulePattern = /\._cached_\.(?:[cm]?js|[cm]?ts|jsx|tsx)$/i;
|
|
3
|
+
let reloadId = 0;
|
|
4
|
+
function isLoaderInstance() {
|
|
5
|
+
return new URL(import.meta.url).searchParams.has('loader');
|
|
6
|
+
}
|
|
7
|
+
function isThisModule(url) {
|
|
8
|
+
const candidate = new URL(url);
|
|
9
|
+
const current = new URL(import.meta.url);
|
|
10
|
+
candidate.search = '';
|
|
11
|
+
current.search = '';
|
|
12
|
+
return candidate.href === current.href;
|
|
13
|
+
}
|
|
14
|
+
function isCachedModule(url) {
|
|
15
|
+
return cachedModulePattern.test(url.pathname);
|
|
16
|
+
}
|
|
17
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
18
|
+
const result = await nextResolve(specifier, context);
|
|
19
|
+
if (isThisModule(result.url) || isBuiltin(result.url) || context.parentURL === undefined) {
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
const url = new URL(result.url);
|
|
23
|
+
// A *._cached_.js/ts module and its dependency graph use Node's ordinary
|
|
24
|
+
// process-wide module cache instead of inheriting the test file instance.
|
|
25
|
+
if (isCachedModule(url))
|
|
26
|
+
return result;
|
|
27
|
+
const parentUrl = new URL(context.parentURL);
|
|
28
|
+
const instance = url.searchParams.get('reload') === ''
|
|
29
|
+
? `sxy-test-runner-reload-${reloadId++}`
|
|
30
|
+
: parentUrl.searchParams.get('instance');
|
|
31
|
+
if (instance === null)
|
|
32
|
+
return result;
|
|
33
|
+
url.searchParams.delete('reload');
|
|
34
|
+
url.searchParams.set('instance', instance);
|
|
35
|
+
return {
|
|
36
|
+
...result,
|
|
37
|
+
url: url.href
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (!isLoaderInstance()) {
|
|
41
|
+
register(`${import.meta.url}?loader`);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=esmReload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmReload.js","sourceRoot":"","sources":["../../src/packages/esmReload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAoCjD,MAAM,mBAAmB,GAAG,2CAA2C,CAAA;AAEvE,IAAI,QAAQ,GAAG,CAAC,CAAA;AAEhB,SAAS,gBAAgB;IACrB,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;AAC9D,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC7B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAA;IACrB,OAAO,CAAC,MAAM,GAAG,EAAE,CAAA;IACnB,OAAO,SAAS,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAA;AAC1C,CAAC;AAED,SAAS,cAAc,CAAC,GAAQ;IAC5B,OAAO,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CACzB,SAAiB,EACjB,OAAuB,EACvB,WAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAEpD,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACvF,OAAO,MAAM,CAAA;IACjB,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAE/B,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,cAAc,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAA;IAEtC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE;QAClD,CAAC,CAAC,0BAA0B,QAAQ,EAAE,EAAE;QACxC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAE5C,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IAEpC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IAE1C,OAAO;QACH,GAAG,MAAM;QACT,GAAG,EAAE,GAAG,CAAC,IAAI;KAChB,CAAA;AACL,CAAC;AAED,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC;IACtB,QAAQ,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;AACzC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmReload.unitTest.d.ts","sourceRoot":"","sources":["../../src/packages/esmReload.unitTest.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { cwd, execPath } from 'node:process';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
function spawnPromisified(command, args) {
|
|
6
|
+
const child = spawn(command, args);
|
|
7
|
+
let stderr = '';
|
|
8
|
+
let stdout = '';
|
|
9
|
+
child.stderr.setEncoding('utf8');
|
|
10
|
+
child.stderr.on('data', (data) => { stderr += data; });
|
|
11
|
+
child.stdout.setEncoding('utf8');
|
|
12
|
+
child.stdout.on('data', (data) => { stdout += data; });
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
child.on('close', (code, signal) => {
|
|
15
|
+
resolve({ code, signal, stderr, stdout });
|
|
16
|
+
});
|
|
17
|
+
child.on('error', reject);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
mochaDescribe('esmReload', function () {
|
|
21
|
+
mochaIt('reloads an ES module and its transitive dependencies', async function () {
|
|
22
|
+
const fixtureDirectory = join(cwd(), 'unitTesting/esmReload/fixtures/simple');
|
|
23
|
+
const fixture = join(fixtureDirectory, 'index.js');
|
|
24
|
+
const transitiveUrl = pathToFileURL(join(fixtureDirectory, 'transitive.js')).href;
|
|
25
|
+
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, [fixture]);
|
|
26
|
+
chaiExpect(code).to.equal(0);
|
|
27
|
+
chaiExpect(signal).to.equal(null);
|
|
28
|
+
chaiExpect(stderr).to.equal('');
|
|
29
|
+
chaiExpect(stdout.trim().split(/\r?\n/)).to.deep.equal([
|
|
30
|
+
transitiveUrl,
|
|
31
|
+
`${transitiveUrl}?instance=sxy-test-runner-reload-0`,
|
|
32
|
+
`${transitiveUrl}?instance=1`,
|
|
33
|
+
`${transitiveUrl}?instance=sxy-test-runner-reload-1`,
|
|
34
|
+
`${transitiveUrl}?instance=2`
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
mochaIt('reloads a file and ordinary modules while sharing _cached_ exports', async function () {
|
|
38
|
+
const fixture = join(cwd(), 'unitTesting/esmReload/fixtures/repeated/index.js');
|
|
39
|
+
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, [fixture]);
|
|
40
|
+
chaiExpect(code).to.equal(0);
|
|
41
|
+
chaiExpect(signal).to.equal(null);
|
|
42
|
+
chaiExpect(stderr).to.equal('');
|
|
43
|
+
chaiExpect(JSON.parse(stdout)).to.deep.equal({
|
|
44
|
+
loadedFileIsFresh: true,
|
|
45
|
+
ordinaryModuleIsFresh: true,
|
|
46
|
+
cachedExportIsShared: true
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=esmReload.unitTest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esmReload.unitTest.js","sourceRoot":"","sources":["../../src/packages/esmReload.unitTest.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sxy-test-runner",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://github.com/RobertSandiford/sxy-test-runner",
|
|
6
6
|
"repository": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
33
|
"sxy-loader.config.js",
|
|
34
|
+
"AGENTS.md",
|
|
34
35
|
"readme.md"
|
|
35
36
|
],
|
|
36
37
|
"dependencies": {
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
"unit-test-add-exports": "mocha --watch --parallel --config unitTesting/.mocharc.cjs --watch-files dist,unitTesting dist/**/addExports.unitTest.js",
|
|
93
94
|
"unit-test-describe": "mocha --watch --parallel --config unitTesting/.mocharc.cjs --watch-files dist,unitTesting dist/**/describe.unitTest.js",
|
|
94
95
|
"unit-test-run-tests": "mocha --watch --parallel --config unitTesting/.mocharc.cjs --watch-files dist,unitTesting dist/**/runTests.unitTest.js",
|
|
96
|
+
"unit-test-esm-reload": "mocha --watch --parallel --config unitTesting/.mocharc.cjs --watch-files dist,unitTesting dist/**/esmReload.unitTest.js",
|
|
95
97
|
"mocha-trial": "mocha --watch --parallel --config unitTesting/.mocharc.cjs --watch-files dist,unitTesting dist/**/runTest.unitTest.js"
|
|
96
98
|
}
|
|
97
99
|
}
|
package/readme.md
CHANGED
|
@@ -4,8 +4,7 @@ Mocha-like test runner for **es modules** (only), with **watch mode**, and **re-
|
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
import { describe } from 'sxy-test-runner'
|
|
7
|
-
import {
|
|
8
|
-
const should = setupShould()
|
|
7
|
+
import { expect } from 'chai'
|
|
9
8
|
|
|
10
9
|
function add(a, b) {
|
|
11
10
|
return a + b
|
|
@@ -14,8 +13,8 @@ function add(a, b) {
|
|
|
14
13
|
describe('The add() function', ({it}) => {
|
|
15
14
|
it('should add 1 and 2, making 3', () => {
|
|
16
15
|
const result = add(1, 2)
|
|
17
|
-
result.
|
|
18
|
-
}
|
|
16
|
+
expect(result).to.equal(3)
|
|
17
|
+
})
|
|
19
18
|
})
|
|
20
19
|
```
|
|
21
20
|
|
|
@@ -104,6 +103,24 @@ sxy-test-runner runs all tests inside one node process, to improve performance (
|
|
|
104
103
|
|
|
105
104
|
Because of this, be careful with globals, which will be shared.
|
|
106
105
|
|
|
106
|
+
### Sharing a module between test files
|
|
107
|
+
|
|
108
|
+
Ordinary test dependencies receive a fresh module instance for each test file. To
|
|
109
|
+
deliberately share a module and its static dependency graph, give its filename a
|
|
110
|
+
`._cached_.` marker:
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
import { database } from './fixtures/database._cached_.js'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The module then uses Node's normal process-wide module cache, so every test file receives
|
|
117
|
+
the same exported values. JavaScript and TypeScript module extensions are supported; in
|
|
118
|
+
TypeScript `NodeNext` source, import the emitted `._cached_.js` name as usual.
|
|
119
|
+
|
|
120
|
+
Cached modules remain loaded for the full one-off run or watch process. Avoid unsynchronised
|
|
121
|
+
mutable state when tests run in parallel, and explicitly clean up shared resources such as
|
|
122
|
+
servers or database connections.
|
|
123
|
+
|
|
107
124
|
## Execution modes
|
|
108
125
|
|
|
109
126
|
**execution** settings let you choose whether to run test files, describe blocks, and individual tests sequentially or in parallel.
|
|
@@ -157,8 +174,7 @@ Not yet implemented.
|
|
|
157
174
|
|
|
158
175
|
```js
|
|
159
176
|
import { describe } from 'sxy-test-runner'
|
|
160
|
-
import {
|
|
161
|
-
const should = setupShould()
|
|
177
|
+
import { expect } from 'chai'
|
|
162
178
|
|
|
163
179
|
describe('something', ({it, beforeEachTest, afterEachTest, afterDescribe}) => {
|
|
164
180
|
|
|
@@ -178,10 +194,10 @@ describe('something', ({it, beforeEachTest, afterEachTest, afterDescribe}) => {
|
|
|
178
194
|
})
|
|
179
195
|
|
|
180
196
|
it('should do something', () => {
|
|
181
|
-
something.
|
|
197
|
+
expect(something.didSomething()).to.equal(true)
|
|
182
198
|
})
|
|
183
199
|
it('should do another thing', () => {
|
|
184
|
-
something.
|
|
200
|
+
expect(something.didAnotherThing()).to.equal(true)
|
|
185
201
|
})
|
|
186
202
|
|
|
187
203
|
// teardown should go in afterDescribe.
|
|
@@ -198,6 +214,7 @@ describe('something', ({it, beforeEachTest, afterEachTest, afterDescribe}) => {
|
|
|
198
214
|
# Exit codes for `once`
|
|
199
215
|
|
|
200
216
|
0: success
|
|
201
|
-
|
|
202
|
-
2:
|
|
203
|
-
|
|
217
|
+
1: non sxyt error
|
|
218
|
+
2: sxyt error
|
|
219
|
+
3: test errors
|
|
220
|
+
It may also crash due to exceptions without giving any of these errors codes
|