From d6f2fc3484c643470cbb49e50cf56948cb02307e Mon Sep 17 00:00:00 2001 From: Rowan Manning <138944+rowanmanning@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:48:04 +0000 Subject: [PATCH 1/2] fix: use absolute paths when running test files This caused an interesting issue with writing tests for this plugin, it's fine if the `cwd` is _actually_ the `cwd` because `glob` returns file paths relative to that. However, in the tests when we're overriding the `cwd` to access fixture files, the relative paths don't work because it assumes the files are relative to wherever Jest is running from. Anyway it shouldn't impact anyone running this normally, just blocks me from writing tests. --- plugins/node-test/src/tasks/node-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/node-test/src/tasks/node-test.ts b/plugins/node-test/src/tasks/node-test.ts index d91cd7225..0e422711b 100644 --- a/plugins/node-test/src/tasks/node-test.ts +++ b/plugins/node-test/src/tasks/node-test.ts @@ -113,7 +113,7 @@ export default class NodeTest extends Task<{ task: typeof NodeTestSchema }> { if (concurrency === true && process.env.CIRCLECI) { concurrency = (await guessCircleCiThreads()) - 1 } - const files = await glob(filePatterns, { cwd, ignore }) + const files = await glob(filePatterns, { absolute: true, cwd, ignore }) let success = true const testStream = run(Object.assign({ concurrency, files, forceExit, watch }, customOptions)) From 12f6196994970d48687bc09a3411eb97f3bee5e7 Mon Sep 17 00:00:00 2001 From: Rowan Manning <138944+rowanmanning@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:50:19 +0000 Subject: [PATCH 2/2] test: write some tests for the node-test plugin This adds in some very basic tests for the node-test plugin, I'd like to build up this test suite so I can think about switching to running tests via the command-line interface - this is required if we want to work with Node.js native test coverage and module mocks. --- plugins/node-test/jest.config.js | 5 ++ plugins/node-test/package.json | 2 +- plugins/node-test/src/tasks/node-test.ts | 4 +- .../test/files/failing-js/example.test.js | 8 ++ .../node-test/test/files/failing-js/throw.js | 2 + .../test/files/file-pattern/example.foo.js | 8 ++ .../test/files/file-pattern/example.test.js | 2 + .../test/files/passing-js/example.test.js | 8 ++ .../passing-js/subfolder/example.test.cjs | 8 ++ .../passing-js/subfolder/example.test.mjs | 8 ++ .../test/files/passing-js/test/example.js | 8 ++ .../node-test/test/files/passing-js/throw.js | 2 + .../test/files/timeout/example.test.js | 10 +++ .../node-test/test/tasks/node-test.test.ts | 79 +++++++++++++++++++ 14 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 plugins/node-test/jest.config.js create mode 100644 plugins/node-test/test/files/failing-js/example.test.js create mode 100644 plugins/node-test/test/files/failing-js/throw.js create mode 100644 plugins/node-test/test/files/file-pattern/example.foo.js create mode 100644 plugins/node-test/test/files/file-pattern/example.test.js create mode 100644 plugins/node-test/test/files/passing-js/example.test.js create mode 100644 plugins/node-test/test/files/passing-js/subfolder/example.test.cjs create mode 100644 plugins/node-test/test/files/passing-js/subfolder/example.test.mjs create mode 100644 plugins/node-test/test/files/passing-js/test/example.js create mode 100644 plugins/node-test/test/files/passing-js/throw.js create mode 100644 plugins/node-test/test/files/timeout/example.test.js create mode 100644 plugins/node-test/test/tasks/node-test.test.ts diff --git a/plugins/node-test/jest.config.js b/plugins/node-test/jest.config.js new file mode 100644 index 000000000..2ea38bb31 --- /dev/null +++ b/plugins/node-test/jest.config.js @@ -0,0 +1,5 @@ +const base = require('../../jest.config.base') + +module.exports = { + ...base.config +} diff --git a/plugins/node-test/package.json b/plugins/node-test/package.json index 75eb82581..5a735a6a5 100644 --- a/plugins/node-test/package.json +++ b/plugins/node-test/package.json @@ -3,7 +3,7 @@ "version": "2.0.1", "main": "lib", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "cd ../../ ; npx jest --silent --projects plugins/node-test" }, "keywords": [], "author": "FT.com Platforms Team ", diff --git a/plugins/node-test/src/tasks/node-test.ts b/plugins/node-test/src/tasks/node-test.ts index 0e422711b..c8a87c5b1 100644 --- a/plugins/node-test/src/tasks/node-test.ts +++ b/plugins/node-test/src/tasks/node-test.ts @@ -15,7 +15,7 @@ import { z } from 'zod' // between Node.js 20 and 22. In future (when we drop Node.js 20 support) we will be able to remove // this and rely on the built-in patterns. // See https://nodejs.org/api/test.html#running-tests-from-the-command-line -const defaultFilePatterns = [ +export const defaultFilePatterns = [ '**/*.test.?(c|m)js', '**/*-test.?(c|m)js', '**/*_test.?(c|m)js', @@ -25,7 +25,7 @@ const defaultFilePatterns = [ ] // We don't want to run tests against files under "node_modules" -const defaultIgnorePatterns = ['**/node_modules/**'] +export const defaultIgnorePatterns = ['**/node_modules/**'] // TODO:IM:20250407 This function has been copied wholesale from // plugins/jest/src/tasks/jest.ts. There isn't a clear shared library to put it diff --git a/plugins/node-test/test/files/failing-js/example.test.js b/plugins/node-test/test/files/failing-js/example.test.js new file mode 100644 index 000000000..faa828f1a --- /dev/null +++ b/plugins/node-test/test/files/failing-js/example.test.js @@ -0,0 +1,8 @@ +const assert = require('node:assert/strict') +const { describe, it } = require('node:test') + +describe('failing test suite', () => { + it('has tests that fail', () => { + assert.ok(false) + }) +}) diff --git a/plugins/node-test/test/files/failing-js/throw.js b/plugins/node-test/test/files/failing-js/throw.js new file mode 100644 index 000000000..83b9aa042 --- /dev/null +++ b/plugins/node-test/test/files/failing-js/throw.js @@ -0,0 +1,2 @@ +// This file does not match the default file patterns +throw new Error('Should not be run as part of the test suite'); diff --git a/plugins/node-test/test/files/file-pattern/example.foo.js b/plugins/node-test/test/files/file-pattern/example.foo.js new file mode 100644 index 000000000..18231fa01 --- /dev/null +++ b/plugins/node-test/test/files/file-pattern/example.foo.js @@ -0,0 +1,8 @@ +const assert = require('node:assert/strict') +const { describe, it } = require('node:test') + +describe('passing test suite', () => { + it('has tests that pass', () => { + assert.ok(true) + }) +}) diff --git a/plugins/node-test/test/files/file-pattern/example.test.js b/plugins/node-test/test/files/file-pattern/example.test.js new file mode 100644 index 000000000..f7eb9bf72 --- /dev/null +++ b/plugins/node-test/test/files/file-pattern/example.test.js @@ -0,0 +1,2 @@ +// This file does not match the configured file patterns +throw new Error('Should not be run as part of the test suite'); diff --git a/plugins/node-test/test/files/passing-js/example.test.js b/plugins/node-test/test/files/passing-js/example.test.js new file mode 100644 index 000000000..18231fa01 --- /dev/null +++ b/plugins/node-test/test/files/passing-js/example.test.js @@ -0,0 +1,8 @@ +const assert = require('node:assert/strict') +const { describe, it } = require('node:test') + +describe('passing test suite', () => { + it('has tests that pass', () => { + assert.ok(true) + }) +}) diff --git a/plugins/node-test/test/files/passing-js/subfolder/example.test.cjs b/plugins/node-test/test/files/passing-js/subfolder/example.test.cjs new file mode 100644 index 000000000..8fb3c138c --- /dev/null +++ b/plugins/node-test/test/files/passing-js/subfolder/example.test.cjs @@ -0,0 +1,8 @@ +const assert = require('node:assert/strict') +const { describe, it } = require('node:test') + +describe("passing test suite (CJS)", () => { + it('has tests that pass', () => { + assert.ok(true) + }) +}) diff --git a/plugins/node-test/test/files/passing-js/subfolder/example.test.mjs b/plugins/node-test/test/files/passing-js/subfolder/example.test.mjs new file mode 100644 index 000000000..c941359b1 --- /dev/null +++ b/plugins/node-test/test/files/passing-js/subfolder/example.test.mjs @@ -0,0 +1,8 @@ +import assert from 'node:assert/strict' +import { describe, it } from 'node:test' + +describe("passing test suite (MJS)", () => { + it('has tests that pass', () => { + assert.ok(true) + }) +}) diff --git a/plugins/node-test/test/files/passing-js/test/example.js b/plugins/node-test/test/files/passing-js/test/example.js new file mode 100644 index 000000000..fe82c0c83 --- /dev/null +++ b/plugins/node-test/test/files/passing-js/test/example.js @@ -0,0 +1,8 @@ +const assert = require('node:assert/strict') +const { describe, it } = require('node:test') + +describe("passing test suite (test folder)", () => { + it('has tests that pass', () => { + assert.ok(true) + }) +}) diff --git a/plugins/node-test/test/files/passing-js/throw.js b/plugins/node-test/test/files/passing-js/throw.js new file mode 100644 index 000000000..83b9aa042 --- /dev/null +++ b/plugins/node-test/test/files/passing-js/throw.js @@ -0,0 +1,2 @@ +// This file does not match the default file patterns +throw new Error('Should not be run as part of the test suite'); diff --git a/plugins/node-test/test/files/timeout/example.test.js b/plugins/node-test/test/files/timeout/example.test.js new file mode 100644 index 000000000..4802d0a98 --- /dev/null +++ b/plugins/node-test/test/files/timeout/example.test.js @@ -0,0 +1,10 @@ +const assert = require('node:assert/strict') +const { describe, it } = require('node:test') +const { setTimeout } = require('node:timers/promises') + +describe('passing test suite', () => { + it('has tests that pass', async () => { + await setTimeout(1000); + assert.ok(true) + }) +}) diff --git a/plugins/node-test/test/tasks/node-test.test.ts b/plugins/node-test/test/tasks/node-test.test.ts new file mode 100644 index 000000000..91dff3c0b --- /dev/null +++ b/plugins/node-test/test/tasks/node-test.test.ts @@ -0,0 +1,79 @@ +import { describe, it, expect } from '@jest/globals' +import { join, resolve } from 'node:path' +import NodeTest, { defaultFilePatterns, defaultIgnorePatterns } from '../../src/tasks/node-test' +import winston, { Logger } from 'winston' +import { ToolKitError } from '@dotcom-tool-kit/error' +import { PassThrough } from 'node:stream' + +const fixturesPath = resolve(__dirname, '..', 'files') + +const defaultOptions = { + concurrency: false, + files: defaultFilePatterns, + forceExit: false, + ignore: defaultIgnorePatterns, + watch: false +} + +describe('NodeTest', () => { + let logger: Logger + let log: jest.Mock + + beforeEach(() => { + log = jest.fn() + const logStream = new PassThrough() + logStream.on('data', (data) => log(data.toString())) + + logger = winston.createLogger({ + transports: [new winston.transports.Stream({ stream: logStream })] + }) + }) + + it('does not error when the tests pass', async () => { + const task = new NodeTest(logger, 'NodeTest', {}, defaultOptions) + await expect(task.run({ command: 'test:local', cwd: join(fixturesPath, 'passing-js') })).resolves.toBe( + undefined + ) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/passing test suite/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/pass 4/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/fail 0/)) + }) + + it('errors when the tests fail', async () => { + const task = new NodeTest(logger, 'NodeTest', {}, defaultOptions) + await expect(task.run({ command: 'test:local', cwd: join(fixturesPath, 'failing-js') })).rejects.toThrow( + ToolKitError + ) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/failing test suite/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/pass 0/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/fail 1/)) + }) + + it('finds tests based on the given file patterns', async () => { + const task = new NodeTest(logger, 'NodeTest', {}, { ...defaultOptions, files: ['**/*.foo.js'] }) + await expect(task.run({ command: 'test:local', cwd: join(fixturesPath, 'file-pattern') })).resolves.toBe( + undefined + ) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/passing test suite/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/pass 1/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/fail 0/)) + }) + + it('ignores tests based on the given file patterns', async () => { + const task = new NodeTest(logger, 'NodeTest', {}, { ...defaultOptions, ignore: ['subfolder/*'] }) + await expect(task.run({ command: 'test:local', cwd: join(fixturesPath, 'passing-js') })).resolves.toBe( + undefined + ) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/passing test suite/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/pass 2/)) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/fail 0/)) + }) + + it('can accept custom options (timeout)', async () => { + const task = new NodeTest(logger, 'NodeTest', {}, {...defaultOptions, customOptions: { timeout: 100 }}) + await expect(task.run({ command: 'test:local', cwd: join(fixturesPath, 'timeout') })).rejects.toThrow( + ToolKitError + ) + expect(log).toHaveBeenCalledWith(expect.stringMatching(/✖/)) + }) +})