1'use strict';
2
3require('../common');
4const fixtures = require('../common/fixtures');
5const tmpdir = require('../common/tmpdir');
6const { describe, it } = require('node:test');
7const { spawnSync } = require('node:child_process');
8const assert = require('node:assert');
9const path = require('node:path');
10const fs = require('node:fs');
11
12const testFile = fixtures.path('test-runner/reporters.js');
13tmpdir.refresh();
14
15let tmpFiles = 0;
16describe('node:test reporters', { concurrency: true }, () => {
17  it('should default to outputing TAP to stdout', async () => {
18    const child = spawnSync(process.execPath, ['--test', testFile]);
19    assert.strictEqual(child.stderr.toString(), '');
20    assert.match(child.stdout.toString(), /TAP version 13/);
21    assert.match(child.stdout.toString(), /ok 1 - ok/);
22    assert.match(child.stdout.toString(), /not ok 2 - failing/);
23    assert.match(child.stdout.toString(), /ok 2 - top level/);
24  });
25
26  it('should default destination to stdout when passing a single reporter', async () => {
27    const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', testFile]);
28    assert.strictEqual(child.stderr.toString(), '');
29    assert.strictEqual(child.stdout.toString(), '.XX.\n');
30  });
31
32  it('should throw when passing reporters without a destination', async () => {
33    const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', '--test-reporter', 'tap', testFile]);
34    assert.match(child.stderr.toString(), /The argument '--test-reporter' must match the number of specified '--test-reporter-destination'\. Received \[ 'dot', 'tap' \]/);
35    assert.strictEqual(child.stdout.toString(), '');
36  });
37
38  it('should throw when passing a destination without a reporter', async () => {
39    const child = spawnSync(process.execPath, ['--test', '--test-reporter-destination', 'tap', testFile]);
40    assert.match(child.stderr.toString(), /The argument '--test-reporter' must match the number of specified '--test-reporter-destination'\. Received \[\]/);
41    assert.strictEqual(child.stdout.toString(), '');
42  });
43
44  it('should support stdout as a destination', async () => {
45    const child = spawnSync(process.execPath,
46                            ['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stdout', testFile]);
47    assert.strictEqual(child.stderr.toString(), '');
48    assert.strictEqual(child.stdout.toString(), '.XX.\n');
49  });
50
51  it('should support stderr as a destination', async () => {
52    const child = spawnSync(process.execPath,
53                            ['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stderr', testFile]);
54    assert.strictEqual(child.stderr.toString(), '.XX.\n');
55    assert.strictEqual(child.stdout.toString(), '');
56  });
57
58  it('should support a file as a destination', async () => {
59    const file = path.join(tmpdir.path, `${tmpFiles++}.out`);
60    const child = spawnSync(process.execPath,
61                            ['--test', '--test-reporter', 'dot', '--test-reporter-destination', file, testFile]);
62    assert.strictEqual(child.stderr.toString(), '');
63    assert.strictEqual(child.stdout.toString(), '');
64    assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n');
65  });
66
67  it('should disallow using v8-serializer as reporter', async () => {
68    const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'v8-serializer', testFile]);
69    assert.strictEqual(child.stdout.toString(), '');
70    assert(child.status > 0);
71    assert.match(child.stderr.toString(), /ERR_MODULE_NOT_FOUND/);
72  });
73
74  it('should support multiple reporters', async () => {
75    const file = path.join(tmpdir.path, `${tmpFiles++}.out`);
76    const file2 = path.join(tmpdir.path, `${tmpFiles++}.out`);
77    const child = spawnSync(process.execPath,
78                            ['--test',
79                             '--test-reporter', 'dot', '--test-reporter-destination', file,
80                             '--test-reporter', 'spec', '--test-reporter-destination', file2,
81                             '--test-reporter', 'tap', '--test-reporter-destination', 'stdout',
82                             testFile]);
83    assert.match(child.stdout.toString(), /TAP version 13/);
84    assert.match(child.stdout.toString(), /# duration_ms/);
85    assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n');
86    const file2Contents = fs.readFileSync(file2, 'utf8');
87    assert.match(file2Contents, /▶ nested/);
88    assert.match(file2Contents, /✔ ok/);
89    assert.match(file2Contents, /✖ failing/);
90  });
91
92  ['js', 'cjs', 'mjs'].forEach((ext) => {
93    it(`should support a '${ext}' file as a custom reporter`, async () => {
94      const filename = `custom.${ext}`;
95      const child = spawnSync(process.execPath,
96                              ['--test', '--test-reporter', fixtures.fileURL('test-runner/custom_reporters/', filename),
97                               testFile]);
98      assert.strictEqual(child.stderr.toString(), '');
99      const stdout = child.stdout.toString();
100      assert.match(stdout, /{"test:enqueue":5,"test:dequeue":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/);
101      assert.strictEqual(stdout.slice(0, filename.length + 2), `${filename} {`);
102    });
103  });
104
105  it('should support a custom reporter from node_modules', async () => {
106    const child = spawnSync(process.execPath,
107                            ['--test', '--test-reporter', 'reporter-cjs', 'reporters.js'],
108                            { cwd: fixtures.path('test-runner') });
109    assert.strictEqual(child.stderr.toString(), '');
110    assert.match(
111      child.stdout.toString(),
112      /^package: reporter-cjs{"test:enqueue":5,"test:dequeue":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
113    );
114  });
115
116  it('should support a custom ESM reporter from node_modules', async () => {
117    const child = spawnSync(process.execPath,
118                            ['--test', '--test-reporter', 'reporter-esm', 'reporters.js'],
119                            { cwd: fixtures.path('test-runner') });
120    assert.strictEqual(child.stderr.toString(), '');
121    assert.match(
122      child.stdout.toString(),
123      /^package: reporter-esm{"test:enqueue":5,"test:dequeue":5,"test:start":4,"test:pass":2,"test:fail":2,"test:plan":2,"test:diagnostic":\d+}$/,
124    );
125  });
126
127  it('should throw when reporter setup throws asynchronously', async () => {
128    const child = spawnSync(
129      process.execPath,
130      ['--test', '--test-reporter', fixtures.fileURL('empty.js'), 'reporters.js'],
131      { cwd: fixtures.path('test-runner') }
132    );
133    assert.strictEqual(child.status, 7);
134    assert.strictEqual(child.signal, null);
135    assert.strictEqual(child.stdout.toString(), '');
136    assert.match(child.stderr.toString(), /ERR_INVALID_ARG_TYPE/);
137  });
138
139  it('should throw when reporter errors', async () => {
140    const child = spawnSync(process.execPath,
141                            ['--test', '--test-reporter', fixtures.fileURL('test-runner/custom_reporters/throwing.js'),
142                             fixtures.path('test-runner/default-behavior/index.test.js')]);
143    assert.strictEqual(child.status, 7);
144    assert.strictEqual(child.signal, null);
145    assert.strictEqual(child.stdout.toString(), 'Going to throw an error\n');
146    assert.match(child.stderr.toString(), /Error: Reporting error\r?\n\s+at customReporter/);
147  });
148
149  it('should throw when reporter errors asynchronously', async () => {
150    const child = spawnSync(process.execPath,
151                            ['--test', '--test-reporter',
152                             fixtures.fileURL('test-runner/custom_reporters/throwing-async.js'),
153                             fixtures.path('test-runner/default-behavior/index.test.js')]);
154    assert.strictEqual(child.status, 7);
155    assert.strictEqual(child.signal, null);
156    assert.strictEqual(child.stdout.toString(), 'Going to throw an error\n');
157    assert.match(child.stderr.toString(), /Emitted 'error' event on Duplex instance/);
158  });
159});
160