tape-six 1.2.0 → 1.3.1

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
@@ -236,6 +236,8 @@ See [set-up tests](https://github.com/uhop/tape-six/wiki/Set-up-tests) for detai
236
236
 
237
237
  The most recent releases:
238
238
 
239
+ - 1.3.1 _Bugfix for web browser using JSONL reporter._
240
+ - 1.3.0 _Bugfixes, updated dependencies, new feature: proxied console calls._
239
241
  - 1.2.0 _Updated dependencies + added an optional prefix for JSON lines._
240
242
  - 1.1.2 _Updated dependencies._
241
243
  - 1.1.1 _Technical re-release with the missing `index.d.ts` file._
package/bin/tape6-bun.js CHANGED
@@ -41,7 +41,8 @@ const config = () => {
41
41
  d: 'showData',
42
42
  o: 'failOnce',
43
43
  n: 'showAssertNumber',
44
- m: 'monochrome'
44
+ m: 'monochrome',
45
+ c: 'dontCaptureConsole'
45
46
  };
46
47
 
47
48
  let flagIsSet = false,
package/bin/tape6-deno.js CHANGED
@@ -41,7 +41,8 @@ const config = () => {
41
41
  d: 'showData',
42
42
  o: 'failOnce',
43
43
  n: 'showAssertNumber',
44
- m: 'monochrome'
44
+ m: 'monochrome',
45
+ c: 'dontCaptureConsole'
45
46
  };
46
47
 
47
48
  let flagIsSet = false,
package/bin/tape6-node.js CHANGED
@@ -42,7 +42,8 @@ const config = () => {
42
42
  d: 'showData',
43
43
  o: 'failOnce',
44
44
  n: 'showAssertNumber',
45
- m: 'monochrome'
45
+ m: 'monochrome',
46
+ c: 'dontCaptureConsole'
46
47
  };
47
48
 
48
49
  let flagIsSet = false,
package/index.js CHANGED
@@ -22,7 +22,8 @@ const optionNames = {
22
22
  o: 'failOnce',
23
23
  n: 'showAssertNumber',
24
24
  m: 'monochrome',
25
- j: 'useJsonL'
25
+ j: 'useJsonL',
26
+ c: 'dontCaptureConsole'
26
27
  };
27
28
 
28
29
  const init = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tape-six",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "TAP the test harness for the modern JavaScript (ES6).",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -71,8 +71,8 @@
71
71
  }
72
72
  },
73
73
  "devDependencies": {
74
- "@types/node": "^25.0.5",
75
- "puppeteer": "^24.34.0",
74
+ "@types/node": "^25.0.8",
75
+ "puppeteer": "^24.35.0",
76
76
  "typescript": "^5.9.3"
77
77
  }
78
78
  }
@@ -1,22 +1,29 @@
1
- import {getEnv} from './utils/config.js';
1
+ const getEnv = () => {
2
+ if (typeof Deno == 'object' && Deno?.version) {
3
+ return Deno.env;
4
+ } else if (typeof Bun == 'object' && Bun?.version) {
5
+ return Bun.env;
6
+ } else if (typeof process == 'object' && process?.versions?.node) {
7
+ return process.env;
8
+ }
9
+ return {};
10
+ };
2
11
 
3
- class JSONLReporter {
4
- constructor({renumberAsserts = false} = {}) {
12
+ export class JSONLReporter {
13
+ constructor({renumberAsserts = false, prefix} = {}) {
5
14
  this.renumberAsserts = renumberAsserts;
6
15
  this.assertCounter = 0;
7
16
 
8
- const env = getEnv({});
9
- this.prefix = env.TAPE6_JSONL_PREFIX || '';
17
+ const env = getEnv();
18
+ prefix ||= env.TAPE6_JSONL_PREFIX;
19
+ this.prefix = prefix ? '\n' + prefix : '';
10
20
  }
11
21
  report(event) {
12
22
  if (event.type === 'assert' && this.renumberAsserts) {
13
23
  event = {...event, id: ++this.assertCounter};
14
24
  }
15
- if (this.prefix) {
16
- console.log('\n' + this.prefix + JSON.stringify(event));
17
- } else {
18
- console.log(JSON.stringify(event));
19
- }
25
+ const jsonEvent = JSON.stringify(event);
26
+ console.log(this.prefix ? this.prefix + jsonEvent : jsonEvent);
20
27
  }
21
28
  }
22
29
 
@@ -24,7 +24,7 @@ const successStyle = `\x1B[48;5;${buildColor(0, 32, 0)};1;97m`,
24
24
 
25
25
  // main
26
26
 
27
- class TTYReporter {
27
+ export class TTYReporter {
28
28
  constructor({
29
29
  output = process.stdout,
30
30
  renumberAsserts = false,
@@ -33,7 +33,8 @@ class TTYReporter {
33
33
  showTime = true,
34
34
  showData = true,
35
35
  showAssertNumber = false,
36
- hasColors = true
36
+ hasColors = true,
37
+ dontCaptureConsole = false
37
38
  } = {}) {
38
39
  this.output = output;
39
40
  this.hasColors =
@@ -75,6 +76,8 @@ class TTYReporter {
75
76
  this.success = this.paint(successStyle, reset);
76
77
  this.failure = this.paint(failureStyle, reset);
77
78
  this.skipped = this.paint(skippedStyle, reset);
79
+ this.stdoutPaint = this.paint('\x1B[90m');
80
+ this.stderrPaint = this.paint('\x1B[31m');
78
81
 
79
82
  // watching for console output
80
83
 
@@ -82,31 +85,27 @@ class TTYReporter {
82
85
  this.consoleLastNewLine = false;
83
86
  this.consoleSkipChecks = false;
84
87
 
85
- while (this.output.isTTY) {
88
+ while (!dontCaptureConsole && this.output.isTTY) {
86
89
  this.out('');
87
90
 
88
91
  const isCurrentTTY = this.output === process.stdout || this.output === process.stderr;
89
92
  if (!isCurrentTTY) break;
90
93
 
91
- const oldStdoutWrite = process.stdout.write;
92
- process.stdout.write = process.stderr.write = (chunk, ...args) => {
93
- if (!this.consoleSkipChecks && chunk) {
94
- this.consoleWasUsed = true;
95
- const text = chunk.toString();
96
- this.consoleLastNewLine = text[text.length - 1] === '\n';
94
+ const console = globalThis.console,
95
+ self = this;
96
+ globalThis.console = new Proxy(console, {
97
+ get(target, property, receiver) {
98
+ const prop = Reflect.get(target, property, receiver);
99
+ if (typeof prop !== 'function') return prop;
100
+ return (...args) => {
101
+ if (!self.consoleSkipChecks) {
102
+ self.consoleWasUsed = true;
103
+ self.consoleLastNewLine = true;
104
+ }
105
+ return prop.apply(receiver, args);
106
+ };
97
107
  }
98
- return oldStdoutWrite.call(process.stdout, chunk, ...args);
99
- };
100
-
101
- const oldStderrWrite = process.stderr.write;
102
- process.stderr.write = (chunk, ...args) => {
103
- if (!this.consoleSkipChecks && chunk) {
104
- this.consoleWasUsed = true;
105
- const text = chunk.toString();
106
- this.consoleLastNewLine = text[text.length - 1] === '\n';
107
- }
108
- return oldStderrWrite.call(process.stderr, chunk, ...args);
109
- };
108
+ });
110
109
 
111
110
  break;
112
111
  }
@@ -123,8 +122,8 @@ class TTYReporter {
123
122
  }
124
123
  return this.red(this.italic(JSON.stringify(value)));
125
124
  }
126
- out(text) {
127
- if (this.depth < 2 + this.technicalDepth) {
125
+ out(text, noIndent) {
126
+ if (this.depth < 2 + this.technicalDepth || noIndent) {
128
127
  this.output.write(text + '\n');
129
128
  } else {
130
129
  this.output.write(stringRep(this.depth - 1 - this.technicalDepth, ' ') + text + '\n');
@@ -273,6 +272,45 @@ class TTYReporter {
273
272
  case 'comment':
274
273
  !this.failureOnly && this.out(this.blue(this.italic(event.name || 'empty comment')));
275
274
  break;
275
+ case 'console-log':
276
+ case 'console-info':
277
+ case 'console-warn':
278
+ {
279
+ const lines = event.name.split(/\r?\n/),
280
+ type = /\-(\w+)$/.exec(event.type)[1],
281
+ prefix = this.stdoutPaint(type + ':') + ' ';
282
+ for (const line of lines) {
283
+ this.out(prefix + line, true);
284
+ }
285
+ }
286
+ break;
287
+ case 'console-error':
288
+ {
289
+ const lines = event.name.split(/\r?\n/),
290
+ prefix = this.stderrPaint('error:') + ' ';
291
+ for (const line of lines) {
292
+ this.out(prefix + line, true);
293
+ }
294
+ }
295
+ break;
296
+ case 'stdout':
297
+ {
298
+ const lines = event.name.split(/\r?\n/),
299
+ prefix = this.stdoutPaint('stdout:') + ' ';
300
+ for (const line of lines) {
301
+ this.out(prefix + line, true);
302
+ }
303
+ }
304
+ break;
305
+ case 'stderr':
306
+ {
307
+ const lines = event.name.split(/\r?\n/),
308
+ prefix = this.stderrPaint('stderr:') + ' ';
309
+ for (const line of lines) {
310
+ this.out(prefix + line, true);
311
+ }
312
+ }
313
+ break;
276
314
  case 'bail-out':
277
315
  {
278
316
  text = 'Bail out!';
@@ -15,7 +15,9 @@ const styles = {
15
15
  'font-weight: bold; color: white; background-color: green; padding: 0.5em 1em;',
16
16
  'summary-result-failure':
17
17
  'font-weight: bold; color: white; background-color: red; padding: 0.5em 1em;',
18
- 'bail-out': 'color: white; background-color: red; font-weight: bold; padding: 0.5em 1em;'
18
+ 'bail-out': 'color: white; background-color: red; font-weight: bold; padding: 0.5em 1em;',
19
+ stdout: 'color: gray; font-style: italic;',
20
+ stderr: 'color: darkred; font-style: italic;'
19
21
  };
20
22
 
21
23
  const logger = (text, style) => {
@@ -36,7 +38,7 @@ const formatValue = value => {
36
38
  return JSON.stringify(value);
37
39
  };
38
40
 
39
- class TapReporter {
41
+ export class TapReporter {
40
42
  constructor({write, useJson = false, renumberAsserts = false, hasColors = true} = {}) {
41
43
  this.write = write || (hasColors ? logger : console.log.bind(console));
42
44
  this.renumberAsserts = renumberAsserts;
@@ -63,6 +65,24 @@ class TapReporter {
63
65
  this.open();
64
66
  this.write('# ' + event.name);
65
67
  break;
68
+ case 'console-log':
69
+ case 'console-info':
70
+ case 'console-warn':
71
+ this.open();
72
+ this.write('# ' + /\-(\w+)$/.exec(event.type)[1] + ': ' + event.name, 'stdout');
73
+ break;
74
+ case 'console-error':
75
+ this.open();
76
+ this.write('# error: ' + event.name, 'stderr');
77
+ break;
78
+ case 'stdout':
79
+ this.open();
80
+ this.write('# stdout: ' + event.name, 'stdout');
81
+ break;
82
+ case 'stderr':
83
+ this.open();
84
+ this.write('# stderr: ' + event.name, 'stderr');
85
+ break;
66
86
  case 'end':
67
87
  --this.depth;
68
88
  event.name &&
@@ -3,7 +3,7 @@ import {sep} from 'node:path';
3
3
  import {StopTest} from '../State.js';
4
4
  import EventServer from '../utils/EventServer.js';
5
5
 
6
- const utilName = new URL('../test.js', import.meta.url),
6
+ const srcName = new URL('../', import.meta.url),
7
7
  baseName = Bun.pathToFileURL(Bun.cwd + sep);
8
8
 
9
9
  export default class TestWorker extends EventServer {
@@ -47,7 +47,7 @@ export default class TestWorker extends EventServer {
47
47
  });
48
48
  this.close(id);
49
49
  });
50
- worker.postMessage({testName: testName.href, utilName: utilName.href});
50
+ worker.postMessage({testName: testName.href, srcName: srcName.href, options: this.options});
51
51
  return id;
52
52
  }
53
53
  destroyTask(id) {
package/src/bun/worker.js CHANGED
@@ -1,3 +1,5 @@
1
+ import {format} from 'node:util';
2
+
1
3
  const sanitizeMsg = msg => {
2
4
  if (msg.type !== 'end') return msg;
3
5
  const state = {};
@@ -8,11 +10,32 @@ const sanitizeMsg = msg => {
8
10
  return {...msg, data: state};
9
11
  };
10
12
 
13
+ const consoleVerbs = {log: 1, info: 1, warn: 1, error: 1};
14
+
11
15
  addEventListener('message', async event => {
12
16
  const msg = event.data;
13
17
  try {
14
- const {setReporter} = await import(msg.utilName);
18
+ const {setReporter} = await import(new URL('test.js', msg.srcName));
15
19
  setReporter(msg => postMessage(sanitizeMsg(msg)));
20
+
21
+ if (!msg.options.dontCaptureConsole) {
22
+ const console = globalThis.console;
23
+ globalThis.console = new Proxy(console, {
24
+ get(target, property, receiver) {
25
+ const prop = Reflect.get(target, property, receiver);
26
+ if (typeof prop === 'function') {
27
+ if (consoleVerbs[property] === 1) {
28
+ const type = 'console-' + property;
29
+ return (...args) => {
30
+ postMessage({type, name: format(...args)});
31
+ };
32
+ }
33
+ }
34
+ return prop;
35
+ }
36
+ });
37
+ }
38
+
16
39
  await import(msg.testName);
17
40
  } catch (error) {
18
41
  postMessage({type: 'test', test: 0, time: 0});
@@ -4,7 +4,7 @@ import {sep} from 'node:path';
4
4
  import {StopTest} from '../State.js';
5
5
  import EventServer from '../utils/EventServer.js';
6
6
 
7
- const utilName = new URL('../test.js', import.meta.url),
7
+ const srcName = new URL('../', import.meta.url),
8
8
  baseName = pathToFileURL(Deno.cwd() + sep);
9
9
 
10
10
  export default class TestWorker extends EventServer {
@@ -41,7 +41,7 @@ export default class TestWorker extends EventServer {
41
41
  });
42
42
  this.close(id);
43
43
  });
44
- worker.postMessage({testName: testName.href, utilName: utilName.href});
44
+ worker.postMessage({testName: testName.href, srcName: srcName.href, options: this.options});
45
45
  return id;
46
46
  }
47
47
  destroyTask(id) {
@@ -1,3 +1,5 @@
1
+ import {format} from 'node:util';
2
+
1
3
  const sanitizeMsg = msg => {
2
4
  if (msg.type !== 'end') return msg;
3
5
  const state = {};
@@ -8,11 +10,32 @@ const sanitizeMsg = msg => {
8
10
  return {...msg, data: state};
9
11
  };
10
12
 
13
+ const consoleVerbs = {log: 1, info: 1, warn: 1, error: 1};
14
+
11
15
  addEventListener('message', async event => {
12
16
  const msg = event.data;
13
17
  try {
14
- const {setReporter} = await import(msg.utilName);
18
+ const {setReporter} = await import(new URL('test.js', msg.srcName));
15
19
  setReporter(msg => postMessage(sanitizeMsg(msg)));
20
+
21
+ if (!msg.options.dontCaptureConsole) {
22
+ const console = globalThis.console;
23
+ globalThis.console = new Proxy(console, {
24
+ get(target, property, receiver) {
25
+ const prop = Reflect.get(target, property, receiver);
26
+ if (typeof prop === 'function') {
27
+ if (consoleVerbs[property] === 1) {
28
+ const type = 'console-' + property;
29
+ return (...args) => {
30
+ postMessage({type, name: format(...args)});
31
+ };
32
+ }
33
+ }
34
+ return prop;
35
+ }
36
+ });
37
+ }
38
+
16
39
  await import(msg.testName);
17
40
  } catch (error) {
18
41
  postMessage({type: 'test', test: 0, time: 0});
@@ -6,7 +6,7 @@ import {Worker} from 'node:worker_threads';
6
6
  import {StopTest} from '../State.js';
7
7
  import EventServer from '../utils/EventServer.js';
8
8
 
9
- const utilName = new URL('../test.js', import.meta.url),
9
+ const srcName = new URL('../', import.meta.url),
10
10
  baseName = pathToFileURL(process.cwd() + sep);
11
11
 
12
12
  export default class TestWorker extends EventServer {
@@ -49,7 +49,7 @@ export default class TestWorker extends EventServer {
49
49
  });
50
50
  this.close(id);
51
51
  });
52
- worker.postMessage({testName: testName.href, utilName: utilName.href});
52
+ worker.postMessage({testName: testName.href, srcName: srcName.href, options: this.options});
53
53
  return id;
54
54
  }
55
55
  destroyTask(id) {
@@ -1,4 +1,5 @@
1
1
  import {parentPort} from 'node:worker_threads';
2
+ import {format} from 'node:util';
2
3
 
3
4
  const sanitizeMsg = msg => {
4
5
  if (msg.type !== 'end') return msg;
@@ -10,10 +11,31 @@ const sanitizeMsg = msg => {
10
11
  return {...msg, data: state};
11
12
  };
12
13
 
14
+ const consoleVerbs = {log: 1, info: 1, warn: 1, error: 1};
15
+
13
16
  parentPort.on('message', async msg => {
14
17
  try {
15
- const {setReporter} = await import(msg.utilName);
18
+ const {setReporter} = await import(new URL('test.js', msg.srcName));
16
19
  setReporter(msg => parentPort.postMessage(sanitizeMsg(msg)));
20
+
21
+ if (!msg.options.dontCaptureConsole) {
22
+ const console = globalThis.console;
23
+ globalThis.console = new Proxy(console, {
24
+ get(target, property, receiver) {
25
+ const prop = Reflect.get(target, property, receiver);
26
+ if (typeof prop === 'function') {
27
+ if (consoleVerbs[property] === 1) {
28
+ const type = 'console-' + property;
29
+ return (...args) => {
30
+ parentPort.postMessage({type, name: format(...args)});
31
+ };
32
+ }
33
+ }
34
+ return prop;
35
+ }
36
+ });
37
+ }
38
+
17
39
  await import(msg.testName);
18
40
  } catch (error) {
19
41
  parentPort.postMessage({type: 'test', test: 0, time: 0});
@@ -11,8 +11,10 @@ export default class EventServer {
11
11
  this.passThroughId = null;
12
12
  this.backlog = {};
13
13
  this.closed = {};
14
+ this.finalized = {};
14
15
  }
15
16
  report(id, event) {
17
+ if (this.finalized[id] === 1) return this.reporter(event);
16
18
  if (this.passThroughId === null) this.passThroughId = id;
17
19
  if (this.passThroughId === id) return this.reporter(event);
18
20
  const events = this.backlog[id];
@@ -24,6 +26,7 @@ export default class EventServer {
24
26
  }
25
27
  close(id) {
26
28
  this.destroyTask(id);
29
+ this.finalized[id] = 1;
27
30
  --this.totalTasks;
28
31
  if (this.fileQueue.length) {
29
32
  ++this.totalTasks;
@@ -54,7 +57,16 @@ export default class EventServer {
54
57
  } else {
55
58
  this.closed[id] = 1;
56
59
  }
57
- !this.totalTasks && this.done && this.done();
60
+ if (!this.totalTasks) {
61
+ Object.keys(this.backlog).forEach(id => {
62
+ this.finalized[id] = 1;
63
+ const events = this.backlog[id];
64
+ events.forEach(event => this.reporter(event));
65
+ });
66
+ this.closed = {};
67
+ this.backlog = {};
68
+ this.done && this.done();
69
+ }
58
70
  }
59
71
  createTask(fileName) {
60
72
  if (this.totalTasks < this.numberOfTasks) {