refactor(client): name the compile face in every client test filename
A test file under packages/client now says which face it covers:
`*.client.spec.{ts,tsx}` and its `*.client.{ts,tsx}` helpers belong to the
Client aggregate, `*.host.spec.ts` to the host aggregate. The carrier's four
node-half specs take the Host suffix.
The two suffixes are mutually exclusive, so each aggregate excludes the
other's and both keep one broad test glob: `exclude` wins over `include`, and
`packages/client/**` no longer has to be excluded wholesale from the host
program with per-file `files` entries carved back out of it. A Host-face spec
that reaches only Host source therefore needs no cross-face project
reference, which the split-project rule rejects.
vitest still discovers every file through `**/*.spec.{ts,tsx}`.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* App-shell assembly plugin on the real machinery: bare Context + production
|
||||
* SlotsService + the test-runtime session/workspace doubles. Deliberately NOT
|
||||
* mounted through SlotTestRuntime — its create() installs the capturing
|
||||
* renderer and install() is boot-once; app-shell IS the production installer,
|
||||
* so this bench hands it the uninstalled service exactly as boot does.
|
||||
*/
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { act, cleanup, render } from '@testing-library/react'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { TestSessions, TestWorkspaces } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import type { Stabilizer } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import * as AppShell from '@deepseek-ai/dsh-client-web/src/app-shell.ts'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
const stabilize: Stabilizer = async (fn) => { await act(async () => { await fn() }) }
|
||||
|
||||
async function bench() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SlotsService).await()
|
||||
const slots = ctx.get('slots') as SlotsService
|
||||
ctx.provide('sessions', new TestSessions(stabilize, ctx))
|
||||
ctx.provide('workspaces', new TestWorkspaces(stabilize))
|
||||
ctx.provide('layout', { openDetails: vi.fn(), closeDetails: vi.fn() })
|
||||
const fiber = ctx.plugin({ inject: [...AppShell.inject], apply: AppShell.apply })
|
||||
await fiber.await()
|
||||
return { ctx, slots, fiber }
|
||||
}
|
||||
|
||||
describe('app-shell assembly plugin', () => {
|
||||
it('installs the renderer and provides the assembled appShell face', async () => {
|
||||
const { ctx, slots } = await bench()
|
||||
slots.register({ name: 'root' }, () => <div data-testid="root-probe" />)
|
||||
const shell = ctx.get('appShell')
|
||||
expect(shell).toBeDefined()
|
||||
const view = render(<>{shell!.renderApp()}</>)
|
||||
expect(view.getByTestId('root-probe')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('assembles once: repeated renderApp calls reuse the built closure', async () => {
|
||||
const { ctx, slots } = await bench()
|
||||
slots.register({ name: 'root' }, () => <div data-testid="root-probe" />)
|
||||
const shell = ctx.get('appShell')!
|
||||
const first = render(<>{shell.renderApp()}</>)
|
||||
expect(first.getByTestId('root-probe')).toBeTruthy()
|
||||
first.unmount()
|
||||
// Second call rides the cached closure (renderApp ??=) and still renders.
|
||||
const second = render(<>{shell.renderApp()}</>)
|
||||
expect(second.getByTestId('root-probe')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('fiber dispose retracts the service and uninstalls the renderer', async () => {
|
||||
const { ctx, slots, fiber } = await bench()
|
||||
await stabilize(() => fiber.dispose())
|
||||
expect(ctx.get('appShell')).toBeUndefined()
|
||||
expect(() => slots.renderSlot('root', {})).toThrow('not installed')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user