tape-six 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,8 +30,10 @@ If you are familiar with other TAP-based libraries you'll feel right at home.
30
30
 
31
31
  ## Release notes
32
32
 
33
- The last releases:
33
+ The most recent releases:
34
34
 
35
+ * 0.9.4 *Updated deps. Added test runners for Bun and Deno.*
36
+ * 0.9.3 *Made TTY reporter work with non-TTY streams.*
35
37
  * 0.9.2 *Fixed Windows runner.*
36
38
  * 0.9.1 *More updates related to renaming `tape6` ⇒ `tape-six`.*
37
39
  * 0.9.0 *Initial release.*
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import {resolveTests, resolvePatterns} from '../src/node/config.js';
4
+
5
+ import {getReporter, setReporter} from '../src/test.js';
6
+ import State from '../src/State.js';
7
+ import TapReporter from '../src/TapReporter.js';
8
+ import {selectTimer} from '../src/utils/timer.js';
9
+
10
+ import TestWorker from '../src/bun/TestWorker.js';
11
+
12
+ const options = {},
13
+ rootFolder = process.cwd();
14
+
15
+ let flags = '',
16
+ parallel = '',
17
+ files = [];
18
+
19
+ const config = () => {
20
+ const optionNames = {
21
+ f: 'failureOnly',
22
+ t: 'showTime',
23
+ b: 'showBanner',
24
+ d: 'showData',
25
+ o: 'failOnce',
26
+ n: 'showAssertNumber'
27
+ };
28
+
29
+ let flagIsSet = false,
30
+ parIsSet = false;
31
+
32
+ for (let i = 2; i < Bun.argv.length; ++i) {
33
+ const arg = Bun.argv[i];
34
+ if (arg == '-f' || arg == '--flags') {
35
+ if (++i < Bun.argv.length) {
36
+ flags = Bun.argv[i];
37
+ flagIsSet = true;
38
+ }
39
+ continue;
40
+ }
41
+ if (arg == '-p' || arg == '--par') {
42
+ if (++i < Bun.argv.length) {
43
+ parallel = Bun.argv[i];
44
+ parIsSet = true;
45
+ if (!parallel || isNaN(parallel)) {
46
+ parallel = '';
47
+ parIsSet = false;
48
+ }
49
+ }
50
+ continue;
51
+ }
52
+ files.push(arg);
53
+ }
54
+
55
+ if (!flagIsSet) {
56
+ flags = process.env.TAPE6_FLAGS || flags;
57
+ }
58
+ for (let i = 0; i < flags.length; ++i) {
59
+ const option = flags[i].toLowerCase(),
60
+ name = optionNames[option];
61
+ if (typeof name == 'string') options[name] = option !== flags[i];
62
+ }
63
+
64
+ if (!parIsSet) {
65
+ parallel = process.env.TAPE6_PAR || parallel;
66
+ }
67
+ if (parallel) {
68
+ parallel = Math.max(0, +parallel);
69
+ if (parallel === Infinity) parallel = 0;
70
+ } else {
71
+ parallel = 0;
72
+ }
73
+ if (!parallel) parallel = navigator.hardwareConcurrency;
74
+ };
75
+
76
+ const init = async () => {
77
+ let reporter = getReporter();
78
+ if (!reporter) {
79
+ if (!process.env.TAPE6_TAP) {
80
+ const TTYReporter = (await import('../src/TTYReporter.js')).default,
81
+ ttyReporter = new TTYReporter(options);
82
+ ttyReporter.testCounter = -2;
83
+ ttyReporter.technicalDepth = 1;
84
+ reporter = ttyReporter.report.bind(ttyReporter);
85
+ }
86
+ if (!reporter) {
87
+ const tapReporter = new TapReporter({useJson: true});
88
+ reporter = tapReporter.report.bind(tapReporter);
89
+ }
90
+ setReporter(reporter);
91
+ }
92
+
93
+ if (files.length) {
94
+ files = await resolvePatterns(rootFolder, files);
95
+ } else {
96
+ files = await resolveTests(rootFolder, 'deno');
97
+ }
98
+ };
99
+
100
+ const main = async () => {
101
+ config();
102
+ await init();
103
+ await selectTimer();
104
+
105
+ process.on('uncaughtException', error => console.error('UNHANDLED ERROR:', error));
106
+
107
+ const rootState = new State(null, {callback: getReporter(), failOnce: options.failOnce}),
108
+ worker = new TestWorker(event => rootState.emit(event), parallel, options);
109
+
110
+ rootState.emit({type: 'test', test: 0, time: rootState.timer.now()});
111
+
112
+ await new Promise(resolve => {
113
+ worker.done = () => resolve();
114
+ worker.execute(files);
115
+ });
116
+
117
+ rootState.emit({
118
+ type: 'end',
119
+ test: 0,
120
+ time: rootState.timer.now(),
121
+ fail: rootState.failed > 0,
122
+ data: rootState
123
+ });
124
+
125
+ process.exit(rootState.failed > 0 ? 1 : 0);
126
+ };
127
+
128
+ main().catch(error => console.error('ERROR:', error));
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env -S deno run --allow-read --allow-env --allow-hrtime --ext=js
2
+
3
+ import {resolveTests, resolvePatterns} from '../src/node/config.js';
4
+
5
+ import {getReporter, setReporter} from '../src/test.js';
6
+ import State from '../src/State.js';
7
+ import TapReporter from '../src/TapReporter.js';
8
+ import {selectTimer} from '../src/utils/timer.js';
9
+
10
+ import TestWorker from '../src/deno/TestWorker.js';
11
+
12
+ const options = {},
13
+ rootFolder = Deno.cwd();
14
+
15
+ let flags = '',
16
+ parallel = '',
17
+ files = [];
18
+
19
+ const config = () => {
20
+ const optionNames = {
21
+ f: 'failureOnly',
22
+ t: 'showTime',
23
+ b: 'showBanner',
24
+ d: 'showData',
25
+ o: 'failOnce',
26
+ n: 'showAssertNumber'
27
+ };
28
+
29
+ let flagIsSet = false,
30
+ parIsSet = false;
31
+
32
+ for (let i = 0; i < Deno.args.length; ++i) {
33
+ const arg = Deno.args[i];
34
+ if (arg == '-f' || arg == '--flags') {
35
+ if (++i < Deno.args.length) {
36
+ flags = Deno.args[i];
37
+ flagIsSet = true;
38
+ }
39
+ continue;
40
+ }
41
+ if (arg == '-p' || arg == '--par') {
42
+ if (++i < Deno.args.length) {
43
+ parallel = Deno.args[i];
44
+ parIsSet = true;
45
+ if (!parallel || isNaN(parallel)) {
46
+ parallel = '';
47
+ parIsSet = false;
48
+ }
49
+ }
50
+ continue;
51
+ }
52
+ files.push(arg);
53
+ }
54
+
55
+ if (!flagIsSet) {
56
+ flags = Deno.env.get('TAPE6_FLAGS') || flags;
57
+ }
58
+ for (let i = 0; i < flags.length; ++i) {
59
+ const option = flags[i].toLowerCase(),
60
+ name = optionNames[option];
61
+ if (typeof name == 'string') options[name] = option !== flags[i];
62
+ }
63
+
64
+ if (!parIsSet) {
65
+ parallel = Deno.env.get('TAPE6_PAR') || parallel;
66
+ }
67
+ if (parallel) {
68
+ parallel = Math.max(0, +parallel);
69
+ if (parallel === Infinity) parallel = 0;
70
+ } else {
71
+ parallel = 0;
72
+ }
73
+ if (!parallel) parallel = navigator.hardwareConcurrency;
74
+ };
75
+
76
+ const init = async () => {
77
+ let reporter = getReporter();
78
+ if (!reporter) {
79
+ if (!Deno.env.get('TAPE6_TAP')) {
80
+ const TTYReporter = (await import('../src/TTYReporter.js')).default,
81
+ ttyReporter = new TTYReporter(options);
82
+ ttyReporter.testCounter = -2;
83
+ ttyReporter.technicalDepth = 1;
84
+ reporter = ttyReporter.report.bind(ttyReporter);
85
+ }
86
+ if (!reporter) {
87
+ const tapReporter = new TapReporter({useJson: true});
88
+ reporter = tapReporter.report.bind(tapReporter);
89
+ }
90
+ setReporter(reporter);
91
+ }
92
+
93
+ if (files.length) {
94
+ files = await resolvePatterns(rootFolder, files);
95
+ } else {
96
+ files = await resolveTests(rootFolder, 'deno');
97
+ }
98
+ };
99
+
100
+ const main = async () => {
101
+ config();
102
+ await init();
103
+ await selectTimer();
104
+
105
+ addEventListener('error', event => {
106
+ console.log('UNHANDLED ERROR:', event.message);
107
+ event.preventDefault();
108
+ });
109
+
110
+ const rootState = new State(null, {callback: getReporter(), failOnce: options.failOnce}),
111
+ worker = new TestWorker(event => rootState.emit(event), parallel, options);
112
+
113
+ rootState.emit({type: 'test', test: 0, time: rootState.timer.now()});
114
+
115
+ await new Promise(resolve => {
116
+ worker.done = () => resolve();
117
+ worker.execute(files);
118
+ });
119
+
120
+ rootState.emit({
121
+ type: 'end',
122
+ test: 0,
123
+ time: rootState.timer.now(),
124
+ fail: rootState.failed > 0,
125
+ data: rootState
126
+ });
127
+
128
+ Deno.exit(rootState.failed > 0 ? 1 : 0);
129
+ };
130
+
131
+ main().catch(error => console.error('ERROR:', error));
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import http from 'http';
4
- import fs from 'fs';
5
- import path from 'path';
3
+ import http from 'node:http';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import process from 'node:process';
6
7
 
7
8
  import {resolveTests, resolvePatterns} from '../src/node/config.js';
8
9
 
@@ -34,8 +35,9 @@ const mimeTable = {
34
35
  defaultMime = 'application/octet-stream',
35
36
  rootFolder = process.env.SERVER_ROOT || process.cwd(),
36
37
  traceCalls = process.argv.includes('--trace'),
37
- isTTY = process.stdout.isTTY,
38
- hasColors = isTTY && process.stdout.hasColors();
38
+ hasColors =
39
+ process.stdout.isTTY &&
40
+ (typeof process.stdout.hasColors == 'function' ? process.stdout.hasColors() : true);
39
41
 
40
42
  let webAppPath = process.env.WEBAPP_PATH;
41
43
  if (!webAppPath) {
@@ -72,7 +74,7 @@ const sendFile = (req, res, fileName, ext, justHeaders) => {
72
74
  if (!ext) {
73
75
  ext = path.extname(fileName).toLowerCase();
74
76
  }
75
- let mime = ext && mimeTable[ext.substr(1)];
77
+ let mime = ext && mimeTable[ext.substring(1)];
76
78
  if (!mime || typeof mime != 'string') {
77
79
  mime = defaultMime;
78
80
  }
@@ -160,8 +162,8 @@ const server = http.createServer(async (req, res) => {
160
162
  bailOut(req, res);
161
163
  });
162
164
 
163
- server.on('clientError', (err, socket) => {
164
- if (err.code === 'ECONNRESET' || !socket.writable) return;
165
+ server.on('clientError', (error, socket) => {
166
+ if (error.code === 'ECONNRESET' || !socket.writable) return;
165
167
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
166
168
  });
167
169
 
package/bin/tape6.js CHANGED
@@ -1,19 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import cluster from 'cluster';
4
- import os from 'os';
5
- import path from 'path';
3
+ import cluster from 'node:cluster';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import process from 'node:process';
6
7
 
7
8
  import {resolveTests, resolvePatterns} from '../src/node/config.js';
8
9
 
9
- import {
10
- getTests,
11
- clearTests,
12
- getReporter,
13
- setReporter,
14
- runTests,
15
- setConfiguredFlag
16
- } from '../src/test.js';
10
+ import {getReporter, setReporter} from '../src/test.js';
17
11
  import State from '../src/State.js';
18
12
  import TapReporter from '../src/TapReporter.js';
19
13
  import TestWorker from '../src/node/TestWorker.js';
@@ -27,7 +21,7 @@ let flags = '',
27
21
  parallel = '',
28
22
  files = [];
29
23
 
30
- const masterConfiguration = () => {
24
+ const mainConfiguration = () => {
31
25
  const optionNames = {
32
26
  f: 'failureOnly',
33
27
  t: 'showTime',
@@ -83,10 +77,10 @@ const masterConfiguration = () => {
83
77
  if (!parallel) parallel = os.cpus().length;
84
78
  };
85
79
 
86
- const masterInitialization = async () => {
80
+ const mainInitialization = async () => {
87
81
  let reporter = getReporter();
88
82
  if (!reporter) {
89
- if (process.stdout.isTTY && !process.env.TAPE6_TAP) {
83
+ if (!process.env.TAPE6_TAP) {
90
84
  const TTYReporter = (await import('../src/TTYReporter.js')).default,
91
85
  ttyReporter = new TTYReporter(options);
92
86
  ttyReporter.testCounter = -2;
@@ -107,11 +101,13 @@ const masterInitialization = async () => {
107
101
  }
108
102
  };
109
103
 
110
- const masterProcess = async () => {
111
- masterConfiguration();
112
- await masterInitialization();
104
+ const mainProcess = async () => {
105
+ mainConfiguration();
106
+ await mainInitialization();
113
107
  await selectTimer();
114
108
 
109
+ process.on('uncaughtException', error => console.error('UNHANDLED ERROR:', error));
110
+
115
111
  const rootState = new State(null, {callback: getReporter(), failOnce: options.failOnce}),
116
112
  worker = new TestWorker(event => rootState.emit(event), parallel, options);
117
113
 
@@ -191,7 +187,6 @@ const workerProcess = async () => {
191
187
  await import(name);
192
188
  } catch (error) {
193
189
  reject(error);
194
- return;
195
190
  }
196
191
  });
197
192
  process.send({started: true});
@@ -204,11 +199,8 @@ const workerProcess = async () => {
204
199
  }
205
200
  };
206
201
 
207
- const main = async () => {
208
- if (cluster.isMaster) {
209
- await masterProcess();
210
- } else if (cluster.isWorker) {
211
- await workerProcess();
212
- }
202
+ const main = () => {
203
+ if (cluster.isWorker) return workerProcess();
204
+ return mainProcess();
213
205
  };
214
206
  main().catch(error => console.error('ERROR:', error));
package/index.js CHANGED
@@ -18,7 +18,7 @@ defer(async () => {
18
18
  if (typeof window.__tape6_flags == 'string') {
19
19
  flags = window.__tape6_flags;
20
20
  } else if (window.location.search) {
21
- flags = (new URLSearchParams(window.location.search.substr(1))).get('flags') || '';
21
+ flags = (new URLSearchParams(window.location.search.substring(1))).get('flags') || '';
22
22
  }
23
23
  } else if (isNode) {
24
24
  flags = process.env.TAPE6_FLAGS || '';
@@ -33,14 +33,14 @@ defer(async () => {
33
33
  let reporter = getReporter();
34
34
  if (!reporter) {
35
35
  if (isBrowser) {
36
- const id = window.__tape6_id || (new URLSearchParams(window.location.search.substr(1))).get('id');
36
+ const id = window.__tape6_id || (new URLSearchParams(window.location.search.substring(1))).get('id');
37
37
  if (typeof window.__tape6_reporter == 'function') {
38
38
  reporter = event => window.__tape6_reporter(id, event);
39
39
  } else if (window.parent && typeof window.parent.__tape6_reporter == 'function') {
40
40
  reporter = event => window.parent.__tape6_reporter(id, event);
41
41
  }
42
42
  } else if (isNode) {
43
- if (process.stdout.isTTY && !process.env.TAPE6_TAP) {
43
+ if (!process.env.TAPE6_TAP) {
44
44
  const TTYReporter = (await import('./src/TTYReporter.js')).default,
45
45
  ttyReporter = new TTYReporter(options);
46
46
  reporter = ttyReporter.report.bind(ttyReporter);
@@ -66,8 +66,8 @@ defer(async () => {
66
66
 
67
67
  if (isNode) {
68
68
  !process.env.TAPE6_WORKER && process.exit(rootState.failed > 0 ? 1 : 0);
69
- } else if (typeof __reportTape6Results == 'function') {
70
- __reportTape6Results(rootState.failed > 0 ? 'failure' : 'success');
69
+ } else if (typeof __tape6_reportResults == 'function') {
70
+ __tape6_reportResults(rootState.failed > 0 ? 'failure' : 'success');
71
71
  }
72
72
  });
73
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tape-six",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "TAP for the modern JavaScript (ES6).",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -10,6 +10,8 @@
10
10
  },
11
11
  "bin": {
12
12
  "tape6": "bin/tape6.js",
13
+ "tape6-bun": "bin/tape6-bun.js",
14
+ "tape6-deno": "bin/tape6-deno.js",
13
15
  "tape6-server": "bin/tape6-server.js"
14
16
  },
15
17
  "scripts": {
@@ -18,7 +20,7 @@
18
20
  "copyDeep6": "node scripts/copyFolder.js --src ./vendors/deep6/src --dst ./src/deep6 --clear",
19
21
  "build": "npm run copyDeep6",
20
22
  "prepublishOnly": "npm run build",
21
- "test": "node ./bin/tape6.js"
23
+ "test": "node ./bin/tape6.js --flags FO"
22
24
  },
23
25
  "github": "http://github.com/uhop/tape-six",
24
26
  "repository": {
@@ -50,6 +52,6 @@
50
52
  ]
51
53
  },
52
54
  "devDependencies": {
53
- "puppeteer": "^15.3.2"
55
+ "puppeteer": "^22.8.1"
54
56
  }
55
57
  }
@@ -1,4 +1,12 @@
1
- import {stringRep, normalizeBox, padBox, padBoxLeft, drawBox, stackHorizontally} from './utils/box.js';
1
+ import process from 'node:process';
2
+ import {
3
+ stringRep,
4
+ normalizeBox,
5
+ padBox,
6
+ padBoxLeft,
7
+ drawBox,
8
+ stackHorizontally
9
+ } from './utils/box.js';
2
10
  import {formatNumber, formatTime} from './utils/formatters.js';
3
11
 
4
12
  // colors
@@ -19,15 +27,15 @@ class TTYReporter {
19
27
  output = process.stdout,
20
28
  renumberAsserts = false,
21
29
  failureOnly = false,
22
- showBanner = true,
30
+ showBanner = output.isTTY,
23
31
  showTime = true,
24
- showData = false,
32
+ showData = true,
25
33
  showAssertNumber = false
26
34
  } = {}) {
27
- if (!output || !output.isTTY) throw Error('Module TTYReporter works only with TTY output streams.');
28
-
29
35
  this.output = output;
30
- this.hasColors = this.output.hasColors(256);
36
+ this.hasColors =
37
+ this.output.isTTY &&
38
+ (typeof this.output.hasColors == 'function' ? this.output.hasColors(256) : true);
31
39
  this.renumberAsserts = renumberAsserts;
32
40
  this.failureOnly = failureOnly;
33
41
  this.showBanner = showBanner;
@@ -35,7 +43,13 @@ class TTYReporter {
35
43
  this.showData = showData;
36
44
  this.showAssertNumber = showAssertNumber;
37
45
 
38
- this.depth = this.assertCounter = this.failedAsserts = this.successfulAsserts = this.skippedAsserts = this.todoAsserts = 0;
46
+ this.depth =
47
+ this.assertCounter =
48
+ this.failedAsserts =
49
+ this.successfulAsserts =
50
+ this.skippedAsserts =
51
+ this.todoAsserts =
52
+ 0;
39
53
  this.testCounter = -1;
40
54
  this.technicalDepth = 0;
41
55
 
@@ -58,7 +72,7 @@ class TTYReporter {
58
72
  this.failure = this.paint(failureStyle, reset);
59
73
  this.skipped = this.paint(skippedStyle, reset);
60
74
 
61
- this.out('');
75
+ this.output.isTTY && this.out('');
62
76
  }
63
77
  paint(prefix, suffix = '\x1B[39m') {
64
78
  return this.hasColors ? text => join(prefix, text, suffix) : text => text;
@@ -78,12 +92,16 @@ class TTYReporter {
78
92
  return this;
79
93
  }
80
94
  report(event) {
81
- this.output.moveCursor(0, -1);
82
- this.output.clearLine(0);
95
+ if (this.output.isTTY) {
96
+ this.output.moveCursor(0, -1);
97
+ this.output.clearLine(0);
98
+ }
83
99
  let text;
84
100
  switch (event.type) {
85
101
  case 'test':
86
- this.depth > this.technicalDepth && !this.failureOnly && this.out('\u25CB ' + (event.name || 'anonymous test'));
102
+ this.depth > this.technicalDepth &&
103
+ !this.failureOnly &&
104
+ this.out('\u25CB ' + (event.name || 'anonymous test'));
87
105
  ++this.depth;
88
106
  ++this.testCounter;
89
107
  this.testStack.push({name: event.name, lines: this.lines, fail: false});
@@ -108,7 +126,7 @@ class TTYReporter {
108
126
  total = state.asserts - state.skipped,
109
127
  success = total - state.failed;
110
128
 
111
- if (!this.showBanner) {
129
+ if (!this.showBanner || !this.output.isTTY) {
112
130
  this.out(
113
131
  this.blackBg(
114
132
  ' ' +
@@ -139,7 +157,14 @@ class TTYReporter {
139
157
  box1 = drawBox(box1);
140
158
  box1 = padBox(box1, 0, 3);
141
159
  box1 = normalizeBox(
142
- [...box1, '', 'Passed: ' + (event.fail ? formatNumber((total > 0 ? success / total : 1) * 100, 1) + '%' : '100%')],
160
+ [
161
+ ...box1,
162
+ '',
163
+ 'Passed: ' +
164
+ (event.fail
165
+ ? formatNumber((total > 0 ? success / total : 1) * 100, 1) + '%'
166
+ : '100%')
167
+ ],
143
168
  ' ',
144
169
  'center'
145
170
  );
@@ -161,7 +186,18 @@ class TTYReporter {
161
186
  'left'
162
187
  );
163
188
  box2 = padBoxLeft(box2, 1);
164
- box2 = stackHorizontally(normalizeBox(['tests:', 'asserts:', ' passed:', ' failed:', ' skipped:', ' todo:', 'time:']), box2);
189
+ box2 = stackHorizontally(
190
+ normalizeBox([
191
+ 'tests:',
192
+ 'asserts:',
193
+ ' passed:',
194
+ ' failed:',
195
+ ' skipped:',
196
+ ' todo:',
197
+ 'time:'
198
+ ]),
199
+ box2
200
+ );
165
201
 
166
202
  box2[0] = this.brightWhite(box2[0]);
167
203
  // box2[1] = this.brightYellow(box2[1]);
@@ -205,7 +241,7 @@ class TTYReporter {
205
241
  event.skip && ++this.skippedAsserts;
206
242
  event.todo && ++this.todoAsserts;
207
243
  if (!isFailed && this.failureOnly) break;
208
- text = (event.fail ? '✗' : '✓');
244
+ text = event.fail ? '✗' : '✓';
209
245
  if (this.showAssertNumber) {
210
246
  text += ' ' + (this.renumberAsserts ? ++this.assertCounter : event.id);
211
247
  }
@@ -239,14 +275,17 @@ class TTYReporter {
239
275
  if (event.hasOwnProperty('actual')) {
240
276
  this.out(this.lowWhite(' actual: ') + this.formatValue(event.actual));
241
277
  }
242
- const stack = event.actual && event.actual.type === 'Error' && typeof event.actual.stack == 'string' ? event.actual.stack : event.marker.stack;
278
+ const stack =
279
+ event.actual && event.actual.type === 'Error' && typeof event.actual.stack == 'string'
280
+ ? event.actual.stack
281
+ : event.marker.stack;
243
282
  if (typeof stack == 'string') {
244
283
  this.out(this.lowWhite(' stack: |-'));
245
284
  stack.split('\n').forEach(line => this.out(this.lowWhite(' ' + line)));
246
285
  }
247
286
  break;
248
287
  }
249
- this.showScore();
288
+ this.output.isTTY && this.showScore();
250
289
  }
251
290
  showScore() {
252
291
  this.out(
@@ -0,0 +1,63 @@
1
+ import {StopTest} from '../State.js';
2
+ import EventServer from '../utils/EventServer.js';
3
+
4
+ const utilName = new URL('../test.js', import.meta.url),
5
+ baseName = new URL('../../', import.meta.url);
6
+
7
+ export default class TestWorker extends EventServer {
8
+ constructor(reporter, numberOfTasks = navigator.hardwareConcurrency, options) {
9
+ super(reporter, numberOfTasks, options);
10
+ this.counter = 0;
11
+ this.idToWorker = {};
12
+ }
13
+ makeTask(fileName) {
14
+ const testName = new URL(fileName, baseName),
15
+ id = String(++this.counter),
16
+ worker = new Worker(new URL('./worker.js', import.meta.url), {
17
+ type: 'module'
18
+ });
19
+ this.idToWorker[id] = worker;
20
+ worker.addEventListener('message', event => {
21
+ const msg = event.data;
22
+ try {
23
+ this.report(id, msg);
24
+ } catch (error) {
25
+ if (error instanceof StopTest) {
26
+ console.error('# immediate StopTest:', error.message || 'StopTest is activated');
27
+ worker.terminate();
28
+ process.exit(1);
29
+ }
30
+ throw error;
31
+ }
32
+ if (msg.type === 'end' && msg.test === 0) {
33
+ this.close(id);
34
+ return;
35
+ }
36
+ });
37
+ worker.addEventListener('error', error => {
38
+ this.report(id, {
39
+ type: 'comment',
40
+ name: 'fail to load: ' + (error.message || 'Worker error`'),
41
+ test: 0
42
+ });
43
+ this.close(id);
44
+ });
45
+ worker.addEventListener('messageerror', error => {
46
+ this.report(id, {
47
+ type: 'comment',
48
+ name: 'fail to load: ' + (error.message || 'Worker error`'),
49
+ test: 0
50
+ });
51
+ this.close(id);
52
+ });
53
+ worker.postMessage({testName: testName.href, utilName: utilName.href});
54
+ return id;
55
+ }
56
+ destroyTask(id) {
57
+ const worker = this.idToWorker[id];
58
+ if (worker) {
59
+ worker.terminate();
60
+ delete this.idToWorker[id];
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,21 @@
1
+ const sanitizeMsg = msg => {
2
+ if (msg.type !== 'end') return msg;
3
+ const state = {};
4
+ for (const [key, value] of Object.entries(msg.data)) {
5
+ if (typeof value != 'number') continue;
6
+ state[key] = value;
7
+ }
8
+ return {...msg, data: state};
9
+ };
10
+
11
+ addEventListener('message', async event => {
12
+ const msg = event.data;
13
+ try {
14
+ const {setReporter} = await import(msg.utilName);
15
+ setReporter(msg => postMessage(sanitizeMsg(msg)));
16
+ await import(msg.testName);
17
+ } catch (error) {
18
+ postMessage({type: 'comment', name: 'fail to load: ' + error.message, test: 0});
19
+ close();
20
+ }
21
+ });
@@ -0,0 +1,82 @@
1
+ import {StopTest} from '../State.js';
2
+ import EventServer from '../utils/EventServer.js';
3
+
4
+ const utilName = new URL('../test.js', import.meta.url),
5
+ baseName = new URL('../../', import.meta.url);
6
+
7
+ export default class TestWorker extends EventServer {
8
+ constructor(reporter, numberOfTasks = navigator.hardwareConcurrency, options) {
9
+ super(reporter, numberOfTasks, options);
10
+ this.counter = 0;
11
+ this.idToWorker = {};
12
+ }
13
+ makeTask(fileName) {
14
+ const testName = new URL(fileName, baseName),
15
+ id = String(++this.counter),
16
+ worker = new Worker(
17
+ new URL(
18
+ 'data:text/javascript;charset=utf-8,' +
19
+ encodeURIComponent(`
20
+ import {setReporter} from ${JSON.stringify(utilName.href)};
21
+
22
+ const sanitizeMsg = msg => {
23
+ if (msg.type !== 'end') return msg;
24
+ const state = {};
25
+ for (const [key, value] of Object.entries(msg.data)) {
26
+ if (typeof value != 'number') continue;
27
+ state[key] = value;
28
+ }
29
+ return {...msg, data: state};
30
+ };
31
+
32
+ setReporter(msg => postMessage(sanitizeMsg(msg)));
33
+
34
+ try {
35
+ await import(${JSON.stringify(testName.href)});
36
+ } catch (error) {
37
+ postMessage({type: 'comment', name: 'fail to load: ' + error.message, test: 0});
38
+ close();
39
+ }
40
+ `)
41
+ ),
42
+ {
43
+ type: 'module'
44
+ // deno: {permissions: 'inherit'}
45
+ }
46
+ );
47
+ this.idToWorker[id] = worker;
48
+ worker.addEventListener('message', event => {
49
+ const msg = event.data;
50
+ try {
51
+ this.report(id, msg);
52
+ } catch (error) {
53
+ if (error instanceof StopTest) {
54
+ console.error('# immediate StopTest:', error.message || 'StopTest is activated');
55
+ worker.terminate();
56
+ Deno.exit(1);
57
+ }
58
+ throw error;
59
+ }
60
+ if (msg.type === 'end' && msg.test === 0) {
61
+ this.close(id);
62
+ return;
63
+ }
64
+ });
65
+ worker.addEventListener('error', error => {
66
+ this.report(id, {
67
+ type: 'comment',
68
+ name: 'fail to load: ' + (error.message || 'Worker error`'),
69
+ test: 0
70
+ });
71
+ this.close(id);
72
+ });
73
+ return id;
74
+ }
75
+ destroyTask(id) {
76
+ const worker = this.idToWorker[id];
77
+ if (worker) {
78
+ worker.terminate();
79
+ delete this.idToWorker[id];
80
+ }
81
+ }
82
+ }
@@ -1,6 +1,8 @@
1
- import cluster from 'cluster';
2
- import os from 'os';
1
+ import cluster from 'node:cluster';
2
+ import os from 'node:os';
3
+ import process from 'node:process';
3
4
 
5
+ import {StopTest} from '../State.js';
4
6
  import EventServer from '../utils/EventServer.js';
5
7
 
6
8
  export default class TestWorker extends EventServer {
@@ -13,14 +15,22 @@ export default class TestWorker extends EventServer {
13
15
  worker.on('message', msg => {
14
16
  if (msg.started) {
15
17
  worker.send({id, fileName, options: this.options});
16
- } else {
17
- let done = false;
18
- msg.events.forEach(event => {
19
- this.report(id, event);
20
- if (event.type === 'end' && event.test === 0) done = true;
21
- });
22
- worker.send(done? {done: true} : {received: true});
18
+ return;
23
19
  }
20
+ let done = false;
21
+ msg.events.forEach(event => {
22
+ try {
23
+ this.report(id, event);
24
+ } catch (error) {
25
+ if (error instanceof StopTest) {
26
+ console.error('# immediate StopTest:', error.message || 'StopTest is activated');
27
+ process.exit(1);
28
+ }
29
+ throw error;
30
+ }
31
+ if (event.type === 'end' && event.test === 0) done = true;
32
+ });
33
+ worker.send(done ? {done: true} : {received: true});
24
34
  });
25
35
  worker.on('exit', (code, signal) => {
26
36
  let errorMsg = '';
@@ -1,5 +1,5 @@
1
- import {promises as fsp} from 'fs';
2
- import path from 'path';
1
+ import {promises as fsp} from 'node:fs';
2
+ import path from 'node:path';
3
3
 
4
4
  import {listing, wildToRe} from './listing.js';
5
5
  import {union, exclude} from '../utils/fileSets.js';
@@ -8,7 +8,7 @@ export const resolvePatterns = async (rootFolder, patterns) => {
8
8
  let result = [];
9
9
  for (const item of patterns) {
10
10
  if (item.length && item[0] == '!') {
11
- result = exclude(result, wildToRe(rootFolder, item.substr(1)));
11
+ result = exclude(result, wildToRe(rootFolder, item.substring(1)));
12
12
  } else {
13
13
  result = union(result, await listing(rootFolder, item));
14
14
  }
@@ -1,5 +1,5 @@
1
- import {promises as fsp} from 'fs';
2
- import path from 'path';
1
+ import {promises as fsp} from 'node:fs';
2
+ import path from 'node:path';
3
3
 
4
4
  const notSep = '[^\\' + path.sep + ']*',
5
5
  notDotSep = '[^\\.\\' + path.sep + ']*';
package/src/test.js CHANGED
@@ -164,9 +164,9 @@ Tester.prototype.todo = async function todo(name, options, testFn) {
164
164
  options = processArgs(name, options, testFn);
165
165
  if (this.state.skip) {
166
166
  this.comment('SKIP test: ' + options.name);
167
- } else {
168
- await runTests(this.state, [{options: {...options, todo: true}}]);
167
+ return;
169
168
  }
169
+ await runTests(this.state, [{options: {...options, todo: true}}]);
170
170
  };
171
171
 
172
172
  Tester.prototype.asPromise = async function asPromise(name, options, testFn) {
package/src/utils/box.js CHANGED
@@ -24,7 +24,7 @@ export const normalizeBox = (strings, symbol = ' ', align = 'right') => {
24
24
  return padding + s;
25
25
  case 'center':
26
26
  const half = padding.length >> 1;
27
- return padding.substr(0, half) + s + padding.substr(half);
27
+ return padding.substring(0, half) + s + padding.substring(half);
28
28
  }
29
29
  return s + padding;
30
30
  });
@@ -1,32 +1,57 @@
1
- let deferImplementation;
1
+ let deferImplementation = globalThis.setTimeout;
2
2
 
3
3
  // initialize the variable
4
- if (typeof setImmediate == 'function') {
5
- deferImplementation = setImmediate;
6
- } else if (typeof window == 'object' && typeof window.postMessage == 'function' && typeof window.addEventListener == 'function') {
7
- let buffer = [];
8
- window.addEventListener(
9
- 'message',
10
- evt => {
11
- const src = evt.source;
12
- if ((src === window || src === null) && evt.data === 'tape6-process-tick') {
13
- evt.stopPropagation();
14
- if (buffer.length) {
15
- const tasks = buffer.slice(0);
16
- buffer = [];
17
- tasks.forEach(fn => fn());
4
+ do {
5
+ // The selection below doesn't work
6
+ // if (typeof queueMicrotask == 'function') {
7
+ // deferImplementation = globalThis.queueMicrotask;
8
+ // console.log('\nSelected: queueMicrotask()\n');
9
+ // break;
10
+ // }
11
+
12
+ // The selection below doesn't work in Bun
13
+ // if (typeof process == 'object' && typeof process?.nextTick == 'function') {
14
+ // deferImplementation = process.nextTick;
15
+ // break;
16
+ // }
17
+
18
+ if (typeof setImmediate == 'function') {
19
+ deferImplementation = setImmediate;
20
+ break;
21
+ }
22
+
23
+ if (typeof window != 'object') break;
24
+ // below are browser-only implementations
25
+
26
+ if (typeof window.requestIdleCallback == 'function') {
27
+ deferImplementation = window.requestIdleCallback;
28
+ break;
29
+ }
30
+
31
+ if (typeof window.postMessage == 'function' && typeof window.addEventListener == 'function') {
32
+ let buffer = [];
33
+ window.addEventListener(
34
+ 'message',
35
+ evt => {
36
+ const src = evt.source;
37
+ if ((src === window || src === null) && evt.data === 'tape6-process-tick') {
38
+ evt.stopPropagation();
39
+ if (buffer.length) {
40
+ const tasks = buffer.slice(0);
41
+ buffer = [];
42
+ tasks.forEach(fn => fn());
43
+ }
18
44
  }
19
- }
20
- },
21
- true
22
- );
23
- deferImplementation = fn => {
24
- buffer.push(fn);
25
- window.postMessage('tape6-process-tick', '*');
26
- };
27
- } else {
28
- deferImplementation = setTimeout;
29
- }
45
+ },
46
+ true
47
+ );
48
+ deferImplementation = fn => {
49
+ buffer.push(fn);
50
+ window.postMessage('tape6-process-tick', '*');
51
+ };
52
+ break;
53
+ }
54
+ } while (false);
30
55
 
31
56
  const defer = fn => deferImplementation(fn);
32
57
 
@@ -4,8 +4,8 @@ export const formatNumber = (n, precision = 0) => {
4
4
  if (!f && i.length <= 3) return n < 0 ? '-' + s : s;
5
5
  const parts = [];
6
6
  let start = i.length % 3;
7
- start && parts.push(i.substr(0, start));
8
- for (; start < i.length; start += 3) parts.push(i.substr(start, 3));
7
+ start && parts.push(i.substring(0, start));
8
+ for (; start < i.length; start += 3) parts.push(i.substring(start, start + 3));
9
9
  let result = parts.join(',');
10
10
  f && !/^0*$/.test(f) && (result += '.' + f.replace(/0+$/, ''));
11
11
  return n < 0 ? '-' + result : result;
@@ -13,7 +13,7 @@ export const selectTimer = async () => {
13
13
  if (typeof process == 'object' && typeof process.exit == 'function') {
14
14
  // Node
15
15
  try {
16
- const {performance} = await import('perf_hooks');
16
+ const {performance} = await import('node:perf_hooks');
17
17
  setTimer(performance);
18
18
  return;
19
19
  } catch (error) {
@@ -27,8 +27,10 @@ export default class TestWorker extends EventServer {
27
27
  iframe.contentWindow.document.open();
28
28
  iframe.contentWindow.document.write(`
29
29
  <!doctype html>
30
- <html>
30
+ <html lang="en">
31
31
  <head>
32
+ <meta charset="utf-8" />
33
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
32
34
  <title>Test IFRAME</title>
33
35
  <script type="module">
34
36
  window.__tape6_id = ${JSON.stringify(id)};
package/webApp/index.html CHANGED
@@ -1,8 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
- <title>Tape-six test runner</title>
4
+ <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Tape-six test runner</title>
6
7
  <link rel="stylesheet" href="./style.css" />
7
8
  <link rel="stylesheet" href="./tape6-donut.css" />
8
9
  <script type="module" src="./tape6-donut.js"></script>
@@ -57,10 +58,18 @@
57
58
  </div>
58
59
 
59
60
  <form class="tile tools">
60
- Show:
61
- &nbsp;&nbsp;<label class="tape6-toggle"><input type="checkbox" name="failed-only" value="0" /><span class="toggle"></span>&nbsp;failed only</label>
62
- &nbsp;&nbsp;<label class="tape6-toggle"><input type="checkbox" name="show-data" value="0" /><span class="toggle"></span>&nbsp;data</label>
63
- &nbsp;&nbsp;<label class="tape6-toggle"><input type="checkbox" name="show-stack" value="0" /><span class="toggle"></span>&nbsp;stack</label>
61
+ Show: &nbsp;&nbsp;<label class="tape6-toggle"
62
+ ><input type="checkbox" name="failed-only" value="0" /><span class="toggle"></span
63
+ >&nbsp;failed only</label
64
+ >
65
+ &nbsp;&nbsp;<label class="tape6-toggle"
66
+ ><input type="checkbox" name="show-data" value="0" /><span class="toggle"></span
67
+ >&nbsp;data</label
68
+ >
69
+ &nbsp;&nbsp;<label class="tape6-toggle"
70
+ ><input type="checkbox" name="show-stack" value="0" /><span class="toggle"></span
71
+ >&nbsp;stack</label
72
+ >
64
73
  </form>
65
74
 
66
75
  <div class="tile report collapse-passed"></div>
package/webApp/index.js CHANGED
@@ -17,7 +17,7 @@ let flags = '',
17
17
  patterns = [];
18
18
 
19
19
  if (window.location.search) {
20
- const searchParams = new URLSearchParams(window.location.search.substr(1));
20
+ const searchParams = new URLSearchParams(window.location.search.substring(1));
21
21
  flags = searchParams.get('flags') || '';
22
22
  parallel = searchParams.get('par') || '';
23
23
  if (parallel && !isNaN(parallel)) {
@@ -108,7 +108,7 @@ window.addEventListener('DOMContentLoaded', () => {
108
108
  });
109
109
 
110
110
  if (typeof window.__tape6_reportResults == 'function') {
111
- window.__tape6_reportResults(rootState.failed > 0 ? 'failure' : 'success');
111
+ await window.__tape6_reportResults(rootState.failed > 0 ? 'failure' : 'success');
112
112
  }
113
113
  });
114
114
  });