fix(e24): review round 2 — a buried history entry, a stale hero, and a spinner that was not ours
Nine of the round's ten surviving findings. The tenth (a failed shelf reorder leaving an order the server rejected on screen) is on ROADMAP.md: its obvious remedy reintroduces the overlapping-move defect the code already documents, so it wants a design, not a review edit. - `navigateOverOverlays` buried the live overlay entries before knowing whether the navigation pushed one. `setPage` no-ops when the page is already current and Home is the default landing page, so Shift+W over a player ON Home marked its entry buried under a push that never happened: the unmount consumed the mark instead of reclaiming the entry, and the next browser Back was a press that did nothing. `setPage` now reports whether it navigated, and one `buryUnder` rule serves both entry points — `closeThenNavigate` takes the same no-op navigation from a palette command for the page you are already on. - The hero could answer a new time budget with the previous one's pick. The chip row resets seed/seen/press but cannot reset an answer that already resolved, and `keepPreviousData` chained through the disabled seed-0 query — so a reroll under "10 perc" showed the film picked for "Bármennyi" while it loaded, and kept it if the request failed. `lastPick` now carries the server's own budget echo, and the placeholder is scoped to the budget. - The reroll button spun and swallowed clicks after mark-watched, bookmark, pin and shelf moves: the pick key sits under the invalidated `discover` prefix on purpose, so raw `isFetching` was true for background refetches. It now means "a press of mine is outstanding" — which still covers the first press, where there is no placeholder. - Failed writes on Home toasted the page's LOAD-failure string over a fully loaded page; they get their own key (HU+EN). The empty-state reroll had no `loading`, so the press was unacknowledged and a second click skipped a tier. `danger` moved into the shared unpin row so the same command is not red on the shelf and neutral in the rail below it. - Two comments claimed things the code does not do: the `exclude` cap guards no "unbounded scan" (it never reaches SQL), and the route test that named the cap sends three keys. Tests: the burial rule gets its own file (it is the one assertion that lets a reconcile issue a real programmatic back()); App's keymap wiring gets a source contract, because dropping it left all 818 tests green; `add_pin`'s IntegrityError arm was reached by nothing. Every new test mutation-checked red, both edges where the predicate has two. Gate: ruff, ruff format, 3x tsc, eslint (0 errors), prettier, knip, vitest 822, pytest 1005.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// ONE test, ONE file, for the same reason CommandPalette.ordering.dom.test.tsx has its own: this is
|
||||
// the only assertion in the suite that lets a reconcile issue a REAL programmatic history.back(),
|
||||
// and history.ts's overlay bookkeeping is module-global. history.dom.test.ts states its own
|
||||
// invariant — every layer there is closed by a genuine Back, so no programmatic back() runs and the
|
||||
// shared state stays clean between its tests — and this test would break it.
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import { navigateOverOverlays, useBackToClose } from "./history";
|
||||
|
||||
const settle = () =>
|
||||
act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
describe("navigateOverOverlays · burying a navigation that never happened", () => {
|
||||
// The round-2 defect. `navigateOverOverlays` marked every live overlay entry buried BEFORE it knew
|
||||
// whether the navigation pushed anything, and `setPage` early-returns when the requested page is
|
||||
// already current. Home is the default landing page, so Shift+W pressed while already on Home with
|
||||
// the player open buried the player's entry under a push that never happened: its unmount then
|
||||
// consumed the mark instead of reclaiming the entry with history.back(), leaving an orphan. The
|
||||
// user's next browser Back popped onto an identical `sfPage` with an empty overlay stack — a press
|
||||
// that changed nothing on screen, one more per play → Shift+W → close cycle.
|
||||
it("leaves the overlay's entry reclaimable when the navigation reports it pushed nothing", async () => {
|
||||
const overlay = renderHook(() => useBackToClose(() => {}));
|
||||
await settle();
|
||||
const back = vi.spyOn(window.history, "back");
|
||||
try {
|
||||
// What `setPage` returns from its early return: asked for the page we are already on.
|
||||
const navigate = vi.fn(() => false);
|
||||
act(() => navigateOverOverlays(navigate));
|
||||
expect(navigate).toHaveBeenCalledTimes(1);
|
||||
|
||||
overlay.unmount();
|
||||
await settle();
|
||||
// Nothing was pushed over it, so the entry is still ours — reclaimed, not silently orphaned.
|
||||
expect(back).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
back.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user