@semiont/react-ui 0.2.33 → 0.2.34-build.89
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/dist/EventBusContext-BmzEcGHZ.d.mts +177 -0
- package/dist/{PdfAnnotationCanvas.client-FGV33CWN.mjs → PdfAnnotationCanvas.client-VLNA5O5M.mjs} +7 -7
- package/dist/PdfAnnotationCanvas.client-VLNA5O5M.mjs.map +1 -0
- package/dist/{chunk-YPYLOBA2.mjs → chunk-C63BARI7.mjs} +3 -2
- package/dist/chunk-C63BARI7.mjs.map +1 -0
- package/dist/{chunk-FC6SGLLT.mjs → chunk-M7SZRRIE.mjs} +24 -16
- package/dist/chunk-M7SZRRIE.mjs.map +1 -0
- package/dist/chunk-ULIET3MW.mjs +31 -0
- package/dist/chunk-ULIET3MW.mjs.map +1 -0
- package/dist/index.d.mts +33 -60
- package/dist/index.mjs +171 -363
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +3 -5
- package/dist/test-utils.mjs +2 -2
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +2 -3
- package/src/components/CodeMirrorRenderer.tsx +4 -4
- package/src/components/DetectionProgressWidget.tsx +3 -3
- package/src/components/LiveRegion.tsx +1 -1
- package/src/components/Toolbar.tsx +1 -1
- package/src/components/annotation/AnnotateToolbar.tsx +5 -5
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +4 -4
- package/src/components/annotation-popups/JsonLdView.tsx +1 -1
- package/src/components/image-annotation/AnnotationOverlay.tsx +9 -9
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +4 -4
- package/src/components/navigation/ObservableLink.tsx +1 -1
- package/src/components/navigation/SimpleNavigation.tsx +1 -1
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +6 -6
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +2 -2
- package/src/components/resource/AnnotateView.tsx +5 -4
- package/src/components/resource/AnnotationHistory.tsx +1 -1
- package/src/components/resource/BrowseView.tsx +5 -4
- package/src/components/resource/ResourceViewer.tsx +5 -4
- package/src/components/resource/__tests__/BrowseView.test.tsx +11 -22
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +1 -1
- package/src/components/resource/event-formatting.ts +1 -1
- package/src/components/resource/panels/AssessmentEntry.tsx +2 -2
- package/src/components/resource/panels/AssessmentPanel.tsx +4 -4
- package/src/components/resource/panels/CommentEntry.tsx +2 -2
- package/src/components/resource/panels/CommentsPanel.tsx +4 -4
- package/src/components/resource/panels/DetectSection.tsx +3 -3
- package/src/components/resource/panels/HighlightEntry.tsx +2 -2
- package/src/components/resource/panels/HighlightPanel.tsx +2 -2
- package/src/components/resource/panels/JsonLdPanel.tsx +1 -1
- package/src/components/resource/panels/ReferenceEntry.tsx +6 -6
- package/src/components/resource/panels/ReferencesPanel.tsx +5 -5
- package/src/components/resource/panels/ResourceInfoPanel.tsx +3 -3
- package/src/components/resource/panels/StatisticsPanel.tsx +1 -1
- package/src/components/resource/panels/TagEntry.tsx +2 -2
- package/src/components/resource/panels/TaggingPanel.tsx +5 -5
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +1 -1
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +5 -5
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +10 -10
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +5 -5
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +9 -9
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +5 -5
- package/src/components/settings/SettingsPanel.tsx +5 -5
- package/src/components/viewers/ImageViewer.tsx +1 -1
- package/src/features/resource-compose/components/ResourceComposePage.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceCard.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +7 -5
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +5 -4
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +5 -5
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +29 -43
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +20 -39
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +38 -46
- package/src/features/resource-viewer/__tests__/ResolutionFlowIntegration.test.tsx +36 -43
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +8 -8
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +14 -21
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +8 -7
- package/dist/EventBusContext-CJjL_cCf.d.mts +0 -462
- package/dist/PdfAnnotationCanvas.client-FGV33CWN.mjs.map +0 -1
- package/dist/chunk-FC6SGLLT.mjs.map +0 -1
- package/dist/chunk-XS27QKGP.mjs +0 -55
- package/dist/chunk-XS27QKGP.mjs.map +0 -1
- package/dist/chunk-YPYLOBA2.mjs.map +0 -1
|
@@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
6
6
|
import '@testing-library/jest-dom';
|
|
7
7
|
import { TaggingPanel } from '../TaggingPanel';
|
|
8
8
|
import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../../../contexts/EventBusContext';
|
|
9
|
-
import type { components } from '@semiont/
|
|
9
|
+
import type { components } from '@semiont/core';
|
|
10
10
|
|
|
11
11
|
type Annotation = components['schemas']['Annotation'];
|
|
12
12
|
|
|
@@ -29,16 +29,16 @@ function createEventTracker() {
|
|
|
29
29
|
events.push({ event: eventName, payload });
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const panelEvents = ['annotation:create', 'detection:start'];
|
|
32
|
+
const panelEvents = ['annotation:create', 'detection:start'] as const;
|
|
33
33
|
|
|
34
34
|
panelEvents.forEach(eventName => {
|
|
35
35
|
const handler = trackEvent(eventName);
|
|
36
|
-
eventBus.
|
|
37
|
-
handlers.push(
|
|
36
|
+
const subscription = eventBus.get(eventName).subscribe(handler);
|
|
37
|
+
handlers.push(subscription);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
return () => {
|
|
41
|
-
handlers.forEach(
|
|
41
|
+
handlers.forEach(sub => sub.unsubscribe());
|
|
42
42
|
};
|
|
43
43
|
}, [eventBus]);
|
|
44
44
|
|
|
@@ -38,7 +38,7 @@ export function SettingsPanel({
|
|
|
38
38
|
const handleLocaleChange = (newLocale: string) => {
|
|
39
39
|
const localeName = LOCALES.find(l => l.code === newLocale)?.nativeName || newLocale;
|
|
40
40
|
announceLanguageChanging(localeName);
|
|
41
|
-
eventBus.
|
|
41
|
+
eventBus.get('settings:locale-changed').next({ locale: newLocale });
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// Announce when language has successfully changed
|
|
@@ -67,7 +67,7 @@ export function SettingsPanel({
|
|
|
67
67
|
type="button"
|
|
68
68
|
role="switch"
|
|
69
69
|
aria-checked={showLineNumbers}
|
|
70
|
-
onClick={() => eventBus.
|
|
70
|
+
onClick={() => eventBus.get('settings:line-numbers-toggled').next(undefined)}
|
|
71
71
|
className={`semiont-toggle ${
|
|
72
72
|
showLineNumbers ? 'semiont-toggle--active' : ''
|
|
73
73
|
}`}
|
|
@@ -91,7 +91,7 @@ export function SettingsPanel({
|
|
|
91
91
|
</label>
|
|
92
92
|
<div className="semiont-settings-panel__button-group">
|
|
93
93
|
<button
|
|
94
|
-
onClick={() => eventBus.
|
|
94
|
+
onClick={() => eventBus.get('settings:theme-changed').next({ theme: 'light' })}
|
|
95
95
|
className={`semiont-panel-button ${
|
|
96
96
|
theme === 'light' ? 'semiont-panel-button-active' : ''
|
|
97
97
|
}`}
|
|
@@ -100,7 +100,7 @@ export function SettingsPanel({
|
|
|
100
100
|
☀️ {t('themeLight')}
|
|
101
101
|
</button>
|
|
102
102
|
<button
|
|
103
|
-
onClick={() => eventBus.
|
|
103
|
+
onClick={() => eventBus.get('settings:theme-changed').next({ theme: 'dark' })}
|
|
104
104
|
className={`semiont-panel-button ${
|
|
105
105
|
theme === 'dark' ? 'semiont-panel-button-active' : ''
|
|
106
106
|
}`}
|
|
@@ -109,7 +109,7 @@ export function SettingsPanel({
|
|
|
109
109
|
🌙 {t('themeDark')}
|
|
110
110
|
</button>
|
|
111
111
|
<button
|
|
112
|
-
onClick={() => eventBus.
|
|
112
|
+
onClick={() => eventBus.get('settings:theme-changed').next({ theme: 'system' })}
|
|
113
113
|
className={`semiont-panel-button ${
|
|
114
114
|
theme === 'system' ? 'semiont-panel-button-active' : ''
|
|
115
115
|
}`}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import React, { useState, useEffect } from 'react';
|
|
10
|
-
import type { components } from '@semiont/
|
|
10
|
+
import type { components } from '@semiont/core';
|
|
11
11
|
import { isImageMimeType, isPdfMimeType, LOCALES } from '@semiont/api-client';
|
|
12
12
|
import { buttonStyles } from '../../../lib/button-styles';
|
|
13
13
|
import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React, { useState, useCallback, useRef } from 'react';
|
|
9
|
-
import type { components } from '@semiont/
|
|
9
|
+
import type { components } from '@semiont/core';
|
|
10
10
|
import { getResourceId } from '@semiont/api-client';
|
|
11
11
|
import { useRovingTabIndex } from '../../../hooks/useRovingTabIndex';
|
|
12
12
|
import { Toolbar } from '../../../components/Toolbar';
|
|
@@ -25,10 +25,10 @@ import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../..
|
|
|
25
25
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
26
26
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
27
27
|
import { SemiontApiClient } from '@semiont/api-client';
|
|
28
|
-
import { resourceUri } from '@semiont/
|
|
28
|
+
import { resourceUri } from '@semiont/core';
|
|
29
29
|
import type { Emitter } from 'mitt';
|
|
30
|
-
import type { EventMap } from '
|
|
31
|
-
import type { Motivation, Selector } from '@semiont/
|
|
30
|
+
import type { EventMap } from '@semiont/core';
|
|
31
|
+
import type { Motivation, Selector } from '@semiont/core';
|
|
32
32
|
|
|
33
33
|
const TEST_URI = resourceUri('http://localhost:4000/resources/test-resource');
|
|
34
34
|
|
|
@@ -85,7 +85,7 @@ function renderDetectionFlow(testUri: string) {
|
|
|
85
85
|
return {
|
|
86
86
|
getEventBus: () => eventBusInstance,
|
|
87
87
|
emit: <K extends keyof EventMap>(event: K, payload: EventMap[K]) => {
|
|
88
|
-
eventBusInstance.
|
|
88
|
+
eventBusInstance.get(event).next(payload);
|
|
89
89
|
},
|
|
90
90
|
};
|
|
91
91
|
}
|
|
@@ -239,7 +239,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
|
|
|
239
239
|
const createdListener = vi.fn();
|
|
240
240
|
// Set listener after first render so eventBus is captured
|
|
241
241
|
await waitFor(() => expect(getEventBus()).toBeDefined());
|
|
242
|
-
getEventBus().
|
|
242
|
+
const subscription = getEventBus().get('annotation:created').subscribe(createdListener);
|
|
243
243
|
|
|
244
244
|
act(() => {
|
|
245
245
|
emit('annotation:requested', { selector: TEXT_SELECTOR, motivation: 'linking' });
|
|
@@ -257,6 +257,8 @@ describe('Annotation creation clears pendingAnnotation', () => {
|
|
|
257
257
|
expect(createdListener).toHaveBeenCalledTimes(1);
|
|
258
258
|
expect(createdListener).toHaveBeenCalledWith({ annotation: MOCK_ANNOTATION });
|
|
259
259
|
});
|
|
260
|
+
|
|
261
|
+
subscription.unsubscribe();
|
|
260
262
|
});
|
|
261
263
|
|
|
262
264
|
it('does NOT clear pendingAnnotation if API call fails', async () => {
|
|
@@ -30,7 +30,8 @@ import { useDetectionFlow } from '../../../hooks/useDetectionFlow';
|
|
|
30
30
|
import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
31
31
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
32
32
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
33
|
-
import { SemiontApiClient
|
|
33
|
+
import { SemiontApiClient } from '@semiont/api-client';
|
|
34
|
+
import { resourceUri, accessToken } from '@semiont/core';
|
|
34
35
|
|
|
35
36
|
describe('Annotation Deletion - Feature Integration', () => {
|
|
36
37
|
let deleteAnnotationSpy: ReturnType<typeof vi.fn>;
|
|
@@ -78,7 +79,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
78
79
|
return {
|
|
79
80
|
emitDelete: (annotationId: string) => {
|
|
80
81
|
act(() => {
|
|
81
|
-
eventBusInstance!.
|
|
82
|
+
eventBusInstance!.get('annotation:delete').next({ annotationId });
|
|
82
83
|
});
|
|
83
84
|
},
|
|
84
85
|
eventBus: eventBusInstance!,
|
|
@@ -127,7 +128,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
127
128
|
const deletedListener = vi.fn();
|
|
128
129
|
|
|
129
130
|
// Subscribe to success event
|
|
130
|
-
eventBus.
|
|
131
|
+
eventBus.get('annotation:deleted').subscribe(deletedListener);
|
|
131
132
|
|
|
132
133
|
emitDelete('annotation-789');
|
|
133
134
|
|
|
@@ -152,7 +153,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
152
153
|
const failedListener = vi.fn();
|
|
153
154
|
|
|
154
155
|
// Subscribe to failure event
|
|
155
|
-
eventBus.
|
|
156
|
+
eventBus.get('annotation:delete-failed').subscribe(failedListener);
|
|
156
157
|
|
|
157
158
|
emitDelete('annotation-error');
|
|
158
159
|
|
|
@@ -76,7 +76,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
76
76
|
|
|
77
77
|
// Emit detection:start event (exactly like production)
|
|
78
78
|
act(() => {
|
|
79
|
-
eventBusInstance.
|
|
79
|
+
eventBusInstance.get('detection:start').next({
|
|
80
80
|
motivation: 'linking',
|
|
81
81
|
options: { entityTypes: ['Location'] }
|
|
82
82
|
});
|
|
@@ -124,7 +124,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
124
124
|
|
|
125
125
|
// Emit detection:progress event (exactly like production)
|
|
126
126
|
act(() => {
|
|
127
|
-
eventBusInstance.
|
|
127
|
+
eventBusInstance.get('detection:progress').next({
|
|
128
128
|
status: 'started',
|
|
129
129
|
resourceId: 'test',
|
|
130
130
|
totalEntityTypes: 1,
|
|
@@ -183,7 +183,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
183
183
|
// Exactly like production logs
|
|
184
184
|
act(() => {
|
|
185
185
|
console.log('[EventBus] emit: detection:start {motivation: "linking", options: {...}}');
|
|
186
|
-
eventBusInstance.
|
|
186
|
+
eventBusInstance.get('detection:start').next({
|
|
187
187
|
motivation: 'linking',
|
|
188
188
|
options: { entityTypes: ['Location'] }
|
|
189
189
|
});
|
|
@@ -193,7 +193,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
193
193
|
|
|
194
194
|
act(() => {
|
|
195
195
|
console.log('[EventBus] emit: detection:progress {status: "started", ...}');
|
|
196
|
-
eventBusInstance.
|
|
196
|
+
eventBusInstance.get('detection:progress').next({
|
|
197
197
|
status: 'started',
|
|
198
198
|
resourceId: 'f45fd44f9cb0b0fe1b7980d3d034bc61',
|
|
199
199
|
totalEntityTypes: 1,
|
|
@@ -206,7 +206,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
206
206
|
|
|
207
207
|
act(() => {
|
|
208
208
|
console.log('[EventBus] emit: detection:progress {status: "scanning", ...}');
|
|
209
|
-
eventBusInstance.
|
|
209
|
+
eventBusInstance.get('detection:progress').next({
|
|
210
210
|
status: 'scanning',
|
|
211
211
|
resourceId: 'f45fd44f9cb0b0fe1b7980d3d034bc61',
|
|
212
212
|
currentEntityType: 'Location',
|
|
@@ -30,32 +30,16 @@ import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../..
|
|
|
30
30
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
31
31
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
32
32
|
import { SSEClient } from '@semiont/api-client';
|
|
33
|
-
import type { Motivation } from '@semiont/
|
|
34
|
-
import { resourceUri } from '@semiont/
|
|
33
|
+
import type { Motivation } from '@semiont/core';
|
|
34
|
+
import { resourceUri } from '@semiont/core';
|
|
35
35
|
import type { Emitter } from 'mitt';
|
|
36
|
-
import type { EventMap } from '
|
|
36
|
+
import type { EventMap } from '@semiont/core';
|
|
37
37
|
|
|
38
|
-
// Mock SSE stream
|
|
38
|
+
// Mock SSE stream - SSE now emits directly to EventBus, no callbacks
|
|
39
39
|
const createMockSSEStream = () => {
|
|
40
|
-
|
|
41
|
-
onProgressCallback: null as ((chunk: any) => void) | null,
|
|
42
|
-
onCompleteCallback: null as (() => void) | null,
|
|
43
|
-
onErrorCallback: null as ((error: Error) => void) | null,
|
|
44
|
-
onProgress: vi.fn((callback: (chunk: any) => void) => {
|
|
45
|
-
stream.onProgressCallback = callback;
|
|
46
|
-
return stream;
|
|
47
|
-
}),
|
|
48
|
-
onComplete: vi.fn((callback: () => void) => {
|
|
49
|
-
stream.onCompleteCallback = callback;
|
|
50
|
-
return stream;
|
|
51
|
-
}),
|
|
52
|
-
onError: vi.fn((callback: (error: Error) => void) => {
|
|
53
|
-
stream.onErrorCallback = callback;
|
|
54
|
-
return stream;
|
|
55
|
-
}),
|
|
40
|
+
return {
|
|
56
41
|
close: vi.fn(),
|
|
57
42
|
};
|
|
58
|
-
return stream;
|
|
59
43
|
};
|
|
60
44
|
|
|
61
45
|
describe('Detection Flow - Feature Integration', () => {
|
|
@@ -102,14 +86,14 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
102
86
|
expect(detectReferencesSpy).toHaveBeenCalledTimes(1);
|
|
103
87
|
});
|
|
104
88
|
|
|
105
|
-
// Verify correct parameters
|
|
89
|
+
// Verify correct parameters (eventBus is passed but we don't need to verify its exact value)
|
|
106
90
|
expect(detectReferencesSpy).toHaveBeenCalledWith(
|
|
107
91
|
testUri,
|
|
108
92
|
{
|
|
109
93
|
entityTypes: ['Person', 'Organization'],
|
|
110
94
|
includeDescriptiveReferences: false,
|
|
111
95
|
},
|
|
112
|
-
{ auth: undefined }
|
|
96
|
+
expect.objectContaining({ auth: undefined })
|
|
113
97
|
);
|
|
114
98
|
});
|
|
115
99
|
|
|
@@ -117,7 +101,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
117
101
|
const testUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
118
102
|
|
|
119
103
|
// Render with state observer
|
|
120
|
-
const { emitDetectionStart } = renderDetectionFlow(testUri);
|
|
104
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
|
|
121
105
|
|
|
122
106
|
// Start detection
|
|
123
107
|
act(() => {
|
|
@@ -131,9 +115,9 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
131
115
|
expect(detectReferencesSpy).toHaveBeenCalled();
|
|
132
116
|
});
|
|
133
117
|
|
|
134
|
-
// Simulate SSE progress
|
|
118
|
+
// Simulate SSE progress event being emitted to EventBus (how SSE actually works now)
|
|
135
119
|
act(() => {
|
|
136
|
-
|
|
120
|
+
getEventBus().get('detection:progress').next({
|
|
137
121
|
status: 'scanning',
|
|
138
122
|
message: 'Scanning for Person...',
|
|
139
123
|
currentEntityType: 'Person',
|
|
@@ -152,7 +136,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
152
136
|
|
|
153
137
|
it('should handle multiple progress updates correctly', async () => {
|
|
154
138
|
const testUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
155
|
-
const { emitDetectionStart } = renderDetectionFlow(testUri);
|
|
139
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testUri);
|
|
156
140
|
|
|
157
141
|
// Start detection
|
|
158
142
|
act(() => {
|
|
@@ -165,9 +149,9 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
165
149
|
expect(detectHighlightsSpy).toHaveBeenCalledTimes(1);
|
|
166
150
|
});
|
|
167
151
|
|
|
168
|
-
// First progress update
|
|
152
|
+
// First progress update via EventBus
|
|
169
153
|
act(() => {
|
|
170
|
-
|
|
154
|
+
getEventBus().get('detection:progress').next({
|
|
171
155
|
status: 'started',
|
|
172
156
|
message: 'Starting analysis...',
|
|
173
157
|
percentage: 0,
|
|
@@ -178,9 +162,9 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
178
162
|
expect(screen.getByTestId('progress')).toHaveTextContent('Starting analysis...');
|
|
179
163
|
});
|
|
180
164
|
|
|
181
|
-
// Second progress update
|
|
165
|
+
// Second progress update via EventBus
|
|
182
166
|
act(() => {
|
|
183
|
-
|
|
167
|
+
getEventBus().get('detection:progress').next({
|
|
184
168
|
status: 'analyzing',
|
|
185
169
|
message: 'Analyzing text...',
|
|
186
170
|
percentage: 50,
|
|
@@ -191,9 +175,9 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
191
175
|
expect(screen.getByTestId('progress')).toHaveTextContent('Analyzing text...');
|
|
192
176
|
});
|
|
193
177
|
|
|
194
|
-
// Final progress update
|
|
178
|
+
// Final progress update via EventBus
|
|
195
179
|
act(() => {
|
|
196
|
-
|
|
180
|
+
getEventBus().get('detection:progress').next({
|
|
197
181
|
status: 'complete',
|
|
198
182
|
message: 'Created 14 highlights',
|
|
199
183
|
percentage: 100,
|
|
@@ -218,9 +202,9 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
218
202
|
expect(screen.getByTestId('detecting')).toHaveTextContent('highlighting');
|
|
219
203
|
});
|
|
220
204
|
|
|
221
|
-
// Send final progress
|
|
205
|
+
// Send final progress via EventBus
|
|
222
206
|
act(() => {
|
|
223
|
-
|
|
207
|
+
getEventBus().get('detection:progress').next({
|
|
224
208
|
status: 'complete',
|
|
225
209
|
message: 'Created 14 highlights',
|
|
226
210
|
});
|
|
@@ -232,7 +216,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
232
216
|
|
|
233
217
|
// Emit completion event
|
|
234
218
|
act(() => {
|
|
235
|
-
getEventBus().
|
|
219
|
+
getEventBus().get('detection:complete').next({ motivation: 'highlighting' });
|
|
236
220
|
});
|
|
237
221
|
|
|
238
222
|
// Verify: detecting flag cleared BUT progress still visible
|
|
@@ -251,9 +235,9 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
251
235
|
emitDetectionStart('linking', { entityTypes: ['Person'] });
|
|
252
236
|
});
|
|
253
237
|
|
|
254
|
-
// Add some progress
|
|
238
|
+
// Add some progress via EventBus
|
|
255
239
|
act(() => {
|
|
256
|
-
|
|
240
|
+
getEventBus().get('detection:progress').next({
|
|
257
241
|
status: 'scanning',
|
|
258
242
|
message: 'Scanning...',
|
|
259
243
|
});
|
|
@@ -265,7 +249,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
265
249
|
|
|
266
250
|
// Emit failure
|
|
267
251
|
act(() => {
|
|
268
|
-
getEventBus().
|
|
252
|
+
getEventBus().get('detection:failed').next({ type: 'job.failed', resourceId: 'test-resource' as any, payload: { jobId: 'job-1' as any, jobType: 'detection', error: 'Network error' } });
|
|
269
253
|
});
|
|
270
254
|
|
|
271
255
|
// Verify: both detecting and progress cleared
|
|
@@ -288,7 +272,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
288
272
|
expect(detectHighlightsSpy).toHaveBeenCalledTimes(1);
|
|
289
273
|
expect(detectHighlightsSpy).toHaveBeenCalledWith(testUri, {
|
|
290
274
|
instructions: 'Find important text',
|
|
291
|
-
}, { auth: undefined });
|
|
275
|
+
}, expect.objectContaining({ auth: undefined }));
|
|
292
276
|
});
|
|
293
277
|
|
|
294
278
|
// Reset for next test
|
|
@@ -309,7 +293,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
309
293
|
expect(detectCommentsSpy).toHaveBeenCalledWith(testUri, {
|
|
310
294
|
instructions: 'Add helpful comments',
|
|
311
295
|
tone: 'educational',
|
|
312
|
-
}, { auth: undefined });
|
|
296
|
+
}, expect.objectContaining({ auth: undefined }));
|
|
313
297
|
});
|
|
314
298
|
});
|
|
315
299
|
|
|
@@ -322,7 +306,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
322
306
|
|
|
323
307
|
// Add an additional event listener (simulating multiple subscribers)
|
|
324
308
|
const additionalListener = vi.fn();
|
|
325
|
-
getEventBus().
|
|
309
|
+
const subscription = getEventBus().get('detection:start').subscribe(additionalListener);
|
|
326
310
|
|
|
327
311
|
// Trigger detection
|
|
328
312
|
act(() => {
|
|
@@ -339,6 +323,8 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
339
323
|
|
|
340
324
|
// VERIFY: Our additional listener was called (events work)
|
|
341
325
|
expect(additionalListener).toHaveBeenCalledTimes(1);
|
|
326
|
+
|
|
327
|
+
subscription.unsubscribe();
|
|
342
328
|
});
|
|
343
329
|
});
|
|
344
330
|
|
|
@@ -381,7 +367,7 @@ function renderDetectionFlow(testUri: string) {
|
|
|
381
367
|
|
|
382
368
|
return {
|
|
383
369
|
emitDetectionStart: (motivation: Motivation, options: any) => {
|
|
384
|
-
eventBusInstance.
|
|
370
|
+
eventBusInstance.get('detection:start').next({ motivation, options });
|
|
385
371
|
},
|
|
386
372
|
getEventBus: () => eventBusInstance,
|
|
387
373
|
};
|
|
@@ -25,7 +25,8 @@ import { useDetectionFlow } from '../../../hooks/useDetectionFlow';
|
|
|
25
25
|
import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../../contexts/EventBusContext';
|
|
26
26
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
27
27
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
28
|
-
import { SSEClient
|
|
28
|
+
import { SSEClient } from '@semiont/api-client';
|
|
29
|
+
import { resourceUri } from '@semiont/core';
|
|
29
30
|
|
|
30
31
|
describe('Detection Progress Dismissal Bug', () => {
|
|
31
32
|
let mockStream: any;
|
|
@@ -36,9 +37,6 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
36
37
|
vi.clearAllMocks();
|
|
37
38
|
|
|
38
39
|
mockStream = {
|
|
39
|
-
onProgress: vi.fn().mockReturnThis(),
|
|
40
|
-
onComplete: vi.fn().mockReturnThis(),
|
|
41
|
-
onError: vi.fn().mockReturnThis(),
|
|
42
40
|
close: vi.fn(),
|
|
43
41
|
};
|
|
44
42
|
|
|
@@ -85,7 +83,7 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
85
83
|
|
|
86
84
|
// User clicks detect button (emits detection:start)
|
|
87
85
|
act(() => {
|
|
88
|
-
eventBusInstance.
|
|
86
|
+
eventBusInstance.get('detection:start').next({
|
|
89
87
|
motivation: 'linking',
|
|
90
88
|
options: { entityTypes: ['Location'] }
|
|
91
89
|
});
|
|
@@ -98,7 +96,7 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
98
96
|
|
|
99
97
|
// SSE sends progress update
|
|
100
98
|
act(() => {
|
|
101
|
-
eventBusInstance.
|
|
99
|
+
eventBusInstance.get('detection:progress').next({
|
|
102
100
|
status: 'scanning',
|
|
103
101
|
message: 'Processing: Location',
|
|
104
102
|
currentEntityType: 'Location',
|
|
@@ -112,7 +110,7 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
112
110
|
|
|
113
111
|
// Detection completes (SSE finishes, backend emits detection:complete)
|
|
114
112
|
act(() => {
|
|
115
|
-
eventBusInstance.
|
|
113
|
+
eventBusInstance.get('detection:complete').next({ motivation: 'linking' });
|
|
116
114
|
});
|
|
117
115
|
|
|
118
116
|
// detectingMotivation cleared immediately
|
|
@@ -151,15 +149,15 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
151
149
|
|
|
152
150
|
// First detection with stuck progress
|
|
153
151
|
act(() => {
|
|
154
|
-
eventBusInstance.
|
|
152
|
+
eventBusInstance.get('detection:start').next({ motivation: 'linking', options: {} });
|
|
155
153
|
});
|
|
156
154
|
|
|
157
155
|
act(() => {
|
|
158
|
-
eventBusInstance.
|
|
156
|
+
eventBusInstance.get('detection:progress').next({ message: 'Old progress stuck here' });
|
|
159
157
|
});
|
|
160
158
|
|
|
161
159
|
act(() => {
|
|
162
|
-
eventBusInstance.
|
|
160
|
+
eventBusInstance.get('detection:complete').next({ motivation: 'linking' });
|
|
163
161
|
});
|
|
164
162
|
|
|
165
163
|
await waitFor(() => {
|
|
@@ -168,7 +166,7 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
168
166
|
|
|
169
167
|
// WORKAROUND: Start new detection clears old progress
|
|
170
168
|
act(() => {
|
|
171
|
-
eventBusInstance.
|
|
169
|
+
eventBusInstance.get('detection:start').next({ motivation: 'highlighting', options: {} });
|
|
172
170
|
});
|
|
173
171
|
|
|
174
172
|
await waitFor(() => {
|
|
@@ -204,18 +202,18 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
204
202
|
|
|
205
203
|
// Show progress
|
|
206
204
|
act(() => {
|
|
207
|
-
eventBusInstance.
|
|
205
|
+
eventBusInstance.get('detection:start').next({ motivation: 'linking', options: {} });
|
|
208
206
|
});
|
|
209
207
|
|
|
210
208
|
act(() => {
|
|
211
|
-
eventBusInstance.
|
|
209
|
+
eventBusInstance.get('detection:progress').next({
|
|
212
210
|
status: 'complete',
|
|
213
211
|
message: 'Complete! Created 5 annotations'
|
|
214
212
|
});
|
|
215
213
|
});
|
|
216
214
|
|
|
217
215
|
act(() => {
|
|
218
|
-
eventBusInstance.
|
|
216
|
+
eventBusInstance.get('detection:complete').next({ motivation: 'linking' });
|
|
219
217
|
});
|
|
220
218
|
|
|
221
219
|
// Progress visible initially
|
|
@@ -231,12 +229,10 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
231
229
|
});
|
|
232
230
|
});
|
|
233
231
|
|
|
234
|
-
it('FIXED:
|
|
232
|
+
it('FIXED: SSE emits final completion chunk data as detection:progress', async () => {
|
|
235
233
|
/**
|
|
236
|
-
* This test verifies
|
|
237
|
-
*
|
|
238
|
-
* FIX: useResolutionFlow.ts stream.onComplete(finalChunk) now emits detection:progress
|
|
239
|
-
* with the final chunk data BEFORE emitting detection:complete.
|
|
234
|
+
* This test verifies that SSE emits the final chunk as detection:progress
|
|
235
|
+
* BEFORE emitting detection:complete.
|
|
240
236
|
*
|
|
241
237
|
* This ensures the UI can display the final completion message with status:'complete'.
|
|
242
238
|
*/
|
|
@@ -255,20 +251,6 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
255
251
|
);
|
|
256
252
|
}
|
|
257
253
|
|
|
258
|
-
// Mock SSE stream to simulate backend behavior
|
|
259
|
-
let onProgressCallback: any;
|
|
260
|
-
let onCompleteCallback: any;
|
|
261
|
-
|
|
262
|
-
mockStream.onProgress.mockImplementation((cb: any) => {
|
|
263
|
-
onProgressCallback = cb;
|
|
264
|
-
return mockStream;
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
mockStream.onComplete.mockImplementation((cb: any) => {
|
|
268
|
-
onCompleteCallback = cb;
|
|
269
|
-
return mockStream;
|
|
270
|
-
});
|
|
271
|
-
|
|
272
254
|
render(
|
|
273
255
|
<EventBusProvider>
|
|
274
256
|
<AuthTokenProvider token={null}>
|
|
@@ -281,15 +263,15 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
281
263
|
|
|
282
264
|
// Start detection (triggers SSE stream creation)
|
|
283
265
|
act(() => {
|
|
284
|
-
eventBusInstance.
|
|
266
|
+
eventBusInstance.get('detection:start').next({
|
|
285
267
|
motivation: 'linking',
|
|
286
268
|
options: { entityTypes: ['Location'] }
|
|
287
269
|
});
|
|
288
270
|
});
|
|
289
271
|
|
|
290
|
-
// Simulate SSE scanning chunk
|
|
272
|
+
// Simulate SSE scanning chunk
|
|
291
273
|
act(() => {
|
|
292
|
-
|
|
274
|
+
eventBusInstance.get('detection:progress').next({
|
|
293
275
|
status: 'scanning',
|
|
294
276
|
message: 'Processing: Location',
|
|
295
277
|
});
|
|
@@ -299,10 +281,9 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
299
281
|
expect(screen.getByTestId('progress-status')).toHaveTextContent('scanning');
|
|
300
282
|
});
|
|
301
283
|
|
|
302
|
-
// Simulate
|
|
303
|
-
// useResolutionFlow should forward this as detection:progress
|
|
284
|
+
// Simulate SSE emitting final chunk as detection:progress
|
|
304
285
|
act(() => {
|
|
305
|
-
|
|
286
|
+
eventBusInstance.get('detection:progress').next({
|
|
306
287
|
status: 'complete',
|
|
307
288
|
message: 'Complete! Found 5 entities',
|
|
308
289
|
foundCount: 5,
|