From 8a27789459955250549d47a7d9deb3ffdcd557e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Weslley=20Ara=C3=BAjo?= <46850407+wellwelwel@users.noreply.github.com> Date: Mon, 1 Jul 2024 23:10:31 -0300 Subject: [PATCH] feat(watch): reset and rerun all tests by entering `rs` (#493) --- package.json | 1 + src/bin/index.ts | 142 +++++++++++------- src/modules/helpers/list-files.ts | 2 - src/services/watch.ts | 2 +- test/c8.test.ts | 63 +++++--- .../docs/documentation/poku/options/watch.mdx | 34 +++-- 6 files changed, 160 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index 4e9546a8..5553b327 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "šŸ· Poku makes testing easy for Node.js, Bun, Deno, and you at the same time.", "main": "./lib/modules/index.js", "license": "MIT", + "type": "commonjs", "bin": { "poku": "./lib/bin/index.js" }, diff --git a/src/bin/index.ts b/src/bin/index.ts index 8c62dbaf..0a3e18b7 100644 --- a/src/bin/index.ts +++ b/src/bin/index.ts @@ -14,7 +14,7 @@ import { platformIsValid } from '../parsers/get-runtime.js'; import { format } from '../services/format.js'; import { kill } from '../modules/helpers/kill.js'; import { mapTests, normalizePath } from '../services/map-tests.js'; -import { watch } from '../services/watch.js'; +import { watch, type Watcher } from '../services/watch.js'; import { onSigint, poku } from '../modules/essentials/poku.js'; import { Write } from '../services/write.js'; @@ -106,72 +106,110 @@ if (debug) { console.dir(options, { depth: null, colors: true }); } -Promise.all(tasks).then(() => { - poku(dirs, options).then(() => { - if (watchMode) { - const executing = new Set(); - const interval = Number(getArg('watch-interval')) || 1500; +const watchers: Set = new Set(); +const executing = new Set(); +const interval = Number(getArg('watch-interval')) || 1500; - const resultsClear = () => { - fileResults.success.clear(); - fileResults.fail.clear(); - }; +let isRunning = false; - process.removeListener('SIGINT', onSigint); - resultsClear(); +const listenStdin = (input: Buffer | string) => { + if (isRunning || executing.size > 0) { + return; + } + + if (String(input).trim() === 'rs') { + watchers.forEach((watcher) => watcher.stop()); + watchers.clear(); + resultsClear(); + startTests(); + } +}; + +const resultsClear = () => { + fileResults.success.clear(); + fileResults.fail.clear(); +}; + +const startTests = () => { + if (isRunning || executing.size > 0) { + return; + } + + isRunning = true; + + Promise.all(tasks).then(() => { + poku(dirs, options) + .then(() => { + if (watchMode) { + process.stdin.removeListener('data', listenStdin); + process.removeListener('SIGINT', onSigint); + resultsClear(); + + mapTests('.', dirs, options.filter, options.exclude).then( + (mappedTests) => { + for (const mappedTest of Array.from(mappedTests.keys())) { + const currentWatcher = watch(mappedTest, (file, event) => { + if (event === 'change') { + const filePath = normalizePath(file); + if (executing.has(filePath)) { + return; + } + + executing.add(filePath); + resultsClear(); + + const tests = mappedTests.get(filePath); + if (!tests) { + return; + } + + poku(Array.from(tests), options).then(() => { + setTimeout(() => { + executing.delete(filePath); + }, interval); + }); + } + }); + + currentWatcher.then((watcher) => watchers.add(watcher)); + } + } + ); - mapTests('.', dirs, options.filter, options.exclude).then( - (mappedTests) => { - for (const mappedTest of Array.from(mappedTests.keys())) { - watch(mappedTest, (file, event) => { + for (const dir of dirs) { + const currentWatcher = watch(dir, (file, event) => { if (event === 'change') { - const filePath = normalizePath(file); - if (executing.has(filePath)) { + if (executing.has(file)) { return; } - executing.add(filePath); + executing.add(file); resultsClear(); - const tests = mappedTests.get(filePath); - if (!tests) { - return; - } - - poku(Array.from(tests), options).then(() => { + poku(file, options).then(() => { setTimeout(() => { - executing.delete(filePath); + executing.delete(file); }, interval); }); } }); - } - } - ); - for (const dir of dirs) { - watch(dir, (file, event) => { - if (event === 'change') { - if (executing.has(file)) { - return; - } + currentWatcher.then((watcher) => watchers.add(watcher)); + } - executing.add(file); - resultsClear(); + Write.hr(); + Write.log( + `${format('Watching:').bold()} ${format(dirs.join(', ')).underline()}` + ); - poku(file, options).then(() => { - setTimeout(() => { - executing.delete(file); - }, interval); - }); - } - }); - } - - Write.hr(); - Write.log( - `${format('Watching:').bold()} ${format(dirs.join(', ')).underline()}` - ); - } + process.stdin.setEncoding('utf-8'); + process.stdin.on('data', listenStdin); + } + }) + .finally(() => { + isRunning = false; + }); }); -}); +}; + +startTests(); diff --git a/src/modules/helpers/list-files.ts b/src/modules/helpers/list-files.ts index 80beba15..eacd870a 100644 --- a/src/modules/helpers/list-files.ts +++ b/src/modules/helpers/list-files.ts @@ -30,7 +30,6 @@ export const isFile = async (fullPath: string) => export const escapeRegExp = (string: string) => string.replace(regex.safeRegExp, '\\$&'); -/* c8 ignore next 3 */ const envFilter = env.FILTER?.trim() ? new RegExp(escapeRegExp(env.FILTER), 'i') : undefined; @@ -42,7 +41,6 @@ export const getAllFiles = async ( ): Promise> => { const currentFiles = await readdir(sanitizePath(dirPath)); - /* c8 ignore next 3 */ const filter: RegExp = envFilter ? envFilter : configs?.filter instanceof RegExp diff --git a/src/services/watch.ts b/src/services/watch.ts index 0100d193..faafa3a7 100644 --- a/src/services/watch.ts +++ b/src/services/watch.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { readdir, stat } from '../polyfills/fs.js'; import { listFiles } from '../modules/helpers/list-files.js'; -class Watcher { +export class Watcher { private rootDir: string; private files: string[] = []; private fileWatchers: Map = new Map(); diff --git a/test/c8.test.ts b/test/c8.test.ts index 79ebb584..f2463601 100644 --- a/test/c8.test.ts +++ b/test/c8.test.ts @@ -2,13 +2,11 @@ import { poku, test, describe, it, assert } from '../src/modules/index.js'; import { isWindows } from '../src/parsers/get-runner.js'; import { inspectCLI } from './helpers/capture-cli.test.js'; -console.log('\nšŸ˜“ It will be really slow (press "Ctrl + C" twice to cancel)\n'); - test(async () => { await describe('CLI', async () => { - await it('Sequential (Unit)', async () => { + await it('Sequential (Just Touch)', async () => { const results = await inspectCLI( - 'npx tsx src/bin/index.ts --platform=node --include=test/unit' + 'npx tsx src/bin/index.ts --platform=node --include=test/integration/import.test.ts' ); console.log(results.stdout); @@ -17,9 +15,9 @@ test(async () => { assert.strictEqual(results.exitCode, 0, 'Passed'); }); - await it('Parallel (Unit)', async () => { + await it('Parallel (Just Touch)', async () => { const results = await inspectCLI( - 'npx tsx src/bin/index.ts --platform=node --parallel --include=test/unit' + 'npx tsx src/bin/index.ts --platform=node --parallel --include=test/integration/import.test.ts' ); console.log(results.stdout); @@ -28,11 +26,38 @@ test(async () => { assert.strictEqual(results.exitCode, 0, 'Passed'); }); - await it('Parallel + Unit + Options', async () => { + await it('Parallel (FILTER Env)', async () => { + const results = await inspectCLI( + 'npx tsx src/bin/index.ts --platform=node --parallel --include=test/integration', + { + env: { ...process.env, FILTER: 'import' }, + } + ); + + console.log(results.stdout); + console.log(results.stderr); + + assert.strictEqual(results.exitCode, 0, 'Passed'); + }); + + await it('Sequential + Options (Just Touch)', async () => { const results = await inspectCLI( isWindows - ? 'npx tsx src/bin/index.ts --parallel --concurrency=4 --platform=node --fast-fail --debug --exclude=".bak" --kill-port=4000 --kill-range="4000-4001" --include=test/unit --filter=".test.|.spec."' - : 'npx tsx src/bin/index.ts --parallel --concurrency=4 --platform=node --fast-fail --debug --exclude=.bak --kill-port=4000 --kill-range=4000-4001 --include=test/unit --filter=.test.|.spec.' + ? 'npx tsx src/bin/index.ts --concurrency=4 --platform=node --fast-fail --debug --exclude=".bak" --kill-port=4000 --kill-range="4000-4001" --include=test/integration/import.test.ts --filter=".test.|.spec."' + : 'npx tsx src/bin/index.ts --concurrency=4 --platform=node --fast-fail --debug --exclude=.bak --kill-port=4000 --kill-range=4000-4001 --include=test/integration/import.test.ts --filter=.test.|.spec.' + ); + + console.log(results.stdout); + console.log(results.stderr); + + assert.strictEqual(results.exitCode, 0, 'Passed'); + }); + + await it('Parallel + Options (Just Touch)', async () => { + const results = await inspectCLI( + isWindows + ? 'npx tsx src/bin/index.ts --parallel --concurrency=4 --platform=node --fast-fail --debug --exclude=".bak" --kill-port=4000 --kill-range="4000-4001" --include=test/integration/import.test.ts --filter=".test.|.spec."' + : 'npx tsx src/bin/index.ts --parallel --concurrency=4 --platform=node --fast-fail --debug --exclude=.bak --kill-port=4000 --kill-range=4000-4001 --include=test/integration/import.test.ts --filter=.test.|.spec.' ); console.log(results.stdout); @@ -43,8 +68,8 @@ test(async () => { }); await describe('API', async () => { - await it('Sequential (Unit)', async () => { - const exitCode = await poku(['test/unit'], { + await it('Sequential (Single Input)', async () => { + const exitCode = await poku('test/integration/import.test.ts', { platform: 'node', noExit: true, }); @@ -52,36 +77,39 @@ test(async () => { assert.strictEqual(exitCode, 0, 'Passed'); }); - await it('Sequential (File)', async () => { + await it('Parallel (Single Input)', async () => { const exitCode = await poku('test/integration/import.test.ts', { platform: 'node', + parallel: true, noExit: true, }); assert.strictEqual(exitCode, 0, 'Passed'); }); - await it('Parallel (File)', async () => { - const exitCode = await poku('test/integration/import.test.ts', { + await it('Unit (Exclude as Regex)', async () => { + const exitCode = await poku('test/unit', { platform: 'node', parallel: true, + exclude: /watch|map-tests/, noExit: true, }); assert.strictEqual(exitCode, 0, 'Passed'); }); - await it('Parallel (Unit)', async () => { - const exitCode = await poku(['test/unit'], { + await it('Unit (Exclude as Array of Regex)', async () => { + const exitCode = await poku('test/unit', { platform: 'node', parallel: true, + exclude: [/watch/, /map-tests/], noExit: true, }); assert.strictEqual(exitCode, 0, 'Passed'); }); - await it('Parallel + All + Options', async () => { + await it('Parallel + Unit + Integration + E2E + Options', async () => { const exitCode = await poku( ['test/unit', 'test/integration', 'test/e2e'], { @@ -89,7 +117,6 @@ test(async () => { parallel: true, debug: true, concurrency: 4, - exclude: [/import.test/], filter: /\.(test|spec)\./, failFast: true, noExit: true, diff --git a/website/docs/documentation/poku/options/watch.mdx b/website/docs/documentation/poku/options/watch.mdx index aae94c71..62a6719e 100644 --- a/website/docs/documentation/poku/options/watch.mdx +++ b/website/docs/documentation/poku/options/watch.mdx @@ -22,21 +22,33 @@ npx poku --watch --watch-interval=1500
+:::tip + +Quickly reset and rerun all tests by typing rs on the console, followed by Enter. + +- This will clean and repopulate all the directories and files to be watched. +- It's not possible to use it while the tests are running. + +::: + +
+ +:::info + +If it's not possible to determine which test file depends on the changed source file, no test will be run. + +::: + +
+ :::note -Currently, the deep dependency search doesn't consider line-breaking imports, for example: +āŒ Dynamic paths are not supported: ```ts -import { - moduleA, - moduleB, - moduleC, - moduleD, - moduleE, - moduleF, -} from './some.js'; -``` +const dynamicPath = './some-file.js'; -- See [**#499**](https://github.com/wellwelwel/poku/issues/449). +await import(dynamicPath); +``` :::