| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- /** Test-owned workspaces face: the renderer standard-kit observable plus recorded actions. */
- import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
- import type {
- IWorkspaces, WorkspaceId, WorkspaceSnapshot, WorkspaceView,
- } from '@deepseek-ai/dsh-api-workspace-controller/client'
- import type { SessionId } from '@deepseek-ai/dsh-session/types'
- import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
- import { workspaceSnapshot } from './fixtures.ts'
- import type { FixtureSnapshot, Stabilizer } from './fixtures.ts'
- /** Writable test representation of the immutable Workspace Controller snapshot. */
- type WorkspaceFixtureSnapshot = FixtureSnapshot<WorkspaceSnapshot>
- /** Callable command names on the production Workspace Controller face. */
- type WorkspaceAction = {
- [Key in keyof IWorkspaces]: IWorkspaces[Key] extends (...args: never[]) => unknown ? Key : never
- }[keyof IWorkspaces]
- /** Test replacement retaining one Controller command's parameters and result. */
- type WorkspaceStub<Key extends WorkspaceAction> = (
- ...args: Parameters<IWorkspaces[Key]>
- ) => ReturnType<IWorkspaces[Key]>
- /**
- * Workspaces test double. Implements the same IWorkspaces face features
- * receive as `ctx.workspaces`, so a production face change breaks this
- * double at compile time. Every action records into {@link
- * TestWorkspaces.calls}; defaults are inert echoes — feature tests needing
- * richer behavior replace them via {@link TestWorkspaces.stub}.
- */
- export class TestWorkspaces implements IWorkspaces {
- /** The useWorkspaces standard feed. */
- readonly list: SnapshotStore<WorkspaceFixtureSnapshot>
- /** Calls observed on the action face, newest last. */
- readonly calls: { method: string; args: unknown[] }[] = []
- /** Replaceable action seat: feature tests may stub richer behavior. */
- private readonly stubs = new Map<WorkspaceAction, (...args: unknown[]) => unknown>()
- /**
- * @param stabilize - the owning runtime's act wrapper.
- */
- constructor(private readonly stabilize: Stabilizer) {
- this.list = createSnapshotStore<WorkspaceFixtureSnapshot>({ ...workspaceSnapshot() })
- }
- /**
- * Update the workspace list state through an immer draft.
- * @param mutate - draft mutator.
- */
- async update(mutate: (draft: WorkspaceFixtureSnapshot) => void): Promise<void> {
- await this.stabilize(() => { this.list.update(mutate) })
- }
- /**
- * Replace an action's behavior (the recorded call is still appended first).
- * @param method - Controller action name (e.g. 'create').
- * @param impl - replacement behavior.
- */
- stub<Key extends WorkspaceAction>(method: Key, impl: WorkspaceStub<Key>): void {
- this.stubs.set(method, impl as (...args: unknown[]) => unknown)
- }
- /**
- * Create a Workspace (recorded). The default echoes a view derived from
- * the input; stub for failure or list-coupled flows.
- * @param input - the Host create payload.
- * @returns the created Workspace view.
- */
- async create(input: { path: string }): Promise<WorkspaceView> {
- this.calls.push({ method: 'create', args: [input] })
- const stub = this.stubs.get('create')
- if (stub !== undefined) return await (stub(input) as Promise<WorkspaceView>)
- return {
- workspaceId: `ws-${input.path}` as WorkspaceId,
- title: input.path,
- path: input.path,
- sessionIds: [],
- } as unknown as WorkspaceView
- }
- /**
- * Rename a Workspace (recorded). The default echoes a minimal view.
- * @param workspaceId - target workspace.
- * @param title - new title.
- * @returns the updated view.
- */
- async rename(workspaceId: WorkspaceId, title: string): Promise<WorkspaceView> {
- this.calls.push({ method: 'rename', args: [workspaceId, title] })
- const stub = this.stubs.get('rename')
- if (stub !== undefined) return await (stub(workspaceId, title) as Promise<WorkspaceView>)
- return { workspaceId, title, path: `/${title}`, sessionIds: [] } as unknown as WorkspaceView
- }
- /**
- * Delete a Workspace (recorded; default no-op).
- * @param workspaceId - target workspace.
- */
- async delete(workspaceId: WorkspaceId): Promise<void> {
- this.calls.push({ method: 'delete', args: [workspaceId] })
- await (this.stubs.get('delete')?.(workspaceId) as Promise<void> | undefined)
- }
- /**
- * Move a Workspace in display order (recorded; default no-op).
- * @param workspaceId - Workspace to move.
- * @param beforeWorkspaceId - Anchor; omitted appends.
- */
- async insertBefore(workspaceId: WorkspaceId, beforeWorkspaceId?: WorkspaceId): Promise<void> {
- this.calls.push({ method: 'insertBefore', args: [workspaceId, beforeWorkspaceId] })
- await (this.stubs.get('insertBefore')?.(workspaceId, beforeWorkspaceId) as Promise<void> | undefined)
- }
- /**
- * Move an accounted session (recorded). The default echoes a minimal view.
- * @param workspaceId - target workspace.
- * @param sessionId - session to move.
- * @param beforeSessionId - anchor; omitted appends.
- * @returns the updated view.
- */
- async insertSessionBefore(workspaceId: WorkspaceId, sessionId: SessionId, beforeSessionId?: SessionId): Promise<WorkspaceView> {
- this.calls.push({ method: 'insertSessionBefore', args: [workspaceId, sessionId, beforeSessionId] })
- const stub = this.stubs.get('insertSessionBefore')
- if (stub !== undefined) return await (stub(workspaceId, sessionId, beforeSessionId) as Promise<WorkspaceView>)
- return { workspaceId, title: '', path: '', sessionIds: [sessionId] } as unknown as WorkspaceView
- }
- /**
- * Archive a session (recorded). The default mirrors the production face's
- * observable effect: the id joins the list state's archive set.
- * @param sessionId - session to archive.
- */
- async archiveSession(sessionId: SessionId): Promise<void> {
- this.calls.push({ method: 'archiveSession', args: [sessionId] })
- const stub = this.stubs.get('archiveSession')
- if (stub !== undefined) {
- await (stub(sessionId) as Promise<void>)
- return
- }
- await this.update((draft) => {
- draft.archivedSessionIds = [...draft.archivedSessionIds, sessionId]
- })
- }
- }
|