From 6d6ae95601fe3d5f15dae83607806cd426755584 Mon Sep 17 00:00:00 2001 From: James Whitney Date: Thu, 10 Jul 2025 12:03:05 +1000 Subject: [PATCH] This PR adds a `--pass-with-no-tests` option. This allows for parity with vitest's [`--passWithNoTests`](https://vitest.dev/guide/cli.html#passwithnotests) and jest's [`--passWithNoTests`](https://jestjs.io/docs/cli#--passwithnotests) options. --- source/cli.ts | 19 ++++++++++++------- source/lib/index.ts | 15 +++++++++++++-- source/test/cli.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/source/cli.ts b/source/cli.ts index 2309840..7b964a0 100644 --- a/source/cli.ts +++ b/source/cli.ts @@ -11,13 +11,14 @@ const cli = meow(` The given directory must contain a package.json and a typings file. Info - --help Display help text - --version Display version info + --help Display help text + --version Display version info Options - --typings -t Type definition file to test [Default: "types" property in package.json] - --files -f Glob of files to test [Default: '/path/test-d/**/*.test-d.ts' or '.tsx'] - --show-diff Show type error diffs [Default: don't show] + --typings -t Type definition file to test [Default: "types" property in package.json] + --files -f Glob of files to test [Default: '/path/test-d/**/*.test-d.ts' or '.tsx'] + --show-diff Show type error diffs [Default: don't show] + --pass-with-no-tests Pass when no tests are found [Default: false] Examples $ tsd /path/to/project @@ -42,6 +43,10 @@ const cli = meow(` showDiff: { type: 'boolean', }, + passWithNoTests: { + default: false, + type: 'boolean', + }, }, }); @@ -64,9 +69,9 @@ const exit = (message: string, {isError = true}: {isError?: boolean} = {}) => { (async () => { try { const cwd = cli.input.length > 0 ? cli.input[0] : process.cwd(); - const {typings: typingsFile, files: testFiles, showDiff} = cli.flags; + const {typings: typingsFile, files: testFiles, showDiff, passWithNoTests} = cli.flags; - const diagnostics = await tsd({cwd, typingsFile, testFiles}); + const diagnostics = await tsd({cwd, typingsFile, testFiles, passWithNoTests}); if (diagnostics.length > 0) { const hasErrors = diagnostics.some(diagnostic => diagnostic.severity === 'error'); diff --git a/source/lib/index.ts b/source/lib/index.ts index e59193c..da86d12 100644 --- a/source/lib/index.ts +++ b/source/lib/index.ts @@ -11,6 +11,7 @@ export interface Options { cwd: string; typingsFile?: string; testFiles?: readonly string[]; + passWithNoTests?: boolean; } const findTypingsFile = async (pkg: PackageJsonWithTsdConfig, options: Options): Promise => { @@ -39,9 +40,13 @@ const normalizeTypingsFilePath = (typingsFilePath: string, options: Options) => return typingsFilePath; }; -const findCustomTestFiles = async (testFilesPattern: readonly string[], cwd: string) => { +const findCustomTestFiles = async (testFilesPattern: readonly string[], cwd: string, passWithNoTests: boolean) => { const testFiles = await globby(testFilesPattern, {cwd}); + if (testFiles.length === 0 && passWithNoTests) { + return []; + } + if (testFiles.length === 0) { throw new TsdError('Could not find any test files with the given pattern(s). Create one and try again.'); } @@ -50,8 +55,10 @@ const findCustomTestFiles = async (testFilesPattern: readonly string[], cwd: str }; const findTestFiles = async (typingsFilePath: string, options: Options & {config: Config}) => { + const passWithNoTests = options.passWithNoTests === true; + if (options.testFiles?.length) { - return findCustomTestFiles(options.testFiles, options.cwd); + return findCustomTestFiles(options.testFiles, options.cwd, passWithNoTests); } // Return only the filename if the `typingsFile` option is used. @@ -64,6 +71,10 @@ const findTestFiles = async (typingsFilePath: string, options: Options & {config let testFiles = await globby([testFile, tsxTestFile], {cwd: options.cwd}); const testDirExists = await pathExists(path.join(options.cwd, testDir)); + if (testFiles.length === 0 && passWithNoTests) { + return []; + } + if (testFiles.length === 0 && !testDirExists) { throw new TsdError(`The test file \`${testFile}\` or \`${tsxTestFile}\` does not exist in \`${options.cwd}\`. Create one and try again.`); } diff --git a/source/test/cli.ts b/source/test/cli.ts index b499fe7..002213f 100644 --- a/source/test/cli.ts +++ b/source/test/cli.ts @@ -123,6 +123,32 @@ test('cli typings and files flags', async t => { ]); }); +test('cli passWithNoTests flag', async t => { + const runTest = async (arg: '--passWithNoTests') => { + const {exitCode} = await execa('../../../cli.js', [arg], { + cwd: path.join(__dirname, 'fixtures/no-test') + }); + + t.is(exitCode, 0); + }; + + await runTest('--passWithNoTests'); +}); + +test('cli passWithNoTests flag and files', async t => { + const runTest = async (arg: '--pass-with-no-tests') => { + const testFile = path.join(__dirname, 'fixtures/does-not-exist.test-d.ts'); + + const {exitCode} = await execa('../../../cli.js', [arg, `--files ${testFile}`], { + cwd: path.join(__dirname, 'fixtures/no-test') + }); + + t.is(exitCode, 0); + }; + + await runTest('--pass-with-no-tests'); +}); + test('tsd logs stacktrace on failure', async t => { const {exitCode, stderr} = await t.throwsAsync(execa('../../../cli.js', { cwd: path.join(__dirname, 'fixtures/empty-package-json')