1import * as common from '../common/index.mjs'; 2import * as fixtures from '../common/fixtures.mjs'; 3import { join } from 'node:path'; 4import { describe, it, run } from 'node:test'; 5import { dot, spec, tap } from 'node:test/reporters'; 6import assert from 'node:assert'; 7 8const testFixtures = fixtures.path('test-runner'); 9 10describe('require(\'node:test\').run', { concurrency: true }, () => { 11 12 it('should run with no tests', async () => { 13 const stream = run({ files: [] }); 14 stream.on('test:fail', common.mustNotCall()); 15 stream.on('test:pass', common.mustNotCall()); 16 // eslint-disable-next-line no-unused-vars 17 for await (const _ of stream); 18 }); 19 20 it('should fail with non existing file', async () => { 21 const stream = run({ files: ['a-random-file-that-does-not-exist.js'] }); 22 stream.on('test:fail', common.mustCall(1)); 23 stream.on('test:pass', common.mustNotCall()); 24 // eslint-disable-next-line no-unused-vars 25 for await (const _ of stream); 26 }); 27 28 it('should succeed with a file', async () => { 29 const stream = run({ files: [join(testFixtures, 'test/random.cjs')] }); 30 stream.on('test:fail', common.mustNotCall()); 31 stream.on('test:pass', common.mustCall(1)); 32 // eslint-disable-next-line no-unused-vars 33 for await (const _ of stream); 34 }); 35 36 it('should run same file twice', async () => { 37 const stream = run({ files: [join(testFixtures, 'test/random.cjs'), join(testFixtures, 'test/random.cjs')] }); 38 stream.on('test:fail', common.mustNotCall()); 39 stream.on('test:pass', common.mustCall(2)); 40 // eslint-disable-next-line no-unused-vars 41 for await (const _ of stream); 42 }); 43 44 it('should run a failed test', async () => { 45 const stream = run({ files: [testFixtures] }); 46 stream.on('test:fail', common.mustCall(1)); 47 stream.on('test:pass', common.mustNotCall()); 48 // eslint-disable-next-line no-unused-vars 49 for await (const _ of stream); 50 }); 51 52 it('should support timeout', async () => { 53 const stream = run({ timeout: 50, files: [ 54 fixtures.path('test-runner', 'never_ending_sync.js'), 55 fixtures.path('test-runner', 'never_ending_async.js'), 56 ] }); 57 stream.on('test:fail', common.mustCall(2)); 58 stream.on('test:pass', common.mustNotCall()); 59 // eslint-disable-next-line no-unused-vars 60 for await (const _ of stream); 61 }); 62 63 it('should validate files', async () => { 64 [Symbol(), {}, () => {}, 0, 1, 0n, 1n, '', '1', Promise.resolve([]), true, false] 65 .forEach((files) => assert.throws(() => run({ files }), { 66 code: 'ERR_INVALID_ARG_TYPE' 67 })); 68 }); 69 70 it('should be piped with dot', async () => { 71 const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(dot).toArray(); 72 assert.deepStrictEqual(result, [ 73 '.', 74 '\n', 75 ]); 76 }); 77 78 it('should be piped with spec', async () => { 79 const specReporter = new spec(); 80 const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(specReporter).toArray(); 81 const stringResults = result.map((bfr) => bfr.toString()); 82 assert.match(stringResults[0], /this should pass/); 83 assert.match(stringResults[1], /tests 1/); 84 assert.match(stringResults[1], /pass 1/); 85 }); 86 87 it('should be piped with tap', async () => { 88 const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(tap).toArray(); 89 assert.strictEqual(result.length, 13); 90 assert.strictEqual(result[0], 'TAP version 13\n'); 91 assert.strictEqual(result[1], '# Subtest: this should pass\n'); 92 assert.strictEqual(result[2], 'ok 1 - this should pass\n'); 93 assert.match(result[3], /duration_ms: \d+\.?\d*/); 94 assert.strictEqual(result[4], '1..1\n'); 95 assert.strictEqual(result[5], '# tests 1\n'); 96 assert.strictEqual(result[6], '# suites 0\n'); 97 assert.strictEqual(result[7], '# pass 1\n'); 98 assert.strictEqual(result[8], '# fail 0\n'); 99 assert.strictEqual(result[9], '# cancelled 0\n'); 100 assert.strictEqual(result[10], '# skipped 0\n'); 101 assert.strictEqual(result[11], '# todo 0\n'); 102 assert.match(result[12], /# duration_ms \d+\.?\d*/); 103 }); 104 105 it('should skip tests not matching testNamePatterns - RegExp', async () => { 106 const result = await run({ files: [join(testFixtures, 'test/skip_by_name.cjs')], testNamePatterns: [/executed/] }) 107 .compose(tap) 108 .toArray(); 109 assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP test name does not match pattern\n'); 110 assert.strictEqual(result[5], 'ok 2 - this should be executed\n'); 111 }); 112 113 it('should skip tests not matching testNamePatterns - string', async () => { 114 const result = await run({ files: [join(testFixtures, 'test/skip_by_name.cjs')], testNamePatterns: ['executed'] }) 115 .compose(tap) 116 .toArray(); 117 assert.strictEqual(result[2], 'ok 1 - this should be skipped # SKIP test name does not match pattern\n'); 118 assert.strictEqual(result[5], 'ok 2 - this should be executed\n'); 119 }); 120 121 it('should stop watch mode when abortSignal aborts', async () => { 122 const controller = new AbortController(); 123 const result = await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal }) 124 .compose(async function* (source) { 125 for await (const chunk of source) { 126 if (chunk.type === 'test:pass') { 127 controller.abort(); 128 yield chunk.data.name; 129 } 130 } 131 }) 132 .toArray(); 133 assert.deepStrictEqual(result, ['this should pass']); 134 }); 135 136 it('should emit "test:watch:drained" event on watch mode', async () => { 137 const controller = new AbortController(); 138 await run({ files: [join(testFixtures, 'test/random.cjs')], watch: true, signal: controller.signal }) 139 .on('data', function({ type }) { 140 if (type === 'test:watch:drained') { 141 controller.abort(); 142 } 143 }); 144 }); 145}); 146