settings-remote.ts 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. /** Test double for the `settings` Remote namespace a bench's plugins inject. */
  2. import { vi } from 'vitest'
  3. /** The minimum a scripted namespace view carries for the double's own bookkeeping. */
  4. export interface ScriptedNamespace {
  5. /** Namespace key the write addresses. */
  6. ns: string
  7. }
  8. /** One scripted `settings` namespace face plus the controls a bench drives it with. */
  9. export interface ScriptedSettingsRemote<View extends ScriptedNamespace> {
  10. /**
  11. * The namespace face handed to `TestRemote` as `settings`. A plugin injecting
  12. * `remote.settings` unparks on it, which is what most benches need; the
  13. * describe answer is the same one the shared mirror would read.
  14. */
  15. settings: {
  16. describe(): Promise<{ ok: true; value: { writable: boolean; hasDocument: boolean; namespaces: readonly View[] } }>
  17. update(ns: string, patch: unknown, expectedRevision: number | undefined): Promise<
  18. | { ok: true; value: View }
  19. | { ok: false; error: { code: string; message: string; details: object } }
  20. >
  21. replace(ns: string, section: unknown, expectedRevision: number | undefined): Promise<
  22. | { ok: true; value: View }
  23. | { ok: false; error: { code: string; message: string; details: object } }
  24. >
  25. mutate(ns: string, ops: unknown, expectedRevision: number | undefined): Promise<
  26. | { ok: true; value: View }
  27. | { ok: false; error: { code: string; message: string; details: object } }
  28. >
  29. }
  30. /** Spy behind `settings.update`, for argument assertions. */
  31. update: ReturnType<typeof vi.fn>
  32. /** Spy behind `settings.replace`, for argument assertions. */
  33. replace: ReturnType<typeof vi.fn>
  34. /** Spy behind `settings.mutate`, for argument assertions. */
  35. mutate: ReturnType<typeof vi.fn>
  36. /**
  37. * Replace what the next describe answers with, as a Host commit would.
  38. * @param namespaces - the namespace views to serve from now on.
  39. */
  40. publish(namespaces: readonly View[]): void
  41. }
  42. /**
  43. * Build a scripted `settings` Remote namespace for a bench. Each write answers
  44. * with the addressed namespace unchanged, so a bench that only needs its
  45. * plugins to activate scripts nothing; one asserting a write reads the
  46. * corresponding spy or replaces the face.
  47. * @param namespaces - namespace views the first describe answers with.
  48. * @param options - deployment facts the describe answer reports.
  49. * @returns the face and its controls.
  50. */
  51. export function scriptedSettingsRemote<View extends ScriptedNamespace>(
  52. namespaces: readonly View[] = [],
  53. options: { writable?: boolean; hasDocument?: boolean } = {},
  54. ): ScriptedSettingsRemote<View> {
  55. let served = namespaces
  56. const writable = options.writable ?? true
  57. const hasDocument = options.hasDocument ?? false
  58. const answer = (ns: string) => {
  59. const view = served.find(candidate => candidate.ns === ns)
  60. return Promise.resolve(view === undefined
  61. ? {
  62. ok: false as const,
  63. error: { code: 'settings/rejected', message: `no scripted namespace "${ns}"`, details: { ns } },
  64. }
  65. : { ok: true as const, value: view })
  66. }
  67. const update = vi.fn((ns: string, _patch: unknown, _expectedRevision: number | undefined) => answer(ns))
  68. const replace = vi.fn((ns: string, _section: unknown, _expectedRevision: number | undefined) => answer(ns))
  69. const mutate = vi.fn((ns: string, _ops: unknown, _expectedRevision: number | undefined) => answer(ns))
  70. return {
  71. settings: {
  72. describe: () => Promise.resolve({ ok: true as const, value: { writable, hasDocument, namespaces: served } }),
  73. update: (ns, patch, expectedRevision) => update(ns, patch, expectedRevision),
  74. replace: (ns, section, expectedRevision) => replace(ns, section, expectedRevision),
  75. mutate: (ns, ops, expectedRevision) => mutate(ns, ops, expectedRevision),
  76. },
  77. update,
  78. replace,
  79. mutate,
  80. publish(next) { served = next },
  81. }
  82. }