test: adapt suites to the provider-hosted conversation shell

Test-side catch-up with the session-maybe conversation architecture: the
provide channel's descriptor shape and maybeProvideInfo in fakes, the shared
chat-store handle asserted on conversation.session (the session-maybe shell
carries no store), startSession fakes exposing the workspace list snapshot,
strict session slots declining (not throwing) without a session, AppFrame's
removed empty seat and loading gate, and the hero draft asserted on the
machine (the chat-store mirror binds with ConversationSession). Plus three
lint fixes (max-len split, boolean-compare, arrow-parens/unbound-method).
This commit is contained in:
imccyu
2026-07-27 04:28:42 +08:00
parent d1e43fcd8c
commit 45eee34faf
6 changed files with 31 additions and 22 deletions

View File

@@ -162,10 +162,12 @@ describe('ConversationRoot resident composer', () => {
// Hero chrome present, view ring absent.
expect(b.view.getByText("Let's start building")).toBeTruthy()
expect(b.view.queryByTestId('view-chat')).toBeNull()
// The same machine-backed textarea is live in the hero.
// The same machine-backed textarea is live in the hero. The chat-store
// mirror binds with ConversationSession (unmounted in hero), so the
// draft's truth here is the machine itself.
const box = b.view.getByRole('textbox')
fireEvent.change(box, { target: { value: 'draft in hero' } })
expect(b.chat.store.getSnapshot().draft).toBe('draft in hero')
expect((box as HTMLTextAreaElement).value).toBe('draft in hero')
// Picker: open through the chip; a pick switches to the other
// workspace's blank session (draft carry is apply-layer wiring).
fireEvent.click(b.view.getByRole('button', { name: 'Choose workspace' }))

View File

@@ -151,22 +151,22 @@ describe('AppFrame', () => {
expect(slotCalls.find((c) => c.key === 'details')!.props).toEqual({})
})
it('renders the New Session view state through the empty seat while no session is current', () => {
// No current session = the pure view state: the conversation.empty slot
// renders in the center column; no session slot dispatches.
it('keeps the conversation slot mounted while no session is current', () => {
// No current session: the session-maybe conversation shell owns the New
// Session view itself — the center column renders it unconditionally.
sessionMode.current = false
const { slotCalls, getByTestId, queryByTestId } = mountFrame()
expect(getByTestId('empty-content')).toBeTruthy()
expect(queryByTestId('center-content')).toBeNull()
expect(slotCalls.map((c) => c.key)).toContain('conversation.empty')
expect(slotCalls.map((c) => c.key)).not.toContain('conversation')
const { slotCalls, getByTestId } = mountFrame()
expect(getByTestId('center-content')).toBeTruthy()
expect(slotCalls.map((c) => c.key)).toContain('conversation')
})
it('keeps the loading branch until both object-layer baselines are ready', () => {
it('renders both column occupants before baselines settle (no loading gate)', () => {
// The loading branch is gone: fixed tree positions from first paint, the
// occupants render their own pending states.
baselinesReady.current = false
const { slotCalls, getByRole } = mountFrame()
expect(getByRole('status').textContent).toContain('Loading workspaces and sessions')
expect(slotCalls.map((c) => c.key)).not.toContain('conversation')
const { slotCalls } = mountFrame()
expect(slotCalls.map((c) => c.key)).toContain('conversation')
expect(slotCalls.map((c) => c.key)).toContain('details')
})
it('sidebar slot receives live concession output as owner props', () => {

View File

@@ -40,7 +40,7 @@ describe('ui-layout client apply', () => {
expect(slots.entries('root')).toHaveLength(1)
// …and declared the three children in the ledger.
expect(slots.spec('sidebar')).toEqual({ kind: 'single', scope: 'root' })
expect(slots.spec('conversation')).toEqual({ kind: 'single', scope: 'session' })
expect(slots.spec('conversation')).toEqual({ kind: 'single', scope: 'session-maybe' })
expect(slots.spec('details')).toEqual({ kind: 'single', scope: 'session' })
})

View File

@@ -9,7 +9,10 @@ async function bench(declare = true) {
const ctx = new Context()
await ctx.plugin(SlotsService).await()
const layout = { toggleSidebar: vi.fn() }
const workspaces = { connectWorkspace: vi.fn(async () => 'blank-1' as never) }
const workspaces = {
connectWorkspace: vi.fn(async () => 'blank-1' as never),
list: { getSnapshot: () => ({ recentWorkspaceId: undefined }) },
}
const sessions = { open: vi.fn(), clear: vi.fn() }
ctx.provide('layout', layout)
ctx.provide('sessions', sessions as never)

View File

@@ -19,7 +19,10 @@ async function bench() {
const insertSessionBefore = vi.fn(async () => ({}))
const open = vi.fn()
const clear = vi.fn()
ctx.provide('workspaces', { create, connectWorkspace, rename, insertSessionBefore } as never)
ctx.provide('workspaces', {
create, connectWorkspace, rename, insertSessionBefore,
list: { getSnapshot: () => ({ recentWorkspaceId: undefined }) },
} as never)
ctx.provide('sessions', { open, clear } as never)
return { ctx, slots: ctx.get('slots') as SlotsService, create, connectWorkspace, rename, insertSessionBefore, open, clear }
}

View File

@@ -662,14 +662,15 @@ describe('standard-kit synthesis', () => {
expect(seen2.at(-1)!['SessionProvider']).toBeUndefined()
})
it('fails loud when a session slot renders outside SessionProvider', () => {
it('renders nothing for a strict session slot while no session is current', () => {
// Strict session entries decline (render null) without a session; the
// loud path is reserved for a missing root binding provider.
const h = makeHost()
h.declare('k.session', SINGLE_SESSION)
h.add('k.session', { component: () => <b>x</b> })
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
expect(() => mountRoot(h, { 'k.session': SINGLE_SESSION },
(renderSlot) => renderSlot('k.session', {}))).toThrow(/outside SessionProvider/)
spy.mockRestore()
const { view } = mountRoot(h, { 'k.session': SINGLE_SESSION },
(renderSlot) => renderSlot('k.session', {}))
expect(view.container.querySelector('b')).toBeNull()
})
it('delivers the store pair for store-declaring entries and writes through baked actions', () => {