test: tests that assumed an idle machine wait for what they assert, or carry a budget for their work
Under a busy machine (the gate's lanes side by side, 12 busy cores) these went red at random: a press landed while the button was still busy, an answer was settled before it was asked for, a page grew past one wait's budget, a lazy chunk was transformed inside a 1 s wait, focus was taken a frame after the press. They now wait for the condition they depend on, preload the chunks they open, and the tests that do heavy work (whole-repo scans, the type-aware linter, module resets) carry an explicit budget naming that work. The whole suite four times under 12 busy-loop processes: red 4/4 before, 0/4 after.
This commit is contained in:
@@ -33,13 +33,18 @@ beforeAll(async () => {
|
||||
await import("./api/core");
|
||||
}, 60_000);
|
||||
|
||||
// Warm or not, each case still loads and evaluates the modules it imports afresh after its module
|
||||
// reset: tens of ms idle, but the same reload put sibling suites past 5 s when the full suite's
|
||||
// workers share a busy machine.
|
||||
const CASE_BUDGET = 30_000;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
vi.resetModules(); // both stores load at import: each test needs a fresh one
|
||||
});
|
||||
|
||||
describe("stores read at import, in a tab not yet pinned", () => {
|
||||
describe("stores read at import, in a tab not yet pinned", { timeout: CASE_BUDGET }, () => {
|
||||
it("keep the account's notification history and settings once the tab is pinned", async () => {
|
||||
localStorage.setItem("siftlode.notifications.5", JSON.stringify([entry(7, "older")]));
|
||||
localStorage.setItem("siftlode.notifSettings.5", JSON.stringify({ durationMs: 0 }));
|
||||
@@ -65,7 +70,7 @@ describe("stores read at import, in a tab not yet pinned", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("a pinned tab pinned to another account", () => {
|
||||
describe("a pinned tab pinned to another account", { timeout: CASE_BUDGET }, () => {
|
||||
it("leaves the previous account's notices with that account", async () => {
|
||||
// BootGate moves a tab whose account was signed out elsewhere onto the one the server answers as.
|
||||
sessionStorage.setItem("siftlode.activeAccount", "4");
|
||||
|
||||
@@ -71,7 +71,9 @@ beforeAll(async () => {
|
||||
slices[name] = slice;
|
||||
}
|
||||
api = ((await import("../api")) as { api: Slice }).api;
|
||||
});
|
||||
// The imports above transform the whole api graph and i18n with every locale: under a ~0.6 s idle,
|
||||
// but past the 10 s hook budget when the full suite's workers share a busy machine.
|
||||
}, 60_000);
|
||||
|
||||
describe("api façade is a collision-free spread of domain slices", () => {
|
||||
it("defines no method name in more than one slice (object spread would silently drop one)", () => {
|
||||
|
||||
@@ -13,19 +13,25 @@ async function hits(source: string, filePath = "src/components/__probe.tsx"): Pr
|
||||
}
|
||||
|
||||
describe("a hand-rolled button", () => {
|
||||
it("is refused in a feature component, the lab and lib, raw or focus-safe", async () => {
|
||||
const raw = 'export const A = () => <button className="px-3">x</button>;\n';
|
||||
expect(await hits(raw)).toBe(1);
|
||||
expect(await hits(raw, "src/design-lab/__probe.tsx")).toBe(1);
|
||||
expect(await hits(raw, "src/lib/__probe.tsx")).toBe(1);
|
||||
// …and in a ui/ COMPOSITE: a pager or a side panel is built from the primitives too.
|
||||
expect(await hits(raw, "src/components/ui/Pager.tsx")).toBe(1);
|
||||
expect(
|
||||
await hits(
|
||||
'import { FocusSafeButton } from "./ui/FocusSafeButton";\nexport const B = () => <FocusSafeButton>x</FocusSafeButton>;\n',
|
||||
),
|
||||
).toBe(1);
|
||||
});
|
||||
// Each new path's first lint makes the type-aware project service load it (0.4–1 s apiece, a repeat
|
||||
// ~0): this case lints four, ~1.4 s idle, past 5 s on a busy machine.
|
||||
it(
|
||||
"is refused in a feature component, the lab and lib, raw or focus-safe",
|
||||
{ timeout: 30_000 },
|
||||
async () => {
|
||||
const raw = 'export const A = () => <button className="px-3">x</button>;\n';
|
||||
expect(await hits(raw)).toBe(1);
|
||||
expect(await hits(raw, "src/design-lab/__probe.tsx")).toBe(1);
|
||||
expect(await hits(raw, "src/lib/__probe.tsx")).toBe(1);
|
||||
// …and in a ui/ COMPOSITE: a pager or a side panel is built from the primitives too.
|
||||
expect(await hits(raw, "src/components/ui/Pager.tsx")).toBe(1);
|
||||
expect(
|
||||
await hits(
|
||||
'import { FocusSafeButton } from "./ui/FocusSafeButton";\nexport const B = () => <FocusSafeButton>x</FocusSafeButton>;\n',
|
||||
),
|
||||
).toBe(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("is allowed only in a control primitive, and the primitives pass everywhere", async () => {
|
||||
for (const file of ["IconButton.tsx", "SegmentedToggle.tsx", "AnchoredPopover.tsx"])
|
||||
|
||||
@@ -33,6 +33,11 @@ beforeAll(async () => {
|
||||
await import("./api");
|
||||
}, 60_000);
|
||||
|
||||
// Warm or not, each case still loads and evaluates the modules it imports afresh after its module
|
||||
// reset: tens of ms idle, but the same reload put sibling suites past 5 s when the full suite's
|
||||
// workers share a busy machine.
|
||||
const CASE_BUDGET = 30_000;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.resetModules();
|
||||
@@ -69,7 +74,7 @@ const raised = (level: "DEBUG" | "TRACE", hours = 24) => ({
|
||||
const codes = (batch: unknown[] | undefined) =>
|
||||
(batch ?? []).map((e) => (e as { event: string }).event);
|
||||
|
||||
describe("diagnostics level", () => {
|
||||
describe("diagnostics level", { timeout: CASE_BUDGET }, () => {
|
||||
it("the base level at boot sets INFO, streams nothing and logs nothing", async () => {
|
||||
const d = await fresh();
|
||||
d.applyDiagnostics(
|
||||
|
||||
@@ -54,6 +54,11 @@ beforeAll(async () => {
|
||||
}, 60_000);
|
||||
|
||||
let current: typeof import("./diagnostics") | null = null;
|
||||
// Warm or not, each case still loads and evaluates the modules it imports afresh after its module
|
||||
// reset: tens of ms idle, but the same reload put sibling suites past 5 s when the full suite's
|
||||
// workers share a busy machine.
|
||||
const CASE_BUDGET = 30_000;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.resetModules();
|
||||
@@ -73,7 +78,7 @@ async function fresh() {
|
||||
|
||||
const flat = () => sent.flat();
|
||||
|
||||
describe("a debug session applied to the tab", () => {
|
||||
describe("a debug session applied to the tab", { timeout: CASE_BUDGET }, () => {
|
||||
it("raises the base level to its own, and tags and streams what it keeps", async () => {
|
||||
const d = await fresh();
|
||||
d.applyDiagnostics(BASE, recording("DEBUG"), () => {});
|
||||
|
||||
@@ -14,16 +14,22 @@ async function hits(source: string, filePath = "src/components/__probe.tsx"): Pr
|
||||
}
|
||||
|
||||
describe("a hand-rolled field", () => {
|
||||
it("is refused anywhere outside the field primitives, text box or native control", async () => {
|
||||
const text = 'export const A = () => <input className="border px-2" />;\n';
|
||||
expect(await hits(text)).toBe(1);
|
||||
expect(await hits(text, "src/design-lab/__probe.tsx")).toBe(1);
|
||||
// …and in a ui/ composite: the pager's page box is an Input too.
|
||||
expect(await hits(text, "src/components/ui/Pager.tsx")).toBe(1);
|
||||
expect(await hits("export const B = () => <textarea />;\n")).toBe(1);
|
||||
expect(await hits('export const C = () => <input type="checkbox" />;\n')).toBe(1);
|
||||
expect(await hits('export const D = () => <input type="range" />;\n')).toBe(1);
|
||||
});
|
||||
// Each new path's first lint makes the type-aware project service load it (0.4–1 s apiece, a repeat
|
||||
// ~0): this case lints three, ~1 s idle, past 5 s on a busy machine.
|
||||
it(
|
||||
"is refused anywhere outside the field primitives, text box or native control",
|
||||
{ timeout: 30_000 },
|
||||
async () => {
|
||||
const text = 'export const A = () => <input className="border px-2" />;\n';
|
||||
expect(await hits(text)).toBe(1);
|
||||
expect(await hits(text, "src/design-lab/__probe.tsx")).toBe(1);
|
||||
// …and in a ui/ composite: the pager's page box is an Input too.
|
||||
expect(await hits(text, "src/components/ui/Pager.tsx")).toBe(1);
|
||||
expect(await hits("export const B = () => <textarea />;\n")).toBe(1);
|
||||
expect(await hits('export const C = () => <input type="checkbox" />;\n')).toBe(1);
|
||||
expect(await hits('export const D = () => <input type="range" />;\n')).toBe(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("passes in a field primitive, for a file picker, and for the primitives themselves", async () => {
|
||||
for (const file of ["Input.tsx", "NativeControls.tsx"])
|
||||
|
||||
@@ -103,7 +103,8 @@ describe("plan codes", () => {
|
||||
expect(pattern("PLAN_CODE")).toBe(PLAN_CODES.map(js).join("|"));
|
||||
});
|
||||
|
||||
it("are not cited in the code", () => {
|
||||
// Reads every tracked and new file of the repo: ~0.6 s idle, past 5 s on a busy machine's disk and CPU.
|
||||
it("are not cited in the code", { timeout: 30_000 }, () => {
|
||||
const files = tracked();
|
||||
expect(files.length).toBeGreaterThan(1000); // a scan that reads nothing is green over nothing
|
||||
const hits = files.flatMap((f) =>
|
||||
|
||||
@@ -38,7 +38,8 @@ function files(dir: string): string[] {
|
||||
}
|
||||
|
||||
describe("removed features stay removed", () => {
|
||||
it("leaves no trace of the demo account", () => {
|
||||
// Reads every source file of the repo: ~0.4 s idle, past 5 s on a busy machine's disk and CPU.
|
||||
it("leaves no trace of the demo account", { timeout: 30_000 }, () => {
|
||||
const hits = ROOTS.flatMap(files)
|
||||
.filter((f) => !ALLOWED.has(f))
|
||||
.flatMap((f) =>
|
||||
|
||||
@@ -18,6 +18,11 @@ beforeAll(async () => {
|
||||
await import("./useVerifyEntry");
|
||||
}, 60_000);
|
||||
|
||||
// Warm or not, each case still loads and evaluates the hook's graph afresh after its module reset:
|
||||
// ~60 ms idle, past 5 s when the full suite's workers share a busy machine — and a case cut off
|
||||
// mid-mount leaves its render behind for the next one to trip over.
|
||||
const CASE_BUDGET = 30_000;
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.resetModules();
|
||||
@@ -52,7 +57,7 @@ async function mountAt(url: string, verifyEmail: ReturnType<typeof vi.fn>) {
|
||||
return () => screen.getByTestId("outcome").textContent;
|
||||
}
|
||||
|
||||
describe("confirming an emailed verification link", () => {
|
||||
describe("confirming an emailed verification link", { timeout: CASE_BUDGET }, () => {
|
||||
it("posts the fragment token and reports the confirmed address", async () => {
|
||||
const verifyEmail = vi.fn().mockResolvedValue({ ok: true, email: "someone@example.com" });
|
||||
|
||||
@@ -253,52 +258,56 @@ describe("confirming an emailed verification link", () => {
|
||||
// already runs the app to a URL differing only in the fragment is a same-document navigation, so
|
||||
// nothing reloads, the import-time snapshot never sees the token, and it sat unconsumed in the
|
||||
// address bar — the same silence the sprint set out to remove, wearing a different hat.
|
||||
describe("a verification link that arrives as a fragment-only navigation", () => {
|
||||
it("consumes a token delivered by hashchange, and clears it from the address bar", async () => {
|
||||
const verifyEmail = vi.fn().mockResolvedValue({ ok: true, email: "pasted@example.com" });
|
||||
const outcome = await mountAt("/", verifyEmail);
|
||||
expect(verifyEmail).not.toHaveBeenCalled();
|
||||
describe(
|
||||
"a verification link that arrives as a fragment-only navigation",
|
||||
{ timeout: CASE_BUDGET },
|
||||
() => {
|
||||
it("consumes a token delivered by hashchange, and clears it from the address bar", async () => {
|
||||
const verifyEmail = vi.fn().mockResolvedValue({ ok: true, email: "pasted@example.com" });
|
||||
const outcome = await mountAt("/", verifyEmail);
|
||||
expect(verifyEmail).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
window.history.replaceState(null, "", "/#verify=pasted-token");
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
});
|
||||
|
||||
expect(verifyEmail).toHaveBeenCalledWith("pasted-token");
|
||||
expect(outcome()).toBe("ok:pasted@example.com");
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
|
||||
it("replaces a standing outcome rather than leaving the previous banner up", async () => {
|
||||
const verifyEmail = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ ok: true, email: "first@example.com" })
|
||||
// The server's own refusal, not a bare Error: only a 400 carrying {code:"verify"} means the
|
||||
// token is spent, and this case is about the SECOND outcome replacing the first.
|
||||
.mockImplementationOnce(async () => {
|
||||
const { HttpError } = await import("./api/core");
|
||||
throw new HttpError(400, "verify", { code: "verify" });
|
||||
await act(async () => {
|
||||
window.history.replaceState(null, "", "/#verify=pasted-token");
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
});
|
||||
const outcome = await mountAt("/#verify=first-token", verifyEmail);
|
||||
expect(outcome()).toBe("ok:first@example.com");
|
||||
|
||||
await act(async () => {
|
||||
window.history.replaceState(null, "", "/#verify=spent-token");
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
expect(verifyEmail).toHaveBeenCalledWith("pasted-token");
|
||||
expect(outcome()).toBe("ok:pasted@example.com");
|
||||
expect(window.location.hash).toBe("");
|
||||
});
|
||||
|
||||
expect(outcome()).toBe("invalid:-");
|
||||
});
|
||||
it("replaces a standing outcome rather than leaving the previous banner up", async () => {
|
||||
const verifyEmail = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ ok: true, email: "first@example.com" })
|
||||
// The server's own refusal, not a bare Error: only a 400 carrying {code:"verify"} means the
|
||||
// token is spent, and this case is about the SECOND outcome replacing the first.
|
||||
.mockImplementationOnce(async () => {
|
||||
const { HttpError } = await import("./api/core");
|
||||
throw new HttpError(400, "verify", { code: "verify" });
|
||||
});
|
||||
const outcome = await mountAt("/#verify=first-token", verifyEmail);
|
||||
expect(outcome()).toBe("ok:first@example.com");
|
||||
|
||||
it("posts a given token once even if the same fragment arrives again", async () => {
|
||||
const verifyEmail = vi.fn().mockResolvedValue({ ok: true, email: "same@example.com" });
|
||||
await mountAt("/#verify=same-token", verifyEmail);
|
||||
await act(async () => {
|
||||
window.history.replaceState(null, "", "/#verify=spent-token");
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
window.history.replaceState(null, "", "/#verify=same-token");
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
expect(outcome()).toBe("invalid:-");
|
||||
});
|
||||
|
||||
expect(verifyEmail).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
it("posts a given token once even if the same fragment arrives again", async () => {
|
||||
const verifyEmail = vi.fn().mockResolvedValue({ ok: true, email: "same@example.com" });
|
||||
await mountAt("/#verify=same-token", verifyEmail);
|
||||
|
||||
await act(async () => {
|
||||
window.history.replaceState(null, "", "/#verify=same-token");
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
});
|
||||
|
||||
expect(verifyEmail).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user