import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render } from "@testing-library/react"; import type { ComponentProps } from "react"; import type { Tab } from "@/types/ui-types"; import type { SplitSummary } from "@/shell/split/split-tabs"; vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (key: string, opts?: Record) => opts ? `${key}:${JSON.stringify(opts)}` : key, }), })); vi.mock("@/lib/electron", () => ({ isElectron: () => true })); import { TabBar } from "@/shell/TabBar"; afterEach(cleanup); const tabs = [ { id: "dashboard", type: "dashboard", label: "Dashboard" }, { id: "terminal-1", type: "terminal", label: "web-01" }, ] as Tab[]; function props( overrides: Partial> = {}, ): ComponentProps { return { tabs, activeTabId: "dashboard", splits: [], activeSplitFull: false, onSetActiveTab: () => {}, onCloseTab: () => {}, onRefreshTab: () => {}, onReorderTabs: () => {}, onSplitAction: () => {}, isAppFullscreen: false, onToggleAppFullscreen: () => {}, ...overrides, }; } describe("TabBar workspace continuity", () => { it("keeps one shared indicator on the active workspace", () => { const { container, rerender } = render(); expect( container.querySelector('[data-tab-indicator="dashboard"]'), ).toBeTruthy(); rerender(); expect(container.querySelectorAll("[data-tab-indicator]")).toHaveLength(1); expect( container.querySelector('[data-tab-indicator="terminal-1"]'), ).toBeTruthy(); }); it("activates a workspace immediately when its tab is clicked", () => { const onSetActiveTab = vi.fn(); const { getByText } = render(); fireEvent.click(getByText("web-01")); expect(onSetActiveTab).toHaveBeenCalledWith("terminal-1"); }); }); describe("TabBar split menu", () => { const split: SplitSummary = { id: "split-1", label: "Split 1", full: false, panes: [ { id: "p1", index: 1, tabId: "terminal-9", label: "db-01" }, { id: "p2", index: 2, tabId: null, label: null }, ], }; function openMenu(overrides: Partial> = {}) { const onSplitAction = vi.fn(); const view = render(); fireEvent.contextMenu(view.getByText("web-01")); return { ...view, onSplitAction }; } it("splits the clicked tab right or down", () => { const { getByText, onSplitAction } = openMenu(); fireEvent.click(getByText("splitScreen.splitDown")); expect(onSplitAction).toHaveBeenCalledWith({ kind: "split", tabId: "terminal-1", edge: "bottom", }); }); it("lists every pane of every split, so the target is always clear", () => { const { getByText, onSplitAction } = openMenu({ splits: [split] }); expect( getByText('splitScreen.addToSplit:{"split":"Split 1"}'), ).toBeTruthy(); expect( getByText('splitScreen.paneItem:{"index":1,"label":"db-01"}'), ).toBeTruthy(); fireEvent.click(getByText('splitScreen.paneEmptyItem:{"index":2}')); expect(onSplitAction).toHaveBeenCalledWith({ kind: "addToPane", tabId: "terminal-1", splitTabId: "split-1", paneId: "p2", }); }); it("adds a new pane to an existing split", () => { const { getByText, onSplitAction } = openMenu({ splits: [split] }); fireEvent.click(getByText("splitScreen.newPaneBelow")); expect(onSplitAction).toHaveBeenCalledWith({ kind: "addPane", tabId: "terminal-1", splitTabId: "split-1", edge: "bottom", }); }); it("disables new panes on a full split", () => { const { getByText } = openMenu({ splits: [{ ...split, full: true }] }); expect( ( getByText("splitScreen.newPaneRight").closest( "button", ) as HTMLButtonElement ).disabled, ).toBe(true); }); it("offers unsplit on a split tab instead of splitting it", () => { const splitTab = { id: "split-1", type: "split-screen", label: "Split 1", split: { root: { kind: "pane", id: "p1", tabId: null }, focusedPaneId: "p1", }, } as Tab; const onSplitAction = vi.fn(); const { getByText, queryByText } = render( , ); fireEvent.contextMenu(getByText("Split 1")); expect(queryByText("splitScreen.splitRight")).toBeNull(); fireEvent.click(getByText("splitScreen.unsplit")); expect(onSplitAction).toHaveBeenCalledWith({ kind: "unsplit", splitTabId: "split-1", }); }); });