tape-six 1.7.0 → 1.7.2

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/llms.txt ADDED
@@ -0,0 +1,243 @@
1
+ # tape-six
2
+
3
+ > TAP-based unit test library for modern JavaScript and TypeScript. Works in Node, Deno, Bun, and browsers.
4
+
5
+ ## Install
6
+
7
+ npm i -D tape-six
8
+
9
+ ## Quick start
10
+
11
+ ```js
12
+ import test from 'tape-six';
13
+
14
+ test('my test', async t => {
15
+ t.ok(true, 'truthy value');
16
+ t.equal(1 + 1, 2, 'addition works');
17
+ t.deepEqual({a: 1}, {a: 1}, 'objects are equal');
18
+ });
19
+ ```
20
+
21
+ Run: `node my-test.js` or `npx tape6 --flags FO`
22
+
23
+ ## API
24
+
25
+ ### test(name, options, testFn)
26
+
27
+ Registers a test. All three arguments are optional and can be in any order (recognized by type).
28
+
29
+ - `name` (string) — test name. Defaults to function name or '(anonymous)'.
30
+ - `options` (object) — see TestOptions below.
31
+ - `testFn` (function) — `async (t: Tester) => void`. Can be sync or async.
32
+
33
+ Returns a promise that resolves when the test finishes. Usually no need to await.
34
+
35
+ Aliases: `suite`, `describe`, `it` — all are the same function.
36
+
37
+ ```js
38
+ import {test, describe, it, suite} from 'tape-six';
39
+ ```
40
+
41
+ #### test.skip(name, options, testFn)
42
+
43
+ Registers a test that will be skipped (not executed).
44
+
45
+ #### test.todo(name, options, testFn)
46
+
47
+ Registers a test marked as work-in-progress. Failures are reported but not counted.
48
+
49
+ #### test.asPromise(name, options, testPromiseFn)
50
+
51
+ Registers a test using callback-style completion: `(t, resolve, reject) => void`.
52
+
53
+ ### TestOptions
54
+
55
+ - `name` (string) — test name.
56
+ - `testFn` (function) — test function.
57
+ - `skip` (boolean) — skip this test.
58
+ - `todo` (boolean) — mark as TODO.
59
+ - `timeout` (number) — timeout in ms. Test is marked failed if exceeded.
60
+ - `beforeAll` / `before` (function) — run before all embedded tests.
61
+ - `afterAll` / `after` (function) — run after all embedded tests.
62
+ - `beforeEach` (function) — run before each embedded test.
63
+ - `afterEach` (function) — run after each embedded test.
64
+
65
+ ### Tester (the `t` object)
66
+
67
+ The object passed to test functions. Provides assertions and test control.
68
+
69
+ #### Properties
70
+
71
+ - `t.signal` — AbortSignal, aborted when test times out. Use for cancellation.
72
+ - `t.any` (or `t._`) — symbol for matching any value in deepEqual/match.
73
+
74
+ #### Assertions (all `msg` arguments are optional)
75
+
76
+ - `t.pass(msg)` — unconditional pass.
77
+ - `t.fail(msg)` — unconditional fail.
78
+ - `t.ok(value, msg)` — assert truthy. Aliases: `true`, `assert`.
79
+ - `t.notOk(value, msg)` — assert falsy. Aliases: `false`, `notok`.
80
+ - `t.error(err, msg)` — assert err is falsy. Aliases: `ifError`, `ifErr`, `iferror`.
81
+ - `t.strictEqual(a, b, msg)` — assert `a === b`. Aliases: `is`, `equal`, `equals`, `isEqual`, `strictEquals`.
82
+ - `t.notStrictEqual(a, b, msg)` — assert `a !== b`. Aliases: `not`, `notEqual`, `notEquals`, `isNotEqual`, `doesNotEqual`, `isUnequal`, `notStrictEquals`, `isNot`.
83
+ - `t.looseEqual(a, b, msg)` — assert `a == b`. Alias: `looseEquals`.
84
+ - `t.notLooseEqual(a, b, msg)` — assert `a != b`. Alias: `notLooseEquals`.
85
+ - `t.deepEqual(a, b, msg)` — recursive strict deep equality. Aliases: `same`, `deepEquals`, `isEquivalent`.
86
+ - `t.notDeepEqual(a, b, msg)` — assert not deeply equal. Aliases: `notSame`, `notDeepEquals`, `notEquivalent`, `notDeeply`, `isNotDeepEqual`, `isNotEquivalent`.
87
+ - `t.deepLooseEqual(a, b, msg)` — recursive loose deep equality.
88
+ - `t.notDeepLooseEqual(a, b, msg)` — assert not deeply loosely equal.
89
+ - `t.throws(fn, msg)` — assert fn() throws.
90
+ - `t.doesNotThrow(fn, msg)` — assert fn() does not throw.
91
+ - `t.matchString(string, regexp, msg)` — assert string matches regexp.
92
+ - `t.doesNotMatchString(string, regexp, msg)` — assert string does not match regexp.
93
+ - `t.match(a, b, msg)` — assert a matches pattern b (structural).
94
+ - `t.doesNotMatch(a, b, msg)` — assert a does not match pattern b.
95
+ - `t.rejects(promise, msg)` — assert promise rejects. **Async: must await.** Alias: `doesNotResolve`.
96
+ - `t.resolves(promise, msg)` — assert promise resolves. **Async: must await.** Alias: `doesNotReject`.
97
+
98
+ #### Embedded tests (all async, should be awaited)
99
+
100
+ - `await t.test(name, options, testFn)` — nested test.
101
+ - `await t.skip(name, options, testFn)` — nested skipped test.
102
+ - `await t.todo(name, options, testFn)` — nested TODO test.
103
+ - `await t.asPromise(name, options, testPromiseFn)` — nested callback-style test.
104
+
105
+ #### Utilities
106
+
107
+ - `t.plan(n)` — declare expected assertion count (rarely needed).
108
+ - `t.comment(msg)` — emit a comment.
109
+ - `t.skipTest(msg)` — skip current test with a message.
110
+ - `t.bailOut(msg)` — abort entire test suite.
111
+
112
+ ### Hooks
113
+
114
+ Hooks can be registered as standalone functions or via test options or Tester methods.
115
+
116
+ ```js
117
+ import {test, beforeAll, afterAll, beforeEach, afterEach} from 'tape-six';
118
+ // `before` is an alias for `beforeAll`, `after` is an alias for `afterAll`.
119
+
120
+ beforeAll(() => { /* runs once before all tests */ });
121
+ afterAll(() => { /* runs once after all tests */ });
122
+ beforeEach(() => { /* runs before each test */ });
123
+ afterEach(() => { /* runs after each test */ });
124
+
125
+ // Or inside a test:
126
+ test('suite', async t => {
127
+ t.beforeAll(() => { /* before embedded tests */ });
128
+ t.afterAll(() => { /* after embedded tests */ });
129
+ t.beforeEach(() => { /* before each embedded test */ });
130
+ t.afterEach(() => { /* after each embedded test */ });
131
+
132
+ await t.test('inner', t => { t.pass(); });
133
+ });
134
+
135
+ // Or via test.beforeAll(), test.afterAll(), etc:
136
+ test.beforeAll(() => { /* ... */ });
137
+ ```
138
+
139
+ ## Common patterns
140
+
141
+ ### Basic test file
142
+
143
+ ```js
144
+ import test from 'tape-six';
145
+
146
+ test('arithmetic', t => {
147
+ t.equal(2 + 2, 4, 'addition');
148
+ t.ok(10 > 5, 'comparison');
149
+ });
150
+
151
+ test('async operation', async t => {
152
+ const result = await fetchData();
153
+ t.ok(result, 'got result');
154
+ t.equal(result.status, 200, 'status is 200');
155
+ });
156
+ ```
157
+
158
+ ### Testing exceptions
159
+
160
+ ```js
161
+ test('errors', async t => {
162
+ t.throws(() => { throw new Error('boom'); }, 'should throw');
163
+ t.doesNotThrow(() => 42, 'should not throw');
164
+ await t.rejects(Promise.reject(new Error('fail')), 'should reject');
165
+ await t.resolves(Promise.resolve(42), 'should resolve');
166
+ });
167
+ ```
168
+
169
+ ### Nested tests with setup/teardown
170
+
171
+ ```js
172
+ test('database tests', async t => {
173
+ let db;
174
+ t.beforeAll(async () => { db = await connect(); });
175
+ t.afterAll(async () => { await db.close(); });
176
+
177
+ await t.test('insert', async t => {
178
+ const result = await db.insert({name: 'Alice'});
179
+ t.ok(result.id, 'got an id');
180
+ });
181
+
182
+ await t.test('query', async t => {
183
+ const rows = await db.query('SELECT * FROM users');
184
+ t.ok(rows.length > 0, 'has rows');
185
+ });
186
+ });
187
+ ```
188
+
189
+ ### Using any for partial matching
190
+
191
+ ```js
192
+ test('partial match', t => {
193
+ const result = {id: 123, name: 'Alice', timestamp: Date.now()};
194
+ t.deepEqual(result, {id: 123, name: 'Alice', timestamp: t.any});
195
+ });
196
+ ```
197
+
198
+ ### Using describe/it style
199
+
200
+ ```js
201
+ import {describe, it} from 'tape-six';
202
+
203
+ describe('my module', () => {
204
+ it('should work', t => {
205
+ t.ok(true);
206
+ });
207
+ });
208
+ ```
209
+
210
+ ### Skip and TODO
211
+
212
+ ```js
213
+ test.skip('not ready yet', t => { t.fail(); });
214
+ test.todo('work in progress', t => { t.equal(1, 2); }); // failure not counted
215
+ ```
216
+
217
+ ## Running tests
218
+
219
+ ```bash
220
+ node test-file.js # run single file directly
221
+ npx tape6 --flags FO # run all configured tests, show failures only + fail once
222
+ npx tape6-seq # run sequentially (no worker threads)
223
+ npx tape6-server --trace # start browser test server
224
+ ```
225
+
226
+ ## Configuration (package.json)
227
+
228
+ ```json
229
+ {
230
+ "scripts": {
231
+ "test": "tape6 --flags FO"
232
+ },
233
+ "tape6": {
234
+ "tests": ["/tests/test-*.*js"]
235
+ }
236
+ }
237
+ ```
238
+
239
+ ## Links
240
+
241
+ - Docs: https://github.com/uhop/tape-six/wiki
242
+ - npm: https://www.npmjs.com/package/tape-six
243
+ - Full LLM reference: https://github.com/uhop/tape-six/blob/master/llms-full.txt
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tape-six",
3
- "version": "1.7.0",
4
- "description": "The test harness for the modern JavaScript and TypeScript.",
3
+ "version": "1.7.2",
4
+ "description": "TAP-based unit test library for Node, Deno, Bun, and browsers. ES modules, TypeScript, zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "module": "index.js",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "bin": {
15
15
  "tape6": "bin/tape6.js",
16
+ "tape6-node": "bin/tape6-node.js",
16
17
  "tape6-bun": "bin/tape6-bun.js",
17
18
  "tape6-deno": "bin/tape6-deno.js",
18
19
  "tape6-seq": "bin/tape6-seq.js",
@@ -30,8 +31,8 @@
30
31
  "test:bun": "bun run ./bin/tape6-bun.js --flags FO",
31
32
  "test:deno": "deno run -A ./bin/tape6-deno.js --flags FO",
32
33
  "test:seq": "node ./bin/tape6-seq.js --flags FO",
33
- "test:bun:seq": "bun run ./bin/tape6-seq.js --flags FO",
34
- "test:deno:seq": "deno run -A ./bin/tape6-seq.js --flags FO",
34
+ "test:seq:bun": "bun run ./bin/tape6-seq.js --flags FO",
35
+ "test:seq:deno": "deno run -A ./bin/tape6-seq.js --flags FO",
35
36
  "test:puppeteer": "node tests/puppeteer-chrome.js",
36
37
  "test:playwright": "node tests/playwright-chrome.js",
37
38
  "ts-check": "tsc --noEmit",
@@ -49,7 +50,18 @@
49
50
  "test",
50
51
  "harness",
51
52
  "assert",
52
- "browser"
53
+ "browser",
54
+ "unit-test",
55
+ "testing",
56
+ "test-runner",
57
+ "esm",
58
+ "es-modules",
59
+ "typescript",
60
+ "deno",
61
+ "bun",
62
+ "nodejs",
63
+ "tap-protocol",
64
+ "cross-runtime"
53
65
  ],
54
66
  "author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com/)",
55
67
  "funding": "https://github.com/sponsors/uhop",
@@ -58,11 +70,15 @@
58
70
  "url": "https://github.com/uhop/tape-six/issues"
59
71
  },
60
72
  "homepage": "https://github.com/uhop/tape-six#readme",
73
+ "llms": "https://raw.githubusercontent.com/uhop/tape-six/master/llms.txt",
74
+ "llmsFull": "https://raw.githubusercontent.com/uhop/tape-six/master/llms-full.txt",
61
75
  "files": [
62
76
  "index.*",
63
77
  "bin",
64
78
  "web-app",
65
- "src"
79
+ "src",
80
+ "llms.txt",
81
+ "llms-full.txt"
66
82
  ],
67
83
  "tape6": {
68
84
  "tests": [
@@ -85,10 +101,10 @@
85
101
  },
86
102
  "devDependencies": {
87
103
  "@types/chai": "^5.2.3",
88
- "@types/node": "^25.2.1",
104
+ "@types/node": "^25.3.0",
89
105
  "chai": "^6.2.2",
90
106
  "playwright": "^1.58.2",
91
- "puppeteer": "^24.37.2",
107
+ "puppeteer": "^24.37.5",
92
108
  "typescript": "^5.9.3"
93
109
  }
94
110
  }
@@ -0,0 +1,37 @@
1
+ import Reporter from './Reporter.js';
2
+
3
+ const getEnvVar = name => {
4
+ if (typeof Deno == 'object' && Deno?.version) {
5
+ return Deno.env.get(name);
6
+ } else if (typeof Bun == 'object' && Bun?.version) {
7
+ return Bun.env[name];
8
+ } else if (typeof process == 'object' && process?.versions?.node) {
9
+ return process.env[name];
10
+ }
11
+ return undefined;
12
+ };
13
+
14
+ export class MinReporter extends Reporter {
15
+ constructor({failOnce = false, originalConsole} = {}) {
16
+ super({failOnce});
17
+ this.console = originalConsole || console;
18
+ }
19
+ report(event) {
20
+ event = this.state?.preprocess(event) || event;
21
+ const handler = Reporter.EVENT_MAP[event.type];
22
+ typeof handler == 'string' && this[handler]?.(event);
23
+ this.console.log(
24
+ 'Test:',
25
+ event.test,
26
+ 'Type:',
27
+ event.type,
28
+ 'Name:',
29
+ event.name,
30
+ 'Fail:',
31
+ event.fail
32
+ );
33
+ this.state?.postprocess(event);
34
+ }
35
+ }
36
+
37
+ export default MinReporter;
@@ -58,7 +58,7 @@ export class TTYReporter extends Reporter {
58
58
  this.hasColors =
59
59
  hasColors &&
60
60
  this.output.isTTY &&
61
- (typeof this.output.hasColors == 'function' ? this.output.hasColors(256) : true);
61
+ (typeof this.output.hasColors == 'function' ? this.output.hasColors(16) : true);
62
62
  this.renumberAsserts = renumberAsserts;
63
63
  this.failureOnly = failureOnly;
64
64
  this.showBanner = showBanner;
@@ -221,7 +221,7 @@ export class TTYReporter extends Reporter {
221
221
  paint = method === 'error' || method === 'assert' ? 'stderrPaint' : 'stdoutPaint',
222
222
  prefix = this[paint](consoleDict[method] + ':') + ' ';
223
223
  for (const line of lines) {
224
- this.out(prefix + line);
224
+ this.out(prefix + line, this.failureOnly);
225
225
  }
226
226
  }
227
227
  }
@@ -231,7 +231,7 @@ export class TTYReporter extends Reporter {
231
231
  const lines = event.name.split(/\r?\n/),
232
232
  prefix = this.stdoutPaint('stdout:') + ' ';
233
233
  for (const line of lines) {
234
- this.out(prefix + line);
234
+ this.out(prefix + line, this.failureOnly);
235
235
  }
236
236
  }
237
237
  break;
@@ -240,7 +240,7 @@ export class TTYReporter extends Reporter {
240
240
  const lines = event.name.split(/\r?\n/),
241
241
  prefix = this.stderrPaint('stderr:') + ' ';
242
242
  for (const line of lines) {
243
- this.out(prefix + line);
243
+ this.out(prefix + line, this.failureOnly);
244
244
  }
245
245
  }
246
246
  break;
@@ -1,8 +1,36 @@
1
- const reportToParent = fileName => msg => {
2
- if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
3
- msg.name = 'FILE: /' + fileName;
4
- }
5
- postMessage(msg);
1
+ const DEFAULT_START_TIMEOUT = 5_000;
2
+
3
+ const getTimeout = () => {
4
+ const timeoutValue = Bun.env.TAPE6_WORKER_START_TIMEOUT;
5
+ if (!timeoutValue) return DEFAULT_START_TIMEOUT;
6
+ let timeout = Number(timeoutValue);
7
+ if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
8
+ return timeout;
9
+ };
10
+
11
+ const reportToParent = fileName => {
12
+ const timeout = getTimeout();
13
+ let timeoutBeforeStartId = setTimeout(() => {
14
+ postMessage({type: 'test', test: 0, name: 'FILE: /' + fileName});
15
+ postMessage({
16
+ name: `No tests found in ${timeout}ms`,
17
+ test: 0,
18
+ marker: new Error(),
19
+ operator: 'error',
20
+ fail: true
21
+ });
22
+ postMessage({type: 'end', test: 0, name: 'FILE: /' + fileName, fail: true});
23
+ }, timeout);
24
+ return msg => {
25
+ if (timeoutBeforeStartId) {
26
+ clearTimeout(timeoutBeforeStartId);
27
+ timeoutBeforeStartId = null;
28
+ }
29
+ if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
30
+ msg.name = 'FILE: /' + fileName;
31
+ }
32
+ postMessage(msg);
33
+ };
6
34
  };
7
35
 
8
36
  addEventListener('message', async event => {
@@ -1,8 +1,36 @@
1
- const reportToParent = fileName => msg => {
2
- if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
3
- msg.name = 'FILE: /' + fileName;
4
- }
5
- postMessage(msg);
1
+ const DEFAULT_START_TIMEOUT = 5_000;
2
+
3
+ const getTimeout = () => {
4
+ const timeoutValue = Deno.env.get('TAPE6_WORKER_START TIMEOUT');
5
+ if (!timeoutValue) return DEFAULT_START_TIMEOUT;
6
+ let timeout = Number(timeoutValue);
7
+ if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
8
+ return timeout;
9
+ };
10
+
11
+ const reportToParent = fileName => {
12
+ const timeout = getTimeout();
13
+ let timeoutBeforeStartId = setTimeout(() => {
14
+ postMessage({type: 'test', test: 0, name: 'FILE: /' + fileName});
15
+ postMessage({
16
+ name: `No tests found in ${timeout}ms`,
17
+ test: 0,
18
+ marker: new Error(),
19
+ operator: 'error',
20
+ fail: true
21
+ });
22
+ postMessage({type: 'end', test: 0, name: 'FILE: /' + fileName, fail: true});
23
+ }, timeout);
24
+ return msg => {
25
+ if (timeoutBeforeStartId) {
26
+ clearTimeout(timeoutBeforeStartId);
27
+ timeoutBeforeStartId = null;
28
+ }
29
+ if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
30
+ msg.name = 'FILE: /' + fileName;
31
+ }
32
+ postMessage(msg);
33
+ };
6
34
  };
7
35
 
8
36
  addEventListener('message', async event => {
@@ -1,10 +1,39 @@
1
+ import process from 'node:process';
1
2
  import {parentPort} from 'node:worker_threads';
2
3
 
3
- const reportToParent = fileName => msg => {
4
- if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
5
- msg.name = 'FILE: /' + fileName;
6
- }
7
- parentPort.postMessage(msg);
4
+ const DEFAULT_START_TIMEOUT = 5_000;
5
+
6
+ const getTimeout = () => {
7
+ const timeoutValue = process.env.TAPE6_WORKER_START_TIMEOUT;
8
+ if (!timeoutValue) return DEFAULT_START_TIMEOUT;
9
+ let timeout = Number(timeoutValue);
10
+ if (isNaN(timeout) || timeout <= 0 || timeout === Infinity) timeout = DEFAULT_START_TIMEOUT;
11
+ return timeout;
12
+ };
13
+
14
+ const reportToParent = fileName => {
15
+ const timeout = getTimeout();
16
+ let timeoutBeforeStartId = setTimeout(() => {
17
+ parentPort.postMessage({type: 'test', test: 0, name: 'FILE: /' + fileName});
18
+ parentPort.postMessage({
19
+ name: `No tests found in ${timeout}ms`,
20
+ test: 0,
21
+ marker: new Error(),
22
+ operator: 'error',
23
+ fail: true
24
+ });
25
+ parentPort.postMessage({type: 'end', test: 0, name: 'FILE: /' + fileName, fail: true});
26
+ }, timeout);
27
+ return msg => {
28
+ if (timeoutBeforeStartId) {
29
+ clearTimeout(timeoutBeforeStartId);
30
+ timeoutBeforeStartId = null;
31
+ }
32
+ if ((msg.type === 'test' || msg.type === 'end') && !msg.test && !msg.name) {
33
+ msg.name = 'FILE: /' + fileName;
34
+ }
35
+ parentPort.postMessage(msg);
36
+ };
8
37
  };
9
38
 
10
39
  parentPort.on('message', async msg => {
@@ -4,6 +4,7 @@ import {pathToFileURL} from 'node:url';
4
4
 
5
5
  import {isStopTest} from '../../State.js';
6
6
  import EventServer from '../../utils/EventServer.js';
7
+ import {getTimeoutValue} from '../../utils/config.js';
7
8
  import {getOriginalConsole, setCurrentReporter} from '../../utils/capture-console.js';
8
9
  import {
9
10
  clearBeforeAll,
@@ -11,7 +12,8 @@ import {
11
12
  clearBeforeEach,
12
13
  clearAfterEach,
13
14
  setReporter,
14
- registerNotifyCallback
15
+ registerNotifyCallback,
16
+ unregisterNotifyCallback
15
17
  } from '../../test.js';
16
18
  import BypassReporter from './BypassReporter.js';
17
19
 
@@ -23,10 +25,16 @@ export default class TestWorker extends EventServer {
23
25
  this.testRunner = options.testRunner;
24
26
  this.counter = 0;
25
27
  this.originalConsole = getOriginalConsole() || globalThis.console;
28
+ this.timeout = getTimeoutValue();
29
+ this.timeoutId = null;
26
30
  }
27
31
  makeTask(fileName) {
28
32
  const id = String(++this.counter),
29
33
  reporter = new BypassReporter(this.reporter, event => {
34
+ if (this.timeoutId) {
35
+ clearTimeout(this.timeoutId);
36
+ this.timeoutId = null;
37
+ }
30
38
  try {
31
39
  this.report(id, event);
32
40
  } catch (error) {
@@ -39,33 +47,22 @@ export default class TestWorker extends EventServer {
39
47
  setReporter(reporter);
40
48
  setCurrentReporter(this.reporter);
41
49
  process.env.TAPE6_TEST_FILE_NAME = fileName;
42
- import(new URL(fileName, baseName))
50
+ const url = new URL(fileName, baseName);
51
+ import(url)
43
52
  .then(async () => {
44
53
  const testRunner = await this.testRunner;
45
54
  registerNotifyCallback(testRunner);
46
- })
47
- .catch(error => {
48
- this.report(id, {
49
- type: 'comment',
50
- name: 'fail to load: ' + (error.message || 'Worker error'),
51
- test: 0
52
- });
53
- try {
54
- this.report(id, {
55
- name: String(error),
56
- test: 0,
57
- marker: new Error(),
58
- operator: 'error',
59
- fail: true,
60
- data: {
61
- actual: error
62
- }
63
- });
64
- } catch (error) {
65
- if (!isStopTest(error)) throw error;
55
+ if (this.timeoutId) {
56
+ clearTimeout(this.timeoutId);
57
+ this.timeoutId = null;
66
58
  }
67
- this.close(id);
68
- });
59
+ this.timeoutId = setTimeout(() => {
60
+ this.timeoutId = null;
61
+ unregisterNotifyCallback(testRunner);
62
+ this.#reportTimeout(id, fileName);
63
+ }, this.timeout);
64
+ })
65
+ .catch(error => this.#reportError(id, error));
69
66
  return id;
70
67
  }
71
68
  destroyTask() {
@@ -76,4 +73,51 @@ export default class TestWorker extends EventServer {
76
73
  clearBeforeEach();
77
74
  clearAfterEach();
78
75
  }
76
+ #reportError(id, error) {
77
+ this.report(id, {
78
+ type: 'comment',
79
+ name: 'fail to load: ' + (error.message || 'Worker error'),
80
+ test: 0
81
+ });
82
+ try {
83
+ this.report(id, {
84
+ name: String(error),
85
+ test: 0,
86
+ marker: new Error(),
87
+ operator: 'error',
88
+ fail: true,
89
+ data: {
90
+ actual: error
91
+ }
92
+ });
93
+ } catch (error) {
94
+ if (!isStopTest(error)) throw error;
95
+ }
96
+ this.close(id);
97
+ }
98
+ #reportTimeout(id, fileName) {
99
+ this.report(id, {
100
+ type: 'test',
101
+ test: 0,
102
+ name: 'FILE: /' + fileName
103
+ });
104
+ try {
105
+ this.report(id, {
106
+ name: `No tests found in ${this.timeout}ms`,
107
+ test: 0,
108
+ marker: new Error(),
109
+ operator: 'error',
110
+ fail: true
111
+ });
112
+ } catch (error) {
113
+ if (!isStopTest(error)) throw error;
114
+ }
115
+ this.report(id, {
116
+ type: 'end',
117
+ test: 0,
118
+ name: 'FILE: /' + fileName,
119
+ fail: true
120
+ });
121
+ this.close(id);
122
+ }
79
123
  }
package/src/test.js CHANGED
@@ -12,7 +12,7 @@ let tests = [],
12
12
  reporter = null,
13
13
  testCounter = 0,
14
14
  isConfigured = false,
15
- notifyCallback = [];
15
+ notifyCallback = new Set();
16
16
 
17
17
  export const getConfiguredFlag = () => isConfigured;
18
18
  export const setConfiguredFlag = value => (isConfigured = !!value);
@@ -26,10 +26,12 @@ export const registerNotifyCallback = callback => {
26
26
  if (tests.length) {
27
27
  defer(callback);
28
28
  } else {
29
- notifyCallback.push(callback);
29
+ notifyCallback.add(callback);
30
30
  }
31
31
  };
32
32
 
33
+ export const unregisterNotifyCallback = callback => notifyCallback.delete(callback);
34
+
33
35
  const testers = [];
34
36
 
35
37
  export const getTesters = () => testers;
@@ -86,9 +88,9 @@ export const test = async (name, options, testFn) => {
86
88
  isTimerSet = true;
87
89
  }
88
90
  const deferred = makeDeferred();
89
- if (tests.push({options, deferred}) === 1 && notifyCallback.length) {
91
+ if (tests.push({options, deferred}) === 1 && notifyCallback.size) {
90
92
  for (const callback of notifyCallback) defer(callback);
91
- notifyCallback = [];
93
+ notifyCallback = new Set();
92
94
  }
93
95
 
94
96
  return deferred.promise;