Skip to content

Commit 921c161

Browse files
fix: keep watching when the last test file is removed (#6355)
1 parent 3382fdd commit 921c161

5 files changed

Lines changed: 64 additions & 4 deletions

File tree

‎lib/cli/collect-files.cjs‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const { castArray } = require("../utils.cjs");
2828
* @private
2929
*/
3030
module.exports = ({
31+
allowEmpty,
3132
ignore,
3233
extension,
3334
file: fileArgs,
@@ -94,11 +95,17 @@ module.exports = ({
9495
// give full message details when only 1 file is missing
9596
const noneFoundMsg =
9697
unmatchedSpecFiles.length === 1
97-
? `Error: No test files found: ${JSON.stringify(
98+
? `No test files found: ${JSON.stringify(
9899
unmatchedSpecFiles[0].pattern,
99100
)}` // stringify to print escaped characters raw
100-
: "Error: No test files found";
101-
console.error(pc.red(noneFoundMsg));
101+
: "No test files found";
102+
103+
if (allowEmpty) {
104+
console.warn(pc.yellow(`Warning: ${noneFoundMsg}`));
105+
return { files: [], unmatchedFiles: [] };
106+
}
107+
108+
console.error(pc.red(`Error: ${noneFoundMsg}`));
102109
process.exit(1);
103110
} else {
104111
// print messages as a warning

‎lib/cli/run-helpers.cjs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ exports.runMocha = async (mocha, options) => {
229229
} = options;
230230

231231
const fileCollectParams = {
232+
allowEmpty: watch,
232233
ignore,
233234
extension,
234235
file,

‎lib/nodejs/file-unloader.js‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,18 @@ const require = createRequire(import.meta.url);
1010

1111
/**
1212
* Deletes a file from the `require` cache.
13+
* A file that no longer exists on disk has nothing left to unload.
1314
* @param {string} file - File
1415
*/
1516
export function unloadFile(file) {
16-
delete require.cache[require.resolve(file)];
17+
let resolved;
18+
try {
19+
resolved = require.resolve(file);
20+
} catch (err) {
21+
if (err.code === "MODULE_NOT_FOUND") {
22+
return;
23+
}
24+
throw err;
25+
}
26+
delete require.cache[resolved];
1727
}

‎lib/types.d.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ export type PluginFinalizer = (
194194
* An object to configure how Mocha gathers test files
195195
*/
196196
export interface FileCollectionOptions {
197+
/** Return an empty list instead of exiting when no test files are found */
198+
allowEmpty?: boolean;
197199
/** File extensions to use */
198200
extension?: string[];
199201
/** Files, dirs, globs to run */

‎test/integration/options/watch.spec.cjs‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ describe("--watch", function () {
6767
});
6868
});
6969

70+
it("keeps watching when the last test file is removed", function () {
71+
const testFile = path.join(tempDir, "test/a.js");
72+
copyFixture(DEFAULT_FIXTURE, testFile);
73+
74+
return runMochaWatchJSONAsync(
75+
["test/**/*.js", "--watch-files", "test/**/*.js"],
76+
{ cwd: tempDir, expectedRuns: 3 },
77+
async (_mochaProcess, { waitForRuns }) => {
78+
fs.rmSync(testFile, { force: true });
79+
await waitForRuns(2);
80+
copyFixture("passing.fixture.cjs", path.join(tempDir, "test/b.js"));
81+
},
82+
).then((results) => {
83+
expect(results, "to have length", 3);
84+
expect(results[0].passes, "to have length", 1);
85+
expect(results[1].passes, "to have length", 0);
86+
expect(results[2].passes, "to have length", 2);
87+
});
88+
});
89+
7090
describe("when in parallel mode", function () {
7191
it("reruns test when watched test file is touched", function () {
7292
const testFile = path.join(tempDir, "test.js");
@@ -78,6 +98,26 @@ describe("--watch", function () {
7898
expect(results, "to have length", 2);
7999
});
80100
});
101+
102+
it("keeps watching when the last test file is removed", function () {
103+
const testFile = path.join(tempDir, "test/a.js");
104+
copyFixture(DEFAULT_FIXTURE, testFile);
105+
106+
return runMochaWatchJSONAsync(
107+
["--parallel", "test/**/*.js", "--watch-files", "test/**/*.js"],
108+
{ cwd: tempDir, expectedRuns: 3 },
109+
async (_mochaProcess, { waitForRuns }) => {
110+
fs.rmSync(testFile, { force: true });
111+
await waitForRuns(2);
112+
copyFixture("passing.fixture.cjs", path.join(tempDir, "test/b.js"));
113+
},
114+
).then((results) => {
115+
expect(results, "to have length", 3);
116+
expect(results[0].passes, "to have length", 1);
117+
expect(results[1].passes, "to have length", 0);
118+
expect(results[2].passes, "to have length", 2);
119+
});
120+
});
81121
});
82122

83123
it("reruns test when file matching --watch-files changes", function () {

0 commit comments

Comments
 (0)