fix: address Copilot review round 1 on #2323

All nine findings taken.

Two budgets I had lowered are restored as named exceptions rather than folded
into the shared value. cli's e2e child timer goes back to 15s and the two tests
that spawn it get E2E_SPAWN_MS = 25s, so the bound carrying the diagnostic still
wins without taking load tolerance off the one suite that spawns a real process.
App.test.tsx's three-modal sequence keeps THREE_MODAL_SEQUENCE_MS = 20s — a
value chosen against an observed failure, which the shared ceiling would have
replaced with a general one.

Three real false-positive paths in the guard are closed. It now discovers every
Vitest config under clients/ and fails on one it is not configured to check —
without which "a project with no row is an error" was vacuous, since an
undiscovered config yields no project to reject. It strips comments before
looking for configure() and requires the exact expected value, so neither a
commented-out call nor asyncUtilTimeout: 1 passes. And it compares each
resolved project's setupFiles against the site it claims to configure, so
deleting a setupFiles entry can no longer leave both projects reported as
configured while Testing Library silently reverts to 1000ms. Each has a test,
and the setupFiles check was observed to fire against a real deletion.

Three stale comments the change had invalidated: main.yml still counted four
durable guards, PreviewAnnotations.stories.tsx still said the storybook project
had no setupFiles, and the new AGENTS.md rule read as though no Group F row had
been settled when this PR settles F6. browser-timeouts.test.mjs gains the
purpose header this repo's files carry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-12 10:46:28 -04:00
commit 7ee8a14e56
2 changed files with 376 additions and 81 deletions

View file

@ -4295,58 +4295,67 @@ describe("App dedupes concurrent OAuth clears that share a storage key (#2217)",
);
}
// Three full modal interaction sequences against the whole App tree. This
// used to carry `{ timeout: 20000 }` because the project ran on Vitest's 5s
// default and this test tripped it under load; the project now states 15000
// itself (#2323), so the raise has nothing left to say that the shared value
// does not.
it("suppresses a second clear for another entry with the same URL, and allows one after it settles", async () => {
// `delay: null` drops userEvent's inter-event waits, which dominate here.
const user = userEvent.setup({ delay: null });
renderWithMantine(<App />);
// Three full modal interaction sequences against the whole App tree — three
// times the work of any other test in this file, and the only one here that
// ever tripped the old 5s default under load. It keeps its own budget after
// #2323 rather than folding into the shared 15000: this is the "genuinely
// different work" exception that change establishes, and the 20000 was a
// value somebody had already chosen against an observed failure. Lowering it
// to the shared ceiling would trade a measured number for a general one and
// could reintroduce the flake (Copilot).
const THREE_MODAL_SEQUENCE_MS = 20_000;
await user.click(screen.getByText("connect"));
await waitFor(() => expect(clientInstances).toHaveLength(1));
// The instances are typed `EventTarget`; an intersection names the
// test-only spy without erasing that (Copilot).
const client = clientInstances[0] as EventTarget & {
clearOAuthTokens: ReturnType<typeof vi.fn>;
};
it(
"suppresses a second clear for another entry with the same URL, and allows one after it settles",
async () => {
// `delay: null` drops userEvent's inter-event waits, which dominate here.
const user = userEvent.setup({ delay: null });
renderWithMantine(<App />);
// Hold the first clear open, as a pending RFC 7009 request would.
let settle: (v: { status: string; reason: string }) => void = () => {};
client.clearOAuthTokens.mockImplementation(
() =>
new Promise((resolve) => {
settle = resolve as typeof settle;
}),
);
await user.click(screen.getByText("connect"));
await waitFor(() => expect(clientInstances).toHaveLength(1));
// The instances are typed `EventTarget`; an intersection names the
// test-only spy without erasing that (Copilot).
const client = clientInstances[0] as EventTarget & {
clearOAuthTokens: ReturnType<typeof vi.fn>;
};
await clearFromSettings(user, "open-settings");
await waitFor(() =>
expect(client.clearOAuthTokens).toHaveBeenCalledTimes(1),
);
// Hold the first clear open, as a pending RFC 7009 request would.
let settle: (v: { status: string; reason: string }) => void = () => {};
client.clearOAuthTokens.mockImplementation(
() =>
new Promise((resolve) => {
settle = resolve as typeof settle;
}),
);
// Entry B: a different catalog id, the same OAuth storage key. Both clears
// route through the live client precisely because they share that key, so
// an id-keyed guard would let this second one straight through.
await clearFromSettings(user, "open-settings-b");
expect(client.clearOAuthTokens).toHaveBeenCalledTimes(1);
await clearFromSettings(user, "open-settings");
await waitFor(() =>
expect(client.clearOAuthTokens).toHaveBeenCalledTimes(1),
);
// Suppression is for the duration of the in-flight clear only — the key
// must be released when it settles, or the control is dead for the rest of
// the session.
await act(async () => {
settle({ status: "skipped", reason: "no_endpoint" });
await Promise.resolve();
});
client.clearOAuthTokens.mockResolvedValue({
status: "skipped",
reason: "no_endpoint",
});
await clearFromSettings(user, "open-settings-b");
await waitFor(() =>
expect(client.clearOAuthTokens).toHaveBeenCalledTimes(2),
);
});
// Entry B: a different catalog id, the same OAuth storage key. Both clears
// route through the live client precisely because they share that key, so
// an id-keyed guard would let this second one straight through.
await clearFromSettings(user, "open-settings-b");
expect(client.clearOAuthTokens).toHaveBeenCalledTimes(1);
// Suppression is for the duration of the in-flight clear only — the key
// must be released when it settles, or the control is dead for the rest of
// the session.
await act(async () => {
settle({ status: "skipped", reason: "no_endpoint" });
await Promise.resolve();
});
client.clearOAuthTokens.mockResolvedValue({
status: "skipped",
reason: "no_endpoint",
});
await clearFromSettings(user, "open-settings-b");
await waitFor(() =>
expect(client.clearOAuthTokens).toHaveBeenCalledTimes(2),
);
},
THREE_MODAL_SEQUENCE_MS,
);
});

View file

@ -8,8 +8,15 @@ import { theme } from "../theme/theme";
// `.storybook/vitest.setup.ts` used to call `setProjectAnnotations([...])` to
// hand the preview annotations to the Storybook vitest project. Since Storybook
// 10.3 `@storybook/addon-vitest` applies them automatically — and *skips* doing
// so when it finds such a setup file — so the file was removed and the
// `setupFiles` entry dropped from the `storybook` project in `vite.config.ts`.
// so when it finds a setup file that is both inside `configDir` and calls
// `setProjectAnnotations` — so that file was removed.
//
// The project does have a `setupFiles` entry again (#2323), and it is a
// different thing: `src/test/storybookSetup.ts` configures Testing Library's
// `asyncUtilTimeout` and nothing else. It satisfies *neither* half of the
// condition above — it lives outside `.storybook/` and names no annotations —
// so the automatic path is still the one in use, and this guard still asserts
// that it worked.
//
// A green suite is not evidence that the automatic path works. Two things the
// setup file used to provision are silently losable: