@semiont/react-ui 0.4.19 → 0.4.21

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 (108) hide show
  1. package/README.md +8 -5
  2. package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-6ZGFEN2N.mjs} +9 -13
  3. package/dist/PdfAnnotationCanvas.client-6ZGFEN2N.mjs.map +1 -0
  4. package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
  5. package/dist/chunk-KEDFYI6N.mjs +7788 -0
  6. package/dist/chunk-KEDFYI6N.mjs.map +1 -0
  7. package/dist/index.d.mts +171 -1140
  8. package/dist/index.mjs +3263 -13644
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/test-utils.d.mts +46 -21
  11. package/dist/test-utils.mjs +2499 -87
  12. package/dist/test-utils.mjs.map +1 -1
  13. package/package.json +1 -2
  14. package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
  15. package/src/components/CodeMirrorRenderer.tsx +9 -11
  16. package/src/components/StatusDisplay.tsx +42 -16
  17. package/src/components/Toolbar.tsx +4 -4
  18. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
  19. package/src/components/__tests__/StatusDisplay.test.tsx +47 -64
  20. package/src/components/__tests__/Toolbar.test.tsx +4 -4
  21. package/src/components/annotation/AnnotateToolbar.tsx +8 -7
  22. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
  23. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +0 -1
  24. package/src/components/image-annotation/AnnotationOverlay.tsx +12 -13
  25. package/src/components/image-annotation/SvgDrawingCanvas.tsx +7 -7
  26. package/src/components/modals/PermissionDeniedModal.tsx +11 -11
  27. package/src/components/modals/ReferenceWizardModal.tsx +14 -18
  28. package/src/components/modals/ResourceSearchModal.tsx +10 -6
  29. package/src/components/modals/SearchModal.tsx +10 -6
  30. package/src/components/modals/SessionExpiredModal.tsx +11 -11
  31. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
  32. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
  33. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
  34. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
  35. package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
  36. package/src/components/navigation/ObservableLink.tsx +6 -6
  37. package/src/components/navigation/SimpleNavigation.tsx +4 -4
  38. package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
  39. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
  40. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +9 -11
  41. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +0 -1
  42. package/src/components/resource/AnnotateView.tsx +7 -6
  43. package/src/components/resource/AnnotationHistory.tsx +9 -12
  44. package/src/components/resource/BrowseView.tsx +8 -7
  45. package/src/components/resource/ResourceViewer.tsx +17 -25
  46. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
  47. package/src/components/resource/__tests__/BrowseView.test.tsx +34 -83
  48. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +40 -31
  49. package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
  50. package/src/components/resource/panels/AssessmentPanel.tsx +19 -15
  51. package/src/components/resource/panels/AssistSection.tsx +11 -13
  52. package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
  53. package/src/components/resource/panels/CommentEntry.tsx +5 -4
  54. package/src/components/resource/panels/CommentsPanel.tsx +9 -11
  55. package/src/components/resource/panels/HighlightEntry.tsx +5 -4
  56. package/src/components/resource/panels/HighlightPanel.tsx +10 -11
  57. package/src/components/resource/panels/ReferenceEntry.tsx +8 -8
  58. package/src/components/resource/panels/ReferencesPanel.tsx +13 -12
  59. package/src/components/resource/panels/ResourceInfoPanel.tsx +7 -6
  60. package/src/components/resource/panels/TagEntry.tsx +5 -4
  61. package/src/components/resource/panels/TaggingPanel.tsx +10 -16
  62. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +3 -2
  63. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +18 -52
  64. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
  65. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +18 -56
  66. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -1
  67. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +4 -5
  68. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
  69. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
  70. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +15 -47
  71. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -47
  72. package/src/components/settings/SettingsPanel.tsx +8 -8
  73. package/src/components/settings/__tests__/SettingsPanel.test.tsx +12 -12
  74. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -1
  75. package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
  76. package/src/features/admin-exchange/components/ImportCard.tsx +2 -6
  77. package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
  78. package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
  79. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
  80. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
  81. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
  82. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
  83. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
  84. package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -22
  85. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
  86. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
  87. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +38 -45
  88. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +123 -192
  89. package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
  90. package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
  91. package/dist/chunk-OZICDVH7.mjs +0 -62
  92. package/dist/chunk-OZICDVH7.mjs.map +0 -1
  93. package/dist/chunk-R4CCMFJH.mjs +0 -877
  94. package/dist/chunk-R4CCMFJH.mjs.map +0 -1
  95. package/dist/chunk-VN5NY4SN.mjs +0 -200
  96. package/dist/chunk-VN5NY4SN.mjs.map +0 -1
  97. package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
  98. package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
  99. package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
  100. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
  101. package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
  102. package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
  103. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
  104. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
  105. package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
  106. package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
  107. package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
  108. package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
@@ -1,303 +0,0 @@
1
- /**
2
- * Bug Reproduction: Detection Progress Doesn't Dismiss
3
- *
4
- * USER SCENARIO:
5
- * 1. User clicks "Detect References" button
6
- * 2. Progress modal appears showing "Processing: Location"
7
- * 3. Detection completes successfully
8
- * 4. BUG: Progress modal stays visible showing "Processing: Location" indefinitely
9
- *
10
- * ROOT CAUSE:
11
- * - useMarkFlow.ts (line 54-62): mark:assist-finished clears `assistingMotivation` but keeps `progress`
12
- * - AssistSection.tsx (line 214): Shows progress UI whenever `progress` is not null
13
- * - No mechanism to auto-dismiss or manually close the progress display
14
- *
15
- * FIX OPTIONS:
16
- * A) Auto-dismiss after timeout (3s after completion)
17
- * B) Add "Close" button to progress display
18
- * C) Clear progress on next mark:assist-request (already works but not ideal UX)
19
- */
20
-
21
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22
- import { render, screen, waitFor } from '@testing-library/react';
23
- import { act } from 'react';
24
- import { useMarkFlow } from '../../../hooks/useMarkFlow';
25
- import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
26
- import { ApiClientProvider } from '../../../contexts/ApiClientContext';
27
- import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
28
- import { SemiontApiClient } from '@semiont/api-client';
29
- import { resourceId } from '@semiont/core';
30
-
31
- // Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
32
- vi.mock('../../../components/Toast', () => ({
33
- useToast: () => ({
34
- showSuccess: vi.fn(),
35
- showError: vi.fn(),
36
- showInfo: vi.fn(),
37
- showWarning: vi.fn(),
38
- }),
39
- }));
40
-
41
- describe('Detection Progress Dismissal Bug', () => {
42
- const rUri = resourceId('test');
43
-
44
- beforeEach(() => {
45
- vi.clearAllMocks();
46
-
47
- vi.spyOn(SemiontApiClient.prototype, 'annotateReferences').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
48
- vi.spyOn(SemiontApiClient.prototype, 'annotateHighlights').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
49
- vi.spyOn(SemiontApiClient.prototype, 'annotateComments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
50
- vi.spyOn(SemiontApiClient.prototype, 'annotateAssessments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
51
- });
52
-
53
- afterEach(() => {
54
- vi.restoreAllMocks();
55
- });
56
-
57
- it('FIXED: Progress auto-dismisses after detection completes', { timeout: 7000 }, async () => {
58
- let eventBusInstance: any;
59
-
60
- function TestHarness() {
61
- eventBusInstance = useEventBus();
62
- const { assistingMotivation, progress } = useMarkFlow(rUri);
63
-
64
- return (
65
- <div>
66
- <div data-testid="detecting">{assistingMotivation || 'none'}</div>
67
- <div data-testid="progress">
68
- {progress ? progress.message || 'has progress' : 'no progress'}
69
- </div>
70
- </div>
71
- );
72
- }
73
-
74
- render(
75
- <EventBusProvider>
76
- <AuthTokenProvider token={null}>
77
- <ApiClientProvider baseUrl="http://localhost:4000">
78
- <TestHarness />
79
- </ApiClientProvider>
80
- </AuthTokenProvider>
81
- </EventBusProvider>
82
- );
83
-
84
- // Initial state
85
- expect(screen.getByTestId('detecting')).toHaveTextContent('none');
86
- expect(screen.getByTestId('progress')).toHaveTextContent('no progress');
87
-
88
- // User clicks detect button (emits mark:assist-request)
89
- act(() => {
90
- eventBusInstance.get('mark:assist-request').next({
91
- motivation: 'linking',
92
- options: { entityTypes: ['Location'] }
93
- });
94
- });
95
-
96
- // Detection started
97
- await waitFor(() => {
98
- expect(screen.getByTestId('detecting')).toHaveTextContent('linking');
99
- });
100
-
101
- // SSE sends progress update
102
- act(() => {
103
- eventBusInstance.get('mark:progress').next({
104
- status: 'scanning',
105
- message: 'Processing: Location',
106
- currentEntityType: 'Location',
107
- });
108
- });
109
-
110
- // Progress is visible
111
- await waitFor(() => {
112
- expect(screen.getByTestId('progress')).toHaveTextContent('Processing: Location');
113
- });
114
-
115
- // Detection completes (SSE finishes, backend emits mark:assist-finished)
116
- act(() => {
117
- eventBusInstance.get('mark:assist-finished').next({ motivation: 'linking' });
118
- });
119
-
120
- // assistingMotivation cleared immediately
121
- await waitFor(() => {
122
- expect(screen.getByTestId('detecting')).toHaveTextContent('none');
123
- });
124
-
125
- // Wait 5 seconds - progress auto-dismissed after completion
126
- await new Promise(resolve => setTimeout(resolve, 5100));
127
- expect(screen.getByTestId('progress')).toHaveTextContent('no progress');
128
- });
129
-
130
- it('WORKAROUND: Starting new detection clears old progress', async () => {
131
- let eventBusInstance: any;
132
-
133
- function TestHarness() {
134
- eventBusInstance = useEventBus();
135
- const { progress } = useMarkFlow(rUri);
136
-
137
- return (
138
- <div data-testid="progress">
139
- {progress ? progress.message || 'has progress' : 'no progress'}
140
- </div>
141
- );
142
- }
143
-
144
- render(
145
- <EventBusProvider>
146
- <AuthTokenProvider token={null}>
147
- <ApiClientProvider baseUrl="http://localhost:4000">
148
- <TestHarness />
149
- </ApiClientProvider>
150
- </AuthTokenProvider>
151
- </EventBusProvider>
152
- );
153
-
154
- // First detection with stuck progress
155
- act(() => {
156
- eventBusInstance.get('mark:assist-request').next({ motivation: 'linking', options: {} });
157
- });
158
-
159
- act(() => {
160
- eventBusInstance.get('mark:progress').next({ message: 'Old progress stuck here' });
161
- });
162
-
163
- act(() => {
164
- eventBusInstance.get('mark:assist-finished').next({ motivation: 'linking' });
165
- });
166
-
167
- await waitFor(() => {
168
- expect(screen.getByTestId('progress')).toHaveTextContent('Old progress stuck here');
169
- });
170
-
171
- // WORKAROUND: Start new detection clears old progress
172
- act(() => {
173
- eventBusInstance.get('mark:assist-request').next({ motivation: 'highlighting', options: {} });
174
- });
175
-
176
- await waitFor(() => {
177
- expect(screen.getByTestId('progress')).toHaveTextContent('no progress');
178
- });
179
- });
180
-
181
- it('NEW FEATURE: Progress should auto-dismiss after 5 seconds', { timeout: 7000 }, async () => {
182
- // This test verifies the new auto-dismiss feature
183
-
184
- let eventBusInstance: any;
185
-
186
- function TestHarness() {
187
- eventBusInstance = useEventBus();
188
- const { progress } = useMarkFlow(rUri);
189
-
190
- return (
191
- <div data-testid="progress">
192
- {progress ? 'visible' : 'dismissed'}
193
- </div>
194
- );
195
- }
196
-
197
- render(
198
- <EventBusProvider>
199
- <AuthTokenProvider token={null}>
200
- <ApiClientProvider baseUrl="http://localhost:4000">
201
- <TestHarness />
202
- </ApiClientProvider>
203
- </AuthTokenProvider>
204
- </EventBusProvider>
205
- );
206
-
207
- // Show progress
208
- act(() => {
209
- eventBusInstance.get('mark:assist-request').next({ motivation: 'linking', options: {} });
210
- });
211
-
212
- act(() => {
213
- eventBusInstance.get('mark:progress').next({
214
- status: 'complete',
215
- message: 'Complete! Created 5 annotations'
216
- });
217
- });
218
-
219
- act(() => {
220
- eventBusInstance.get('mark:assist-finished').next({ motivation: 'linking' });
221
- });
222
-
223
- // Progress visible initially
224
- await waitFor(() => {
225
- expect(screen.getByTestId('progress')).toHaveTextContent('visible');
226
- });
227
-
228
- // Auto-dismiss after 5 seconds
229
- await new Promise(resolve => setTimeout(resolve, 5100));
230
-
231
- await waitFor(() => {
232
- expect(screen.getByTestId('progress')).toHaveTextContent('dismissed');
233
- });
234
- });
235
-
236
- it('FIXED: SSE emits final completion chunk data as annotate:assist-progress', async () => {
237
- /**
238
- * This test verifies that SSE emits the final chunk as annotate:assist-progress
239
- * BEFORE emitting mark:assist-finished.
240
- *
241
- * This ensures the UI can display the final completion message with status:'complete'.
242
- */
243
-
244
- let eventBusInstance: any;
245
-
246
- function TestHarness() {
247
- eventBusInstance = useEventBus();
248
- const { progress } = useMarkFlow(rUri);
249
-
250
- return (
251
- <div>
252
- <div data-testid="progress-status">{progress?.status || 'none'}</div>
253
- <div data-testid="progress-message">{progress?.message || 'no message'}</div>
254
- </div>
255
- );
256
- }
257
-
258
- render(
259
- <EventBusProvider>
260
- <AuthTokenProvider token={null}>
261
- <ApiClientProvider baseUrl="http://localhost:4000">
262
- <TestHarness />
263
- </ApiClientProvider>
264
- </AuthTokenProvider>
265
- </EventBusProvider>
266
- );
267
-
268
- // Start detection (triggers SSE stream creation)
269
- act(() => {
270
- eventBusInstance.get('mark:assist-request').next({
271
- motivation: 'linking',
272
- options: { entityTypes: ['Location'] }
273
- });
274
- });
275
-
276
- // Simulate SSE scanning chunk
277
- act(() => {
278
- eventBusInstance.get('mark:progress').next({
279
- status: 'scanning',
280
- message: 'Processing: Location',
281
- });
282
- });
283
-
284
- await waitFor(() => {
285
- expect(screen.getByTestId('progress-status')).toHaveTextContent('scanning');
286
- });
287
-
288
- // Simulate SSE emitting final chunk as annotate:assist-progress
289
- act(() => {
290
- eventBusInstance.get('mark:progress').next({
291
- status: 'complete',
292
- message: 'Complete! Found 5 entities',
293
- foundCount: 5,
294
- });
295
- });
296
-
297
- // Verify final chunk is now visible
298
- await waitFor(() => {
299
- expect(screen.getByTestId('progress-status')).toHaveTextContent('complete');
300
- expect(screen.getByTestId('progress-message')).toHaveTextContent('Complete! Found 5 entities');
301
- });
302
- });
303
- });
@@ -1,150 +0,0 @@
1
- /**
2
- * Layer 3: Feature Integration Test - Bind Flow (body update)
3
- *
4
- * Tests the write side of useBindFlow:
5
- * - bind:update-body → calls http.bindAnnotation API (plain POST)
6
- * - bind:update-body → emits bind:body-update-failed on error
7
- * - auth token passed to bindAnnotation
8
- *
9
- * After the UNIFIED-STREAM migration, bind is a plain POST returning
10
- * {correlationId}. The state change arrives on the events-stream as
11
- * mark:body-updated. These tests focus on the POST call, not the
12
- * events-stream delivery (which is tested in AnnotationStore tests).
13
- *
14
- * Uses real providers (EventBus, ApiClient, AuthToken) with mocked API boundary.
15
- */
16
-
17
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
18
- import { render, waitFor } from '@testing-library/react';
19
- import { act } from 'react';
20
- import { useBindFlow } from '../../../hooks/useBindFlow';
21
- import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
22
- import { ApiClientProvider } from '../../../contexts/ApiClientContext';
23
- import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
24
- import { SemiontApiClient } from '@semiont/api-client';
25
- import { resourceId, annotationId } from '@semiont/core';
26
-
27
- const mockShowError = vi.fn();
28
-
29
- vi.mock('../../../components/Toast', () => ({
30
- useToast: () => ({
31
- showSuccess: vi.fn(),
32
- showError: mockShowError,
33
- showInfo: vi.fn(),
34
- showWarning: vi.fn(),
35
- }),
36
- }));
37
-
38
- describe('Bind Flow - Body Update Integration', () => {
39
- let bindAnnotationSpy: ReturnType<typeof vi.fn>;
40
- const testId = resourceId('test-resource');
41
- const testToken = 'test-resolution-token';
42
- const testBaseUrl = 'http://localhost:4000';
43
-
44
- beforeEach(() => {
45
- vi.clearAllMocks();
46
-
47
- // Mock the HTTP bindAnnotation method (plain POST, returns {correlationId})
48
- bindAnnotationSpy = vi.fn().mockResolvedValue({ correlationId: 'corr-test' });
49
- vi.spyOn(SemiontApiClient.prototype, 'bindAnnotation').mockImplementation(bindAnnotationSpy as any);
50
- });
51
-
52
- afterEach(() => {
53
- vi.restoreAllMocks();
54
- });
55
-
56
- // ─── Render helper ──────────────────────────────────────────────────────────
57
-
58
- function renderBindFlow() {
59
- let eventBusInstance: ReturnType<typeof useEventBus> | null = null;
60
-
61
- function TestComponent() {
62
- eventBusInstance = useEventBus();
63
- useBindFlow(testId);
64
- return null;
65
- }
66
-
67
- render(
68
- <AuthTokenProvider token={testToken}>
69
- <EventBusProvider>
70
- <ApiClientProvider baseUrl={testBaseUrl}>
71
- <TestComponent />
72
- </ApiClientProvider>
73
- </EventBusProvider>
74
- </AuthTokenProvider>
75
- );
76
-
77
- return {
78
- getEventBus: () => eventBusInstance!,
79
- };
80
- }
81
-
82
- // ─── bind:update-body ──────────────────────────────────────────────────
83
-
84
- it('bind:update-body calls http.bindAnnotation (plain POST)', async () => {
85
- const { getEventBus } = renderBindFlow();
86
-
87
- act(() => { getEventBus().get('bind:update-body').next({
88
- correlationId: 'corr-1',
89
- annotationId: annotationId('ann-body-1'),
90
- resourceId: resourceId('linked-resource-id'),
91
- operations: [{ op: 'add', item: { type: 'SpecificResource' as const, source: 'linked-resource-id' } }],
92
- }); });
93
-
94
- await waitFor(() => {
95
- expect(bindAnnotationSpy).toHaveBeenCalledTimes(1);
96
- });
97
- });
98
-
99
- it('bind:update-body passes auth token to API call', async () => {
100
- const { getEventBus } = renderBindFlow();
101
-
102
- act(() => { getEventBus().get('bind:update-body').next({
103
- correlationId: 'corr-2',
104
- annotationId: annotationId('ann-auth'),
105
- resourceId: resourceId('resource-id'),
106
- operations: [{ op: 'replace', newItem: { type: 'SpecificResource' as const, source: 'resource-id' } }],
107
- }); });
108
-
109
- await waitFor(() => {
110
- expect(bindAnnotationSpy).toHaveBeenCalled();
111
- });
112
-
113
- const callArgs = bindAnnotationSpy.mock.calls[0];
114
- expect(callArgs[3]).toHaveProperty('auth');
115
- });
116
-
117
- it('bind:update-body shows error toast on API error', async () => {
118
- bindAnnotationSpy.mockRejectedValueOnce(new Error('Update failed'));
119
-
120
- const { getEventBus } = renderBindFlow();
121
-
122
- act(() => { getEventBus().get('bind:update-body').next({
123
- correlationId: 'corr-3',
124
- annotationId: annotationId('ann-fail'),
125
- resourceId: resourceId('resource-id'),
126
- operations: [{ op: 'remove', item: { type: 'SpecificResource' as const, source: 'old-id' } }],
127
- }); });
128
-
129
- await waitFor(() => {
130
- expect(mockShowError).toHaveBeenCalledWith(
131
- expect.stringContaining('Update failed'),
132
- );
133
- });
134
- });
135
-
136
- it('bind:update-body called ONCE — no duplicate subscriptions', async () => {
137
- const { getEventBus } = renderBindFlow();
138
-
139
- act(() => { getEventBus().get('bind:update-body').next({
140
- correlationId: 'corr-4',
141
- annotationId: annotationId('ann-dedup'),
142
- resourceId: resourceId('resource-id'),
143
- operations: [{ op: 'add', item: { type: 'SpecificResource' as const, source: 'resource-id' } }],
144
- }); });
145
-
146
- await waitFor(() => {
147
- expect(bindAnnotationSpy).toHaveBeenCalledTimes(1);
148
- });
149
- });
150
- });
@@ -1,243 +0,0 @@
1
- /**
2
- * FAILING TEST: Reproduces the bug where detection events fire but state doesn't update
3
- *
4
- * Based on console logs from production:
5
- * ✅ mark:assist-request emitted
6
- * ✅ annotate:assist-progress emitted
7
- * ❌ assistingMotivation remains null
8
- * ❌ progress remains null
9
- *
10
- * UPDATED: Now tests useMarkFlow hook instead of DetectionFlowContainer
11
- */
12
-
13
- import { describe, it, expect, vi, beforeEach } from 'vitest';
14
- import { render, screen, waitFor } from '@testing-library/react';
15
- import { act } from 'react';
16
- import { useMarkFlow } from '../../../hooks/useMarkFlow';
17
- import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
18
- import { ApiClientProvider } from '../../../contexts/ApiClientContext';
19
- import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
20
- import { SemiontApiClient } from '@semiont/api-client';
21
-
22
- // Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
23
- vi.mock('../../../components/Toast', () => ({
24
- useToast: () => ({
25
- showSuccess: vi.fn(),
26
- showError: vi.fn(),
27
- showInfo: vi.fn(),
28
- showWarning: vi.fn(),
29
- }),
30
- }));
31
-
32
- describe('REPRODUCING BUG: Detection state not updating', () => {
33
- beforeEach(() => {
34
- vi.clearAllMocks();
35
-
36
- // Minimal mock — namespace methods call these HTTP methods internally
37
- vi.spyOn(SemiontApiClient.prototype, 'annotateReferences').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
38
- vi.spyOn(SemiontApiClient.prototype, 'annotateHighlights').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
39
- vi.spyOn(SemiontApiClient.prototype, 'annotateComments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
40
- vi.spyOn(SemiontApiClient.prototype, 'annotateAssessments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
41
- });
42
-
43
- afterEach(() => {
44
- vi.restoreAllMocks();
45
- });
46
-
47
- it('SHOULD update state when mark:assist-request event is emitted', async () => {
48
- let eventBusInstance: any;
49
- let currentState: any;
50
-
51
- // Component to capture EventBus and hook state
52
- function TestComponent() {
53
- eventBusInstance = useEventBus();
54
- const state = useMarkFlow('test' as any);
55
- currentState = state;
56
-
57
- console.log('[TEST] useMarkFlow state:', {
58
- assistingMotivation: state.assistingMotivation,
59
- progress: state.progress,
60
- });
61
-
62
- return (
63
- <div>
64
- <div data-testid="detecting">{state.assistingMotivation || 'null'}</div>
65
- <div data-testid="progress">{state.progress?.message || 'null'}</div>
66
- </div>
67
- );
68
- }
69
-
70
- render(
71
- <EventBusProvider>
72
- <AuthTokenProvider token={null}>
73
- <ApiClientProvider baseUrl="http://localhost:4000">
74
- <TestComponent />
75
- </ApiClientProvider>
76
- </AuthTokenProvider>
77
- </EventBusProvider>
78
- );
79
-
80
- // Initial state should be null
81
- expect(screen.getByTestId('detecting')).toHaveTextContent('null');
82
- expect(screen.getByTestId('progress')).toHaveTextContent('null');
83
-
84
- console.log('[TEST] Emitting mark:assist-request event...');
85
-
86
- // Emit mark:assist-request event (exactly like production)
87
- act(() => {
88
- eventBusInstance.get('mark:assist-request').next({
89
- motivation: 'linking',
90
- options: { entityTypes: ['Location'] }
91
- });
92
- });
93
-
94
- console.log('[TEST] After mark:assist-request, checking state...');
95
-
96
- // THIS SHOULD PASS but currently FAILS
97
- await waitFor(() => {
98
- expect(screen.getByTestId('detecting')).toHaveTextContent('linking');
99
- }, { timeout: 1000 });
100
-
101
- expect(currentState.assistingMotivation).toBe('linking');
102
- expect(currentState.progress).toBeNull(); // Should clear on start
103
- });
104
-
105
- it('SHOULD update state when annotate:assist-progress event is emitted', async () => {
106
- let eventBusInstance: any;
107
- let currentState: any;
108
-
109
- function TestComponent() {
110
- eventBusInstance = useEventBus();
111
- const state = useMarkFlow('test' as any);
112
- currentState = state;
113
-
114
- return (
115
- <div>
116
- <div data-testid="detecting">{state.assistingMotivation || 'null'}</div>
117
- <div data-testid="progress">{state.progress?.message || 'null'}</div>
118
- </div>
119
- );
120
- }
121
-
122
- render(
123
- <EventBusProvider>
124
- <AuthTokenProvider token={null}>
125
- <ApiClientProvider baseUrl="http://localhost:4000">
126
- <TestComponent />
127
- </ApiClientProvider>
128
- </AuthTokenProvider>
129
- </EventBusProvider>
130
- );
131
-
132
- console.log('[TEST] Emitting annotate:assist-progress event...');
133
-
134
- // Emit annotate:assist-progress event (exactly like production)
135
- act(() => {
136
- eventBusInstance.get('mark:progress').next({
137
- status: 'started',
138
- resourceId: 'test',
139
- totalEntityTypes: 1,
140
- processedEntityTypes: 0,
141
- message: 'Starting entity detection...'
142
- });
143
- });
144
-
145
- console.log('[TEST] After annotate:assist-progress, checking state...');
146
-
147
- // THIS SHOULD PASS but currently FAILS
148
- await waitFor(() => {
149
- expect(screen.getByTestId('progress')).toHaveTextContent('Starting entity detection...');
150
- }, { timeout: 1000 });
151
-
152
- expect(currentState.progress).toMatchObject({
153
- status: 'started',
154
- message: 'Starting entity detection...'
155
- });
156
- });
157
-
158
- it('SHOULD show EXACTLY the production bug', async () => {
159
- let eventBusInstance: any;
160
- const stateSnapshots: any[] = [];
161
-
162
- function TestComponent() {
163
- eventBusInstance = useEventBus();
164
- const state = useMarkFlow('f45fd44f9cb0b0fe1b7980d3d034bc61' as any);
165
-
166
- stateSnapshots.push({
167
- assistingMotivation: state.assistingMotivation,
168
- progress: state.progress,
169
- });
170
-
171
- return (
172
- <div>
173
- <div data-testid="detecting">{state.assistingMotivation || 'null'}</div>
174
- <div data-testid="progress">{state.progress?.message || 'null'}</div>
175
- </div>
176
- );
177
- }
178
-
179
- render(
180
- <EventBusProvider>
181
- <AuthTokenProvider token={null}>
182
- <ApiClientProvider baseUrl="http://localhost:4000">
183
- <TestComponent />
184
- </ApiClientProvider>
185
- </AuthTokenProvider>
186
- </EventBusProvider>
187
- );
188
-
189
- console.log('\n=== REPRODUCING PRODUCTION BUG ===');
190
- console.log('Initial state:', stateSnapshots[stateSnapshots.length - 1]);
191
-
192
- // Exactly like production logs
193
- act(() => {
194
- console.log('[EventBus] emit: mark:assist-request {motivation: "linking", options: {...}}');
195
- eventBusInstance.get('mark:assist-request').next({
196
- motivation: 'linking',
197
- options: { entityTypes: ['Location'] }
198
- });
199
- });
200
-
201
- console.log('After mark:assist-request:', stateSnapshots[stateSnapshots.length - 1]);
202
-
203
- act(() => {
204
- console.log('[EventBus] emit: annotate:assist-progress {status: "started", ...}');
205
- eventBusInstance.get('mark:progress').next({
206
- status: 'started',
207
- resourceId: 'f45fd44f9cb0b0fe1b7980d3d034bc61',
208
- totalEntityTypes: 1,
209
- processedEntityTypes: 0,
210
- message: 'Starting entity detection...'
211
- });
212
- });
213
-
214
- console.log('After annotate:assist-progress:', stateSnapshots[stateSnapshots.length - 1]);
215
-
216
- act(() => {
217
- console.log('[EventBus] emit: annotate:assist-progress {status: "scanning", ...}');
218
- eventBusInstance.get('mark:progress').next({
219
- status: 'scanning',
220
- resourceId: 'f45fd44f9cb0b0fe1b7980d3d034bc61',
221
- currentEntityType: 'Location',
222
- totalEntityTypes: 1,
223
- processedEntityTypes: 1,
224
- message: 'Scanning for Location...'
225
- });
226
- });
227
-
228
- console.log('After second annotate:assist-progress:', stateSnapshots[stateSnapshots.length - 1]);
229
- console.log('=== END REPRODUCTION ===\n');
230
-
231
- // THIS IS THE BUG: Events fire but state never updates
232
- // Production logs show: assistingMotivation: null, progress: null
233
- // Even though events were emitted
234
- await waitFor(() => {
235
- const currentSnapshot = stateSnapshots[stateSnapshots.length - 1];
236
- console.log('Final state check:', currentSnapshot);
237
-
238
- // These SHOULD pass but will FAIL if bug is present
239
- expect(currentSnapshot.assistingMotivation).toBe('linking');
240
- expect(currentSnapshot.progress?.message).toBe('Scanning for Location...');
241
- }, { timeout: 2000 });
242
- });
243
- });