settings-document-store.client.spec.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { describe, expect, it, vi } from 'vitest'
  2. import { RemoteError } from '@deepseek-ai/dsh-client-test-runtime'
  3. import type { RemoteResult } from '@deepseek-ai/dsh-api-remotes/client'
  4. import { SettingsDescribeMirror } from '@deepseek-ai/dsh-client-ui-settings/src/client/settings-mirror.ts'
  5. import { SettingsDocumentStore } from '../src/client/settings-document-store.ts'
  6. /** Store over a real mirror derived from the same scripted context. */
  7. function derivedDocumentStore(remote: object) {
  8. const ctx = { remote } as never
  9. return new SettingsDocumentStore(ctx, new SettingsDescribeMirror(ctx))
  10. }
  11. function response(hasDocument = false) {
  12. return { ok: true, value: { writable: true, hasDocument, namespaces: [] } }
  13. }
  14. function opened(): RemoteResult<{ opened: true }> {
  15. return { ok: true, value: { opened: true } }
  16. }
  17. function describeFailed(message: string) {
  18. return { ok: false as const, error: new RemoteError('gateway/internal', message, {}) }
  19. }
  20. describe('SettingsDocumentStore', () => {
  21. it('loads provider metadata and asks the settings domain to open its document', async () => {
  22. const describe = vi.fn(() => Promise.resolve(response(true)))
  23. const openDocument = vi.fn(() => Promise.resolve(opened()))
  24. const controller = derivedDocumentStore({ settings: { describe, openSettingsDocument: openDocument } })
  25. await controller.load()
  26. expect(controller.store.getSnapshot()).toEqual({
  27. status: 'ready', opening: false, error: null,
  28. })
  29. await controller.open()
  30. expect(openDocument).toHaveBeenCalledWith()
  31. })
  32. it('marks absent or failed metadata unavailable without opening anything', async () => {
  33. const openDocument = vi.fn(() => Promise.resolve(opened()))
  34. const absent = derivedDocumentStore({
  35. settings: { describe: () => Promise.resolve(response()), openSettingsDocument: openDocument },
  36. })
  37. await absent.load()
  38. await absent.open()
  39. expect(absent.store.getSnapshot().status).toBe('unavailable')
  40. expect(openDocument).not.toHaveBeenCalled()
  41. const failed = derivedDocumentStore({
  42. settings: { describe: () => Promise.reject(new Error('offline')), openSettingsDocument: openDocument },
  43. })
  44. await failed.load()
  45. expect(failed.store.getSnapshot()).toMatchObject({ status: 'unavailable', error: 'offline' })
  46. const rejected = derivedDocumentStore({
  47. settings: { describe: () => Promise.resolve(describeFailed('provider failed')), openSettingsDocument: openDocument },
  48. })
  49. await rejected.load()
  50. expect(rejected.store.getSnapshot()).toMatchObject({
  51. status: 'unavailable', error: 'provider failed',
  52. })
  53. })
  54. it('collapses concurrent open gestures and recovers after a failure', async () => {
  55. let resolveOpen!: (response: RemoteResult<{ opened: true }>) => void
  56. const openDocument = vi.fn(() => new Promise<RemoteResult<{ opened: true }>>((resolve) => { resolveOpen = resolve }))
  57. const controller = derivedDocumentStore({
  58. settings: { describe: () => Promise.resolve(response(true)), openSettingsDocument: openDocument },
  59. })
  60. await controller.load()
  61. const first = controller.open()
  62. const second = controller.open()
  63. expect(openDocument).toHaveBeenCalledOnce()
  64. resolveOpen({ ok: false, error: new RemoteError('gateway/internal', 'no default editor', {}) })
  65. await Promise.all([first, second])
  66. expect(controller.store.getSnapshot()).toMatchObject({
  67. status: 'ready', opening: false, error: 'no default editor',
  68. })
  69. })
  70. it('recovers availability via a mirror refresh after a failed first read', async () => {
  71. // A first read that failed leaves the action unavailable with the miss
  72. // recorded; the mirror's next refresh (a commit or reconnect) recovers it.
  73. const ctx = {
  74. remote: {
  75. settings: {
  76. describe: vi.fn()
  77. .mockRejectedValueOnce(new Error('offline'))
  78. .mockResolvedValueOnce(response(true)),
  79. openSettingsDocument: vi.fn(),
  80. },
  81. },
  82. } as never
  83. const mirror = new SettingsDescribeMirror(ctx)
  84. const caught = new SettingsDocumentStore(ctx, mirror)
  85. await caught.load()
  86. expect(caught.store.getSnapshot()).toMatchObject({ status: 'unavailable', error: 'offline' })
  87. await mirror.load()
  88. expect(caught.store.getSnapshot()).toMatchObject({ status: 'ready', error: null })
  89. })
  90. })