@semiont/react-ui 0.2.33-build.78 → 0.2.33-build.80
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-7GvDyO0d.d.mts +414 -0
- package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
- package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
- package/dist/{ar-RNNSPLQB.mjs → ar-4ZEORRW2.mjs} +8 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-S2CDL7EC.mjs → bn-SEDE5BQJ.mjs} +8 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-UDX2Q35T.mjs → chunk-D7NBW4RV.mjs} +8 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-35LLVRFK.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
- package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
- package/dist/{cs-RSV675WU.mjs → cs-7W4WF5WD.mjs} +8 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-CHXNPWJC.mjs → da-75XGBCBK.mjs} +8 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-KPEZ53D4.mjs → de-ODJVFLHM.mjs} +8 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MW2BME5T.mjs → el-C4PM4WB3.mjs} +8 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-EVMIX24Y.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-HQ24NYS3.mjs → es-WD33R7QL.mjs} +8 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-W34LRLHG.mjs → fa-2BP6V56P.mjs} +8 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-3U44IGOA.mjs → fi-USRRW24J.mjs} +8 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-N7DKX6NN.mjs → fr-EC5S6WVF.mjs} +8 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-CS4WRXN3.mjs → he-7TBVIKAA.mjs} +8 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-GJDY46KA.mjs → hi-FO4VIZLA.mjs} +8 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-WAEZJK2Y.mjs → id-7U7GGVWY.mjs} +8 -4
- package/dist/id-7U7GGVWY.mjs.map +1 -0
- package/dist/index.css +123 -85
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +699 -529
- package/dist/index.mjs +4291 -3491
- package/dist/index.mjs.map +1 -1
- package/dist/{it-VDNDMZPU.mjs → it-Y4OPL6I2.mjs} +8 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-5PEH56J5.mjs → ja-PK7SQL55.mjs} +8 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-JYPL3WVA.mjs → ko-L25PXMYD.mjs} +8 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-5PZVW76T.mjs → ms-STH777QM.mjs} +8 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-YXES36KM.mjs → nl-Y7LECDDR.mjs} +8 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-XRA2UCQD.mjs → no-KEKCEWU6.mjs} +8 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-WH6LJA5G.mjs → pl-7A7OC75O.mjs} +8 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-7GAG57BM.mjs → pt-35HTM7RA.mjs} +8 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-BTDDRB7N.mjs → ro-VAWL5KQA.mjs} +8 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-7V5C2IT4.mjs → sv-7ZK5EQEB.mjs} +8 -4
- package/dist/sv-7ZK5EQEB.mjs.map +1 -0
- package/dist/test-utils.d.mts +18 -8
- package/dist/test-utils.mjs +36 -14
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-LPKYLBX5.mjs → th-UDWZ4X34.mjs} +8 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-DU4RQL4M.mjs → tr-4WMPK3UX.mjs} +8 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-36UHTDDI.mjs → uk-SSLASQYJ.mjs} +8 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-GDHOUZDH.mjs → vi-IF42Z5PU.mjs} +8 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-TYUID4XZ.mjs → zh-HRQTNTAI.mjs} +8 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +8 -2
- package/src/components/CodeMirrorRenderer.tsx +66 -93
- package/src/components/DetectionProgressWidget.tsx +16 -5
- package/src/components/LiveRegion.tsx +18 -18
- package/src/components/ResizeHandle.tsx +10 -4
- package/src/components/SessionTimer.tsx +2 -2
- package/src/components/Toolbar.tsx +18 -9
- package/src/components/__tests__/SessionTimer.test.tsx +9 -9
- package/src/components/annotation/AnnotateToolbar.tsx +17 -15
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
- package/src/components/annotation/annotation-entries.css +10 -0
- package/src/components/annotation-popups/JsonLdView.tsx +8 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
- package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
- package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
- package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
- package/src/components/modals/ResourceSearchModal.tsx +2 -2
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
- package/src/components/navigation/NavigationTabs.css +36 -24
- package/src/components/navigation/ObservableLink.tsx +91 -0
- package/src/components/navigation/SimpleNavigation.tsx +20 -16
- package/src/components/navigation/SortableResourceTab.tsx +11 -5
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
- package/src/components/resource/AnnotateView.tsx +64 -138
- package/src/components/resource/AnnotationHistory.tsx +12 -13
- package/src/components/resource/BrowseView.tsx +89 -177
- package/src/components/resource/HistoryEvent.tsx +16 -11
- package/src/components/resource/ResourceViewer.tsx +201 -370
- package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
- package/src/components/resource/event-formatting.ts +316 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +137 -31
- package/src/components/resource/panels/CollaborationPanel.tsx +20 -13
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +153 -31
- package/src/components/resource/panels/DetectSection.css +36 -1
- package/src/components/resource/panels/DetectSection.tsx +38 -10
- package/src/components/resource/panels/HighlightEntry.tsx +25 -33
- package/src/components/resource/panels/HighlightPanel.tsx +100 -25
- package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
- package/src/components/resource/panels/ReferencesPanel.tsx +166 -49
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/StatisticsPanel.tsx +9 -19
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +141 -25
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +46 -101
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +566 -0
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +146 -141
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +228 -103
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +586 -0
- package/src/components/settings/SettingsPanel.tsx +15 -12
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
- package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
- package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
- package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
- package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +16 -27
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +504 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +145 -91
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +325 -476
- package/src/styles/motivations/motivation-assessment.css +28 -0
- package/src/styles/patterns/panel-helpers.css +26 -0
- package/translations/ar.json +7 -3
- package/translations/bn.json +7 -3
- package/translations/cs.json +7 -3
- package/translations/da.json +7 -3
- package/translations/de.json +7 -3
- package/translations/el.json +7 -3
- package/translations/en.json +7 -3
- package/translations/es.json +7 -3
- package/translations/fa.json +7 -3
- package/translations/fi.json +7 -3
- package/translations/fr.json +7 -3
- package/translations/he.json +7 -3
- package/translations/hi.json +7 -3
- package/translations/id.json +7 -3
- package/translations/it.json +7 -3
- package/translations/ja.json +7 -3
- package/translations/ko.json +7 -3
- package/translations/ms.json +7 -3
- package/translations/nl.json +7 -3
- package/translations/no.json +7 -3
- package/translations/pl.json +7 -3
- package/translations/pt.json +7 -3
- package/translations/ro.json +7 -3
- package/translations/sv.json +7 -3
- package/translations/th.json +7 -3
- package/translations/tr.json +7 -3
- package/translations/uk.json +7 -3
- package/translations/vi.json +7 -3
- package/translations/zh.json +7 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-RNNSPLQB.mjs.map +0 -1
- package/dist/bn-S2CDL7EC.mjs.map +0 -1
- package/dist/chunk-35LLVRFK.mjs.map +0 -1
- package/dist/chunk-UDX2Q35T.mjs.map +0 -1
- package/dist/cs-RSV675WU.mjs.map +0 -1
- package/dist/da-CHXNPWJC.mjs.map +0 -1
- package/dist/de-KPEZ53D4.mjs.map +0 -1
- package/dist/el-MW2BME5T.mjs.map +0 -1
- package/dist/es-HQ24NYS3.mjs.map +0 -1
- package/dist/fa-W34LRLHG.mjs.map +0 -1
- package/dist/fi-3U44IGOA.mjs.map +0 -1
- package/dist/fr-N7DKX6NN.mjs.map +0 -1
- package/dist/he-CS4WRXN3.mjs.map +0 -1
- package/dist/hi-GJDY46KA.mjs.map +0 -1
- package/dist/id-WAEZJK2Y.mjs.map +0 -1
- package/dist/it-VDNDMZPU.mjs.map +0 -1
- package/dist/ja-5PEH56J5.mjs.map +0 -1
- package/dist/ko-JYPL3WVA.mjs.map +0 -1
- package/dist/ms-5PZVW76T.mjs.map +0 -1
- package/dist/nl-YXES36KM.mjs.map +0 -1
- package/dist/no-XRA2UCQD.mjs.map +0 -1
- package/dist/pl-WH6LJA5G.mjs.map +0 -1
- package/dist/pt-7GAG57BM.mjs.map +0 -1
- package/dist/ro-BTDDRB7N.mjs.map +0 -1
- package/dist/sv-7V5C2IT4.mjs.map +0 -1
- package/dist/th-LPKYLBX5.mjs.map +0 -1
- package/dist/tr-DU4RQL4M.mjs.map +0 -1
- package/dist/uk-36UHTDDI.mjs.map +0 -1
- package/dist/vi-GDHOUZDH.mjs.map +0 -1
- package/dist/zh-TYUID4XZ.mjs.map +0 -1
- /package/dist/{en-EVMIX24Y.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3: Feature Integration Test - Generation Flow Architecture
|
|
3
|
+
*
|
|
4
|
+
* Tests the COMPLETE generation flow with real component composition:
|
|
5
|
+
* - EventBusProvider (REAL)
|
|
6
|
+
* - ApiClientProvider (REAL, with MOCKED client)
|
|
7
|
+
* - useGenerationFlow (REAL)
|
|
8
|
+
* - useGenerationProgress (REAL)
|
|
9
|
+
* - useEventOperations (REAL)
|
|
10
|
+
* - useEventSubscriptions (REAL)
|
|
11
|
+
*
|
|
12
|
+
* This test focuses on ARCHITECTURE and EVENT WIRING:
|
|
13
|
+
* - Verifies API called exactly ONCE (catches duplicate subscriptions)
|
|
14
|
+
* - Tests event propagation through the event bus
|
|
15
|
+
* - Validates modal workflow (open → submit → SSE stream)
|
|
16
|
+
* - Ensures generation progress updates correctly
|
|
17
|
+
* - Tests success/error handling
|
|
18
|
+
*
|
|
19
|
+
* NO BACKEND SERVER - only mocked API client boundary
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
23
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
24
|
+
import { act } from 'react';
|
|
25
|
+
import { useGenerationFlow } from '../../../hooks/useGenerationFlow';
|
|
26
|
+
import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
27
|
+
import { ApiClientProvider, useApiClient } from '../../../contexts/ApiClientContext';
|
|
28
|
+
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
29
|
+
import { useEventOperations } from '../../../contexts/useEventOperations';
|
|
30
|
+
import { SSEClient } from '@semiont/api-client';
|
|
31
|
+
import type { SemiontApiClient, ResourceUri, AnnotationUri } from '@semiont/api-client';
|
|
32
|
+
import { resourceUri, annotationUri } from '@semiont/api-client';
|
|
33
|
+
import type { Emitter } from 'mitt';
|
|
34
|
+
import type { EventMap } from '../../../contexts/EventBusContext';
|
|
35
|
+
|
|
36
|
+
// Mock SSE stream that we can control in tests
|
|
37
|
+
const createMockGenerationStream = () => {
|
|
38
|
+
const stream = {
|
|
39
|
+
onProgressCallback: null as ((chunk: any) => void) | null,
|
|
40
|
+
onCompleteCallback: null as ((finalChunk: any) => void) | null,
|
|
41
|
+
onErrorCallback: null as ((error: Error) => void) | null,
|
|
42
|
+
onProgress: vi.fn((callback: (chunk: any) => void) => {
|
|
43
|
+
stream.onProgressCallback = callback;
|
|
44
|
+
return stream;
|
|
45
|
+
}),
|
|
46
|
+
onComplete: vi.fn((callback: (finalChunk: any) => void) => {
|
|
47
|
+
stream.onCompleteCallback = callback;
|
|
48
|
+
return stream;
|
|
49
|
+
}),
|
|
50
|
+
onError: vi.fn((callback: (error: Error) => void) => {
|
|
51
|
+
stream.onErrorCallback = callback;
|
|
52
|
+
return stream;
|
|
53
|
+
}),
|
|
54
|
+
close: vi.fn(),
|
|
55
|
+
};
|
|
56
|
+
return stream;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
describe('Generation Flow - Feature Integration', () => {
|
|
60
|
+
let mockStream: ReturnType<typeof createMockGenerationStream>;
|
|
61
|
+
let generateResourceSpy: any;
|
|
62
|
+
let mockShowSuccess: ReturnType<typeof vi.fn>;
|
|
63
|
+
let mockShowError: ReturnType<typeof vi.fn>;
|
|
64
|
+
let mockCacheManager: { invalidate: ReturnType<typeof vi.fn> };
|
|
65
|
+
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks();
|
|
68
|
+
resetEventBusForTesting();
|
|
69
|
+
|
|
70
|
+
// Create fresh mock stream for each test
|
|
71
|
+
mockStream = createMockGenerationStream();
|
|
72
|
+
|
|
73
|
+
// Spy on SSEClient prototype method
|
|
74
|
+
generateResourceSpy = vi.spyOn(SSEClient.prototype, 'generateResourceFromAnnotation').mockReturnValue(mockStream as any);
|
|
75
|
+
|
|
76
|
+
// Mock callbacks
|
|
77
|
+
mockShowSuccess = vi.fn();
|
|
78
|
+
mockShowError = vi.fn();
|
|
79
|
+
mockCacheManager = { invalidate: vi.fn() };
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
vi.restoreAllMocks();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should open modal when generation:modal-open event is emitted', async () => {
|
|
87
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
88
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
89
|
+
|
|
90
|
+
const { emitModalOpen } = renderGenerationFlow(
|
|
91
|
+
testResourceUri
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Emit modal open event
|
|
95
|
+
act(() => {
|
|
96
|
+
emitModalOpen(testAnnotationUri, testResourceUri, 'Test Reference');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Verify modal state updated
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(screen.getByTestId('modal-open')).toHaveTextContent('true');
|
|
102
|
+
expect(screen.getByTestId('reference-id')).toHaveTextContent(testAnnotationUri);
|
|
103
|
+
expect(screen.getByTestId('default-title')).toHaveTextContent('Test Reference');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should call generateResourceFromAnnotation exactly ONCE when generation starts', async () => {
|
|
108
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
109
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
110
|
+
|
|
111
|
+
const { emitGenerationStart } = renderGenerationFlow(
|
|
112
|
+
testResourceUri
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Trigger generation with full options
|
|
116
|
+
act(() => {
|
|
117
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
118
|
+
title: 'Generated Document',
|
|
119
|
+
prompt: 'Create a comprehensive document',
|
|
120
|
+
language: 'en',
|
|
121
|
+
temperature: 0.7,
|
|
122
|
+
maxTokens: 2000,
|
|
123
|
+
context: {
|
|
124
|
+
sourceText: 'Reference text from the document',
|
|
125
|
+
entityTypes: ['Person', 'Organization'],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// CRITICAL ASSERTION: API called exactly once (not twice!)
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(generateResourceSpy).toHaveBeenCalledTimes(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Verify correct parameters
|
|
136
|
+
expect(generateResourceSpy).toHaveBeenCalledWith(
|
|
137
|
+
testResourceUri,
|
|
138
|
+
testAnnotationUri,
|
|
139
|
+
{
|
|
140
|
+
title: 'Generated Document',
|
|
141
|
+
prompt: 'Create a comprehensive document',
|
|
142
|
+
language: 'en',
|
|
143
|
+
temperature: 0.7,
|
|
144
|
+
maxTokens: 2000,
|
|
145
|
+
context: {
|
|
146
|
+
sourceText: 'Reference text from the document',
|
|
147
|
+
entityTypes: ['Person', 'Organization'],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{ auth: undefined }
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should propagate SSE progress events to useGenerationProgress state', async () => {
|
|
155
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
156
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
157
|
+
|
|
158
|
+
const { emitGenerationStart } = renderGenerationFlow(
|
|
159
|
+
testResourceUri
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Start generation
|
|
163
|
+
act(() => {
|
|
164
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
165
|
+
title: 'Test Doc',
|
|
166
|
+
context: { sourceText: 'test' },
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Wait for stream to be created
|
|
171
|
+
await waitFor(() => {
|
|
172
|
+
expect(generateResourceSpy).toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Simulate SSE progress callback being invoked
|
|
176
|
+
act(() => {
|
|
177
|
+
mockStream.onProgressCallback!({
|
|
178
|
+
status: 'generating',
|
|
179
|
+
message: 'Generating content...',
|
|
180
|
+
percentage: 25,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Verify progress propagated to UI
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('Generating content...');
|
|
187
|
+
expect(screen.getByTestId('is-generating')).toHaveTextContent('true');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should handle multiple progress updates correctly', async () => {
|
|
192
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
193
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
194
|
+
|
|
195
|
+
const { emitGenerationStart } = renderGenerationFlow(
|
|
196
|
+
testResourceUri
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Start generation
|
|
200
|
+
act(() => {
|
|
201
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
202
|
+
title: 'Test',
|
|
203
|
+
context: { sourceText: 'test' },
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
expect(generateResourceSpy).toHaveBeenCalledTimes(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// First progress update
|
|
212
|
+
act(() => {
|
|
213
|
+
mockStream.onProgressCallback!({
|
|
214
|
+
status: 'started',
|
|
215
|
+
message: 'Starting generation...',
|
|
216
|
+
percentage: 0,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await waitFor(() => {
|
|
221
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('Starting generation...');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Second progress update
|
|
225
|
+
act(() => {
|
|
226
|
+
mockStream.onProgressCallback!({
|
|
227
|
+
status: 'generating',
|
|
228
|
+
message: 'Creating document structure...',
|
|
229
|
+
percentage: 50,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('Creating document structure...');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Final progress update via onComplete
|
|
238
|
+
act(() => {
|
|
239
|
+
mockStream.onCompleteCallback!({
|
|
240
|
+
status: 'complete',
|
|
241
|
+
message: 'Document created successfully',
|
|
242
|
+
percentage: 100,
|
|
243
|
+
resourceName: 'Generated Document',
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('Document created successfully');
|
|
249
|
+
// Progress stays visible after completion (like detection flow)
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should show success toast on generation complete', async () => {
|
|
254
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
255
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
256
|
+
|
|
257
|
+
const { emitGenerationStart, getEventBus } = renderGenerationFlow(
|
|
258
|
+
testResourceUri
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// Start generation
|
|
262
|
+
act(() => {
|
|
263
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
264
|
+
title: 'Test',
|
|
265
|
+
context: { sourceText: 'test' },
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await waitFor(() => {
|
|
270
|
+
expect(generateResourceSpy).toHaveBeenCalled();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Simulate completion with final chunk
|
|
274
|
+
act(() => {
|
|
275
|
+
mockStream.onProgressCallback!({
|
|
276
|
+
status: 'complete',
|
|
277
|
+
message: 'Complete',
|
|
278
|
+
resourceName: 'Generated Document',
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Emit completion event
|
|
283
|
+
act(() => {
|
|
284
|
+
getEventBus().emit('generation:complete', {
|
|
285
|
+
annotationUri: testAnnotationUri,
|
|
286
|
+
progress: {
|
|
287
|
+
status: 'complete',
|
|
288
|
+
resourceName: 'Generated Document',
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Verify generation completes successfully
|
|
294
|
+
// Note: Progress stays visible after completion (like detection flow)
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should clear progress on generation failure', async () => {
|
|
298
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
299
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
300
|
+
|
|
301
|
+
const { emitGenerationStart, getEventBus } = renderGenerationFlow(
|
|
302
|
+
testResourceUri
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Start generation
|
|
306
|
+
act(() => {
|
|
307
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
308
|
+
title: 'Test',
|
|
309
|
+
context: { sourceText: 'test' },
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Add some progress
|
|
314
|
+
act(() => {
|
|
315
|
+
mockStream.onProgressCallback!({
|
|
316
|
+
status: 'generating',
|
|
317
|
+
message: 'Generating...',
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await waitFor(() => {
|
|
322
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('Generating...');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Emit failure
|
|
326
|
+
act(() => {
|
|
327
|
+
getEventBus().emit('generation:failed', { error: new Error('Network error') });
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Verify: progress cleared and not generating
|
|
331
|
+
await waitFor(() => {
|
|
332
|
+
expect(screen.getByTestId('is-generating')).toHaveTextContent('false');
|
|
333
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('No progress');
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should only call API once even with multiple event listeners', async () => {
|
|
338
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
339
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
340
|
+
|
|
341
|
+
const { emitGenerationStart, getEventBus } = renderGenerationFlow(
|
|
342
|
+
testResourceUri
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Add an additional event listener (simulating multiple subscribers)
|
|
346
|
+
const additionalListener = vi.fn();
|
|
347
|
+
getEventBus().on('generation:start', additionalListener);
|
|
348
|
+
|
|
349
|
+
// Trigger generation
|
|
350
|
+
act(() => {
|
|
351
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
352
|
+
title: 'Test',
|
|
353
|
+
context: { sourceText: 'test' },
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Wait for operation to complete
|
|
358
|
+
await waitFor(() => {
|
|
359
|
+
expect(generateResourceSpy).toHaveBeenCalled();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// VERIFY: API called exactly once, even though multiple listeners exist
|
|
363
|
+
expect(generateResourceSpy).toHaveBeenCalledTimes(1);
|
|
364
|
+
|
|
365
|
+
// VERIFY: Our additional listener was called (events work)
|
|
366
|
+
expect(additionalListener).toHaveBeenCalledTimes(1);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should forward final chunk as progress before emitting complete', async () => {
|
|
370
|
+
const testResourceUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
371
|
+
const testAnnotationUri = annotationUri('http://localhost:4000/resources/test-resource/annotations/test-annotation');
|
|
372
|
+
|
|
373
|
+
const { emitGenerationStart } = renderGenerationFlow(
|
|
374
|
+
testResourceUri
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
// Start generation
|
|
378
|
+
act(() => {
|
|
379
|
+
emitGenerationStart(testAnnotationUri, testResourceUri, {
|
|
380
|
+
title: 'Test',
|
|
381
|
+
context: { sourceText: 'test' },
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
await waitFor(() => {
|
|
386
|
+
expect(generateResourceSpy).toHaveBeenCalled();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Simulate onComplete with final chunk
|
|
390
|
+
act(() => {
|
|
391
|
+
mockStream.onCompleteCallback!({
|
|
392
|
+
status: 'complete',
|
|
393
|
+
message: 'Document created: My Document',
|
|
394
|
+
resourceName: 'My Document',
|
|
395
|
+
percentage: 100,
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Verify final chunk is visible as progress
|
|
400
|
+
await waitFor(() => {
|
|
401
|
+
expect(screen.getByTestId('progress')).toHaveTextContent('Document created: My Document');
|
|
402
|
+
// Progress stays visible after completion (like detection flow)
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Helper: Render useGenerationFlow hook with real component composition
|
|
409
|
+
* Returns methods to interact with the rendered component
|
|
410
|
+
*/
|
|
411
|
+
function renderGenerationFlow(
|
|
412
|
+
testResourceUri: ResourceUri
|
|
413
|
+
) {
|
|
414
|
+
let eventBusInstance: Emitter<EventMap>;
|
|
415
|
+
|
|
416
|
+
// Component to capture EventBus instance and set up event operations
|
|
417
|
+
function EventBusCapture() {
|
|
418
|
+
eventBusInstance = useEventBus();
|
|
419
|
+
const client = useApiClient();
|
|
420
|
+
|
|
421
|
+
// Set up event operations (this is what makes the SSE calls)
|
|
422
|
+
useEventOperations(eventBusInstance, {
|
|
423
|
+
client: client as SemiontApiClient,
|
|
424
|
+
resourceUri: testResourceUri,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Test harness component that uses the hook
|
|
431
|
+
function GenerationFlowTestHarness() {
|
|
432
|
+
const {
|
|
433
|
+
generationProgress,
|
|
434
|
+
generationModalOpen,
|
|
435
|
+
generationReferenceId,
|
|
436
|
+
generationDefaultTitle,
|
|
437
|
+
} = useGenerationFlow(
|
|
438
|
+
'en',
|
|
439
|
+
testResourceUri.split('/resources/')[1] || 'test-resource',
|
|
440
|
+
vi.fn(),
|
|
441
|
+
vi.fn(),
|
|
442
|
+
null,
|
|
443
|
+
vi.fn()
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
<div>
|
|
448
|
+
<div data-testid="modal-open">{generationModalOpen ? 'true' : 'false'}</div>
|
|
449
|
+
<div data-testid="reference-id">{generationReferenceId || 'none'}</div>
|
|
450
|
+
<div data-testid="default-title">{generationDefaultTitle || 'none'}</div>
|
|
451
|
+
<div data-testid="is-generating">
|
|
452
|
+
{generationProgress ? 'true' : 'false'}
|
|
453
|
+
</div>
|
|
454
|
+
<div data-testid="progress">
|
|
455
|
+
{generationProgress?.message || 'No progress'}
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
render(
|
|
462
|
+
<EventBusProvider>
|
|
463
|
+
<AuthTokenProvider token={null}>
|
|
464
|
+
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
465
|
+
<EventBusCapture />
|
|
466
|
+
<GenerationFlowTestHarness />
|
|
467
|
+
</ApiClientProvider>
|
|
468
|
+
</AuthTokenProvider>
|
|
469
|
+
</EventBusProvider>
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
emitModalOpen: (
|
|
474
|
+
annotationUri: AnnotationUri,
|
|
475
|
+
resourceUri: ResourceUri,
|
|
476
|
+
defaultTitle: string
|
|
477
|
+
) => {
|
|
478
|
+
eventBusInstance.emit('generation:modal-open', {
|
|
479
|
+
annotationUri,
|
|
480
|
+
resourceUri,
|
|
481
|
+
defaultTitle,
|
|
482
|
+
});
|
|
483
|
+
},
|
|
484
|
+
emitGenerationStart: (
|
|
485
|
+
annotationUri: AnnotationUri,
|
|
486
|
+
resourceUri: ResourceUri,
|
|
487
|
+
options: {
|
|
488
|
+
title: string;
|
|
489
|
+
prompt?: string;
|
|
490
|
+
language?: string;
|
|
491
|
+
temperature?: number;
|
|
492
|
+
maxTokens?: number;
|
|
493
|
+
context: any;
|
|
494
|
+
}
|
|
495
|
+
) => {
|
|
496
|
+
eventBusInstance.emit('generation:start', {
|
|
497
|
+
annotationUri,
|
|
498
|
+
resourceUri,
|
|
499
|
+
options,
|
|
500
|
+
});
|
|
501
|
+
},
|
|
502
|
+
getEventBus: () => eventBusInstance,
|
|
503
|
+
};
|
|
504
|
+
}
|