// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup, fireEvent, render, screen } from '@testing-library/react' import type { SidebarRootComponentProps, SidebarSectionOwnerProps, SidebarSettingsOwnerProps } from '../src/client/contract/slots.ts' import { SidebarRoot } from '../src/client/SidebarRoot.tsx' import { en } from '../src/client/locales.ts' // English-dictionary translate stub: the shell renders the same copy the // assertions below query by accessible name. const t: SidebarRootComponentProps['t'] = key => (en as Record)[key] ?? key afterEach(() => { cleanup() vi.useRealTimers() }) // The shell never reads the global hooks itself, but they ride the standard // props share; stub them as never-called functions. const neverHook = (() => { throw new Error('shell must not read global hooks') }) as never function mountShell({ collapsed = false, width = 300 }: { collapsed?: boolean; width?: number } = {}) { const startSession = vi.fn() const toggleSidebar = vi.fn() let regionOwner: SidebarSectionOwnerProps | undefined let settingsOwner: SidebarSettingsOwnerProps | undefined let current = { collapsed, width } const root = () => ( { if (key === 'sidebar.settings') { settingsOwner = owner return
} regionOwner = owner as SidebarSectionOwnerProps return
}) as SidebarRootComponentProps['renderSlot']} /> ) const view = render(root()) return { startSession, toggleSidebar, regionOwner: () => { if (regionOwner === undefined) throw new Error('region owner not rendered') return regionOwner }, settingsOwner: () => { if (settingsOwner === undefined) throw new Error('settings owner not rendered') return settingsOwner }, rerender(next: Partial) { current = { ...current, ...next } view.rerender(root()) }, } } describe('SidebarRoot shell', () => { it('routes New Session (capsule + wordmark) and the column toggle', () => { const b = mountShell() // Expanded, both the wordmark and the capsule start a session. const starters = screen.getAllByRole('button', { name: 'New session' }) expect(starters).toHaveLength(2) for (const button of starters) fireEvent.click(button) expect(b.startSession).toHaveBeenCalledTimes(2) fireEvent.click(screen.getByRole('button', { name: 'Collapse sidebar' })) expect(b.toggleSidebar).toHaveBeenCalledOnce() }) it('hands the region its wide flag and clamps expandSidebar to the collapsed state', () => { const b = mountShell() expect(b.regionOwner().wide).toBe(true) // The settings seat rides the same wide flag (ui-settings renders the row). expect(b.settingsOwner().wide).toBe(true) // Expanded: the request is a no-op (no accidental collapse). b.regionOwner().expandSidebar() expect(b.toggleSidebar).not.toHaveBeenCalled() }) it('keeps the region mounted through collapse and expands on its request', () => { vi.useFakeTimers() const b = mountShell() b.rerender({ collapsed: true }) // Wide content survives the crossfade window, then settles into the rail. expect(b.regionOwner().wide).toBe(true) vi.advanceTimersByTime(200) b.rerender({}) expect(b.regionOwner().wide).toBe(false) expect(screen.getByTestId('region')).toBeTruthy() b.regionOwner().expandSidebar() expect(b.toggleSidebar).toHaveBeenCalledOnce() }) it('renders statically collapsed on a cold start (no crossfade classes)', () => { const b = mountShell({ collapsed: true }) expect(b.regionOwner().wide).toBe(false) expect(screen.getByRole('button', { name: 'Open sidebar' })).toBeTruthy() }) })