test(mcp): hold the watcher's sync so staleness-banner edits stay pending (#2105)

`MCP staleness banner > lists pending files under "Pending sync"` flaked in
the full suite (2 of 4 runs): status showed "0 modified" and no Pending sync
section. The tests used `debounceMs: 4000` to keep an edit pending while they
queried, but since #1397 a lone edit syncs after a 300ms quiet window whatever
the configured debounce. codegraph_status awaits a freshly spawned worker to
count changes before reading the pending set, and under load that outlasted
the window, so the sync had already cleared the entry.

A 500ms pause before the query fails the status, banner and #1968 path tests
deterministically. Hold the watcher's sync open with a never-settling spy in
those four setups instead; no assertion changes, and the test that checks the
banner drops after a sync still runs a real one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-29 18:52:32 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 85dd8b2a15
commit 26d6ccece5
+16 -1
View File
@@ -40,6 +40,18 @@ function waitFor(condition: () => boolean, timeoutMs = 2000, intervalMs = 25): P
});
}
/**
* Keep the watcher's edits pending for the rest of the test. A long
* `debounceMs` alone does not: a lone edit syncs after a 300ms quiet window
* whatever the configured debounce (#1397), and under full-suite load a tool
* call can outlast that (status spawns a worker to count changes), so the sync
* cleared the entry before the response was built. A sync that never settles
* leaves each entry pending (marked as indexing once it starts).
*/
function holdWatcherSync(cg: CodeGraph): void {
vi.spyOn(cg, 'sync').mockReturnValue(new Promise<never>(() => {}));
}
describe('MCP staleness banner', () => {
let testDir: string;
let cg: CodeGraph;
@@ -92,7 +104,7 @@ describe('MCP staleness banner', () => {
};
it('prepends a stale banner when the response references a pending file', async () => {
// Long debounce so the edit lingers in pendingFiles while we query.
holdWatcherSync(cg);
cg.watch({ debounceMs: 4000, inertForTests: true });
await cg.waitUntilWatcherReady();
@@ -124,6 +136,7 @@ describe('MCP staleness banner', () => {
});
it('uses the footer (not the banner) when pending files are not referenced', async () => {
holdWatcherSync(cg);
cg.watch({ debounceMs: 4000, inertForTests: true });
await cg.waitUntilWatcherReady();
@@ -164,6 +177,7 @@ describe('MCP staleness banner', () => {
});
it('lists pending files under "Pending sync" in codegraph_status', async () => {
holdWatcherSync(cg);
cg.watch({ debounceMs: 4000, inertForTests: true });
await cg.waitUntilWatcherReady();
@@ -244,6 +258,7 @@ describe('MCP staleness banner — matching whole paths (#1968)', () => {
cg = CodeGraph.initSync(testDir, { config: { include: ['**/*.ts', '**/*.tsx'], exclude: [] } });
await cg.indexAll();
handler = new ToolHandler(cg);
holdWatcherSync(cg);
cg.watch({ debounceMs: 4000, inertForTests: true });
await cg.waitUntilWatcherReady();
});