settings-scope.ts 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. /** Test double for the client settings-scope seam. */
  2. import { vi } from 'vitest'
  3. import type {
  4. SettingsScope, SettingsScopeSnapshot,
  5. } from '@deepseek-ai/dsh-client-ui-settings/client'
  6. /** Handle over one stubbed scope: the scope, its write spy, and publication controls. */
  7. export interface StubSettingsScope<T> {
  8. /** The scope face handed to the service under test. */
  9. scope: SettingsScope<T>
  10. /** Spy behind `scope.set`; resolves immediately. */
  11. set: ReturnType<typeof vi.fn>
  12. /** Spy behind `scope.mutate`; resolves immediately. */
  13. mutate: ReturnType<typeof vi.fn>
  14. /** Spy behind `scope.unset`; resolves immediately. */
  15. unset: ReturnType<typeof vi.fn>
  16. /** @returns how many listeners are currently subscribed (disposal assertions). */
  17. listenerCount(): number
  18. /**
  19. * Replace part of the snapshot and notify subscribers, as a Host
  20. * acceptance would.
  21. * @param next - snapshot fields to replace.
  22. */
  23. publish(next: Partial<SettingsScopeSnapshot<T>>): void
  24. }
  25. /**
  26. * Build an in-memory settings scope for service specs: starts in the host
  27. * loading state, records writes, and lets the test publish Host acceptances.
  28. * @returns the stub handle.
  29. */
  30. export function stubSettingsScope<T>(): StubSettingsScope<T> {
  31. let snapshot: SettingsScopeSnapshot<T> = {
  32. status: 'loading', value: undefined, base: undefined, user: undefined,
  33. revision: undefined, writable: false, mode: 'host',
  34. }
  35. const listeners = new Set<() => void>()
  36. const set = vi.fn(() => Promise.resolve())
  37. const mutate = vi.fn(() => Promise.resolve())
  38. const unset = vi.fn(() => Promise.resolve())
  39. return {
  40. scope: {
  41. getSnapshot: () => snapshot,
  42. subscribe: (listener) => {
  43. listeners.add(listener)
  44. return () => { listeners.delete(listener) }
  45. },
  46. mutate,
  47. set,
  48. unset,
  49. },
  50. set,
  51. mutate,
  52. unset,
  53. listenerCount: () => listeners.size,
  54. publish: (next) => {
  55. snapshot = { ...snapshot, ...next }
  56. for (const listener of [...listeners]) listener()
  57. },
  58. }
  59. }