tape-six 1.5.0 → 1.6.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/README.md +2 -0
- package/package.json +8 -6
- package/src/State.js +72 -13
- package/src/Tester.js +10 -0
- package/src/bun/TestWorker.js +28 -20
- package/src/deno/TestWorker.js +14 -10
- package/src/node/TestWorker.js +28 -20
- package/src/reporters/Reporter.js +12 -6
- package/src/reporters/TTYReporter.js +68 -62
- package/src/reporters/TapReporter.js +84 -56
- package/src/test.js +52 -4
- package/src/utils/EventServer.js +8 -3
- package/src/utils/capture-console.js +8 -3
- package/web-app/TestWorker.js +15 -10
package/README.md
CHANGED
|
@@ -380,6 +380,8 @@ Test output can be controlled by flags. See [Supported flags](https://github.com
|
|
|
380
380
|
|
|
381
381
|
The most recent releases:
|
|
382
382
|
|
|
383
|
+
- 1.6.0 _New features: support for `AssertionError` and 3rd-party assertion libraries based on it like `node:assert` and `chai`, support for `console.assert()`, support for `signal` to cancel asynchronous operations, tests wait for embedded tests, improved reporting of errors, updated dev dependencies._
|
|
384
|
+
- 1.5.1 _Better support for stopping parallel tests, better support for "failed to load" errors._
|
|
383
385
|
- 1.5.0 _Internal refactoring (moved state to reporters), added type identification of values in the DOM and TTY reporters, multiple minor fixes._
|
|
384
386
|
- 1.4.5 _Internal: added flags support for custom test runners._
|
|
385
387
|
- 1.4.4 _Refreshed the lock file._
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tape-six",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "The test harness for the modern JavaScript and TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"module": "index.js",
|
|
@@ -74,14 +74,16 @@
|
|
|
74
74
|
"importmap": {
|
|
75
75
|
"imports": {
|
|
76
76
|
"tape-six": "../index.js",
|
|
77
|
-
"tape-six/": "../src/"
|
|
77
|
+
"tape-six/": "../src/",
|
|
78
|
+
"chai": "../node_modules/chai/index.js"
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
|
-
"@types/node": "^25.0
|
|
83
|
-
"
|
|
84
|
-
"
|
|
83
|
+
"@types/node": "^25.2.0",
|
|
84
|
+
"chai": "^6.2.2",
|
|
85
|
+
"playwright": "^1.58.1",
|
|
86
|
+
"puppeteer": "^24.36.1",
|
|
85
87
|
"typescript": "^5.9.3"
|
|
86
88
|
}
|
|
87
89
|
}
|
package/src/State.js
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
import {getTimer} from './utils/timer.js';
|
|
2
2
|
|
|
3
|
-
export class StopTest extends Error {}
|
|
4
|
-
|
|
5
3
|
export const signature = 'tape6-!@#$%^&*';
|
|
6
4
|
|
|
7
|
-
export
|
|
5
|
+
export class StopTest extends Error {
|
|
6
|
+
constructor(...args) {
|
|
7
|
+
super(...args);
|
|
8
|
+
|
|
9
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, CustomError);
|
|
10
|
+
|
|
11
|
+
this.name = 'StopTest';
|
|
12
|
+
if (!this.message) this.message = 'Test stopped';
|
|
13
|
+
this[signature] = signature;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const isStopTest = error =>
|
|
18
|
+
error instanceof StopTest ||
|
|
19
|
+
(error &&
|
|
20
|
+
typeof error == 'object' &&
|
|
21
|
+
error[signature] === signature &&
|
|
22
|
+
error.name === 'StopTest' &&
|
|
23
|
+
typeof error.message == 'string');
|
|
24
|
+
|
|
25
|
+
export const isAssertionError = error =>
|
|
26
|
+
error &&
|
|
27
|
+
typeof error == 'object' &&
|
|
28
|
+
error.name === 'AssertionError' &&
|
|
29
|
+
typeof error.message == 'string' &&
|
|
30
|
+
typeof error.operator == 'string';
|
|
31
|
+
|
|
32
|
+
export const getStackList = error => {
|
|
33
|
+
const stackList = [];
|
|
34
|
+
for (const line of error.stack.split('\n')) {
|
|
35
|
+
const result = /^\s+at\s+(.*)$/.exec(line);
|
|
36
|
+
if (result) stackList.push(result[1].trimEnd());
|
|
37
|
+
}
|
|
38
|
+
return stackList;
|
|
39
|
+
};
|
|
8
40
|
|
|
9
41
|
const replacer =
|
|
10
42
|
(seen = new Set()) =>
|
|
@@ -72,6 +104,20 @@ export class State {
|
|
|
72
104
|
this.stopTest = false;
|
|
73
105
|
this.timer = timer || parent.timer || getTimer();
|
|
74
106
|
this.startTime = this.time = time || this.timer.now();
|
|
107
|
+
this.abortController = new AbortController();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get signal() {
|
|
111
|
+
return this.abortController.signal;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
abort() {
|
|
115
|
+
if (this.abortController.signal.aborted) return;
|
|
116
|
+
this.abortController.abort();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
dispose() {
|
|
120
|
+
this.abort();
|
|
75
121
|
}
|
|
76
122
|
|
|
77
123
|
updateParent() {
|
|
@@ -94,7 +140,7 @@ export class State {
|
|
|
94
140
|
|
|
95
141
|
const isFailed = event.fail && !event.todo && !event.skip;
|
|
96
142
|
|
|
97
|
-
if (event.type === 'assert') {
|
|
143
|
+
if (event.type === 'assert' || event.type === 'assertion-error') {
|
|
98
144
|
++this.asserts;
|
|
99
145
|
event.skip && ++this.skipped;
|
|
100
146
|
isFailed && ++this.failed;
|
|
@@ -107,20 +153,29 @@ export class State {
|
|
|
107
153
|
if (
|
|
108
154
|
event.type === 'assert' &&
|
|
109
155
|
(event.operator === 'error' || event.operator === 'exception') &&
|
|
110
|
-
event.data
|
|
111
|
-
event.data.actual &&
|
|
112
|
-
typeof event.data.actual.stack == 'string'
|
|
156
|
+
typeof event.data?.actual?.stack == 'string'
|
|
113
157
|
) {
|
|
114
|
-
|
|
115
|
-
event.at =
|
|
158
|
+
event.stackList = getStackList(event.data.actual);
|
|
159
|
+
event.at = event.stackList[0];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (event.type === 'assertion-error' && typeof event.data?.error?.stack == 'string') {
|
|
163
|
+
event.stackList = getStackList(event.data.error);
|
|
164
|
+
event.at = event.stackList[0];
|
|
116
165
|
}
|
|
117
166
|
|
|
118
|
-
if (!event.at &&
|
|
119
|
-
|
|
120
|
-
event.at =
|
|
167
|
+
if (!event.at && typeof event.marker?.stack == 'string') {
|
|
168
|
+
event.stackList = getStackList(event.marker);
|
|
169
|
+
event.at =
|
|
170
|
+
event.stackList[
|
|
171
|
+
Math.min(
|
|
172
|
+
typeof event.markerIndex == 'number' ? event.markerIndex : 1,
|
|
173
|
+
event.stackList.length
|
|
174
|
+
)
|
|
175
|
+
];
|
|
121
176
|
}
|
|
122
177
|
|
|
123
|
-
if (event.type === 'assert' && event.data) {
|
|
178
|
+
if ((event.type === 'assert' || event.type === 'assertion-error') && event.data) {
|
|
124
179
|
if (typeof event.expected != 'string' && event.data.hasOwnProperty('expected')) {
|
|
125
180
|
event.expected = serialize(event.data.expected);
|
|
126
181
|
}
|
|
@@ -129,6 +184,10 @@ export class State {
|
|
|
129
184
|
event.actual = serialize(event.data.actual);
|
|
130
185
|
}
|
|
131
186
|
if (typeof event.actual == 'string') delete event.data.actual;
|
|
187
|
+
if (typeof event.error != 'string' && event.data.hasOwnProperty('error')) {
|
|
188
|
+
event.error = serialize(event.data.error);
|
|
189
|
+
}
|
|
190
|
+
if (typeof event.error == 'string') delete event.data.error;
|
|
132
191
|
}
|
|
133
192
|
|
|
134
193
|
switch (event.type) {
|
package/src/Tester.js
CHANGED
|
@@ -15,6 +15,16 @@ export class Tester {
|
|
|
15
15
|
this.testNumber = testNumber;
|
|
16
16
|
this.reporter = reporter;
|
|
17
17
|
this.timer = reporter.timer || getTimer();
|
|
18
|
+
this.embeddedTests = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async dispose() {
|
|
22
|
+
this.reporter.abort();
|
|
23
|
+
await Promise.allSettled(this.embeddedTests);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get signal() {
|
|
27
|
+
return this.reporter.signal;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
plan(_n) {
|
package/src/bun/TestWorker.js
CHANGED
|
@@ -37,16 +37,20 @@ export default class TestWorker extends EventServer {
|
|
|
37
37
|
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
38
38
|
test: 0
|
|
39
39
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
try {
|
|
41
|
+
this.report(id, {
|
|
42
|
+
name: String(error),
|
|
43
|
+
test: 0,
|
|
44
|
+
marker: new Error(),
|
|
45
|
+
operator: 'error',
|
|
46
|
+
fail: true,
|
|
47
|
+
data: {
|
|
48
|
+
actual: error
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (!isStopTest(error)) throw error;
|
|
53
|
+
}
|
|
50
54
|
this.close(id);
|
|
51
55
|
});
|
|
52
56
|
worker.addEventListener('messageerror', error => {
|
|
@@ -55,16 +59,20 @@ export default class TestWorker extends EventServer {
|
|
|
55
59
|
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
56
60
|
test: 0
|
|
57
61
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
try {
|
|
63
|
+
this.report(id, {
|
|
64
|
+
name: String(error),
|
|
65
|
+
test: 0,
|
|
66
|
+
marker: new Error(),
|
|
67
|
+
operator: 'error',
|
|
68
|
+
fail: true,
|
|
69
|
+
data: {
|
|
70
|
+
actual: error
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (!isStopTest(error)) throw error;
|
|
75
|
+
}
|
|
68
76
|
this.close(id);
|
|
69
77
|
});
|
|
70
78
|
worker.postMessage({
|
package/src/deno/TestWorker.js
CHANGED
|
@@ -39,16 +39,20 @@ export default class TestWorker extends EventServer {
|
|
|
39
39
|
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
40
40
|
test: 0
|
|
41
41
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
try {
|
|
43
|
+
this.report(id, {
|
|
44
|
+
name: String(error),
|
|
45
|
+
test: 0,
|
|
46
|
+
marker: new Error(),
|
|
47
|
+
operator: 'error',
|
|
48
|
+
fail: true,
|
|
49
|
+
data: {
|
|
50
|
+
actual: error
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (!isStopTest(error)) throw error;
|
|
55
|
+
}
|
|
52
56
|
this.close(id);
|
|
53
57
|
});
|
|
54
58
|
worker.postMessage({
|
package/src/node/TestWorker.js
CHANGED
|
@@ -39,16 +39,20 @@ export default class TestWorker extends EventServer {
|
|
|
39
39
|
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
40
40
|
test: 0
|
|
41
41
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
try {
|
|
43
|
+
this.report(id, {
|
|
44
|
+
name: String(error),
|
|
45
|
+
test: 0,
|
|
46
|
+
marker: new Error(),
|
|
47
|
+
operator: 'error',
|
|
48
|
+
fail: true,
|
|
49
|
+
data: {
|
|
50
|
+
actual: error
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (!isStopTest(error)) throw error;
|
|
55
|
+
}
|
|
52
56
|
this.close(id);
|
|
53
57
|
});
|
|
54
58
|
worker.on('messageerror', error => {
|
|
@@ -57,16 +61,20 @@ export default class TestWorker extends EventServer {
|
|
|
57
61
|
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
58
62
|
test: 0
|
|
59
63
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
try {
|
|
65
|
+
this.report(id, {
|
|
66
|
+
name: String(error),
|
|
67
|
+
test: 0,
|
|
68
|
+
marker: new Error(),
|
|
69
|
+
operator: 'error',
|
|
70
|
+
fail: true,
|
|
71
|
+
data: {
|
|
72
|
+
actual: error
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (!isStopTest(error)) throw error;
|
|
77
|
+
}
|
|
70
78
|
this.close(id);
|
|
71
79
|
});
|
|
72
80
|
worker.postMessage({
|
|
@@ -9,6 +9,14 @@ export class Reporter {
|
|
|
9
9
|
this.timer = timer || getTimer();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
get signal() {
|
|
13
|
+
return this.state?.signal;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
abort() {
|
|
17
|
+
this.state?.abort();
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
onTest(event) {
|
|
13
21
|
this.state = new State(this.state, {
|
|
14
22
|
name: event.name,
|
|
@@ -31,6 +39,7 @@ export class Reporter {
|
|
|
31
39
|
if (theState) {
|
|
32
40
|
theState.updateParent();
|
|
33
41
|
this.state = theState.parent;
|
|
42
|
+
theState.dispose();
|
|
34
43
|
--this.depth;
|
|
35
44
|
}
|
|
36
45
|
return theState;
|
|
@@ -42,8 +51,7 @@ export class Reporter {
|
|
|
42
51
|
if (reportingMethod) {
|
|
43
52
|
this[reportingMethod]({type: 'end', test: theState.test, name: theState.name});
|
|
44
53
|
} else {
|
|
45
|
-
|
|
46
|
-
this.state = theState.parent;
|
|
54
|
+
this.onEnd();
|
|
47
55
|
}
|
|
48
56
|
if (theState.test === event.test && theState.name === event.name) break;
|
|
49
57
|
}
|
|
@@ -63,11 +71,9 @@ export class Reporter {
|
|
|
63
71
|
end: 'onEnd',
|
|
64
72
|
terminated: 'onTerminated',
|
|
65
73
|
assert: 'onAssert',
|
|
74
|
+
'assertion-error': 'onAssertionError',
|
|
66
75
|
comment: 'onComment',
|
|
67
|
-
|
|
68
|
-
'console-info': 'onConsoleInfo',
|
|
69
|
-
'console-warn': 'onConsoleWarn',
|
|
70
|
-
'console-error': 'onConsoleError',
|
|
76
|
+
console: 'onConsole',
|
|
71
77
|
stdout: 'onStdout',
|
|
72
78
|
stderr: 'onStderr',
|
|
73
79
|
bailOut: 'onBailOut'
|
|
@@ -22,7 +22,8 @@ const consoleDict = {
|
|
|
22
22
|
log: 'log',
|
|
23
23
|
info: 'inf',
|
|
24
24
|
warn: 'wrn',
|
|
25
|
-
error: 'err'
|
|
25
|
+
error: 'err',
|
|
26
|
+
assert: 'srt'
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
const getType = value => {
|
|
@@ -204,26 +205,24 @@ export class TTYReporter extends Reporter {
|
|
|
204
205
|
this.onTerminated(event, 'reportInternal');
|
|
205
206
|
break;
|
|
206
207
|
case 'comment':
|
|
207
|
-
!this.failureOnly
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
case 'console-warn':
|
|
212
|
-
if (!this.failureOnly && !this.hideStreams) {
|
|
213
|
-
const lines = event.name.split(/\r?\n/),
|
|
214
|
-
type = /\-(\w+)$/.exec(event.type)[1],
|
|
215
|
-
prefix = this.stdoutPaint(consoleDict[type] + ':') + ' ';
|
|
216
|
-
for (const line of lines) {
|
|
217
|
-
this.out(prefix + line);
|
|
208
|
+
if (!this.failureOnly) {
|
|
209
|
+
const message = event.name || 'empty comment';
|
|
210
|
+
for (const line of message.split(/\r?\n/)) {
|
|
211
|
+
this.out(this.blue(this.italic(line)));
|
|
218
212
|
}
|
|
219
213
|
}
|
|
220
214
|
break;
|
|
221
|
-
case 'console
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
case 'console':
|
|
216
|
+
{
|
|
217
|
+
const method = event.data?.method,
|
|
218
|
+
isShown = method === 'error' || method === 'assert' || !this.failureOnly;
|
|
219
|
+
if (isShown && !this.hideStreams) {
|
|
220
|
+
const lines = event.name.split(/\r?\n/),
|
|
221
|
+
paint = method === 'error' || method === 'assert' ? 'stderrPaint' : 'stdoutPaint',
|
|
222
|
+
prefix = this[paint](consoleDict[method] + ':') + ' ';
|
|
223
|
+
for (const line of lines) {
|
|
224
|
+
this.out(prefix + line);
|
|
225
|
+
}
|
|
227
226
|
}
|
|
228
227
|
}
|
|
229
228
|
break;
|
|
@@ -257,56 +256,63 @@ export class TTYReporter extends Reporter {
|
|
|
257
256
|
}
|
|
258
257
|
break;
|
|
259
258
|
case 'assert':
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
259
|
+
case 'assertion-error':
|
|
260
|
+
{
|
|
261
|
+
const isFailed = event.fail && !event.skip && !event.todo,
|
|
262
|
+
nameLines = event.name ? event.name.split(/\r?\n/g) : [];
|
|
263
|
+
isFailed ? ++this.failedAsserts : ++this.successfulAsserts;
|
|
264
|
+
event.skip && ++this.skippedAsserts;
|
|
265
|
+
event.todo && ++this.todoAsserts;
|
|
266
|
+
if (!isFailed && this.failureOnly) break;
|
|
267
|
+
text = event.fail ? '✗' : '✓';
|
|
268
|
+
if (this.showAssertNumber) {
|
|
269
|
+
text += ' ' + (this.renumberAsserts ? ++this.assertCounter : event.id);
|
|
270
|
+
}
|
|
271
|
+
if (event.skip) {
|
|
272
|
+
text += ' SKIP';
|
|
273
|
+
} else if (event.todo) {
|
|
274
|
+
text += ' ' + this.brightYellow('TODO');
|
|
275
|
+
}
|
|
276
|
+
nameLines[0] && (text += ' ' + nameLines[0]);
|
|
277
|
+
if (event.skip) {
|
|
278
|
+
text = this.blue(text);
|
|
279
|
+
} else {
|
|
280
|
+
text = isFailed ? this.red(text) : this.green(text);
|
|
281
|
+
}
|
|
282
|
+
this.showTime && (text += this.lowWhite(' - ' + formatTime(event.diffTime)));
|
|
283
|
+
event.fail && event.at && (text += this.lowWhite(' - ' + event.at));
|
|
284
|
+
if (this.failureOnly) {
|
|
285
|
+
--this.visibleDepth;
|
|
286
|
+
this.out(this.brightRed('✗ ' + (this.state?.name || 'anonymous test')));
|
|
287
|
+
++this.visibleDepth;
|
|
288
|
+
}
|
|
289
|
+
this.out(text);
|
|
288
290
|
|
|
289
|
-
|
|
291
|
+
if (!event.fail || event.skip || !this.showData) break;
|
|
290
292
|
|
|
291
|
-
|
|
293
|
+
this.out(this.lowWhite(' operator: ') + event.operator);
|
|
292
294
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
if (nameLines.length > 1) {
|
|
296
|
+
this.out(
|
|
297
|
+
this.lowWhite(
|
|
298
|
+
' message: |-' + (event.generatedMessage ? ' ' + this.italic('(generated)') : '')
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
nameLines.forEach(line => this.out(' ' + line));
|
|
302
|
+
}
|
|
297
303
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
304
|
+
const expected = event.expected && JSON.parse(event.expected);
|
|
305
|
+
if (event.hasOwnProperty('expected')) {
|
|
306
|
+
this.out(this.lowWhite(' expected: ') + this.formatValue(expected));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const actual = event.actual && JSON.parse(event.actual);
|
|
310
|
+
if (event.hasOwnProperty('actual')) {
|
|
311
|
+
this.out(this.lowWhite(' actual: ') + this.formatValue(actual));
|
|
312
|
+
}
|
|
302
313
|
|
|
303
|
-
const stack =
|
|
304
|
-
actual?.type === 'Error' && typeof actual.stack == 'string'
|
|
305
|
-
? actual.stack
|
|
306
|
-
: event.marker?.stack;
|
|
307
|
-
if (typeof stack == 'string') {
|
|
308
314
|
this.out(this.lowWhite(' stack: |-'));
|
|
309
|
-
|
|
315
|
+
event.stackList.forEach(line => this.out(this.lowWhite(' at ' + line)));
|
|
310
316
|
}
|
|
311
317
|
break;
|
|
312
318
|
}
|
|
@@ -85,25 +85,54 @@ export class TapReporter extends Reporter {
|
|
|
85
85
|
break;
|
|
86
86
|
case 'comment':
|
|
87
87
|
this.open();
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.write('# ' + /\-(\w+)$/.exec(event.type)[1] + ': ' + event.name, 'stdout');
|
|
88
|
+
{
|
|
89
|
+
const message = event.name || 'empty comment';
|
|
90
|
+
for (const line of message.split(/\r?\n/)) {
|
|
91
|
+
this.write('# ' + line);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
95
94
|
break;
|
|
96
|
-
case 'console
|
|
95
|
+
case 'console':
|
|
97
96
|
this.open();
|
|
98
|
-
|
|
97
|
+
switch (event.data?.method) {
|
|
98
|
+
case 'log':
|
|
99
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
100
|
+
this.write('# log: ' + line, 'stdout');
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
case 'info':
|
|
104
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
105
|
+
this.write('# info: ' + line, 'stdout');
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case 'warn':
|
|
109
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
110
|
+
this.write('# warn: ' + line, 'stdout');
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case 'error':
|
|
114
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
115
|
+
this.write('# error: ' + line, 'stderr');
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case 'assert':
|
|
119
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
120
|
+
this.write('# assert: ' + line, 'stdout');
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
99
124
|
break;
|
|
100
125
|
case 'stdout':
|
|
101
126
|
this.open();
|
|
102
|
-
|
|
127
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
128
|
+
this.write('# stdout: ' + line, 'stdout');
|
|
129
|
+
}
|
|
103
130
|
break;
|
|
104
131
|
case 'stderr':
|
|
105
132
|
this.open();
|
|
106
|
-
|
|
133
|
+
for (const line of event.name.split(/\r?\n/)) {
|
|
134
|
+
this.write('# stderr: ' + line, 'stderr');
|
|
135
|
+
}
|
|
107
136
|
break;
|
|
108
137
|
case 'bail-out':
|
|
109
138
|
this.open();
|
|
@@ -112,55 +141,54 @@ export class TapReporter extends Reporter {
|
|
|
112
141
|
this.write(text, 'bail-out');
|
|
113
142
|
break;
|
|
114
143
|
case 'assert':
|
|
144
|
+
case 'assertion-error':
|
|
115
145
|
this.open();
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
text += ' # time=' + event.diffTime.toFixed(3) + 'ms';
|
|
127
|
-
this.write(text, event.fail ? 'failure' : 'success');
|
|
128
|
-
if (event.fail) {
|
|
129
|
-
this.write(' ---', 'yaml');
|
|
130
|
-
if (this.useJson) {
|
|
131
|
-
this.write(' operator: ' + event.operator, 'yaml');
|
|
132
|
-
if (event.hasOwnProperty('expected')) {
|
|
133
|
-
this.write(' expected: ' + formatValue(event.expected), 'yaml');
|
|
134
|
-
}
|
|
135
|
-
if (event.hasOwnProperty('actual')) {
|
|
136
|
-
this.write(' actual: ' + formatValue(event.actual), 'yaml');
|
|
137
|
-
}
|
|
138
|
-
event.at && this.write(' at: ' + event.at, 'yaml');
|
|
139
|
-
} else {
|
|
140
|
-
yamlFormatter({operator: event.operator}, formatterOptions).forEach(line =>
|
|
141
|
-
this.write(line, 'yaml')
|
|
142
|
-
);
|
|
143
|
-
yamlFormatter(
|
|
144
|
-
{
|
|
145
|
-
expected: event.expected && JSON.parse(event.expected),
|
|
146
|
-
actual: event.actual && JSON.parse(event.actual)
|
|
147
|
-
},
|
|
148
|
-
formatterOptions
|
|
149
|
-
).forEach(line => this.write(line, 'yaml'));
|
|
150
|
-
yamlFormatter({at: event.at}, formatterOptions).forEach(line =>
|
|
151
|
-
this.write(line, 'yaml')
|
|
152
|
-
);
|
|
146
|
+
{
|
|
147
|
+
const nameLines = event.name ? event.name.split(/\r?\n/g) : [];
|
|
148
|
+
text =
|
|
149
|
+
(event.fail ? 'not ok' : 'ok') +
|
|
150
|
+
' ' +
|
|
151
|
+
(this.renumberAsserts ? ++this.assertCounter : event.id);
|
|
152
|
+
if (event.skip) {
|
|
153
|
+
text += ' # SKIP';
|
|
154
|
+
} else if (event.todo) {
|
|
155
|
+
text += ' # TODO';
|
|
153
156
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
nameLines[0] && (text += ' ' + nameLines[0]);
|
|
158
|
+
text += ' # time=' + event.diffTime.toFixed(3) + 'ms';
|
|
159
|
+
this.write(text, event.fail ? 'failure' : 'success');
|
|
160
|
+
if (event.fail) {
|
|
161
|
+
this.write(' ---', 'yaml');
|
|
162
|
+
if (this.useJson) {
|
|
163
|
+
this.write(' operator: ' + event.operator, 'yaml');
|
|
164
|
+
this.write(' message: ' + formatValue(nameLines), 'yaml');
|
|
165
|
+
if (event.hasOwnProperty('expected')) {
|
|
166
|
+
this.write(' expected: ' + formatValue(event.expected), 'yaml');
|
|
167
|
+
}
|
|
168
|
+
if (event.hasOwnProperty('actual')) {
|
|
169
|
+
this.write(' actual: ' + formatValue(event.actual), 'yaml');
|
|
170
|
+
}
|
|
171
|
+
event.at && this.write(' at: ' + event.at, 'yaml');
|
|
172
|
+
} else {
|
|
173
|
+
yamlFormatter(
|
|
174
|
+
{operator: event.operator, message: nameLines},
|
|
175
|
+
formatterOptions
|
|
176
|
+
).forEach(line => this.write(line, 'yaml'));
|
|
177
|
+
yamlFormatter(
|
|
178
|
+
{
|
|
179
|
+
expected: event.expected && JSON.parse(event.expected),
|
|
180
|
+
actual: event.actual && JSON.parse(event.actual)
|
|
181
|
+
},
|
|
182
|
+
formatterOptions
|
|
183
|
+
).forEach(line => this.write(line, 'yaml'));
|
|
184
|
+
yamlFormatter({at: event.at}, formatterOptions).forEach(line =>
|
|
185
|
+
this.write(line, 'yaml')
|
|
186
|
+
);
|
|
187
|
+
}
|
|
160
188
|
this.write(' stack: |-', 'yaml');
|
|
161
|
-
|
|
189
|
+
event.stackList.forEach(line => this.write(' at ' + line, 'yaml'));
|
|
190
|
+
this.write(' ...', 'yaml');
|
|
162
191
|
}
|
|
163
|
-
this.write(' ...', 'yaml');
|
|
164
192
|
}
|
|
165
193
|
break;
|
|
166
194
|
}
|
package/src/test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {selectTimer} from './utils/timer.js';
|
|
2
|
-
import {isStopTest} from './State.js';
|
|
2
|
+
import {isAssertionError, isStopTest} from './State.js';
|
|
3
3
|
import getDeferred from './utils/getDeferred.js';
|
|
4
4
|
import timeout from './utils/timeout.js';
|
|
5
5
|
import {formatTime} from './utils/formatters.js';
|
|
@@ -25,6 +25,11 @@ export const registerNotifyCallback = callback => {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
const testers = [];
|
|
29
|
+
|
|
30
|
+
export const getTesters = () => testers;
|
|
31
|
+
export const getTester = () => (testers.length ? testers[testers.length - 1] : null);
|
|
32
|
+
|
|
28
33
|
const processArgs = (name, options, testFn) => {
|
|
29
34
|
// normalize arguments
|
|
30
35
|
if (typeof name == 'function') {
|
|
@@ -61,6 +66,11 @@ const processArgs = (name, options, testFn) => {
|
|
|
61
66
|
|
|
62
67
|
let isTimerSet = false;
|
|
63
68
|
export const test = async (name, options, testFn) => {
|
|
69
|
+
const currentTester = getTester();
|
|
70
|
+
if (currentTester) {
|
|
71
|
+
return currentTester.test(name, options, testFn);
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
options = processArgs(name, options, testFn);
|
|
65
75
|
if (!isTimerSet) {
|
|
66
76
|
await selectTimer();
|
|
@@ -93,6 +103,7 @@ export const runTests = async tests => {
|
|
|
93
103
|
deferred && deferred.resolve(tester.state);
|
|
94
104
|
return;
|
|
95
105
|
}
|
|
106
|
+
testers.push(tester);
|
|
96
107
|
try {
|
|
97
108
|
tester.reporter.report({
|
|
98
109
|
type: 'test',
|
|
@@ -117,6 +128,7 @@ export const runTests = async tests => {
|
|
|
117
128
|
test: testNumber,
|
|
118
129
|
time: tester.timer.now()
|
|
119
130
|
});
|
|
131
|
+
tester.reporter.abort();
|
|
120
132
|
await result;
|
|
121
133
|
}
|
|
122
134
|
}
|
|
@@ -133,6 +145,22 @@ export const runTests = async tests => {
|
|
|
133
145
|
marker: new Error(),
|
|
134
146
|
time: tester.timer.now()
|
|
135
147
|
});
|
|
148
|
+
} else if (isAssertionError(error)) {
|
|
149
|
+
tester.reporter.report({
|
|
150
|
+
type: 'assertion-error',
|
|
151
|
+
name: String(error),
|
|
152
|
+
test: testNumber,
|
|
153
|
+
marker: new Error(),
|
|
154
|
+
time: tester.timer.now(),
|
|
155
|
+
operator: error.operator,
|
|
156
|
+
generatedMessage: error.generatedMessage,
|
|
157
|
+
fail: true,
|
|
158
|
+
data: {
|
|
159
|
+
actual: error.actual,
|
|
160
|
+
expected: error.expected,
|
|
161
|
+
error
|
|
162
|
+
}
|
|
163
|
+
});
|
|
136
164
|
} else {
|
|
137
165
|
tester.reporter.report({
|
|
138
166
|
name: 'UNEXPECTED EXCEPTION: ' + String(error),
|
|
@@ -155,6 +183,8 @@ export const runTests = async tests => {
|
|
|
155
183
|
});
|
|
156
184
|
}
|
|
157
185
|
}
|
|
186
|
+
await tester.dispose();
|
|
187
|
+
testers.pop();
|
|
158
188
|
tester.reporter.report({
|
|
159
189
|
type: 'end',
|
|
160
190
|
name: options.name,
|
|
@@ -168,16 +198,28 @@ export const runTests = async tests => {
|
|
|
168
198
|
};
|
|
169
199
|
|
|
170
200
|
test.skip = function skip(name, options, testFn) {
|
|
201
|
+
const currentTester = getTester();
|
|
202
|
+
if (currentTester) {
|
|
203
|
+
return currentTester.skip(name, options, testFn);
|
|
204
|
+
}
|
|
171
205
|
options = processArgs(name, options, testFn);
|
|
172
206
|
return test({...options, skip: true});
|
|
173
207
|
};
|
|
174
208
|
|
|
175
209
|
test.todo = function todo(name, options, testFn) {
|
|
210
|
+
const currentTester = getTester();
|
|
211
|
+
if (currentTester) {
|
|
212
|
+
return currentTester.todo(name, options, testFn);
|
|
213
|
+
}
|
|
176
214
|
options = processArgs(name, options, testFn);
|
|
177
215
|
return test({...options, todo: true});
|
|
178
216
|
};
|
|
179
217
|
|
|
180
218
|
test.asPromise = function asPromise(name, options, testFn) {
|
|
219
|
+
const currentTester = getTester();
|
|
220
|
+
if (currentTester) {
|
|
221
|
+
return currentTester.asPromise(name, options, testFn);
|
|
222
|
+
}
|
|
181
223
|
options = processArgs(name, options, testFn);
|
|
182
224
|
if (options.testFn) {
|
|
183
225
|
const testFn = options.testFn;
|
|
@@ -200,7 +242,9 @@ Tester.prototype.test = async function test(name, options, testFn) {
|
|
|
200
242
|
if (this.reporter.state?.skip) {
|
|
201
243
|
this.comment('SKIP test: ' + options.name);
|
|
202
244
|
} else {
|
|
203
|
-
|
|
245
|
+
const promise = runTests([{options}]);
|
|
246
|
+
this.embeddedTests.push(promise);
|
|
247
|
+
return promise;
|
|
204
248
|
}
|
|
205
249
|
};
|
|
206
250
|
|
|
@@ -215,7 +259,9 @@ Tester.prototype.todo = async function todo(name, options, testFn) {
|
|
|
215
259
|
this.comment('SKIP test: ' + options.name);
|
|
216
260
|
return;
|
|
217
261
|
}
|
|
218
|
-
|
|
262
|
+
const promise = runTests([{options: {...options, todo: true}}]);
|
|
263
|
+
this.embeddedTests.push(promise);
|
|
264
|
+
return promise;
|
|
219
265
|
};
|
|
220
266
|
|
|
221
267
|
Tester.prototype.asPromise = async function asPromise(name, options, testFn) {
|
|
@@ -231,7 +277,9 @@ Tester.prototype.asPromise = async function asPromise(name, options, testFn) {
|
|
|
231
277
|
}
|
|
232
278
|
});
|
|
233
279
|
}
|
|
234
|
-
|
|
280
|
+
const promise = runTests([{options}]);
|
|
281
|
+
this.embeddedTests.push(promise);
|
|
282
|
+
return promise;
|
|
235
283
|
};
|
|
236
284
|
|
|
237
285
|
export default test;
|
package/src/utils/EventServer.js
CHANGED
|
@@ -29,9 +29,13 @@ export default class EventServer {
|
|
|
29
29
|
this.finalized[id] = 1;
|
|
30
30
|
--this.totalTasks;
|
|
31
31
|
if (this.fileQueue.length) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
if (this.reporter.state?.stopTest) {
|
|
33
|
+
this.fileQueue = [];
|
|
34
|
+
} else {
|
|
35
|
+
++this.totalTasks;
|
|
36
|
+
const nextFile = this.fileQueue.shift();
|
|
37
|
+
defer(() => this.makeTask(nextFile));
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
if (this.passThroughId === id) {
|
|
37
41
|
this.passThroughId = null;
|
|
@@ -66,6 +70,7 @@ export default class EventServer {
|
|
|
66
70
|
}
|
|
67
71
|
}
|
|
68
72
|
createTask(fileName) {
|
|
73
|
+
if (this.reporter.state?.stopTest) return;
|
|
69
74
|
if (this.totalTasks < this.numberOfTasks) {
|
|
70
75
|
++this.totalTasks;
|
|
71
76
|
this.makeTask(fileName);
|
|
@@ -9,11 +9,16 @@ export const captureConsole = () => {
|
|
|
9
9
|
get(target, property, receiver) {
|
|
10
10
|
const prop = Reflect.get(target, property, receiver);
|
|
11
11
|
if (typeof prop === 'function') {
|
|
12
|
-
if (
|
|
13
|
-
|
|
12
|
+
if (property === 'assert') {
|
|
13
|
+
return (assertion, ...args) => {
|
|
14
|
+
if (assertion) return;
|
|
15
|
+
const reporter = getReporter();
|
|
16
|
+
reporter.report({type: 'console', name: format(...args), data: {method: property}});
|
|
17
|
+
};
|
|
18
|
+
} else if (consoleVerbs[property] === 1) {
|
|
14
19
|
return (...args) => {
|
|
15
20
|
const reporter = getReporter();
|
|
16
|
-
reporter.report({type, name: format(...args)});
|
|
21
|
+
reporter.report({type: 'console', name: format(...args), data: {method: property}});
|
|
17
22
|
};
|
|
18
23
|
}
|
|
19
24
|
}
|
package/web-app/TestWorker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import EventServer from '../src/utils/EventServer.js';
|
|
2
|
+
import {isStopTest} from '../src/State.js';
|
|
2
3
|
|
|
3
4
|
export default class TestWorker extends EventServer {
|
|
4
5
|
constructor(reporter, numberOfTasks, options) {
|
|
@@ -20,16 +21,20 @@ export default class TestWorker extends EventServer {
|
|
|
20
21
|
name: 'fail to load: ' + (error.message || 'Worker error'),
|
|
21
22
|
test: 0
|
|
22
23
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
try {
|
|
25
|
+
this.report(id, {
|
|
26
|
+
name: String(error),
|
|
27
|
+
test: 0,
|
|
28
|
+
marker: new Error(),
|
|
29
|
+
operator: 'error',
|
|
30
|
+
fail: true,
|
|
31
|
+
data: {
|
|
32
|
+
actual: error
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (!isStopTest(error)) throw error;
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
this.close(id);
|
|
35
40
|
};
|