Files
deepseek-harness/packages/client/ui-sidebar/tests/sidebar-root.spec.tsx
T
imccyu c6b552e817 refactor(web): drop the permission RPC pair and turn-anchoring machinery
The session.permissions/setPermission unary pair, the PermissionOption wire
DTO, the client Session wrappers, and the fixture/fake mirrors all leave the
wire: the read side moves to the 'permissions' session projection and the
write side moves to the /permission command in follow-up commits, so the
web protocol gains no permission methods at all.

The pendingSwitches + prompt-submit flush + hasOpenTurn move also goes.
Knob events no longer need turn enclosure: the persistence scanner keeps
standalone events after the last turn/end as part of the preserved prefix
(remove-synthetic-log-only-turns), none of the three knob invariants demand
an open turn, and the setters append bare events. An idle switch commits
immediately; hasOpenTurn stays a user-approval private fold (its audit pair
is the one contract that still requires enclosure).

The old PermissionSelect chip and its mount-time fetch die with the RPCs
(the resident composer broke the mount-once assumption); the projection-fed
replacement lands with the Access seat swap.
2026-07-28 21:35:26 +08:00

98 lines
3.8 KiB
TypeScript

// @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'
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 = () => (
<SidebarRoot
collapsed={current.collapsed} width={current.width}
useSessions={neverHook} useWorkspaces={neverHook}
startSession={startSession} toggleSidebar={toggleSidebar}
renderSlot={((key: string, owner: SidebarSectionOwnerProps | SidebarSettingsOwnerProps) => {
if (key === 'sidebar.settings') {
settingsOwner = owner
return <div data-testid="settings-seat" data-wide={owner.wide} />
}
regionOwner = owner as SidebarSectionOwnerProps
return <div data-testid="region" data-wide={owner.wide} />
}) 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<typeof current>) {
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()
})
})