@semiont/react-ui 0.2.33-build.79 → 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-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-JZIO2A3B.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
- package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
- package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -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 +645 -471
- package/dist/index.mjs +3461 -3025
- package/dist/index.mjs.map +1 -1
- package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -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-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +3 -1
- 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 -134
- package/src/components/resource/BrowseView.tsx +86 -166
- package/src/components/resource/HistoryEvent.tsx +13 -7
- package/src/components/resource/ResourceViewer.tsx +122 -264
- 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/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +121 -28
- 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 +134 -42
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +119 -30
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
- 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 +226 -111
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
- 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 +9 -13
- 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 +135 -88
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +308 -528
- package/translations/ar.json +6 -3
- package/translations/bn.json +6 -3
- package/translations/cs.json +6 -3
- package/translations/da.json +6 -3
- package/translations/de.json +6 -3
- package/translations/el.json +6 -3
- package/translations/en.json +6 -3
- package/translations/es.json +6 -3
- package/translations/fa.json +6 -3
- package/translations/fi.json +6 -3
- package/translations/fr.json +6 -3
- package/translations/he.json +6 -3
- package/translations/hi.json +6 -3
- package/translations/id.json +6 -3
- package/translations/it.json +6 -3
- package/translations/ja.json +6 -3
- package/translations/ko.json +6 -3
- package/translations/ms.json +6 -3
- package/translations/nl.json +6 -3
- package/translations/no.json +6 -3
- package/translations/pl.json +6 -3
- package/translations/pt.json +6 -3
- package/translations/ro.json +6 -3
- package/translations/sv.json +6 -3
- package/translations/th.json +6 -3
- package/translations/tr.json +6 -3
- package/translations/uk.json +6 -3
- package/translations/vi.json +6 -3
- package/translations/zh.json +6 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-EMHEHPCJ.mjs.map +0 -1
- package/dist/bn-OVCI4F6X.mjs.map +0 -1
- package/dist/chunk-JZIO2A3B.mjs.map +0 -1
- package/dist/chunk-LIHZTECW.mjs.map +0 -1
- package/dist/cs-FAN66Q2F.mjs.map +0 -1
- package/dist/da-YBBIHI2O.mjs.map +0 -1
- package/dist/de-MAYU33LB.mjs.map +0 -1
- package/dist/el-MKGSWN4O.mjs.map +0 -1
- package/dist/es-52LHUWJD.mjs.map +0 -1
- package/dist/fa-FJICRANB.mjs.map +0 -1
- package/dist/fi-O455XFCR.mjs.map +0 -1
- package/dist/fr-TXIXHOOE.mjs.map +0 -1
- package/dist/he-JBSOX5IN.mjs.map +0 -1
- package/dist/hi-KGHI3XVT.mjs.map +0 -1
- package/dist/id-5OCPPZLO.mjs.map +0 -1
- package/dist/it-PNBBZSM2.mjs.map +0 -1
- package/dist/ja-LDD7R3TJ.mjs.map +0 -1
- package/dist/ko-F47ZDEY3.mjs.map +0 -1
- package/dist/ms-Z7LMXJWL.mjs.map +0 -1
- package/dist/nl-6SJFBPJ3.mjs.map +0 -1
- package/dist/no-YXPBPSGF.mjs.map +0 -1
- package/dist/pl-P4AZ2QME.mjs.map +0 -1
- package/dist/pt-LHWUS6U6.mjs.map +0 -1
- package/dist/ro-EA5J2ZON.mjs.map +0 -1
- package/dist/sv-DATBS3UQ.mjs.map +0 -1
- package/dist/th-WTFJRWPT.mjs.map +0 -1
- package/dist/tr-IKO3RXOX.mjs.map +0 -1
- package/dist/uk-CF6CTTRK.mjs.map +0 -1
- package/dist/vi-AJLTXPZQ.mjs.map +0 -1
- package/dist/zh-U3ORHHYH.mjs.map +0 -1
- /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -2,13 +2,53 @@
|
|
|
2
2
|
* Tests for ResourceViewerPage component
|
|
3
3
|
*
|
|
4
4
|
* Tests the main resource viewer UI component.
|
|
5
|
-
*
|
|
5
|
+
* All internal data fetching (content, annotations, etc.) is mocked at the hook level.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
9
|
import { render, screen } from '@testing-library/react';
|
|
10
|
+
import React from 'react';
|
|
10
11
|
import { ResourceViewerPage } from '../components/ResourceViewerPage';
|
|
11
12
|
import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
|
|
13
|
+
// Import directly from context file to bypass mocked barrel export
|
|
14
|
+
import { EventBusProvider, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
15
|
+
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
16
|
+
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
17
|
+
import { ToastProvider } from '../../../components/Toast';
|
|
18
|
+
|
|
19
|
+
// jsdom doesn't implement window.matchMedia — mock it for useTheme
|
|
20
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
21
|
+
writable: true,
|
|
22
|
+
value: vi.fn().mockImplementation((query: string) => ({
|
|
23
|
+
matches: false,
|
|
24
|
+
media: query,
|
|
25
|
+
onchange: null,
|
|
26
|
+
addListener: vi.fn(),
|
|
27
|
+
removeListener: vi.fn(),
|
|
28
|
+
addEventListener: vi.fn(),
|
|
29
|
+
removeEventListener: vi.fn(),
|
|
30
|
+
dispatchEvent: vi.fn(),
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Mock internal hooks that fetch data
|
|
35
|
+
vi.mock('../../../hooks/useResourceContent', () => ({
|
|
36
|
+
useResourceContent: () => ({ content: 'Test content', loading: false }),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
vi.mock('../../../lib/api-hooks', () => ({
|
|
40
|
+
useResources: () => ({
|
|
41
|
+
annotations: { useQuery: () => ({ data: { annotations: [] } }) },
|
|
42
|
+
referencedBy: { useQuery: () => ({ data: { referencedBy: [] }, isLoading: false }) },
|
|
43
|
+
}),
|
|
44
|
+
useEntityTypes: () => ({
|
|
45
|
+
list: { useQuery: () => ({ data: { entityTypes: ['Document', 'Article', 'Book'] } }) },
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
vi.mock('../../../hooks/useResourceEvents', () => ({
|
|
50
|
+
useResourceEvents: () => null,
|
|
51
|
+
}));
|
|
12
52
|
|
|
13
53
|
// Mock dependencies that ResourceViewerPage imports
|
|
14
54
|
vi.mock('@tanstack/react-query', async () => {
|
|
@@ -24,7 +64,6 @@ vi.mock('@tanstack/react-query', async () => {
|
|
|
24
64
|
|
|
25
65
|
vi.mock('@semiont/react-ui', async () => {
|
|
26
66
|
const actual = await vi.importActual('@semiont/react-ui');
|
|
27
|
-
const mitt = await import('mitt');
|
|
28
67
|
return {
|
|
29
68
|
...actual,
|
|
30
69
|
ResourceViewer: ({ resource }: any) => <div data-testid="resource-viewer">{resource.name}</div>,
|
|
@@ -39,16 +78,47 @@ vi.mock('@semiont/react-ui', async () => {
|
|
|
39
78
|
createCancelDetectionHandler: () => vi.fn(),
|
|
40
79
|
useGenerationProgress: () => ({
|
|
41
80
|
progress: null,
|
|
42
|
-
startGeneration: vi.fn(),
|
|
43
81
|
clearProgress: vi.fn(),
|
|
44
82
|
}),
|
|
45
83
|
useDebouncedCallback: (fn: any) => fn,
|
|
46
84
|
supportsDetection: () => false,
|
|
47
85
|
MakeMeaningEventBusProvider: ({ children }: any) => children,
|
|
48
|
-
|
|
86
|
+
useResourceLoadingAnnouncements: () => ({
|
|
87
|
+
announceResourceLoading: vi.fn(),
|
|
88
|
+
announceResourceLoaded: vi.fn(),
|
|
89
|
+
}),
|
|
90
|
+
// Don't mock EventBusProvider, useEventBus, resetEventBusForTesting - let actual pass through via ...actual
|
|
91
|
+
useEventSubscriptions: vi.fn(),
|
|
92
|
+
useResourceAnnotations: () => ({
|
|
93
|
+
clearNewAnnotationId: vi.fn(),
|
|
94
|
+
newAnnotationIds: new Set(),
|
|
95
|
+
createAnnotation: vi.fn(),
|
|
96
|
+
deleteAnnotation: vi.fn(),
|
|
97
|
+
triggerSparkleAnimation: vi.fn(),
|
|
98
|
+
}),
|
|
49
99
|
};
|
|
50
100
|
});
|
|
51
101
|
|
|
102
|
+
vi.mock('../../../contexts/OpenResourcesContext', () => ({
|
|
103
|
+
useOpenResources: () => ({
|
|
104
|
+
openResources: [],
|
|
105
|
+
addResource: vi.fn(),
|
|
106
|
+
removeResource: vi.fn(),
|
|
107
|
+
isResourceOpen: vi.fn().mockReturnValue(false),
|
|
108
|
+
}),
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
|
|
112
|
+
useResourceAnnotations: () => ({
|
|
113
|
+
clearNewAnnotationId: vi.fn(),
|
|
114
|
+
newAnnotationIds: new Set(),
|
|
115
|
+
createAnnotation: vi.fn(),
|
|
116
|
+
deleteAnnotation: vi.fn(),
|
|
117
|
+
triggerSparkleAnimation: vi.fn(),
|
|
118
|
+
}),
|
|
119
|
+
ResourceAnnotationsProvider: ({ children }: any) => children,
|
|
120
|
+
}));
|
|
121
|
+
|
|
52
122
|
vi.mock('@/components/toolbar/ToolbarPanels', () => ({
|
|
53
123
|
ToolbarPanels: ({ children }: any) => <div data-testid="toolbar-panels">{children}</div>,
|
|
54
124
|
}));
|
|
@@ -61,7 +131,7 @@ vi.mock('@/components/modals/GenerationConfigModal', () => ({
|
|
|
61
131
|
GenerationConfigModal: () => <div data-testid="generation-modal">Generation Modal</div>,
|
|
62
132
|
}));
|
|
63
133
|
|
|
64
|
-
// Create mock props
|
|
134
|
+
// Create mock props matching the current ResourceViewerPageProps
|
|
65
135
|
const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): ResourceViewerPageProps => ({
|
|
66
136
|
resource: {
|
|
67
137
|
'@context': 'https://www.w3.org/ns/anno.jsonld',
|
|
@@ -80,45 +150,40 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
|
|
|
80
150
|
],
|
|
81
151
|
},
|
|
82
152
|
rUri: 'http://localhost/resources/test-123' as any,
|
|
83
|
-
content: 'Test content for the resource viewer',
|
|
84
|
-
contentLoading: false,
|
|
85
|
-
annotations: [],
|
|
86
|
-
referencedBy: [],
|
|
87
|
-
referencedByLoading: false,
|
|
88
|
-
allEntityTypes: ['Document', 'Article', 'Book'],
|
|
89
153
|
locale: 'en',
|
|
90
|
-
theme: 'light',
|
|
91
|
-
onThemeChange: vi.fn(),
|
|
92
|
-
showLineNumbers: false,
|
|
93
|
-
onLineNumbersToggle: vi.fn(),
|
|
94
|
-
activePanel: null,
|
|
95
|
-
onPanelToggle: vi.fn(),
|
|
96
|
-
setActivePanel: vi.fn(),
|
|
97
|
-
onArchive: vi.fn(),
|
|
98
|
-
onUnarchive: vi.fn(),
|
|
99
|
-
onClone: vi.fn(),
|
|
100
|
-
onUpdateAnnotationBody: vi.fn(),
|
|
101
|
-
onRefetchAnnotations: vi.fn(),
|
|
102
|
-
onCreateAnnotation: vi.fn(),
|
|
103
|
-
onTriggerSparkleAnimation: vi.fn(),
|
|
104
|
-
onClearNewAnnotationId: vi.fn(),
|
|
105
|
-
showSuccess: vi.fn(),
|
|
106
|
-
showError: vi.fn(),
|
|
107
154
|
cacheManager: {},
|
|
108
|
-
client: {},
|
|
109
155
|
Link: ({ children }: any) => <a>{children}</a>,
|
|
110
156
|
routes: {},
|
|
111
|
-
|
|
157
|
+
refetchDocument: vi.fn().mockResolvedValue(undefined),
|
|
158
|
+
ToolbarPanels: ({ children, activePanel }: any) =>
|
|
159
|
+
!activePanel ? null : <div data-testid="toolbar-panels">{children}</div>,
|
|
112
160
|
SearchResourcesModal: () => <div data-testid="search-modal">Search Modal</div>,
|
|
113
161
|
GenerationConfigModal: () => <div data-testid="generation-modal">Generation Modal</div>,
|
|
114
162
|
...overrides,
|
|
115
163
|
});
|
|
116
164
|
|
|
165
|
+
// Test wrapper to provide all required providers
|
|
166
|
+
const renderWithProviders = (ui: React.ReactElement) => {
|
|
167
|
+
return render(
|
|
168
|
+
<ToastProvider>
|
|
169
|
+
<AuthTokenProvider token={null}>
|
|
170
|
+
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
171
|
+
<EventBusProvider>{ui}</EventBusProvider>
|
|
172
|
+
</ApiClientProvider>
|
|
173
|
+
</AuthTokenProvider>
|
|
174
|
+
</ToastProvider>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
117
178
|
describe('ResourceViewerPage', () => {
|
|
179
|
+
beforeEach(() => {
|
|
180
|
+
resetEventBusForTesting();
|
|
181
|
+
});
|
|
182
|
+
|
|
118
183
|
describe('Basic Rendering', () => {
|
|
119
184
|
it('renders without crashing', () => {
|
|
120
185
|
const props = createMockProps();
|
|
121
|
-
|
|
186
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
122
187
|
|
|
123
188
|
// Check for header element specifically
|
|
124
189
|
expect(screen.getByRole('heading', { name: 'Test Resource' })).toBeInTheDocument();
|
|
@@ -132,30 +197,23 @@ describe('ResourceViewerPage', () => {
|
|
|
132
197
|
},
|
|
133
198
|
});
|
|
134
199
|
|
|
135
|
-
|
|
200
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
136
201
|
|
|
137
202
|
expect(screen.getByRole('heading', { name: 'My Special Resource' })).toBeInTheDocument();
|
|
138
203
|
});
|
|
139
204
|
|
|
140
205
|
it('renders toolbar component', () => {
|
|
141
206
|
const props = createMockProps();
|
|
142
|
-
|
|
207
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
143
208
|
|
|
144
209
|
expect(screen.getByTestId('toolbar')).toBeInTheDocument();
|
|
145
210
|
});
|
|
146
211
|
});
|
|
147
212
|
|
|
148
213
|
describe('Content Loading', () => {
|
|
149
|
-
it('shows loading message when content is loading', () => {
|
|
150
|
-
const props = createMockProps({ contentLoading: true });
|
|
151
|
-
render(<ResourceViewerPage {...props} />);
|
|
152
|
-
|
|
153
|
-
expect(screen.getByText('Loading document content...')).toBeInTheDocument();
|
|
154
|
-
});
|
|
155
|
-
|
|
156
214
|
it('shows ResourceViewer when content is loaded', () => {
|
|
157
|
-
const props = createMockProps(
|
|
158
|
-
|
|
215
|
+
const props = createMockProps();
|
|
216
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
159
217
|
|
|
160
218
|
expect(screen.getByTestId('resource-viewer')).toBeInTheDocument();
|
|
161
219
|
});
|
|
@@ -163,43 +221,53 @@ describe('ResourceViewerPage', () => {
|
|
|
163
221
|
|
|
164
222
|
describe('Panel Visibility', () => {
|
|
165
223
|
it('shows annotations panel when activePanel is annotations', () => {
|
|
166
|
-
|
|
167
|
-
|
|
224
|
+
localStorage.setItem('activeToolbarPanel', 'annotations');
|
|
225
|
+
const props = createMockProps();
|
|
226
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
168
227
|
|
|
169
228
|
expect(screen.getByTestId('annotations-panel')).toBeInTheDocument();
|
|
229
|
+
localStorage.clear();
|
|
170
230
|
});
|
|
171
231
|
|
|
172
232
|
it('shows history panel when activePanel is history', () => {
|
|
173
|
-
|
|
174
|
-
|
|
233
|
+
localStorage.setItem('activeToolbarPanel', 'history');
|
|
234
|
+
const props = createMockProps();
|
|
235
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
175
236
|
|
|
176
237
|
expect(screen.getByTestId('history-panel')).toBeInTheDocument();
|
|
238
|
+
localStorage.clear();
|
|
177
239
|
});
|
|
178
240
|
|
|
179
241
|
it('shows info panel when activePanel is info', () => {
|
|
180
|
-
|
|
181
|
-
|
|
242
|
+
localStorage.setItem('activeToolbarPanel', 'info');
|
|
243
|
+
const props = createMockProps();
|
|
244
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
182
245
|
|
|
183
246
|
expect(screen.getByTestId('info-panel')).toBeInTheDocument();
|
|
247
|
+
localStorage.clear();
|
|
184
248
|
});
|
|
185
249
|
|
|
186
250
|
it('shows collaboration panel when activePanel is collaboration', () => {
|
|
187
|
-
|
|
188
|
-
|
|
251
|
+
localStorage.setItem('activeToolbarPanel', 'collaboration');
|
|
252
|
+
const props = createMockProps();
|
|
253
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
189
254
|
|
|
190
255
|
expect(screen.getByTestId('collaboration-panel')).toBeInTheDocument();
|
|
256
|
+
localStorage.clear();
|
|
191
257
|
});
|
|
192
258
|
|
|
193
259
|
it('shows jsonld panel when activePanel is jsonld', () => {
|
|
194
|
-
|
|
195
|
-
|
|
260
|
+
localStorage.setItem('activeToolbarPanel', 'jsonld');
|
|
261
|
+
const props = createMockProps();
|
|
262
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
196
263
|
|
|
197
264
|
expect(screen.getByTestId('jsonld-panel')).toBeInTheDocument();
|
|
265
|
+
localStorage.clear();
|
|
198
266
|
});
|
|
199
267
|
});
|
|
200
268
|
|
|
201
269
|
describe('Archived Status', () => {
|
|
202
|
-
it('
|
|
270
|
+
it('does not show archived badge when not in annotate mode', () => {
|
|
203
271
|
const props = createMockProps({
|
|
204
272
|
resource: {
|
|
205
273
|
...createMockProps().resource,
|
|
@@ -207,10 +275,9 @@ describe('ResourceViewerPage', () => {
|
|
|
207
275
|
},
|
|
208
276
|
});
|
|
209
277
|
|
|
210
|
-
|
|
278
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
211
279
|
|
|
212
280
|
// Archived badge only shows in annotate mode, which defaults to false
|
|
213
|
-
// So we test that it doesn't show when not in annotate mode
|
|
214
281
|
expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
|
|
215
282
|
});
|
|
216
283
|
});
|
|
@@ -218,58 +285,38 @@ describe('ResourceViewerPage', () => {
|
|
|
218
285
|
describe('Modals', () => {
|
|
219
286
|
it('renders search resources modal', () => {
|
|
220
287
|
const props = createMockProps();
|
|
221
|
-
|
|
288
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
222
289
|
|
|
223
290
|
expect(screen.getByTestId('search-modal')).toBeInTheDocument();
|
|
224
291
|
});
|
|
225
292
|
|
|
226
293
|
it('renders generation config modal', () => {
|
|
227
294
|
const props = createMockProps();
|
|
228
|
-
|
|
295
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
229
296
|
|
|
230
297
|
expect(screen.getByTestId('generation-modal')).toBeInTheDocument();
|
|
231
298
|
});
|
|
232
299
|
});
|
|
233
300
|
|
|
234
301
|
describe('Props Integration', () => {
|
|
235
|
-
it('
|
|
236
|
-
const props = createMockProps(
|
|
237
|
-
|
|
238
|
-
contentLoading: false,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
render(<ResourceViewerPage {...props} />);
|
|
302
|
+
it('renders ResourceViewer component', () => {
|
|
303
|
+
const props = createMockProps();
|
|
304
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
242
305
|
|
|
243
|
-
// ResourceViewer is mocked to show resource name
|
|
244
306
|
expect(screen.getByTestId('resource-viewer')).toBeInTheDocument();
|
|
245
307
|
});
|
|
246
308
|
|
|
247
|
-
it('
|
|
309
|
+
it('renders with different resource names', () => {
|
|
248
310
|
const props = createMockProps({
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
type: 'Annotation',
|
|
254
|
-
motivation: 'commenting',
|
|
255
|
-
body: [],
|
|
256
|
-
target: 'http://localhost/resources/test-123',
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
'@context': 'http://www.w3.org/ns/anno.jsonld',
|
|
260
|
-
id: 'http://localhost/annotations/2',
|
|
261
|
-
type: 'Annotation',
|
|
262
|
-
motivation: 'highlighting',
|
|
263
|
-
body: [],
|
|
264
|
-
target: 'http://localhost/resources/test-123',
|
|
265
|
-
},
|
|
266
|
-
],
|
|
311
|
+
resource: {
|
|
312
|
+
...createMockProps().resource,
|
|
313
|
+
name: 'Different Resource Name',
|
|
314
|
+
},
|
|
267
315
|
});
|
|
268
316
|
|
|
269
|
-
|
|
317
|
+
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
270
318
|
|
|
271
|
-
|
|
272
|
-
expect(screen.getByRole('heading', { name: 'Test Resource' })).toBeInTheDocument();
|
|
319
|
+
expect(screen.getByRole('heading', { name: 'Different Resource Name' })).toBeInTheDocument();
|
|
273
320
|
});
|
|
274
321
|
});
|
|
275
322
|
});
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3 Integration Test: Detection Progress Flow UI/UX
|
|
3
|
+
*
|
|
4
|
+
* Tests the complete data flow from UI → EventBus → useEventOperations → SSE (mocked)
|
|
5
|
+
*
|
|
6
|
+
* This test uses COMPOSITION instead of mocking:
|
|
7
|
+
* - Real React components composed together (useDetectionFlow + HighlightPanel + DetectSection)
|
|
8
|
+
* - Real EventBus (mitt) passed via context
|
|
9
|
+
* - Real useEventOperations hook with mock API client passed as prop
|
|
10
|
+
* - Mock SSE stream (simulated API responses) provided via composition
|
|
11
|
+
*
|
|
12
|
+
* This test focuses on USER EXPERIENCE:
|
|
13
|
+
* - Verifies user clicks "Detect" button and sees progress
|
|
14
|
+
* - Tests progress messages appear and update correctly
|
|
15
|
+
* - Validates final message stays visible after completion
|
|
16
|
+
* - Ensures progress clears on error
|
|
17
|
+
*
|
|
18
|
+
* COMPLEMENTARY TEST: See DetectionFlowIntegration.test.tsx for architecture testing
|
|
19
|
+
* - That test verifies SYSTEM ARCHITECTURE (event wiring, API call count)
|
|
20
|
+
* - This test verifies USER EXPERIENCE (button clicks, UI feedback)
|
|
21
|
+
*
|
|
22
|
+
* UPDATED: Now tests useDetectionFlow hook instead of DetectionFlowContainer
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
26
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
27
|
+
import userEvent from '@testing-library/user-event';
|
|
28
|
+
import { act } from 'react';
|
|
29
|
+
import { HighlightPanel } from '../../../components/resource/panels/HighlightPanel';
|
|
30
|
+
import { useDetectionFlow } from '../../../hooks/useDetectionFlow';
|
|
31
|
+
import { EventBusProvider, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
32
|
+
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
33
|
+
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
34
|
+
import { SSEClient } from '@semiont/api-client';
|
|
35
|
+
import type { components } from '@semiont/api-client';
|
|
36
|
+
|
|
37
|
+
type Annotation = components['schemas']['Annotation'];
|
|
38
|
+
|
|
39
|
+
// Mock translations
|
|
40
|
+
const mockT = vi.fn((key: string) => {
|
|
41
|
+
const translations: Record<string, string> = {
|
|
42
|
+
title: 'Highlights',
|
|
43
|
+
noHighlights: 'No highlights yet',
|
|
44
|
+
detectHighlights: 'Detect Highlights',
|
|
45
|
+
instructions: 'Instructions',
|
|
46
|
+
optional: '(optional)',
|
|
47
|
+
instructionsPlaceholder: 'Enter custom instructions...',
|
|
48
|
+
densityLabel: 'Density',
|
|
49
|
+
densitySparse: 'Sparse',
|
|
50
|
+
densityDense: 'Dense',
|
|
51
|
+
detect: 'Detect',
|
|
52
|
+
};
|
|
53
|
+
return translations[key] || key;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
vi.mock('../../../contexts/TranslationContext', () => ({
|
|
57
|
+
useTranslations: () => mockT,
|
|
58
|
+
TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Create a mock SSE stream that we can control
|
|
62
|
+
class MockSSEStream {
|
|
63
|
+
private progressHandlers: Array<(chunk: any) => void> = [];
|
|
64
|
+
private completeHandlers: Array<(finalChunk?: any) => void> = [];
|
|
65
|
+
private errorHandlers: Array<(error: Error) => void> = [];
|
|
66
|
+
|
|
67
|
+
onProgress(handler: (chunk: any) => void) {
|
|
68
|
+
this.progressHandlers.push(handler);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onComplete(handler: (finalChunk?: any) => void) {
|
|
72
|
+
this.completeHandlers.push(handler);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
onError(handler: (error: Error) => void) {
|
|
76
|
+
this.errorHandlers.push(handler);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Test helper methods
|
|
80
|
+
emitProgress(chunk: any) {
|
|
81
|
+
this.progressHandlers.forEach(handler => handler(chunk));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
emitComplete(finalChunk?: any) {
|
|
85
|
+
this.completeHandlers.forEach(handler => handler(finalChunk));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
emitError(error: Error) {
|
|
89
|
+
this.errorHandlers.forEach(handler => handler(error));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Composition: Test component that wires together the pieces we're testing
|
|
94
|
+
function DetectionFlowTestHarness({
|
|
95
|
+
rUri,
|
|
96
|
+
annotations,
|
|
97
|
+
}: {
|
|
98
|
+
rUri: string;
|
|
99
|
+
annotations: Annotation[];
|
|
100
|
+
}) {
|
|
101
|
+
const { detectingMotivation, detectionProgress } = useDetectionFlow(rUri as any);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<HighlightPanel
|
|
105
|
+
annotations={annotations}
|
|
106
|
+
pendingAnnotation={null}
|
|
107
|
+
hoveredAnnotationId={null}
|
|
108
|
+
isDetecting={detectingMotivation === 'highlighting'}
|
|
109
|
+
detectionProgress={detectionProgress}
|
|
110
|
+
annotateMode={true}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
describe('Detection Progress Flow Integration (Layer 3)', () => {
|
|
116
|
+
let mockAnnotations: Annotation[];
|
|
117
|
+
let mockStream: MockSSEStream;
|
|
118
|
+
const rUri = 'https://example.com/resources/test-resource-1';
|
|
119
|
+
|
|
120
|
+
// Helper to render test harness with composition
|
|
121
|
+
const renderDetectionFlow = () => {
|
|
122
|
+
return render(
|
|
123
|
+
<EventBusProvider>
|
|
124
|
+
<AuthTokenProvider token={null}>
|
|
125
|
+
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
126
|
+
<DetectionFlowTestHarness
|
|
127
|
+
rUri={rUri}
|
|
128
|
+
annotations={mockAnnotations}
|
|
129
|
+
/>
|
|
130
|
+
</ApiClientProvider>
|
|
131
|
+
</AuthTokenProvider>
|
|
132
|
+
</EventBusProvider>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
// Reset event bus for test isolation
|
|
138
|
+
resetEventBusForTesting();
|
|
139
|
+
vi.clearAllMocks();
|
|
140
|
+
|
|
141
|
+
// Reset mocks
|
|
142
|
+
mockStream = new MockSSEStream();
|
|
143
|
+
|
|
144
|
+
// Spy on SSEClient prototype methods to inject mock stream
|
|
145
|
+
vi.spyOn(SSEClient.prototype, 'detectHighlights').mockReturnValue(mockStream as any);
|
|
146
|
+
vi.spyOn(SSEClient.prototype, 'detectAssessments').mockReturnValue(mockStream as any);
|
|
147
|
+
vi.spyOn(SSEClient.prototype, 'detectComments').mockReturnValue(mockStream as any);
|
|
148
|
+
vi.spyOn(SSEClient.prototype, 'detectAnnotations').mockReturnValue(mockStream as any);
|
|
149
|
+
|
|
150
|
+
mockAnnotations = [];
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
afterEach(() => {
|
|
154
|
+
vi.restoreAllMocks();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should display detection progress from button click to completion', async () => {
|
|
158
|
+
const user = userEvent.setup();
|
|
159
|
+
|
|
160
|
+
// Render composed components with real EventBus and mock API client
|
|
161
|
+
renderDetectionFlow();
|
|
162
|
+
|
|
163
|
+
// Initial state: no progress visible
|
|
164
|
+
expect(screen.queryByText(/Analyzing/)).not.toBeInTheDocument();
|
|
165
|
+
|
|
166
|
+
// Click detect button
|
|
167
|
+
const detectButton = screen.getByRole('button', { name: /✨ Detect/ });
|
|
168
|
+
await user.click(detectButton);
|
|
169
|
+
|
|
170
|
+
// Simulate SSE progress chunk #1: Starting
|
|
171
|
+
act(() => {
|
|
172
|
+
mockStream.emitProgress({
|
|
173
|
+
status: 'started',
|
|
174
|
+
percentage: 0,
|
|
175
|
+
message: 'Starting detection...',
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Verify progress message appears
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
expect(screen.getByText('Starting detection...')).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Simulate SSE progress chunk #2: Analyzing
|
|
185
|
+
act(() => {
|
|
186
|
+
mockStream.emitProgress({
|
|
187
|
+
status: 'analyzing',
|
|
188
|
+
percentage: 30,
|
|
189
|
+
message: 'Analyzing text...',
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await waitFor(() => {
|
|
194
|
+
expect(screen.getByText('Analyzing text...')).toBeInTheDocument();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Simulate SSE progress chunk #3: Creating annotations
|
|
198
|
+
act(() => {
|
|
199
|
+
mockStream.emitProgress({
|
|
200
|
+
status: 'creating',
|
|
201
|
+
percentage: 60,
|
|
202
|
+
message: 'Creating 14 annotations...',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await waitFor(() => {
|
|
207
|
+
expect(screen.getByText('Creating 14 annotations...')).toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Simulate stream completion with final chunk (onComplete receives the final progress)
|
|
211
|
+
act(() => {
|
|
212
|
+
mockStream.emitComplete({
|
|
213
|
+
status: 'complete',
|
|
214
|
+
percentage: 100,
|
|
215
|
+
message: 'Complete! Created 14 highlights',
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// CRITICAL TEST: Final message should still be visible after completion
|
|
220
|
+
await waitFor(() => {
|
|
221
|
+
expect(screen.getByText('Complete! Created 14 highlights')).toBeInTheDocument();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should handle out-of-order SSE events (complete before final progress)', async () => {
|
|
226
|
+
const user = userEvent.setup();
|
|
227
|
+
|
|
228
|
+
renderDetectionFlow();
|
|
229
|
+
|
|
230
|
+
// Click detect
|
|
231
|
+
const detectButton = screen.getByRole('button', { name: /✨ Detect/ });
|
|
232
|
+
await user.click(detectButton);
|
|
233
|
+
|
|
234
|
+
// Simulate initial progress
|
|
235
|
+
act(() => {
|
|
236
|
+
mockStream.emitProgress({
|
|
237
|
+
status: 'analyzing',
|
|
238
|
+
message: 'Analyzing...',
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(screen.getByText('Analyzing...')).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Simulate race condition: complete arrives BEFORE final progress
|
|
247
|
+
act(() => {
|
|
248
|
+
mockStream.emitComplete();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Then final progress chunk arrives
|
|
252
|
+
act(() => {
|
|
253
|
+
mockStream.emitProgress({
|
|
254
|
+
status: 'complete',
|
|
255
|
+
percentage: 100,
|
|
256
|
+
message: 'Complete! Created 5 highlights',
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Final message should still be visible
|
|
261
|
+
await waitFor(() => {
|
|
262
|
+
expect(screen.getByText('Complete! Created 5 highlights')).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should show progress with request parameters', async () => {
|
|
267
|
+
const user = userEvent.setup();
|
|
268
|
+
|
|
269
|
+
renderDetectionFlow();
|
|
270
|
+
|
|
271
|
+
const detectButton = screen.getByRole('button', { name: /✨ Detect/ });
|
|
272
|
+
await user.click(detectButton);
|
|
273
|
+
|
|
274
|
+
// Simulate progress with request parameters
|
|
275
|
+
act(() => {
|
|
276
|
+
mockStream.emitProgress({
|
|
277
|
+
status: 'analyzing',
|
|
278
|
+
message: 'Analyzing with custom instructions...',
|
|
279
|
+
requestParams: [
|
|
280
|
+
{ label: 'Instructions', value: 'Find important points' },
|
|
281
|
+
{ label: 'Density', value: '5' },
|
|
282
|
+
],
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
expect(screen.getByText('Find important points')).toBeInTheDocument();
|
|
288
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should clear progress on detection:failed', async () => {
|
|
293
|
+
const user = userEvent.setup();
|
|
294
|
+
|
|
295
|
+
renderDetectionFlow();
|
|
296
|
+
|
|
297
|
+
const detectButton = screen.getByRole('button', { name: /✨ Detect/ });
|
|
298
|
+
await user.click(detectButton);
|
|
299
|
+
|
|
300
|
+
// Show progress
|
|
301
|
+
act(() => {
|
|
302
|
+
mockStream.emitProgress({
|
|
303
|
+
status: 'analyzing',
|
|
304
|
+
message: 'Analyzing...',
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
await waitFor(() => {
|
|
309
|
+
expect(screen.getByText('Analyzing...')).toBeInTheDocument();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Simulate error
|
|
313
|
+
act(() => {
|
|
314
|
+
mockStream.emitError(new Error('Network timeout'));
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Progress should be cleared
|
|
318
|
+
await waitFor(() => {
|
|
319
|
+
expect(screen.queryByText('Analyzing...')).not.toBeInTheDocument();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|