@actuate-media/cms-admin 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/dist/__tests__/components/comment-mark.test.d.ts +2 -0
  2. package/dist/__tests__/components/comment-mark.test.d.ts.map +1 -0
  3. package/dist/__tests__/components/comment-mark.test.js +89 -0
  4. package/dist/__tests__/components/comment-mark.test.js.map +1 -0
  5. package/dist/__tests__/components/comment-side-panel.test.d.ts +2 -0
  6. package/dist/__tests__/components/comment-side-panel.test.d.ts.map +1 -0
  7. package/dist/__tests__/components/comment-side-panel.test.js +291 -0
  8. package/dist/__tests__/components/comment-side-panel.test.js.map +1 -0
  9. package/dist/__tests__/components/notification-bell.test.d.ts +2 -0
  10. package/dist/__tests__/components/notification-bell.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/notification-bell.test.js +346 -0
  12. package/dist/__tests__/components/notification-bell.test.js.map +1 -0
  13. package/dist/__tests__/components/offline-status.test.d.ts +2 -0
  14. package/dist/__tests__/components/offline-status.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/offline-status.test.js +56 -0
  16. package/dist/__tests__/components/offline-status.test.js.map +1 -0
  17. package/dist/__tests__/components/presence-chips-helpers.test.d.ts +2 -0
  18. package/dist/__tests__/components/presence-chips-helpers.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/presence-chips-helpers.test.js +35 -0
  20. package/dist/__tests__/components/presence-chips-helpers.test.js.map +1 -0
  21. package/dist/__tests__/lib/active-editor.test.d.ts +2 -0
  22. package/dist/__tests__/lib/active-editor.test.d.ts.map +1 -0
  23. package/dist/__tests__/lib/active-editor.test.js +57 -0
  24. package/dist/__tests__/lib/active-editor.test.js.map +1 -0
  25. package/dist/__tests__/lib/coauthor-client.test.d.ts +2 -0
  26. package/dist/__tests__/lib/coauthor-client.test.d.ts.map +1 -0
  27. package/dist/__tests__/lib/coauthor-client.test.js +173 -0
  28. package/dist/__tests__/lib/coauthor-client.test.js.map +1 -0
  29. package/dist/__tests__/lib/collaboration-provider.test.d.ts +2 -0
  30. package/dist/__tests__/lib/collaboration-provider.test.d.ts.map +1 -0
  31. package/dist/__tests__/lib/collaboration-provider.test.js +335 -0
  32. package/dist/__tests__/lib/collaboration-provider.test.js.map +1 -0
  33. package/dist/__tests__/lib/comment-anchor.test.d.ts +2 -0
  34. package/dist/__tests__/lib/comment-anchor.test.d.ts.map +1 -0
  35. package/dist/__tests__/lib/comment-anchor.test.js +173 -0
  36. package/dist/__tests__/lib/comment-anchor.test.js.map +1 -0
  37. package/dist/__tests__/lib/comments-client.test.d.ts +2 -0
  38. package/dist/__tests__/lib/comments-client.test.d.ts.map +1 -0
  39. package/dist/__tests__/lib/comments-client.test.js +202 -0
  40. package/dist/__tests__/lib/comments-client.test.js.map +1 -0
  41. package/dist/__tests__/lib/notifications-client.test.d.ts +2 -0
  42. package/dist/__tests__/lib/notifications-client.test.d.ts.map +1 -0
  43. package/dist/__tests__/lib/notifications-client.test.js +283 -0
  44. package/dist/__tests__/lib/notifications-client.test.js.map +1 -0
  45. package/dist/__tests__/lib/slash-command.test.d.ts +2 -0
  46. package/dist/__tests__/lib/slash-command.test.d.ts.map +1 -0
  47. package/dist/__tests__/lib/slash-command.test.js +123 -0
  48. package/dist/__tests__/lib/slash-command.test.js.map +1 -0
  49. package/dist/actuate-admin.css +1 -1
  50. package/dist/components/AICoauthorBubbleMenu.d.ts +18 -0
  51. package/dist/components/AICoauthorBubbleMenu.d.ts.map +1 -0
  52. package/dist/components/AICoauthorBubbleMenu.js +113 -0
  53. package/dist/components/AICoauthorBubbleMenu.js.map +1 -0
  54. package/dist/components/CoauthorResultPopover.d.ts +28 -0
  55. package/dist/components/CoauthorResultPopover.d.ts.map +1 -0
  56. package/dist/components/CoauthorResultPopover.js +42 -0
  57. package/dist/components/CoauthorResultPopover.js.map +1 -0
  58. package/dist/components/CommandPalette.d.ts +1 -1
  59. package/dist/components/CommandPalette.d.ts.map +1 -1
  60. package/dist/components/CommandPalette.js +138 -40
  61. package/dist/components/CommandPalette.js.map +1 -1
  62. package/dist/components/CommentMark.d.ts +54 -0
  63. package/dist/components/CommentMark.d.ts.map +1 -0
  64. package/dist/components/CommentMark.js +104 -0
  65. package/dist/components/CommentMark.js.map +1 -0
  66. package/dist/components/CommentSidePanel.d.ts +86 -0
  67. package/dist/components/CommentSidePanel.d.ts.map +1 -0
  68. package/dist/components/CommentSidePanel.js +165 -0
  69. package/dist/components/CommentSidePanel.js.map +1 -0
  70. package/dist/components/LivePreview.d.ts.map +1 -1
  71. package/dist/components/LivePreview.js +80 -16
  72. package/dist/components/LivePreview.js.map +1 -1
  73. package/dist/components/NotificationBell.d.ts +66 -0
  74. package/dist/components/NotificationBell.d.ts.map +1 -0
  75. package/dist/components/NotificationBell.js +142 -0
  76. package/dist/components/NotificationBell.js.map +1 -0
  77. package/dist/components/OfflineStatus.d.ts +39 -0
  78. package/dist/components/OfflineStatus.d.ts.map +1 -0
  79. package/dist/components/OfflineStatus.js +47 -0
  80. package/dist/components/OfflineStatus.js.map +1 -0
  81. package/dist/components/PresenceChips.d.ts +31 -0
  82. package/dist/components/PresenceChips.d.ts.map +1 -0
  83. package/dist/components/PresenceChips.js +58 -0
  84. package/dist/components/PresenceChips.js.map +1 -0
  85. package/dist/components/TipTapEditor.d.ts +61 -1
  86. package/dist/components/TipTapEditor.d.ts.map +1 -1
  87. package/dist/components/TipTapEditor.js +211 -8
  88. package/dist/components/TipTapEditor.js.map +1 -1
  89. package/dist/components/ui/Badge.d.ts +1 -1
  90. package/dist/index.d.ts +8 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +4 -0
  93. package/dist/index.js.map +1 -1
  94. package/dist/lib/active-editor.d.ts +6 -0
  95. package/dist/lib/active-editor.d.ts.map +1 -0
  96. package/dist/lib/active-editor.js +35 -0
  97. package/dist/lib/active-editor.js.map +1 -0
  98. package/dist/lib/api.d.ts +8 -0
  99. package/dist/lib/api.d.ts.map +1 -1
  100. package/dist/lib/api.js +10 -0
  101. package/dist/lib/api.js.map +1 -1
  102. package/dist/lib/coauthor-client.d.ts +72 -0
  103. package/dist/lib/coauthor-client.d.ts.map +1 -0
  104. package/dist/lib/coauthor-client.js +98 -0
  105. package/dist/lib/coauthor-client.js.map +1 -0
  106. package/dist/lib/collaboration-provider.d.ts +142 -0
  107. package/dist/lib/collaboration-provider.d.ts.map +1 -0
  108. package/dist/lib/collaboration-provider.js +168 -0
  109. package/dist/lib/collaboration-provider.js.map +1 -0
  110. package/dist/lib/comment-anchor.d.ts +86 -0
  111. package/dist/lib/comment-anchor.d.ts.map +1 -0
  112. package/dist/lib/comment-anchor.js +139 -0
  113. package/dist/lib/comment-anchor.js.map +1 -0
  114. package/dist/lib/comments-client.d.ts +106 -0
  115. package/dist/lib/comments-client.d.ts.map +1 -0
  116. package/dist/lib/comments-client.js +138 -0
  117. package/dist/lib/comments-client.js.map +1 -0
  118. package/dist/lib/notifications-client.d.ts +121 -0
  119. package/dist/lib/notifications-client.d.ts.map +1 -0
  120. package/dist/lib/notifications-client.js +173 -0
  121. package/dist/lib/notifications-client.js.map +1 -0
  122. package/dist/lib/slash-command.d.ts +39 -0
  123. package/dist/lib/slash-command.d.ts.map +1 -0
  124. package/dist/lib/slash-command.js +50 -0
  125. package/dist/lib/slash-command.js.map +1 -0
  126. package/package.json +12 -3
  127. package/src/__tests__/components/comment-mark.test.ts +99 -0
  128. package/src/__tests__/components/comment-side-panel.test.tsx +343 -0
  129. package/src/__tests__/components/notification-bell.test.tsx +379 -0
  130. package/src/__tests__/components/offline-status.test.tsx +66 -0
  131. package/src/__tests__/components/presence-chips-helpers.test.ts +44 -0
  132. package/src/__tests__/lib/active-editor.test.ts +70 -0
  133. package/src/__tests__/lib/coauthor-client.test.ts +213 -0
  134. package/src/__tests__/lib/collaboration-provider.test.ts +425 -0
  135. package/src/__tests__/lib/comment-anchor.test.ts +188 -0
  136. package/src/__tests__/lib/comments-client.test.ts +239 -0
  137. package/src/__tests__/lib/notifications-client.test.ts +339 -0
  138. package/src/__tests__/lib/slash-command.test.ts +146 -0
  139. package/src/components/AICoauthorBubbleMenu.tsx +187 -0
  140. package/src/components/CoauthorResultPopover.tsx +193 -0
  141. package/src/components/CommandPalette.tsx +397 -209
  142. package/src/components/CommentMark.ts +138 -0
  143. package/src/components/CommentSidePanel.tsx +521 -0
  144. package/src/components/LivePreview.tsx +117 -11
  145. package/src/components/NotificationBell.tsx +314 -0
  146. package/src/components/OfflineStatus.tsx +90 -0
  147. package/src/components/PresenceChips.tsx +150 -0
  148. package/src/components/TipTapEditor.tsx +310 -12
  149. package/src/index.ts +20 -0
  150. package/src/lib/active-editor.ts +39 -0
  151. package/src/lib/api.ts +11 -0
  152. package/src/lib/coauthor-client.ts +160 -0
  153. package/src/lib/collaboration-provider.ts +306 -0
  154. package/src/lib/comment-anchor.ts +179 -0
  155. package/src/lib/comments-client.ts +208 -0
  156. package/src/lib/notifications-client.ts +292 -0
  157. package/src/lib/slash-command.ts +67 -0
@@ -0,0 +1,346 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment happy-dom
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
5
+ import { NotificationBell } from '../../components/NotificationBell.js';
6
+ function ok(value) {
7
+ return { ok: true, result: value };
8
+ }
9
+ function err(error = 'boom', status = 500) {
10
+ return { ok: false, error, status };
11
+ }
12
+ function row(overrides = {}) {
13
+ return {
14
+ id: 'n-1',
15
+ userId: 'user-1',
16
+ kind: 'comment_reply',
17
+ documentId: 'doc-1',
18
+ commentId: 'c-1',
19
+ payload: {
20
+ kind: 'comment_reply',
21
+ actorId: 'someone',
22
+ actorName: 'Someone',
23
+ excerpt: 'hi',
24
+ },
25
+ readAt: null,
26
+ createdAt: '2026-01-01T00:00:00.000Z',
27
+ ...overrides,
28
+ };
29
+ }
30
+ function createFakeApi() {
31
+ let rows = [];
32
+ const calls = [];
33
+ const api = {
34
+ list: vi.fn(async (opts) => {
35
+ calls.push({ name: 'list', args: [opts] });
36
+ return ok(opts.includeRead ? rows : rows.filter((r) => r.readAt === null));
37
+ }),
38
+ unread: vi.fn(async () => {
39
+ calls.push({ name: 'unread', args: [] });
40
+ return ok(rows.filter((r) => r.readAt === null).length);
41
+ }),
42
+ markRead: vi.fn(async (id) => {
43
+ calls.push({ name: 'markRead', args: [id] });
44
+ const target = rows.find((r) => r.id === id);
45
+ if (!target)
46
+ return err('not found', 404);
47
+ target.readAt = '2026-01-02T00:00:00.000Z';
48
+ return ok(target);
49
+ }),
50
+ markAllRead: vi.fn(async () => {
51
+ calls.push({ name: 'markAllRead', args: [] });
52
+ let n = 0;
53
+ for (const r of rows) {
54
+ if (r.readAt === null) {
55
+ r.readAt = '2026-01-02T00:00:00.000Z';
56
+ n++;
57
+ }
58
+ }
59
+ return ok(n);
60
+ }),
61
+ };
62
+ return {
63
+ api,
64
+ calls,
65
+ setRows: (next) => {
66
+ rows = next;
67
+ },
68
+ rows: () => rows,
69
+ };
70
+ }
71
+ describe('NotificationBell — badge', () => {
72
+ it('shows the unread badge from the unread() call', async () => {
73
+ const fake = createFakeApi();
74
+ fake.setRows([row(), row({ id: 'n-2' })]);
75
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0 }));
76
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('2'));
77
+ });
78
+ it('hides the badge when count is 0', async () => {
79
+ const fake = createFakeApi();
80
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0 }));
81
+ // Let the initial unread() settle.
82
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
83
+ expect(screen.queryByTestId('bell-badge')).toBeNull();
84
+ });
85
+ it('clamps the badge at "99+"', async () => {
86
+ const fake = createFakeApi();
87
+ fake.setRows(Array.from({ length: 150 }, (_, i) => row({ id: `n-${i}` })));
88
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0 }));
89
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('99+'));
90
+ });
91
+ it('polls unread() at pollIntervalMs', async () => {
92
+ vi.useFakeTimers({ shouldAdvanceTime: true });
93
+ try {
94
+ const fake = createFakeApi();
95
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 5000 }));
96
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalledTimes(1));
97
+ await act(async () => {
98
+ vi.advanceTimersByTime(5000);
99
+ });
100
+ expect(fake.api.unread).toHaveBeenCalledTimes(2);
101
+ }
102
+ finally {
103
+ vi.useRealTimers();
104
+ }
105
+ });
106
+ });
107
+ describe('NotificationBell — dropdown', () => {
108
+ it('shows the empty state when there are no rows', async () => {
109
+ const fake = createFakeApi();
110
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0 }));
111
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
112
+ fireEvent.click(screen.getByTestId('bell-button'));
113
+ await waitFor(() => expect(screen.getByTestId('bell-dropdown')).toBeTruthy());
114
+ await waitFor(() => expect(screen.getByText('All caught up.')).toBeTruthy());
115
+ });
116
+ it('renders one button per row with the formatted summary', async () => {
117
+ const fake = createFakeApi();
118
+ fake.setRows([
119
+ row({
120
+ id: 'n-a',
121
+ payload: {
122
+ kind: 'comment_reply',
123
+ actorId: 'x',
124
+ actorName: 'Alice',
125
+ excerpt: 'good',
126
+ },
127
+ }),
128
+ row({
129
+ id: 'n-b',
130
+ kind: 'comment_resolved',
131
+ payload: {
132
+ kind: 'comment_resolved',
133
+ actorId: 'y',
134
+ actorName: 'Bob',
135
+ rootExcerpt: 'fix',
136
+ },
137
+ }),
138
+ ]);
139
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0 }));
140
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
141
+ fireEvent.click(screen.getByTestId('bell-button'));
142
+ await waitFor(() => expect(screen.getByTestId('bell-row-n-a')).toBeTruthy());
143
+ expect(screen.getByTestId('bell-row-n-a').textContent).toContain('Alice replied');
144
+ expect(screen.getByTestId('bell-row-n-b').textContent).toContain('Bob resolved');
145
+ });
146
+ it('marks a row read optimistically, then via the API', async () => {
147
+ const fake = createFakeApi();
148
+ const onSelect = vi.fn();
149
+ fake.setRows([row()]);
150
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0, onSelect: onSelect }));
151
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
152
+ fireEvent.click(screen.getByTestId('bell-button'));
153
+ await waitFor(() => expect(screen.getByTestId('bell-row-n-1')).toBeTruthy());
154
+ await act(async () => {
155
+ fireEvent.click(screen.getByTestId('bell-row-n-1'));
156
+ });
157
+ expect(fake.api.markRead).toHaveBeenCalledWith('n-1');
158
+ expect(onSelect).toHaveBeenCalledTimes(1);
159
+ expect(screen.queryByTestId('bell-badge')).toBeNull();
160
+ });
161
+ it('rolls back the optimistic update when markRead fails', async () => {
162
+ const fake = createFakeApi();
163
+ fake.api.markRead.mockResolvedValueOnce(err('server', 500));
164
+ const onError = vi.fn();
165
+ fake.setRows([row()]);
166
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0, onError: onError }));
167
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
168
+ fireEvent.click(screen.getByTestId('bell-button'));
169
+ await waitFor(() => expect(screen.getByTestId('bell-row-n-1')).toBeTruthy());
170
+ await act(async () => {
171
+ fireEvent.click(screen.getByTestId('bell-row-n-1'));
172
+ });
173
+ expect(onError).toHaveBeenCalledWith('server');
174
+ // The badge should bounce back to 1.
175
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
176
+ });
177
+ it('marks all read in one click and disables the button after', async () => {
178
+ const fake = createFakeApi();
179
+ fake.setRows([row(), row({ id: 'n-2' })]);
180
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0 }));
181
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('2'));
182
+ fireEvent.click(screen.getByTestId('bell-button'));
183
+ await waitFor(() => expect(screen.getByTestId('bell-mark-all')).toBeTruthy());
184
+ await act(async () => {
185
+ fireEvent.click(screen.getByTestId('bell-mark-all'));
186
+ });
187
+ expect(fake.api.markAllRead).toHaveBeenCalled();
188
+ expect(screen.getByTestId('bell-mark-all').disabled).toBe(true);
189
+ });
190
+ it('surfaces list() errors via onError', async () => {
191
+ const fake = createFakeApi();
192
+ fake.api.list.mockResolvedValueOnce(err('CSRF', 403));
193
+ const onError = vi.fn();
194
+ render(_jsx(NotificationBell, { api: fake.api, pollIntervalMs: 0, onError: onError }));
195
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
196
+ fireEvent.click(screen.getByTestId('bell-button'));
197
+ await waitFor(() => expect(onError).toHaveBeenCalledWith('CSRF'));
198
+ });
199
+ });
200
+ // ---------------------------------------------------------------------------
201
+ // SSE push channel (Phase 7) — verify the bell wires up subscribe(),
202
+ // processes incoming notifications, and tears down on unmount.
203
+ // ---------------------------------------------------------------------------
204
+ describe('NotificationBell — SSE push', () => {
205
+ function withSubscribe(api) {
206
+ let captured;
207
+ let closed = false;
208
+ const handle = {
209
+ close: () => {
210
+ closed = true;
211
+ },
212
+ };
213
+ const subscribe = vi.fn((opts) => {
214
+ captured = opts;
215
+ return handle;
216
+ });
217
+ return {
218
+ api: { ...api, subscribe },
219
+ push: (n) => captured?.onNotification(n),
220
+ pushError: (e) => captured?.onError?.(e),
221
+ isClosed: () => closed,
222
+ };
223
+ }
224
+ it('subscribes on mount and bumps the badge when a new notification arrives', async () => {
225
+ const fake = createFakeApi();
226
+ const wired = withSubscribe(fake.api);
227
+ render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0 }));
228
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
229
+ await act(async () => {
230
+ wired.push(row({ id: 'sse-1' }));
231
+ });
232
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
233
+ });
234
+ it('does not double-count a notification that arrives via both SSE and REST', async () => {
235
+ const fake = createFakeApi();
236
+ const wired = withSubscribe(fake.api);
237
+ fake.setRows([row({ id: 'race' })]);
238
+ render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0 }));
239
+ // Open the dropdown so the row is in `items` from the REST list.
240
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
241
+ fireEvent.click(screen.getByTestId('bell-button'));
242
+ await waitFor(() => expect(screen.getByTestId('bell-row-race')).toBeTruthy());
243
+ // Now push the same row via SSE — must NOT add a duplicate or
244
+ // bump the badge above 1.
245
+ await act(async () => {
246
+ wired.push(row({ id: 'race' }));
247
+ });
248
+ expect(screen.getAllByTestId('bell-row-race')).toHaveLength(1);
249
+ expect(screen.getByTestId('bell-badge').textContent).toBe('1');
250
+ });
251
+ it('prepends new pushed rows to the open dropdown', async () => {
252
+ const fake = createFakeApi();
253
+ const wired = withSubscribe(fake.api);
254
+ fake.setRows([row({ id: 'old' })]);
255
+ render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0 }));
256
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
257
+ fireEvent.click(screen.getByTestId('bell-button'));
258
+ await waitFor(() => expect(screen.getByTestId('bell-row-old')).toBeTruthy());
259
+ await act(async () => {
260
+ wired.push(row({ id: 'fresh' }));
261
+ });
262
+ const rows = screen.getAllByTestId(/^bell-row-/);
263
+ expect(rows[0].getAttribute('data-testid')).toBe('bell-row-fresh');
264
+ });
265
+ it('does NOT bump the badge when the pushed notification is already read', async () => {
266
+ const fake = createFakeApi();
267
+ const wired = withSubscribe(fake.api);
268
+ render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0 }));
269
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
270
+ await act(async () => {
271
+ wired.push(row({ id: 'already-read', readAt: '2026-01-02T00:00:00.000Z' }));
272
+ });
273
+ // Already-read notifications still appear in the list when the
274
+ // user opens the dropdown with includeRead=true, but they must
275
+ // NOT register a fresh unread bump.
276
+ expect(screen.queryByTestId('bell-badge')).toBeNull();
277
+ });
278
+ it('closes the SSE subscription when the bell unmounts', async () => {
279
+ const fake = createFakeApi();
280
+ const wired = withSubscribe(fake.api);
281
+ const { unmount } = render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0 }));
282
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
283
+ unmount();
284
+ expect(wired.isClosed()).toBe(true);
285
+ });
286
+ it('does not surface transient SSE errors via onError (poll loop handles convergence)', async () => {
287
+ const fake = createFakeApi();
288
+ const wired = withSubscribe(fake.api);
289
+ const onError = vi.fn();
290
+ render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0, onError: onError }));
291
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
292
+ await act(async () => {
293
+ wired.pushError(new Event('error'));
294
+ });
295
+ // Deliberately quiet — see NotificationBell SSE handler comment.
296
+ expect(onError).not.toHaveBeenCalled();
297
+ });
298
+ it('mark-all-read failure does not drop SSE rows that arrived mid-flight', async () => {
299
+ const fake = createFakeApi();
300
+ const wired = withSubscribe(fake.api);
301
+ fake.setRows([row({ id: 'before' })]);
302
+ // Hold the markAllRead promise so we can interleave an SSE push.
303
+ let resolveMark = () => undefined;
304
+ wired.api.markAllRead.mockImplementationOnce(() => new Promise((resolve) => {
305
+ resolveMark = resolve;
306
+ }));
307
+ const onError = vi.fn();
308
+ render(_jsx(NotificationBell, { api: wired.api, pollIntervalMs: 0, onError: onError }));
309
+ await waitFor(() => expect(screen.getByTestId('bell-badge').textContent).toBe('1'));
310
+ fireEvent.click(screen.getByTestId('bell-button'));
311
+ await waitFor(() => expect(screen.getByTestId('bell-row-before')).toBeTruthy());
312
+ // Click mark-all — handler optimistically marks 'before' read,
313
+ // but the API promise is still pending.
314
+ await act(async () => {
315
+ fireEvent.click(screen.getByTestId('bell-mark-all'));
316
+ });
317
+ // While in flight, a fresh notification arrives via SSE.
318
+ await act(async () => {
319
+ wired.push(row({ id: 'mid-flight' }));
320
+ });
321
+ // Now resolve the API with a failure.
322
+ await act(async () => {
323
+ resolveMark({ ok: false, error: 'server', status: 500 });
324
+ // flush promise chain
325
+ await Promise.resolve();
326
+ });
327
+ expect(onError).toHaveBeenCalledWith('server');
328
+ // The original row was rolled back to unread...
329
+ expect(screen.getByTestId('bell-row-before').getAttribute('data-read')).toBe('false');
330
+ // ...and the SSE arrival is still present (not dropped by the
331
+ // legacy snapshot-replace rollback).
332
+ expect(screen.getByTestId('bell-row-mid-flight')).toBeTruthy();
333
+ });
334
+ it('still mounts cleanly when api.subscribe is undefined (no SSE support)', async () => {
335
+ const fake = createFakeApi();
336
+ // Strip the `subscribe` member entirely — defaultApi has it,
337
+ // but consumer-provided test APIs may not.
338
+ const apiWithoutSubscribe = { ...fake.api };
339
+ delete apiWithoutSubscribe.subscribe;
340
+ render(_jsx(NotificationBell, { api: apiWithoutSubscribe, pollIntervalMs: 0 }));
341
+ await waitFor(() => expect(fake.api.unread).toHaveBeenCalled());
342
+ // No crash, badge from unread() shows nothing, no items.
343
+ expect(screen.queryByTestId('bell-badge')).toBeNull();
344
+ });
345
+ });
346
+ //# sourceMappingURL=notification-bell.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-bell.test.js","sourceRoot":"","sources":["../../../src/__tests__/components/notification-bell.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=offline-status.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-status.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/components/offline-status.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment happy-dom
3
+ import { describe, expect, it } from 'vitest';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { OfflineStatus, describeOfflineStatus } from '../../components/OfflineStatus.js';
6
+ describe('describeOfflineStatus', () => {
7
+ it('error wins over everything', () => {
8
+ expect(describeOfflineStatus('connected', 'error').tone).toBe('bad');
9
+ expect(describeOfflineStatus('disconnected', 'error').tone).toBe('bad');
10
+ });
11
+ it('reports Synced when offline is unsupported but socket is connected', () => {
12
+ const out = describeOfflineStatus('connected', 'unsupported');
13
+ expect(out.tone).toBe('good');
14
+ expect(out.label).toBe('Synced');
15
+ });
16
+ it('warns when offline is unsupported and socket is disconnected', () => {
17
+ expect(describeOfflineStatus('disconnected', 'unsupported').tone).toBe('warn');
18
+ });
19
+ it('shows Connecting… when offline is unsupported and socket is connecting', () => {
20
+ expect(describeOfflineStatus('connecting', 'unsupported').label).toBe('Connecting…');
21
+ });
22
+ it('reports Restoring local copy… while IndexedDB is loading', () => {
23
+ expect(describeOfflineStatus('connecting', 'loading').label).toMatch(/Restoring/);
24
+ expect(describeOfflineStatus('connecting', 'pending').label).toMatch(/Restoring/);
25
+ });
26
+ it('reports Saved & synced when both ready', () => {
27
+ const out = describeOfflineStatus('connected', 'ready');
28
+ expect(out.tone).toBe('good');
29
+ expect(out.label).toBe('Saved & synced');
30
+ });
31
+ it('reports Saved locally when offline is ready but socket is disconnected', () => {
32
+ const out = describeOfflineStatus('disconnected', 'ready');
33
+ expect(out.tone).toBe('warn');
34
+ expect(out.label).toMatch(/Saved locally/);
35
+ });
36
+ it('reports Saved locally — reconnecting… while reconnecting', () => {
37
+ expect(describeOfflineStatus('connecting', 'ready').label).toMatch(/reconnecting/);
38
+ });
39
+ });
40
+ describe('<OfflineStatus />', () => {
41
+ it('renders the resolved label + tone', () => {
42
+ render(_jsx(OfflineStatus, { connection: "connected", offline: "ready" }));
43
+ const chip = screen.getByTestId('offline-status');
44
+ expect(chip.textContent).toContain('Saved & synced');
45
+ expect(chip.dataset.tone).toBe('good');
46
+ });
47
+ it('switches tone when conditions change', () => {
48
+ const { rerender } = render(_jsx(OfflineStatus, { connection: "connected", offline: "ready" }));
49
+ expect(screen.getByTestId('offline-status').dataset.tone).toBe('good');
50
+ rerender(_jsx(OfflineStatus, { connection: "disconnected", offline: "ready" }));
51
+ expect(screen.getByTestId('offline-status').dataset.tone).toBe('warn');
52
+ rerender(_jsx(OfflineStatus, { connection: "connected", offline: "error" }));
53
+ expect(screen.getByTestId('offline-status').dataset.tone).toBe('bad');
54
+ });
55
+ });
56
+ //# sourceMappingURL=offline-status.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-status.test.js","sourceRoot":"","sources":["../../../src/__tests__/components/offline-status.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=presence-chips-helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence-chips-helpers.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/components/presence-chips-helpers.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { nameToInitials } from '../../components/PresenceChips.js';
3
+ describe('nameToInitials', () => {
4
+ it('returns the first letter of a single name', () => {
5
+ expect(nameToInitials('Alice')).toBe('A');
6
+ });
7
+ it('returns the first letter of two name parts', () => {
8
+ expect(nameToInitials('Alice Cooper')).toBe('AC');
9
+ });
10
+ it('caps at two letters for three+ part names', () => {
11
+ expect(nameToInitials('Mary Jane Watson')).toBe('MJ');
12
+ });
13
+ it('uppercases the result', () => {
14
+ expect(nameToInitials('alice cooper')).toBe('AC');
15
+ });
16
+ it('trims surrounding whitespace before processing', () => {
17
+ expect(nameToInitials(' Alice ')).toBe('A');
18
+ });
19
+ it('collapses multiple whitespace separators', () => {
20
+ expect(nameToInitials('Alice Cooper')).toBe('AC');
21
+ });
22
+ it('falls back to "?" for an empty string', () => {
23
+ expect(nameToInitials('')).toBe('?');
24
+ });
25
+ it('falls back to "?" for a whitespace-only string', () => {
26
+ expect(nameToInitials(' ')).toBe('?');
27
+ });
28
+ it('handles a single emoji name without crashing', () => {
29
+ const result = nameToInitials('🐱 Whiskers');
30
+ // JS upper-case is a no-op for the cat — test just confirms we don't crash
31
+ // and return *something* with two character-positions.
32
+ expect(result.length).toBeGreaterThan(0);
33
+ });
34
+ });
35
+ //# sourceMappingURL=presence-chips-helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence-chips-helpers.test.js","sourceRoot":"","sources":["../../../src/__tests__/components/presence-chips-helpers.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,CAAC,CAAA;QAC5C,2EAA2E;QAC3E,uDAAuD;QACvD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=active-editor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-editor.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/active-editor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { setActiveEditor, getActiveEditor, clearActiveEditorIf, subscribeActiveEditor, } from '../../lib/active-editor.js';
3
+ function fakeEditor() {
4
+ return {};
5
+ }
6
+ beforeEach(() => {
7
+ setActiveEditor(null);
8
+ });
9
+ describe('active-editor registry', () => {
10
+ it('stores and returns the active editor', () => {
11
+ const a = fakeEditor();
12
+ expect(getActiveEditor()).toBeNull();
13
+ setActiveEditor(a);
14
+ expect(getActiveEditor()).toBe(a);
15
+ });
16
+ it('replaces the active editor', () => {
17
+ const a = fakeEditor();
18
+ const b = fakeEditor();
19
+ setActiveEditor(a);
20
+ setActiveEditor(b);
21
+ expect(getActiveEditor()).toBe(b);
22
+ });
23
+ it('accepts null to clear the active editor', () => {
24
+ setActiveEditor(fakeEditor());
25
+ setActiveEditor(null);
26
+ expect(getActiveEditor()).toBeNull();
27
+ });
28
+ it('notifies subscribers when the active editor changes', () => {
29
+ const listener = vi.fn();
30
+ const unsubscribe = subscribeActiveEditor(listener);
31
+ setActiveEditor(fakeEditor());
32
+ expect(listener).toHaveBeenCalledTimes(1);
33
+ setActiveEditor(fakeEditor());
34
+ expect(listener).toHaveBeenCalledTimes(2);
35
+ unsubscribe();
36
+ setActiveEditor(fakeEditor());
37
+ expect(listener).toHaveBeenCalledTimes(2);
38
+ });
39
+ it('skips notifications when the same editor is set twice', () => {
40
+ const listener = vi.fn();
41
+ const a = fakeEditor();
42
+ subscribeActiveEditor(listener);
43
+ setActiveEditor(a);
44
+ setActiveEditor(a);
45
+ expect(listener).toHaveBeenCalledTimes(1);
46
+ });
47
+ it('clearActiveEditorIf clears only when matched', () => {
48
+ const a = fakeEditor();
49
+ const b = fakeEditor();
50
+ setActiveEditor(a);
51
+ clearActiveEditorIf(b);
52
+ expect(getActiveEditor()).toBe(a);
53
+ clearActiveEditorIf(a);
54
+ expect(getActiveEditor()).toBeNull();
55
+ });
56
+ });
57
+ //# sourceMappingURL=active-editor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-editor.test.js","sourceRoot":"","sources":["../../../src/__tests__/lib/active-editor.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=coauthor-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coauthor-client.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/lib/coauthor-client.test.ts"],"names":[],"mappings":""}