Skip to content

Commit 7284aba

Browse files
test: assert the output directory is never watched, not just never reported
Waiting for the root control file to report a change proves the watcher is live, but not that chokidar has discovered the nested file inside the output directory, so a write could still be lost and the zero-reload assertion could pass over a broken predicate. Assert instead on what the watcher decided to watch, which no longer depends on event timing at all, and rewrite the ignored file throughout the window rather than once so no single lost write can hide a regression. Reverting lib/Server.js to its base version fails all three assertions.
1 parent b862d2c commit 7284aba

1 file changed

Lines changed: 37 additions & 20 deletions

File tree

‎test/server/static-watch-output-path.test.js‎

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,22 @@ import portsMap from "../ports-map.js";
1010

1111
const port = portsMap["static-watch-output-path"];
1212

13-
// Long enough that a watcher which does report the write has reported it: the
14-
// control below rewrites a file beside the output directory and is seen well
15-
// inside this window, so an empty list after it means the write was ignored
16-
// rather than merely slow.
17-
const SETTLE_MS = 3000;
18-
19-
const settle = () =>
13+
// The ignored file is rewritten this many times, this far apart, so that the
14+
// window spans several seconds. A single write could land while chokidar is
15+
// still discovering the nested directory and be lost; a stream of them cannot
16+
// all be.
17+
const REWRITES = 12;
18+
const REWRITE_INTERVAL_MS = 250;
19+
20+
const sleep = (ms) =>
2021
new Promise((resolve) => {
21-
setTimeout(resolve, SETTLE_MS);
22+
setTimeout(resolve, ms);
2223
});
2324

24-
// Rewrites `file` until the watcher reports it, so the caller knows the initial
25-
// scan is over and later writes cannot be silently dropped.
25+
// Rewrites `file` until the watcher reports it, which establishes that the
26+
// watcher is live. It does not establish that every nested path has been
27+
// discovered, so it is only the starting gun — the assertions below do not
28+
// rest on it alone.
2629
const waitUntilWatching = async (reloads, file, timeout = 20000) => {
2730
const started = Date.now();
2831

@@ -32,9 +35,7 @@ const waitUntilWatching = async (reloads, file, timeout = 20000) => {
3235
}
3336

3437
await fsPromises.writeFile(file, `warm-up ${Date.now()}`);
35-
await new Promise((resolve) => {
36-
setTimeout(resolve, 250);
37-
});
38+
await sleep(REWRITE_INTERVAL_MS);
3839
}
3940
};
4041

@@ -117,10 +118,9 @@ describe("static watching and output.path", () => {
117118
await server.start();
118119

119120
// chokidar drops events raised before its initial scan finishes, so a write
120-
// sent too early is simply lost and the absence of a reload would prove
121-
// nothing. Rewriting the control file until the watcher answers establishes
122-
// that it is live, which neither `getWatched()` nor a late `ready` listener
123-
// can: the first fills in during the scan, the second has already fired.
121+
// sent too early is simply lost. `ready` cannot be awaited from here — the
122+
// watcher is created inside `start()` and may already have emitted it — so
123+
// the control file is rewritten until the watcher answers.
124124
await waitUntilWatching(reloads, outsideOutput);
125125
reloads.length = 0;
126126
});
@@ -130,11 +130,28 @@ describe("static watching and output.path", () => {
130130
fs.rmSync(tempDirectory, { recursive: true, force: true });
131131
});
132132

133+
it("should not watch the output directory at all", () => {
134+
// the strongest form of the assertion, and the only one that does not
135+
// depend on event timing: chokidar lists what it decided to watch, so a
136+
// predicate that failed to exclude the output directory shows up here even
137+
// if no write ever raced with the scan
138+
const watched = server.staticWatchers.flatMap((watcher) =>
139+
Object.keys(watcher.getWatched()),
140+
);
141+
142+
expect(watched).toContain(tempDirectory);
143+
expect(watched).not.toContain(outputPath);
144+
});
145+
133146
it("should not reload when a file inside output.path is rewritten", async () => {
134147
// the compilation already reaches the client through the middleware, so a
135-
// reload here only fires because the build wrote its own output
136-
await fsPromises.writeFile(insideOutput, "emitted again");
137-
await settle();
148+
// reload here only fires because the build wrote its own output. Rewriting
149+
// throughout the window rather than once means a single write lost to the
150+
// initial scan cannot hide a broken predicate.
151+
for (let index = 0; index < REWRITES; index++) {
152+
await fsPromises.writeFile(insideOutput, `emitted ${index}`);
153+
await sleep(REWRITE_INTERVAL_MS);
154+
}
138155

139156
expect(reloads).toHaveLength(0);
140157
});

0 commit comments

Comments
 (0)