import { afterEach, describe, expect, test } from "bun:test"; import { act, useEffect, useState } from "react"; import { getDockedPaneIds } from "../../plugins/pane-manager"; import { setSharedRegistryForTests } from "../../plugins/registry"; import { testRender } from "../../renderers/opentui/test-utils"; import { AppContext, createInitialState } from "../../state/app/context"; import { cloneLayout, createDefaultConfig, createPaneInstance, type LayoutConfig } from "../../types/config"; import type { AppNotificationRequest } from "../../types/plugin"; import { VERSION } from "../../version"; import { StatusBar } from "./status-bar"; import { TransientLayoutProvider, useTransientLayout } from "./transient-layout"; let testSetup: Awaited> | undefined; afterEach(() => { if (testSetup) { testSetup.renderer.destroy(); testSetup = undefined; } setSharedRegistryForTests(undefined); }); describe("StatusBar", () => { function SeedTransientLayout({ onActivate, onDeactivate, onExit, }: { onActivate?: () => void; onDeactivate?: () => void; onExit?: () => void; }) { const { setTransientLayout } = useTransientLayout(); const [active, setActive] = useState(true); useEffect(() => { setTransientLayout({ id: "pane-focus", label: "Focus", shortcutActionId: "pane-fullscreen", active, onActivate: () => { onActivate?.(); setActive(true); }, onDeactivate: () => { onDeactivate?.(); setActive(false); }, onExit, }); return () => setTransientLayout(null); }, [active, onActivate, onDeactivate, onExit, setTransientLayout]); return null; } test("opens the current version changelog from the version chip", async () => { const config = createDefaultConfig("/tmp/gloomberb-test"); config.layouts = [{ name: "Home", layout: cloneLayout(config.layout) }]; const state = { ...createInitialState(config), statusBarVisible: true, }; let openedVersion = ""; testSetup = await testRender( {} }}> { openedVersion = version; }} /> , { width: 120, height: 1 }, ); await testSetup.renderOnce(); const frame = testSetup.captureCharFrame(); const versionX = frame.split("\n")[0]?.indexOf(`v${VERSION}`) ?? -1; expect(versionX).toBeGreaterThanOrEqual(0); await testSetup.mockMouse.click(versionX + 1, 0); await testSetup.renderOnce(); expect(openedVersion).toBe(VERSION); }); test("shows a transient focus layout tab without replacing saved layouts", async () => { const config = createDefaultConfig("/tmp/gloomberb-transient-layout-test"); config.layouts = [ { name: "Default", layout: cloneLayout(config.layout) }, { name: "Monitor", layout: cloneLayout(config.layout) }, ]; const state = { ...createInitialState(config), statusBarVisible: true, }; const actions: Array<{ type: string; index?: number }> = []; let activateCount = 0; let deactivateCount = 0; let exitCount = 0; const handleActivate = () => { activateCount += 1; }; const handleDeactivate = () => { deactivateCount += 1; }; const handleExit = () => { exitCount += 1; }; testSetup = await testRender( actions.push(action as { type: string; index?: number }) }}> , { width: 120, height: 1 }, ); await testSetup.renderOnce(); await testSetup.renderOnce(); const frame = testSetup.captureCharFrame(); expect(frame).toContain("^1 Default"); expect(frame).toContain("^2 Monitor"); expect(frame).toContain("^⇧F Focus"); const monitorX = frame.split("\n")[0]?.indexOf("^2 Monitor") ?? -1; expect(monitorX).toBeGreaterThanOrEqual(0); await testSetup.mockMouse.click(monitorX + 1, 0); await testSetup.renderOnce(); await testSetup.renderOnce(); expect(deactivateCount).toBe(1); expect(exitCount).toBe(0); expect(actions).toContainEqual({ type: "SWITCH_LAYOUT", index: 1 }); const afterSwitchFrame = testSetup.captureCharFrame(); expect(afterSwitchFrame).toContain("^⇧F Focus"); const focusX = afterSwitchFrame.split("\n")[0]?.indexOf("^⇧F Focus") ?? -1; expect(focusX).toBeGreaterThanOrEqual(0); await testSetup.mockMouse.click(focusX + 1, 0); await testSetup.renderOnce(); expect(activateCount).toBe(1); const activeFocusFrame = testSetup.captureCharFrame(); const activeFocusX = activeFocusFrame.split("\n")[0]?.indexOf("^⇧F Focus") ?? -1; expect(activeFocusX).toBeGreaterThanOrEqual(0); await testSetup.mockMouse.click(activeFocusX + 1, 0); await testSetup.renderOnce(); expect(exitCount).toBe(1); }); test("reorders saved layout tabs by dragging them left and right", async () => { const config = createDefaultConfig("/tmp/gloomberb-layout-tab-reorder-test"); config.layouts = [ { name: "Home", layout: cloneLayout(config.layout) }, { name: "Research", layout: cloneLayout(config.layout) }, { name: "News", layout: cloneLayout(config.layout) }, ]; const state = { ...createInitialState(config), statusBarVisible: true, }; const actions: Array<{ type: string; fromIndex?: number; toIndex?: number }> = []; testSetup = await testRender( actions.push(action as { type: string; fromIndex?: number; toIndex?: number }) }}> , { width: 120, height: 3 }, ); await testSetup.renderOnce(); const frame = testSetup.captureCharFrame(); const homeX = frame.split("\n")[0]?.indexOf("^1 Home") ?? -1; const newsX = frame.split("\n")[0]?.indexOf("^3 News") ?? -1; expect(homeX).toBeGreaterThanOrEqual(0); expect(newsX).toBeGreaterThan(homeX); await act(async () => { await testSetup!.mockMouse.drag(homeX + 1, 0, newsX + 1, 0); await testSetup!.renderOnce(); }); expect(actions).toContainEqual({ type: "REORDER_LAYOUT", fromIndex: 0, toIndex: 2 }); }); test("offers to tidy three floating windows and tiles them on click", async () => { const config = createDefaultConfig("/tmp/gloomberb-tidy-test"); const floatingLayout: LayoutConfig = { dockRoot: null, instances: Array.from({ length: 3 }, (_, index) => createPaneInstance("chat", { instanceId: `chat-${index}` })), floating: Array.from({ length: 3 }, (_, index) => ({ instanceId: `chat-${index}`, x: index * 40, y: 0, width: 40, height: 20, zIndex: 50 + index, })), detached: [], }; const state = { ...createInitialState({ ...config, layout: floatingLayout, layouts: [{ name: "Default", layout: cloneLayout(floatingLayout) }], }), statusBarVisible: true, }; const actions: Array<{ type: string }> = []; let updatedLayout: LayoutConfig | null = null; const notifications: AppNotificationRequest[] = []; setSharedRegistryForTests({ panes: new Map([["chat", { name: "Chat" }]]), getLayoutFn: () => state.config.layout, getTermSizeFn: () => ({ width: 120, height: 40 }), updateLayoutFn: (layout: LayoutConfig) => { updatedLayout = layout; }, notify: (notification: AppNotificationRequest) => { notifications.push(notification); }, renderSlot: () => null, } as any); testSetup = await testRender( actions.push(action as { type: string }) }}> , { width: 120, height: 1 }, ); await testSetup.renderOnce(); const frame = testSetup.captureCharFrame(); const buttonX = frame.split("\n")[0]?.indexOf("Tidy Windows") ?? -1; expect(buttonX).toBeGreaterThanOrEqual(0); await testSetup.mockMouse.click(buttonX + 1, 0); await testSetup.renderOnce(); expect(updatedLayout?.floating).toHaveLength(0); expect(notifications[0]).toMatchObject({ body: "Windows tidied", type: "success", action: { label: "Revert" }, }); notifications[0]!.action!.onClick(); expect(actions).toContainEqual({ type: "UNDO_LAYOUT" }); }); test("tidies covered windows instead of leaving them floating", async () => { const config = createDefaultConfig("/tmp/gloomberb-covered-test"); const floatingLayout: LayoutConfig = { dockRoot: null, instances: Array.from({ length: 3 }, (_, index) => createPaneInstance("chat", { instanceId: `chat-${index}` })), floating: Array.from({ length: 3 }, (_, index) => ({ instanceId: `chat-${index}`, x: 10, y: 4, width: 50, height: 20, zIndex: 50 + index, })), detached: [], }; const state = { ...createInitialState({ ...config, layout: floatingLayout, layouts: [{ name: "Default", layout: cloneLayout(floatingLayout) }], }), statusBarVisible: true, }; let updatedLayout: LayoutConfig | null = null; const notifications: AppNotificationRequest[] = []; setSharedRegistryForTests({ panes: new Map([["chat", { name: "Chat" }]]), getLayoutFn: () => state.config.layout, getTermSizeFn: () => ({ width: 120, height: 40 }), updateLayoutFn: (layout: LayoutConfig) => { updatedLayout = layout; }, notify: (notification: AppNotificationRequest) => { notifications.push(notification); }, renderSlot: () => null, } as any); testSetup = await testRender( {} }}> , { width: 120, height: 1 }, ); await testSetup.renderOnce(); const frame = testSetup.captureCharFrame(); const tidyX = frame.split("\n")[0]?.indexOf("Tidy Windows") ?? -1; expect(tidyX).toBeGreaterThanOrEqual(0); await testSetup.mockMouse.click(tidyX + 1, 0); await testSetup.renderOnce(); expect(updatedLayout?.floating).toHaveLength(0); expect(getDockedPaneIds(updatedLayout!)).toHaveLength(3); expect(notifications[0]?.body).toBe("Windows tidied"); }); });