test(canvas): cover /orgs 5s polling on in-flight orgs
The test docstring promised polling coverage but I'd only wired the
describe-block header, not the actual tests. Closing that gap — vitest
fake timers drive three cases:
- `provisioning` org → 2nd fetch fires after 5.1s advance
- all `running` → no 2nd fetch even after 10s advance
- `awaiting_payment` org, unmount before timer fires → no post-unmount
fetch (cleanup correctly clears the pollTimer)
The unmount case is the meaningful one: without it a fast nav-away
leaves the 5s interval chasing the CP forever. page.tsx L97-99 does
clear the timer; the test pins the contract.
Local baseline on origin/staging tip ede6597 + this branch:
canvas vitest: 50 files / 781 tests, all green (+3 vs prior commit)
canvas build: clean
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e70cd1c2aa
commit
4db8d30ece
@ -249,3 +249,120 @@ describe("/orgs — fetch includes credentials + timeout signal", () => {
|
||||
expect(callArgs![1].signal).toBeInstanceOf(AbortSignal);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Polling ──────────────────────────────────────────────────────────────────
|
||||
// page.tsx line 83-88: if any org is `provisioning` OR `awaiting_payment`,
|
||||
// schedule a 5s refresh so the user sees the state flip live after Stripe
|
||||
// Checkout returns. Cleanup must clear the timer on unmount; otherwise a
|
||||
// fast-nav-away leaves the interval firing against the CP indefinitely.
|
||||
|
||||
describe("/orgs — polling of in-flight orgs", () => {
|
||||
it("schedules a 5s refetch when at least one org is provisioning", async () => {
|
||||
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||
try {
|
||||
mockFetchSession.mockResolvedValue({ userId: "u-1" });
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
okJson({
|
||||
orgs: [
|
||||
{
|
||||
id: "o-1",
|
||||
slug: "acme",
|
||||
name: "Acme",
|
||||
plan: "pro",
|
||||
status: "provisioning",
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
// Second fetch (the poll refresh) returns a running org so we can
|
||||
// observe the state flip — and to let the test stop re-scheduling.
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
okJson({
|
||||
orgs: [
|
||||
{
|
||||
id: "o-1",
|
||||
slug: "acme",
|
||||
name: "Acme",
|
||||
plan: "pro",
|
||||
status: "running",
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
render(<OrgsPage />);
|
||||
// First fetch resolves
|
||||
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1));
|
||||
// Advance past the 5s scheduled refresh
|
||||
await vi.advanceTimersByTimeAsync(5_100);
|
||||
// Second fetch is the poll refresh
|
||||
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(2));
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("does NOT schedule a refetch when all orgs are running", async () => {
|
||||
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||
try {
|
||||
mockFetchSession.mockResolvedValue({ userId: "u-1" });
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
okJson({
|
||||
orgs: [
|
||||
{
|
||||
id: "o-1",
|
||||
slug: "acme",
|
||||
name: "Acme",
|
||||
plan: "pro",
|
||||
status: "running",
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
render(<OrgsPage />);
|
||||
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1));
|
||||
// Advance well past the 5s poll window — no second fetch must fire
|
||||
await vi.advanceTimersByTimeAsync(10_000);
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("clears the poll timer on unmount — no fetch after unmount", async () => {
|
||||
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||
try {
|
||||
mockFetchSession.mockResolvedValue({ userId: "u-1" });
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
okJson({
|
||||
orgs: [
|
||||
{
|
||||
id: "o-1",
|
||||
slug: "acme",
|
||||
name: "Acme",
|
||||
plan: "pro",
|
||||
status: "awaiting_payment",
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
const { unmount } = render(<OrgsPage />);
|
||||
await vi.waitFor(() => expect(mockFetch).toHaveBeenCalledTimes(1));
|
||||
// Tear down BEFORE the 5s timer fires
|
||||
unmount();
|
||||
await vi.advanceTimersByTimeAsync(10_000);
|
||||
// Fetch count must stay at 1 — the cleanup cleared the timer
|
||||
expect(mockFetch).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user