Files
openchamber/packages/ui/src/stores/useUIStore.contextPanel.test.ts
T

206 lines
8.6 KiB
TypeScript
Raw Normal View History

2026-06-16 23:33:29 +03:00
import { beforeEach, describe, expect, test } from 'bun:test';
import { CONTEXT_SURFACES, sortContextSurfaces } from '../lib/surfaces/registry';
2026-06-16 23:33:29 +03:00
import { useUIStore } from './useUIStore';
beforeEach(() => {
useUIStore.setState({ contextPanelByDirectory: {}, contextRailOrder: [] });
2026-06-16 23:33:29 +03:00
});
describe('useUIStore context panel tabs', () => {
test('updates readOnly when an existing chat tab is reopened', () => {
const directory = '/repo';
useUIStore.getState().openContextPanelTab(directory, {
mode: 'chat',
dedupeKey: 'session:ses_1',
label: 'Session',
readOnly: true,
});
useUIStore.getState().openContextPanelTab(directory, {
mode: 'chat',
dedupeKey: 'session:ses_1',
label: 'Session',
readOnly: false,
});
const tabs = useUIStore.getState().contextPanelByDirectory[directory]?.tabs ?? [];
expect(tabs).toHaveLength(1);
expect(tabs[0]?.readOnly).toBe(false);
});
});
describe('useUIStore openContextSurface', () => {
const directory = '/repo';
test('opens a fresh singleton tab when none of that mode exists', () => {
useUIStore.getState().openContextSurface(directory, 'diff');
const state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.isOpen).toBe(true);
expect(state?.tabs.map((tab) => tab.mode)).toEqual(['diff']);
});
test('activates the existing tab of the requested mode instead of duplicating it', () => {
useUIStore.getState().openContextPanelTab(directory, { mode: 'diff' });
useUIStore.getState().openContextPanelTab(directory, { mode: 'file', targetPath: '/repo/a.ts' });
useUIStore.getState().openContextSurface(directory, 'diff');
const state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.tabs.filter((tab) => tab.mode === 'diff')).toHaveLength(1);
expect(state?.activeTabId).toBe('diff');
expect(state?.isOpen).toBe(true);
});
test('toggles the panel closed when the requested mode is already active and open', () => {
useUIStore.getState().openContextSurface(directory, 'diff');
useUIStore.getState().openContextSurface(directory, 'diff');
const state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.isOpen).toBe(false);
expect(state?.tabs.map((tab) => tab.mode)).toEqual(['diff']);
});
test('does nothing for content-driven modes without existing content', () => {
useUIStore.getState().openContextSurface(directory, 'chat');
expect(useUIStore.getState().contextPanelByDirectory[directory]).toBe(undefined);
});
test('opens an empty editor tab that a real file later replaces', () => {
useUIStore.getState().openContextSurface(directory, 'file');
let state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.isOpen).toBe(true);
expect(state?.tabs.map((tab) => tab.mode)).toEqual(['file']);
expect(state?.tabs[0]?.targetPath).toBe(null);
useUIStore.getState().openContextFile(directory, '/repo/a.ts');
state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.tabs.filter((tab) => tab.mode === 'file')).toHaveLength(1);
expect(state?.tabs.find((tab) => tab.mode === 'file')?.targetPath).toBe('/repo/a.ts');
});
test('activates the most recently touched tab of a content-driven mode', () => {
useUIStore.getState().openContextFile(directory, '/repo/a.ts');
useUIStore.getState().openContextFile(directory, '/repo/b.ts');
useUIStore.getState().openContextPanelTab(directory, { mode: 'diff' });
useUIStore.getState().openContextSurface(directory, 'file');
const state = useUIStore.getState().contextPanelByDirectory[directory];
const activeTab = state?.tabs.find((tab) => tab.id === state.activeTabId);
expect(activeTab?.mode).toBe('file');
expect(activeTab?.targetPath).toBe('/repo/b.ts');
});
});
describe('useUIStore closeContextPanelTab surface stability', () => {
const directory = '/repo';
test('closing an active file tab activates another file tab, not another surface', () => {
useUIStore.getState().openContextPanelTab(directory, { mode: 'terminal' });
useUIStore.getState().openContextFile(directory, '/repo/a.ts');
useUIStore.getState().openContextFile(directory, '/repo/b.ts');
const stateBefore = useUIStore.getState().contextPanelByDirectory[directory];
const activeTabId = stateBefore?.activeTabId as string;
useUIStore.getState().closeContextPanelTab(directory, activeTabId);
const state = useUIStore.getState().contextPanelByDirectory[directory];
const activeTab = state?.tabs.find((tab) => tab.id === state.activeTabId);
expect(activeTab?.mode).toBe('file');
expect(activeTab?.targetPath).toBe('/repo/a.ts');
expect(state?.isOpen).toBe(true);
});
test('closing the last tab of the active surface closes the panel', () => {
useUIStore.getState().openContextPanelTab(directory, { mode: 'terminal' });
useUIStore.getState().openContextFile(directory, '/repo/a.ts');
const stateBefore = useUIStore.getState().contextPanelByDirectory[directory];
useUIStore.getState().closeContextPanelTab(directory, stateBefore?.activeTabId as string);
const state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.isOpen).toBe(false);
expect(state?.tabs.map((tab) => tab.mode)).toEqual(['terminal']);
});
test('closing an inactive tab keeps the active tab untouched', () => {
useUIStore.getState().openContextFile(directory, '/repo/a.ts');
useUIStore.getState().openContextPanelTab(directory, { mode: 'terminal' });
const state0 = useUIStore.getState().contextPanelByDirectory[directory];
const fileTab = state0?.tabs.find((tab) => tab.mode === 'file');
useUIStore.getState().closeContextPanelTab(directory, fileTab?.id as string);
const state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.activeTabId).toBe('terminal');
expect(state?.isOpen).toBe(true);
});
});
describe('useUIStore per-surface panel widths', () => {
const directory = '/repo';
test('setContextPanelWidth stores a clamped manual width for one mode only', () => {
useUIStore.getState().openContextPanelTab(directory, { mode: 'diff' });
useUIStore.getState().setContextPanelWidth(directory, 'diff', 700);
useUIStore.getState().setContextPanelWidth(directory, 'git', 100);
const state = useUIStore.getState().contextPanelByDirectory[directory];
expect(state?.widthByMode.diff).toBe(700);
expect(state?.widthByMode.git).toBe(380);
expect(state?.widthByMode.browser).toBe(undefined);
});
});
describe('useUIStore contextRailOrder', () => {
test('setContextRailOrder drops empty and duplicate ids', () => {
useUIStore.getState().setContextRailOrder(['diff', 'diff', '', 'editor']);
expect(useUIStore.getState().contextRailOrder).toEqual(['diff', 'editor']);
});
test('sortContextSurfaces applies persisted order and appends missing surfaces', () => {
const ordered = sortContextSurfaces(['browser', 'unknown-id', 'diff']);
const ids = ordered.map((surface) => surface.id);
expect(ids.slice(0, 2)).toEqual(['browser', 'diff']);
// Assert against the registry itself so this test cannot go stale when a
// surface is added or removed.
expect(new Set(ids)).toEqual(new Set(CONTEXT_SURFACES.map((surface) => surface.id)));
expect(ids).toHaveLength(CONTEXT_SURFACES.length);
});
});
describe('context panel tab limits', () => {
test('a surface filling up never evicts another surface tab', () => {
const directory = '/repo';
useUIStore.getState().openContextDiff(directory, 'src/app.ts');
for (let index = 0; index < 20; index += 1) {
useUIStore.getState().openContextPreview(directory, `http://localhost:${3000 + index}/`);
}
const tabs = useUIStore.getState().contextPanelByDirectory[directory]?.tabs ?? [];
// The diff tab is not on screen while browsing, so losing it would be a
// disappearance the user never saw happen.
expect(tabs.some((tab) => tab.mode === 'diff')).toBe(true);
expect(tabs.filter((tab) => tab.mode === 'browser').length).toBeLessThan(20);
});
test('keeps the tab that was just opened', () => {
const directory = '/repo';
for (let index = 0; index < 20; index += 1) {
useUIStore.getState().openContextPreview(directory, `http://localhost:${3000 + index}/`);
}
const state = useUIStore.getState().contextPanelByDirectory[directory];
const tabs = state?.tabs ?? [];
expect(tabs.some((tab) => tab.id === state?.activeTabId)).toBe(true);
expect(tabs.some((tab) => tab.targetPath === 'http://localhost:3019/')).toBe(true);
});
});