// Component coverage for the Phase 14 order tab. The Submit button // has been retired — every successful `add` / `remove` triggers // `OrderDraftStore.scheduleSync`, so the tab is mostly a status // surface. Tests assert the per-row status badge transitions and // the bottom-bar sync state. import "@testing-library/jest-dom/vitest"; import "fake-indexeddb/auto"; import { fireEvent, render, waitFor } from "@testing-library/svelte"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import OrderTab from "../src/lib/sidebar/order-tab.svelte"; import { ORDER_DRAFT_CONTEXT_KEY, OrderDraftStore, } from "../src/sync/order-draft.svelte"; import { i18n } from "../src/lib/i18n/index.svelte"; import type { OrderCommand } from "../src/sync/order-types"; import { IDBCache } from "../src/platform/store/idb-cache"; import { openGalaxyDB } from "../src/platform/store/idb"; import { recordingClient } from "./helpers/fake-order-client"; const GAME_ID = "11111111-2222-3333-4444-555555555555"; let db: Awaited>; let dbName: string; beforeEach(async () => { dbName = `galaxy-order-tab-${crypto.randomUUID()}`; db = await openGalaxyDB(dbName); i18n.resetForTests("en"); }); afterEach(async () => { db.close(); await new Promise((resolve) => { const req = indexedDB.deleteDatabase(dbName); req.onsuccess = () => resolve(); req.onerror = () => resolve(); req.onblocked = () => resolve(); }); }); async function makeDraft( commands: OrderCommand[], ): Promise<{ draft: OrderDraftStore; context: Map }> { const cache = new IDBCache(db); const draft = new OrderDraftStore(); await draft.init({ cache, gameId: GAME_ID }); for (const cmd of commands) { await draft.add(cmd); } const context = new Map([ [ORDER_DRAFT_CONTEXT_KEY, draft], ]); return { draft, context }; } describe("order-tab", () => { test("renders the empty state when the draft has no commands", async () => { const { draft, context } = await makeDraft([]); const ui = render(OrderTab, { context }); expect(ui.getByTestId("order-empty")).toBeVisible(); expect(ui.queryByTestId("order-list")).toBeNull(); // The sync bar still renders so the user can see the // idle / synced / error transitions. expect(ui.getByTestId("order-sync")).toBeVisible(); draft.dispose(); }); test("invalid command shows the invalid status badge", async () => { const { draft, context } = await makeDraft([ { kind: "planetRename", id: "id-1", planetNumber: 1, name: "" }, ]); const ui = render(OrderTab, { context }); expect(ui.getByTestId("order-command-status-0")).toHaveTextContent( "invalid", ); draft.dispose(); }); test("auto-sync flips the row to applied and the sync bar to synced", async () => { const handle = recordingClient(GAME_ID, "ok"); const { draft, context } = await makeDraft([]); draft.bindClient(handle.client); const ui = render(OrderTab, { context }); await draft.add({ kind: "planetRename", id: "id-1", planetNumber: 1, name: "Earth", }); await handle.waitForCalls(1); await waitFor(() => { expect(ui.getByTestId("order-command-status-0")).toHaveTextContent( "applied", ); }); await waitFor(() => { expect(ui.getByTestId("order-sync")).toHaveAttribute( "data-sync-status", "synced", ); }); expect(handle.calls).toHaveLength(1); expect(handle.calls[0]!.commandIds).toEqual(["id-1"]); draft.dispose(); }); test("removing the last command sends an empty cmd[] PUT", async () => { const handle = recordingClient(GAME_ID, "ok"); const { draft, context } = await makeDraft([]); draft.bindClient(handle.client); await draft.add({ kind: "planetRename", id: "id-1", planetNumber: 1, name: "Earth", }); await handle.waitForCalls(1); const ui = render(OrderTab, { context }); await fireEvent.click(ui.getByTestId("order-command-delete-0")); await handle.waitForCalls(2); expect(handle.calls[1]!.commandIds).toEqual([]); expect(draft.commands).toEqual([]); await waitFor(() => { expect(ui.getByTestId("order-empty")).toBeVisible(); }); draft.dispose(); }); test("non-ok response surfaces the sync error and a retry button", async () => { const handle = recordingClient(GAME_ID, "rejected"); const { draft, context } = await makeDraft([]); draft.bindClient(handle.client); const ui = render(OrderTab, { context }); await draft.add({ kind: "planetRename", id: "id-1", planetNumber: 1, name: "Earth", }); await handle.waitForCalls(1); await waitFor(() => { expect(ui.getByTestId("order-sync")).toHaveAttribute( "data-sync-status", "error", ); }); expect(ui.getByTestId("order-sync-retry")).toBeVisible(); expect(ui.getByTestId("order-command-status-0")).toHaveTextContent( "rejected", ); // Retry the call now that the fixture answers ok. handle.setOutcome("ok"); await fireEvent.click(ui.getByTestId("order-sync-retry")); await handle.waitForCalls(2); await waitFor(() => { expect(draft.statuses["id-1"]).toBe("applied"); }); await waitFor(() => { expect(ui.getByTestId("order-sync")).toHaveAttribute( "data-sync-status", "synced", ); }); draft.dispose(); }); });