@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.
- package/README.md +8 -5
- package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-6ZGFEN2N.mjs} +9 -13
- package/dist/PdfAnnotationCanvas.client-6ZGFEN2N.mjs.map +1 -0
- package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
- package/dist/chunk-KEDFYI6N.mjs +7788 -0
- package/dist/chunk-KEDFYI6N.mjs.map +1 -0
- package/dist/index.d.mts +171 -1140
- package/dist/index.mjs +3263 -13644
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +46 -21
- package/dist/test-utils.mjs +2499 -87
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +1 -2
- package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
- package/src/components/CodeMirrorRenderer.tsx +9 -11
- package/src/components/StatusDisplay.tsx +42 -16
- package/src/components/Toolbar.tsx +4 -4
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
- package/src/components/__tests__/StatusDisplay.test.tsx +47 -64
- package/src/components/__tests__/Toolbar.test.tsx +4 -4
- package/src/components/annotation/AnnotateToolbar.tsx +8 -7
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +0 -1
- package/src/components/image-annotation/AnnotationOverlay.tsx +12 -13
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +7 -7
- package/src/components/modals/PermissionDeniedModal.tsx +11 -11
- package/src/components/modals/ReferenceWizardModal.tsx +14 -18
- package/src/components/modals/ResourceSearchModal.tsx +10 -6
- package/src/components/modals/SearchModal.tsx +10 -6
- package/src/components/modals/SessionExpiredModal.tsx +11 -11
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
- package/src/components/navigation/ObservableLink.tsx +6 -6
- package/src/components/navigation/SimpleNavigation.tsx +4 -4
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +9 -11
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +0 -1
- package/src/components/resource/AnnotateView.tsx +7 -6
- package/src/components/resource/AnnotationHistory.tsx +9 -12
- package/src/components/resource/BrowseView.tsx +8 -7
- package/src/components/resource/ResourceViewer.tsx +17 -25
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
- package/src/components/resource/__tests__/BrowseView.test.tsx +34 -83
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +40 -31
- package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
- package/src/components/resource/panels/AssessmentPanel.tsx +19 -15
- package/src/components/resource/panels/AssistSection.tsx +11 -13
- package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
- package/src/components/resource/panels/CommentEntry.tsx +5 -4
- package/src/components/resource/panels/CommentsPanel.tsx +9 -11
- package/src/components/resource/panels/HighlightEntry.tsx +5 -4
- package/src/components/resource/panels/HighlightPanel.tsx +10 -11
- package/src/components/resource/panels/ReferenceEntry.tsx +8 -8
- package/src/components/resource/panels/ReferencesPanel.tsx +13 -12
- package/src/components/resource/panels/ResourceInfoPanel.tsx +7 -6
- package/src/components/resource/panels/TagEntry.tsx +5 -4
- package/src/components/resource/panels/TaggingPanel.tsx +10 -16
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +3 -2
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +18 -52
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +18 -56
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +4 -5
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +15 -47
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -47
- package/src/components/settings/SettingsPanel.tsx +8 -8
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +12 -12
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -1
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
- package/src/features/admin-exchange/components/ImportCard.tsx +2 -6
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
- package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -22
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +38 -45
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +123 -192
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
- package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
- package/dist/chunk-OZICDVH7.mjs +0 -62
- package/dist/chunk-OZICDVH7.mjs.map +0 -1
- package/dist/chunk-R4CCMFJH.mjs +0 -877
- package/dist/chunk-R4CCMFJH.mjs.map +0 -1
- package/dist/chunk-VN5NY4SN.mjs +0 -200
- package/dist/chunk-VN5NY4SN.mjs.map +0 -1
- package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
- package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
- 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
|
-
});
|