/** Test double for the client settings-scope seam. */ import { vi } from 'vitest' import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/client' /** Handle over one stubbed scope: the scope, its write spy, and publication controls. */ export interface StubSettingsScope { /** The scope face handed to the service under test. */ scope: SettingsScope /** Spy behind `scope.set`; resolves immediately. */ set: ReturnType /** Spy behind `scope.unset`; resolves immediately. */ unset: ReturnType /** @returns how many listeners are currently subscribed (disposal assertions). */ listenerCount(): number /** * Replace part of the snapshot and notify subscribers, as a Host * acceptance would. * @param next - snapshot fields to replace. */ publish(next: Partial>): void } /** * Build an in-memory settings scope for service specs: starts in the host * loading state, records writes, and lets the test publish Host acceptances. * @returns the stub handle. */ export function stubSettingsScope(): StubSettingsScope { let snapshot: SettingsScopeSnapshot = { status: 'loading', value: undefined, base: undefined, user: undefined, revision: undefined, writable: false, mode: 'host', } const listeners = new Set<() => void>() const set = vi.fn(() => Promise.resolve()) const unset = vi.fn(() => Promise.resolve()) return { scope: { getSnapshot: () => snapshot, subscribe: (listener) => { listeners.add(listener) return () => { listeners.delete(listener) } }, set, unset, }, set, unset, listenerCount: () => listeners.size, publish: (next) => { snapshot = { ...snapshot, ...next } for (const listener of [...listeners]) listener() }, } }