ui/phase-26: history mode (turn navigator + read-only banner)
Split GameStateStore into currentTurn (server's latest) and viewedTurn (displayed snapshot) so history excursions don't corrupt the resume bookmark or the live-turn bound. Add viewTurn / returnToCurrent / historyMode rune, plus a game-history cache namespace that stores past-turn reports for fast re-entry. OrderDraftStore.bindClient takes a getHistoryMode getter and short-circuits add / remove / move while the user is viewing a past turn; RenderedReportSource skips the order overlay in the same case. Header replaces the static "turn N" with a clickable triplet (TurnNavigator), the layout mounts HistoryBanner under the header, and visibility-refresh is a no-op while history is active. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Phase 26 history-banner component tests. The banner is mounted by
|
||||
// the in-game shell layout directly under the header; it renders
|
||||
// only when `gameState.historyMode === true` and carries a return
|
||||
// action delegating to `gameState.returnToCurrent()`.
|
||||
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { fireEvent, render } from "@testing-library/svelte";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { i18n } from "../src/lib/i18n/index.svelte";
|
||||
import HistoryBanner from "../src/lib/header/history-banner.svelte";
|
||||
import {
|
||||
GAME_STATE_CONTEXT_KEY,
|
||||
GameStateStore,
|
||||
} from "../src/lib/game-state.svelte";
|
||||
|
||||
function buildStore(opts: {
|
||||
currentTurn: number;
|
||||
viewedTurn: number;
|
||||
}): GameStateStore {
|
||||
const store = new GameStateStore();
|
||||
store.currentTurn = opts.currentTurn;
|
||||
store.viewedTurn = opts.viewedTurn;
|
||||
store.status = "ready";
|
||||
return store;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
i18n.resetForTests("en");
|
||||
});
|
||||
|
||||
describe("HistoryBanner", () => {
|
||||
test("is hidden in live mode", () => {
|
||||
const store = buildStore({ currentTurn: 5, viewedTurn: 5 });
|
||||
const ui = render(HistoryBanner, {
|
||||
context: new Map([[GAME_STATE_CONTEXT_KEY, store]]),
|
||||
});
|
||||
expect(ui.queryByTestId("history-banner")).toBeNull();
|
||||
});
|
||||
|
||||
test("is visible in history mode with the viewed turn interpolated", () => {
|
||||
const store = buildStore({ currentTurn: 5, viewedTurn: 2 });
|
||||
const ui = render(HistoryBanner, {
|
||||
context: new Map([[GAME_STATE_CONTEXT_KEY, store]]),
|
||||
});
|
||||
const banner = ui.getByTestId("history-banner");
|
||||
expect(banner).toBeInTheDocument();
|
||||
expect(banner).toHaveTextContent("Viewing turn 2");
|
||||
expect(banner).toHaveTextContent("read-only");
|
||||
});
|
||||
|
||||
test("return action delegates to gameState.returnToCurrent", async () => {
|
||||
const store = buildStore({ currentTurn: 5, viewedTurn: 2 });
|
||||
const returnToCurrent = vi
|
||||
.spyOn(store, "returnToCurrent")
|
||||
.mockResolvedValue(undefined);
|
||||
const ui = render(HistoryBanner, {
|
||||
context: new Map([[GAME_STATE_CONTEXT_KEY, store]]),
|
||||
});
|
||||
await fireEvent.click(ui.getByTestId("history-banner-return"));
|
||||
expect(returnToCurrent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user