import { test, expect, type Page } from "@playwright/test"; /** * Inject mock data into the Zustand focus store via the browser console. * * Since we run against the Vite dev server (no Tauri runtime), * we seed stores directly to test the real React rendering pipeline. */ async function seedFocusStore(page: Page): Promise { await page.evaluate(() => { // Access Zustand stores via their internal getState/setState // The stores are module-scoped singletons, accessible from window.__ZUSTAND_STORES__ // We expose them in development via a small shim in main.tsx const w = window as Record; const focusStore = w.__MC_FOCUS_STORE__ as { setState: (state: Record) => void; }; if (focusStore) { focusStore.setState({ current: { id: "mr_review:platform/core:847", title: "Fix authentication token refresh logic", type: "mr_review", project: "platform/core", url: "https://gitlab.com/platform/core/-/merge_requests/847", iid: 847, updatedAt: new Date().toISOString(), contextQuote: "The refresh token logic has a race condition", requestedBy: "johndoe", }, queue: [ { id: "issue:platform/core:42", title: "Users unable to login after password reset", type: "issue", project: "platform/core", url: "https://gitlab.com/platform/core/-/issues/42", iid: 42, updatedAt: new Date( Date.now() - 5 * 24 * 60 * 60 * 1000 ).toISOString(), contextQuote: null, requestedBy: null, }, { id: "mr_review:platform/api:101", title: "Add rate limiting to public endpoints", type: "mr_review", project: "platform/api", url: "https://gitlab.com/platform/api/-/merge_requests/101", iid: 101, updatedAt: new Date( Date.now() - 10 * 24 * 60 * 60 * 1000 ).toISOString(), contextQuote: null, requestedBy: "alice", }, ], isLoading: false, error: null, }); } }); } /** * Expose Zustand stores on the window object for E2E test seeding. * This is done by evaluating a script that patches the store modules. */ async function exposeStores(page: Page): Promise { await page.evaluate(() => { // The stores are ES module singletons. We need to wait for them // to be available. In dev mode, Vite's HMR keeps them accessible. // We use a polling approach to find them. return new Promise((resolve) => { const check = (): void => { const w = window as Record; if (w.__MC_FOCUS_STORE__) { resolve(); } else { setTimeout(check, 50); } }; check(); }); }); } test.describe("Mission Control E2E", () => { test.beforeEach(async ({ page }) => { await page.goto("/"); // Wait for React to mount await page.waitForSelector("nav"); }); test.describe("Focus View", () => { test("shows empty state when no items", async ({ page }) => { await expect(page.getByText("All Clear")).toBeVisible(); await expect( page.getByText("Nothing needs your attention right now") ).toBeVisible(); }); test("shows navigation tabs", async ({ page }) => { await expect(page.getByRole("button", { name: "Focus" })).toBeVisible(); await expect(page.getByRole("button", { name: "Queue" })).toBeVisible(); await expect(page.getByRole("button", { name: "Inbox" })).toBeVisible(); }); test("shows focus item when store has data", async ({ page }) => { try { await exposeStores(page); await seedFocusStore(page); } catch { // Stores not exposed -- skip this test in environments without the shim test.skip(); return; } await expect( page.getByText("Fix authentication token refresh logic") ).toBeVisible(); await expect(page.getByText("MR REVIEW")).toBeVisible(); await expect(page.getByText("!847 in platform/core")).toBeVisible(); }); test("shows action buttons when item is focused", async ({ page }) => { try { await exposeStores(page); await seedFocusStore(page); } catch { test.skip(); return; } await expect(page.getByText("Start")).toBeVisible(); await expect(page.getByText("1 hour")).toBeVisible(); await expect(page.getByText("Tomorrow")).toBeVisible(); await expect(page.getByText("Skip")).toBeVisible(); }); }); test.describe("Navigation", () => { test("switches between Focus and Queue views", async ({ page }) => { // Start in Focus view await expect(page.getByText("All Clear")).toBeVisible(); // Click Queue tab await page.getByRole("button", { name: "Queue" }).click(); await expect(page.getByText("No items in the queue")).toBeVisible(); // Click Focus tab await page.getByRole("button", { name: "Focus" }).click(); await expect(page.getByText("All Clear")).toBeVisible(); }); test("shows Inbox placeholder", async ({ page }) => { await page.getByRole("button", { name: "Inbox" }).click(); await expect(page.getByText("Inbox view coming in Phase 4b")).toBeVisible(); }); test("Queue tab shows item count badge when store has data", async ({ page, }) => { try { await exposeStores(page); await seedFocusStore(page); } catch { test.skip(); return; } // 1 current + 2 queue = 3 await expect(page.getByText("3")).toBeVisible(); }); }); test.describe("Queue View", () => { test("shows items grouped by type when store has data", async ({ page, }) => { try { await exposeStores(page); await seedFocusStore(page); } catch { test.skip(); return; } await page.getByRole("button", { name: "Queue" }).click(); // Should have a Reviews section with 2 items await expect(page.getByText("REVIEWS (2)")).toBeVisible(); // Should have an Issues section with 1 item await expect(page.getByText("ISSUES (1)")).toBeVisible(); }); test("clicking item switches to Focus view", async ({ page }) => { try { await exposeStores(page); await seedFocusStore(page); } catch { test.skip(); return; } await page.getByRole("button", { name: "Queue" }).click(); // Click the issue item await page .getByText("Users unable to login after password reset") .click(); // Should switch to focus view -- wait for the Queue header to disappear await expect(page.getByText("ISSUES (1)")).not.toBeVisible(); // The clicked item should now be THE ONE THING await expect( page.getByRole("heading", { name: "Users unable to login after password reset", }) ).toBeVisible(); }); }); test.describe("Dark mode", () => { test("page has dark background", async ({ page }) => { const body = page.locator("body"); await expect(body).toHaveClass(/bg-surface/); }); test("HTML element has dark class", async ({ page }) => { const html = page.locator("html"); await expect(html).toHaveClass(/dark/); }); }); });