Files
deepseek-harness/packages/client/test-runtime/src/settings-scope.ts
Yichen Jiang 638c9e4bd7 refactor(client): replace the per-field settings preference controller with a namespace settings scope
bindSettingsScope mirrors the Host-side settings owner seam in the browser:
one scope per namespace publishes a snapshot store (status, section value,
revision, writability, host/memory mode), validates sections against the
namespace's serialized wire schema via dsh-client-schema-form, and keeps the
controller's listener-before-read, revisioned serialized writes, latest-wins
publication, conflict recovery, and disposal quiescence. Theme, locale, and
busy-Enter services now take the scope as a constructor collaborator, which
removes the bindPersistence/syncPreference two-phase callback pair and the
defaulted no-op persist writers; hand-written wire guards fall away in favor
of the registered schema. test-runtime gains a stubSettingsScope double.
2026-08-07 23:25:42 +08:00

49 lines
1.6 KiB
TypeScript

/** 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<T> {
/** The scope face handed to the service under test. */
scope: SettingsScope<T>
/** Spy behind `scope.set`; resolves immediately. */
set: ReturnType<typeof vi.fn>
/** @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<SettingsScopeSnapshot<T>>): 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<T>(): StubSettingsScope<T> {
let snapshot: SettingsScopeSnapshot<T> = {
status: 'loading', value: undefined, revision: undefined, writable: false, mode: 'host',
}
const listeners = new Set<() => void>()
const set = vi.fn(() => Promise.resolve())
return {
scope: {
getSnapshot: () => snapshot,
subscribe: (listener) => {
listeners.add(listener)
return () => { listeners.delete(listener) }
},
set,
},
set,
listenerCount: () => listeners.size,
publish: (next) => {
snapshot = { ...snapshot, ...next }
for (const listener of [...listeners]) listener()
},
}
}