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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { describe, expect, it, vi } from 'vitest'
  2. import type { RpcResponse } from '@deepseek-ai/dsh-api-remotes/client'
  3. import { SettingsDescribeMirror } from '@deepseek-ai/dsh-client-ui-settings/src/client/settings-mirror.ts'
  4. import { SettingsDocumentStore } from '../src/client/settings-document-store.ts'
  5. /** Store over a real mirror derived from the same fake wire. */
  6. function derivedDocumentStore(api: object) {
  7. const wire = api as never
  8. return new SettingsDocumentStore(wire, new SettingsDescribeMirror(wire))
  9. }
  10. function response(hasDocument = false): RpcResponse<{
  11. writable: boolean
  12. hasDocument: boolean
  13. namespaces: []
  14. }> {
  15. return {
  16. rpcId: 'settings-document' as never,
  17. result: {
  18. ok: true,
  19. value: { writable: true, hasDocument, namespaces: [] },
  20. },
  21. }
  22. }
  23. function opened(): RpcResponse<{ opened: true }> {
  24. return {
  25. rpcId: 'settings-open' as never,
  26. result: { ok: true, value: { opened: true } },
  27. }
  28. }
  29. function describeFailed(message: string): RpcResponse<never> {
  30. return {
  31. rpcId: 'settings-document-failed' as never,
  32. result: { ok: false, error: { code: 'internal', message, details: {} } },
  33. }
  34. }
  35. describe('SettingsDocumentStore', () => {
  36. it('loads provider metadata and asks the settings domain to open its document', async () => {
  37. const describe = vi.fn(() => Promise.resolve(response(true)))
  38. const openDocument = vi.fn(() => Promise.resolve(opened()))
  39. const controller = derivedDocumentStore({ settings: { describe, openDocument } })
  40. await controller.load()
  41. expect(controller.store.getSnapshot()).toEqual({
  42. status: 'ready', opening: false, error: null,
  43. })
  44. await controller.open()
  45. expect(openDocument).toHaveBeenCalledWith({})
  46. })
  47. it('marks absent or failed metadata unavailable without opening anything', async () => {
  48. const openDocument = vi.fn(() => Promise.resolve(opened()))
  49. const absent = derivedDocumentStore({
  50. settings: { describe: () => Promise.resolve(response()), openDocument },
  51. })
  52. await absent.load()
  53. await absent.open()
  54. expect(absent.store.getSnapshot().status).toBe('unavailable')
  55. expect(openDocument).not.toHaveBeenCalled()
  56. const failed = derivedDocumentStore({
  57. settings: { describe: () => Promise.reject(new Error('offline')), openDocument },
  58. })
  59. await failed.load()
  60. expect(failed.store.getSnapshot()).toMatchObject({ status: 'unavailable', error: 'offline' })
  61. const rejected = derivedDocumentStore({
  62. settings: { describe: () => Promise.resolve(describeFailed('provider failed')), openDocument },
  63. })
  64. await rejected.load()
  65. expect(rejected.store.getSnapshot()).toMatchObject({
  66. status: 'unavailable', error: 'provider failed',
  67. })
  68. })
  69. it('collapses concurrent open gestures and recovers after a failure', async () => {
  70. let resolveOpen!: (response: RpcResponse<{ opened: true }>) => void
  71. const openDocument = vi.fn(() => new Promise<RpcResponse<{ opened: true }>>((resolve) => { resolveOpen = resolve }))
  72. const controller = derivedDocumentStore({
  73. settings: { describe: () => Promise.resolve(response(true)), openDocument },
  74. })
  75. await controller.load()
  76. const first = controller.open()
  77. const second = controller.open()
  78. expect(openDocument).toHaveBeenCalledOnce()
  79. resolveOpen({
  80. rpcId: 'settings-open-failed' as never,
  81. result: { ok: false, error: { code: 'internal', message: 'no default editor', details: {} } },
  82. })
  83. await Promise.all([first, second])
  84. expect(controller.store.getSnapshot()).toMatchObject({
  85. status: 'ready', opening: false, error: 'no default editor',
  86. })
  87. })
  88. it('reports non-Error native failures and recovers availability via a mirror refresh', async () => {
  89. let rejectOpen!: (reason?: unknown) => void
  90. const controller = derivedDocumentStore({
  91. settings: {
  92. describe: vi.fn(() => Promise.resolve(response(true))),
  93. openDocument: () => new Promise((_, reject) => { rejectOpen = reject }),
  94. },
  95. })
  96. await controller.load()
  97. expect(controller.store.getSnapshot().status).toBe('ready')
  98. const opening = controller.open()
  99. rejectOpen('native unavailable')
  100. await opening
  101. expect(controller.store.getSnapshot()).toMatchObject({
  102. status: 'ready', opening: false, error: 'native unavailable',
  103. })
  104. // A first read that failed leaves the action unavailable with the miss
  105. // recorded; the mirror's next refresh (a commit or reconnect) recovers it.
  106. const wire = {
  107. settings: {
  108. describe: vi.fn()
  109. .mockRejectedValueOnce(new Error('offline'))
  110. .mockResolvedValueOnce(response(true)),
  111. openDocument: vi.fn(),
  112. },
  113. } as never
  114. const mirror = new SettingsDescribeMirror(wire)
  115. const caught = new SettingsDocumentStore(wire, mirror)
  116. await caught.load()
  117. expect(caught.store.getSnapshot()).toMatchObject({ status: 'unavailable', error: 'offline' })
  118. await mirror.load()
  119. expect(caught.store.getSnapshot()).toMatchObject({ status: 'ready', error: null })
  120. })
  121. })