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 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,WAsB3B,CAAA"}
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: 'parallel',
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,cAAc,CAAC;IAC7B,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE;QACP,KAAK,EAAE,UAAU;QACjB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,YAAY;KACtB;IACD,KAAK,EAAE;QACH,cAAc,EAAE,GAAG;QACnB,UAAU,EAAE,MAAM;QAClB,gBAAgB,EAAE,sBAAsB;QACxC,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,IAAI;KAC1B;CACJ,CAAA"}
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,4 +1,4 @@
1
- import 'esm-reload';
1
+ import '../../packages/esmReload.js';
2
2
  import type { FinalConfig, TestFile } from '../../types.js';
3
3
  export declare function loadTestFile(config: FinalConfig, testFile: string): Promise<TestFile>;
4
4
  //# sourceMappingURL=loadTestFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loadTestFile.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/loadTestFile.ts"],"names":[],"mappings":"AAGA,OAAO,YAAY,CAAA;AAInB,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
+ {"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 'esm-reload'; // registers import reload hook
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,YAAY,CAAA,CAAC,+BAA+B;AAEnD,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"}
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;AACN,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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=esmReload.additions.unitTest.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=esmReload.unitTest.d.ts.map
@@ -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.5",
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 { should as setupShould } from 'chai'
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.should.equal(3)
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 { should as setupShould } from 'chai'
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.should.do.something
197
+ expect(something.didSomething()).to.equal(true)
182
198
  })
183
199
  it('should do another thing', () => {
184
- something.should.do.another.thing
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
- 3: some test errors
202
- 2: other error
203
- It may also crash due to exceptions without giving any of these errors codes
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