import { render, screen, fireEvent } from "@testing-library/react"; import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { MemoryRouter, Routes, Route } from "react-router-dom"; import { QueryClientProvider } from "@tanstack/react-query"; import { createTestQueryClient } from "../test/createTestQueryClient"; import { Shell } from "./Shell"; import { ChatRoute } from "../routes/ChatRoute"; import { ProposalsRoute } from "./proposals/ProposalsRoute"; import { setWorkbenchPref } from "./workbenchPrefs"; import type { RuntimeStatus } from "../types/api"; // Mock the API queries module vi.mock("../api/queries", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useRuntimeStatus: vi.fn(), }; }); import { useRuntimeStatus } from "../api/queries"; const mockStatus: RuntimeStatus = { backend: "numpy", git_revision: "abcdef1234567890", engine_state_present: true, checkpoint_revision: "deadbeef12345678", revision_warning: false, active_session_id: null, mutation_mode: "read_only", }; function makeClient() { return createTestQueryClient(); } function renderShell(initialPath = "/chat") { const client = makeClient(); return render( }> } /> } /> , ); } describe("Shell", () => { afterEach(() => { vi.unstubAllGlobals(); localStorage.clear(); }); beforeEach(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any vi.mocked(useRuntimeStatus).mockReturnValue({ data: mockStatus, isLoading: false, isError: false, isPending: false, isSuccess: true, status: "success", error: null, } as any); }); it("renders five named regions", () => { renderShell(); expect(screen.getByRole("banner")).toBeInTheDocument(); // topbar is a header expect(document.querySelector('[data-region="topbar"]')).toBeInTheDocument(); expect(document.querySelector('[data-region="leftnav"]')).toBeInTheDocument(); expect(document.querySelector('[data-region="main"]')).toBeInTheDocument(); expect(document.querySelector('[data-region="statusfooter"]')).toBeInTheDocument(); }); it("applies the persisted density mode to the shell", () => { setWorkbenchPref("densityMode", "compact"); renderShell(); expect(document.querySelector('[data-density="compact"]')).toBeInTheDocument(); }); it("LeftNav has exactly 15 items in section-grouped order", () => { renderShell(); const nav = document.querySelector('[data-region="leftnav"]')!; const links = nav.querySelectorAll("a"); expect(links).toHaveLength(15); const labels = Array.from(links).map((l) => l.textContent); // Grouped by section (Converse → Cognition → Determinism → Evidence → // Discipline → Substrate → Settings), derived from the route registry. expect(labels).toEqual([ "Chat", "Trace", "Contemplation", "Tour", "Replay", "Demos", "Proposals", "Runs", "Vault", "Audit", "Evals", "Calibration", "Packs", "CORE-Logos", "Settings", ]); }); it("clicking a nav item changes route (main shows new content)", () => { vi.stubGlobal("fetch", vi.fn(() => new Promise(() => {}))); renderShell("/chat"); expect(screen.getByText("Ask CORE a question.")).toBeInTheDocument(); // Click Proposals nav link const proposalsLink = screen.getByRole("link", { name: "Proposals" }); fireEvent.click(proposalsLink); expect(screen.getByText("Loading proposal queue...")).toBeInTheDocument(); }); it("StatusFooter shows mutation_mode Read Only label for read_only", () => { renderShell(); const footer = document.querySelector('[data-region="statusfooter"]')!; expect(footer.textContent).toContain("Read Only"); expect(footer.textContent).not.toContain("Runtime Turn"); }); it("StatusFooter shows git_revision short SHA", () => { renderShell(); const el = document.querySelector('[data-testid="git-revision"]'); expect(el).toBeInTheDocument(); expect(el!.textContent).toBe("abcdef12"); }); it("StatusFooter shows checkpoint_revision short SHA", () => { renderShell(); const el = document.querySelector('[data-testid="checkpoint-revision"]'); expect(el).toBeInTheDocument(); expect(el!.textContent).toBe("deadbeef"); }); it("mutation_mode runtime_turn renders amber styling and Runtime Turn label", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any vi.mocked(useRuntimeStatus).mockReturnValue({ data: { ...mockStatus, mutation_mode: "runtime_turn" }, isLoading: false, isError: false, isPending: false, isSuccess: true, status: "success", error: null, } as any); renderShell(); const mutationEl = document.querySelector('[data-testid="mutation-mode"]')!; expect(mutationEl.textContent).toBe("Runtime Turn"); // Should use warning color class (amber) expect(mutationEl.className).toContain("warning"); }); it("revision_warning=true makes checkpoint_revision show warning attr, clicking expands note", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any vi.mocked(useRuntimeStatus).mockReturnValue({ data: { ...mockStatus, revision_warning: true }, isLoading: false, isError: false, isPending: false, isSuccess: true, status: "success", error: null, } as any); renderShell(); const cpBtn = document.querySelector('[data-testid="checkpoint-revision"]')!; expect(cpBtn.getAttribute("data-warning")).toBe("true"); // Click to expand note fireEvent.click(cpBtn); const note = document.querySelector('[data-testid="revision-note"]'); expect(note).toBeInTheDocument(); expect(note!.textContent).toContain("ADR-0157 / ADR-0158"); }); });