// @vitest-environment happy-dom import { act, cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { leaves } from "../../layout.js"; import { createLayoutStore } from "../../stores/layoutStore.js"; import type { Session } from "../../types.js"; import { MultiView } from "./MultiView.js"; vi.mock("../terminal/TerminalPane.js", () => ({ TerminalPane: ({ sessionId }: { sessionId: string }) =>
, })); vi.mock("../structured/StructuredPane.js", () => ({ StructuredPane: ({ sessionId }: { sessionId: string }) =>
, })); afterEach(cleanup); function store() { return createLayoutStore({ load: () => null, save: vi.fn(), scheduler: { setTimer: () => 1, clearTimer: () => {} }, }); } function session(id: string, over: Partial = {}): Session { return { id, cwd: "/home/n", command: "claude", env: {}, metadata: {}, status: "Running", created_at: "2026-01-01T00:00:00Z", ...over, } as Session; } describe("MultiView", () => { it("shows the empty state when the active tab has no panes", () => { const s = store(); render(); expect(screen.getByText(/No session open/)).toBeTruthy(); }); it("renders a pane per leaf, dispatching to the terminal or structured body", () => { const s = store(); s.getState().openSession("s1"); s.getState().openSessionBeside("s2"); const sessions = new Map([ ["s1", session("s1")], ["s2", session("s2", { view: "Structured" })], ]); render(); expect(screen.getByTestId("terminal-s1")).toBeTruthy(); expect(screen.getByTestId("structured-s2")).toBeTruthy(); }); it("split right adds a second pane to the tree", () => { const s = store(); s.getState().openSession("s1"); render(); fireEvent.click(screen.getByTitle("Split right")); expect(screen.getAllByText("Empty pane")).toHaveLength(1); }); it("close pane removes it", () => { const s = store(); s.getState().openSession("s1"); const leafId = leaves(s.getState().layout.tabs[0].root)[0].id; s.getState().splitFocused(leafId, "row"); // adds an empty leaf beside s1 render(); // splitFocused(after: true) puts the fresh empty leaf second — close it. fireEvent.click(screen.getAllByTitle("Close pane")[1]); expect(screen.queryByText("Empty pane")).toBeNull(); expect(screen.getByTestId("terminal-s1")).toBeTruthy(); }); it("prunes a leaf whose session no longer exists, once the live set is known", () => { const s = store(); s.getState().openSession("s1"); s.getState().openSessionBeside("dead"); render(); expect(screen.getByTestId("terminal-s1")).toBeTruthy(); expect(screen.queryByTestId("terminal-dead")).toBeNull(); }); it("does not touch the layout while the live set is still unknown", () => { const s = store(); s.getState().openSession("s1"); render(); expect(screen.getByTestId("terminal-s1")).toBeTruthy(); }); describe("preview mode (docs/PREVIEW.md)", () => { it("shows a session no pane holds in a throwaway tab, marked as a preview", () => { const s = store(); s.getState().openSession("s1"); render(); act(() => s.getState().previewSession("s9")); // s1's own tab is still in the strip — the preview never touched it. expect(screen.getByText("Tab 1 · 1")).toBeTruthy(); const pane = document.querySelector('[data-session-id="s9"]'); expect(pane).toBeTruthy(); expect(pane!.querySelector(".bg-preview-tint")).toBeTruthy(); expect(screen.getByText("preview")).toBeTruthy(); }); it("puts the layout back byte for byte once the preview ends", () => { const s = store(); s.getState().openSession("s1"); render(); const before = JSON.stringify(s.getState().layout); act(() => s.getState().previewSession("s9")); act(() => s.getState().previewSession(null)); expect(JSON.stringify(s.getState().layout)).toBe(before); expect(screen.queryByText("preview")).toBeNull(); }); it("does not grab the keyboard while previewing", () => { const s = store(); s.getState().openSession("s1"); render(); act(() => s.getState().previewSession("s9")); const pane = document.querySelector('[data-session-id="s9"]') as HTMLElement; expect(pane.querySelector('[data-testid="terminal-s9"]')).toBeTruthy(); // TerminalPane's `focus` prop is verified by TerminalPane's own tests; // here the preview must never end up as the active-tab's accent pane. expect(pane.className).not.toContain("border-accent"); }); it("clicking into a previewed pane commits it: no more preview, nothing moves a second time", () => { const s = store(); s.getState().openSession("s1"); render(); act(() => s.getState().previewSession("s9")); const pane = document.querySelector('[data-session-id="s9"]') as HTMLElement; fireEvent.pointerDown(pane); expect(s.getState().preview).toBeNull(); expect(screen.queryByText("preview")).toBeNull(); expect(leaves(s.getState().layout.tabs[1].root).map((l) => l.sessionId)).toEqual(["s9"]); }); }); });