@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/dist/EventBusContext-CJjL_cCf.d.mts +462 -0
  2. package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
  3. package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
  4. package/dist/{ar-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
  5. package/dist/ar-4ZEORRW2.mjs.map +1 -0
  6. package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
  7. package/dist/bn-SEDE5BQJ.mjs.map +1 -0
  8. package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
  9. package/dist/chunk-D7NBW4RV.mjs.map +1 -0
  10. package/dist/{chunk-JZIO2A3B.mjs → chunk-QB52Q7EQ.mjs} +206 -146
  11. package/dist/chunk-QB52Q7EQ.mjs.map +1 -0
  12. package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
  13. package/dist/cs-7W4WF5WD.mjs.map +1 -0
  14. package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
  15. package/dist/da-75XGBCBK.mjs.map +1 -0
  16. package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
  17. package/dist/de-ODJVFLHM.mjs.map +1 -0
  18. package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
  19. package/dist/el-C4PM4WB3.mjs.map +1 -0
  20. package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
  21. package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
  22. package/dist/es-WD33R7QL.mjs.map +1 -0
  23. package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
  24. package/dist/fa-2BP6V56P.mjs.map +1 -0
  25. package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
  26. package/dist/fi-USRRW24J.mjs.map +1 -0
  27. package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
  28. package/dist/fr-EC5S6WVF.mjs.map +1 -0
  29. package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
  30. package/dist/he-7TBVIKAA.mjs.map +1 -0
  31. package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
  32. package/dist/hi-FO4VIZLA.mjs.map +1 -0
  33. package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -4
  34. package/dist/id-7U7GGVWY.mjs.map +1 -0
  35. package/dist/index.css +123 -85
  36. package/dist/index.css.map +1 -1
  37. package/dist/index.d.mts +715 -574
  38. package/dist/index.mjs +3898 -3575
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
  41. package/dist/it-Y4OPL6I2.mjs.map +1 -0
  42. package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
  43. package/dist/ja-PK7SQL55.mjs.map +1 -0
  44. package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
  45. package/dist/ko-L25PXMYD.mjs.map +1 -0
  46. package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
  47. package/dist/ms-STH777QM.mjs.map +1 -0
  48. package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
  49. package/dist/nl-Y7LECDDR.mjs.map +1 -0
  50. package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
  51. package/dist/no-KEKCEWU6.mjs.map +1 -0
  52. package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
  53. package/dist/pl-7A7OC75O.mjs.map +1 -0
  54. package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
  55. package/dist/pt-35HTM7RA.mjs.map +1 -0
  56. package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
  57. package/dist/ro-VAWL5KQA.mjs.map +1 -0
  58. package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -4
  59. package/dist/sv-7ZK5EQEB.mjs.map +1 -0
  60. package/dist/test-utils.d.mts +18 -8
  61. package/dist/test-utils.mjs +36 -14
  62. package/dist/test-utils.mjs.map +1 -1
  63. package/dist/{th-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
  64. package/dist/th-UDWZ4X34.mjs.map +1 -0
  65. package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
  66. package/dist/tr-4WMPK3UX.mjs.map +1 -0
  67. package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
  68. package/dist/uk-SSLASQYJ.mjs.map +1 -0
  69. package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
  70. package/dist/vi-IF42Z5PU.mjs.map +1 -0
  71. package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
  72. package/dist/zh-HRQTNTAI.mjs.map +1 -0
  73. package/package.json +3 -1
  74. package/src/components/CodeMirrorRenderer.tsx +66 -93
  75. package/src/components/DetectionProgressWidget.tsx +16 -5
  76. package/src/components/ResizeHandle.tsx +10 -4
  77. package/src/components/SessionExpiryBanner.tsx +2 -3
  78. package/src/components/SessionTimer.tsx +3 -3
  79. package/src/components/Toolbar.tsx +18 -9
  80. package/src/components/__tests__/SessionTimer.test.tsx +33 -33
  81. package/src/components/annotation/AnnotateToolbar.tsx +17 -15
  82. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
  83. package/src/components/annotation/annotation-entries.css +10 -0
  84. package/src/components/annotation-popups/JsonLdView.tsx +8 -2
  85. package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
  86. package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
  87. package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
  88. package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
  89. package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
  90. package/src/components/modals/ResourceSearchModal.tsx +2 -2
  91. package/src/components/modals/SearchModal.tsx +1 -1
  92. package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
  93. package/src/components/navigation/NavigationTabs.css +36 -24
  94. package/src/components/navigation/ObservableLink.tsx +91 -0
  95. package/src/components/navigation/SimpleNavigation.tsx +20 -16
  96. package/src/components/navigation/SortableResourceTab.tsx +11 -5
  97. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
  98. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
  99. package/src/components/resource/AnnotateView.tsx +64 -134
  100. package/src/components/resource/BrowseView.tsx +86 -166
  101. package/src/components/resource/HistoryEvent.tsx +13 -7
  102. package/src/components/resource/ResourceViewer.tsx +122 -264
  103. package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
  104. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
  105. package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
  106. package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
  107. package/src/components/resource/panels/CommentEntry.tsx +38 -32
  108. package/src/components/resource/panels/CommentsPanel.tsx +121 -28
  109. package/src/components/resource/panels/DetectSection.css +36 -1
  110. package/src/components/resource/panels/DetectSection.tsx +49 -15
  111. package/src/components/resource/panels/HighlightEntry.tsx +25 -33
  112. package/src/components/resource/panels/HighlightPanel.tsx +100 -25
  113. package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
  114. package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
  115. package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
  116. package/src/components/resource/panels/TagEntry.tsx +25 -33
  117. package/src/components/resource/panels/TaggingPanel.tsx +118 -30
  118. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
  119. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
  120. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
  121. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
  122. package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
  123. package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
  124. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
  125. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
  126. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
  127. package/src/components/settings/SettingsPanel.tsx +15 -12
  128. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
  129. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
  130. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
  131. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
  132. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
  133. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
  134. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
  135. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
  136. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
  137. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
  138. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
  139. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
  140. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
  141. package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
  142. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
  143. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
  144. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +234 -0
  145. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
  146. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
  147. package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
  148. package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +503 -0
  149. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +139 -93
  150. package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
  151. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +341 -524
  152. package/translations/ar.json +6 -3
  153. package/translations/bn.json +6 -3
  154. package/translations/cs.json +6 -3
  155. package/translations/da.json +6 -3
  156. package/translations/de.json +6 -3
  157. package/translations/el.json +6 -3
  158. package/translations/en.json +6 -3
  159. package/translations/es.json +6 -3
  160. package/translations/fa.json +6 -3
  161. package/translations/fi.json +6 -3
  162. package/translations/fr.json +6 -3
  163. package/translations/he.json +6 -3
  164. package/translations/hi.json +6 -3
  165. package/translations/id.json +6 -3
  166. package/translations/it.json +6 -3
  167. package/translations/ja.json +6 -3
  168. package/translations/ko.json +6 -3
  169. package/translations/ms.json +6 -3
  170. package/translations/nl.json +6 -3
  171. package/translations/no.json +6 -3
  172. package/translations/pl.json +6 -3
  173. package/translations/pt.json +6 -3
  174. package/translations/ro.json +6 -3
  175. package/translations/sv.json +6 -3
  176. package/translations/th.json +6 -3
  177. package/translations/tr.json +6 -3
  178. package/translations/uk.json +6 -3
  179. package/translations/vi.json +6 -3
  180. package/translations/zh.json +6 -3
  181. package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
  182. package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
  183. package/dist/ar-EMHEHPCJ.mjs.map +0 -1
  184. package/dist/bn-OVCI4F6X.mjs.map +0 -1
  185. package/dist/chunk-JZIO2A3B.mjs.map +0 -1
  186. package/dist/chunk-LIHZTECW.mjs.map +0 -1
  187. package/dist/cs-FAN66Q2F.mjs.map +0 -1
  188. package/dist/da-YBBIHI2O.mjs.map +0 -1
  189. package/dist/de-MAYU33LB.mjs.map +0 -1
  190. package/dist/el-MKGSWN4O.mjs.map +0 -1
  191. package/dist/es-52LHUWJD.mjs.map +0 -1
  192. package/dist/fa-FJICRANB.mjs.map +0 -1
  193. package/dist/fi-O455XFCR.mjs.map +0 -1
  194. package/dist/fr-TXIXHOOE.mjs.map +0 -1
  195. package/dist/he-JBSOX5IN.mjs.map +0 -1
  196. package/dist/hi-KGHI3XVT.mjs.map +0 -1
  197. package/dist/id-5OCPPZLO.mjs.map +0 -1
  198. package/dist/it-PNBBZSM2.mjs.map +0 -1
  199. package/dist/ja-LDD7R3TJ.mjs.map +0 -1
  200. package/dist/ko-F47ZDEY3.mjs.map +0 -1
  201. package/dist/ms-Z7LMXJWL.mjs.map +0 -1
  202. package/dist/nl-6SJFBPJ3.mjs.map +0 -1
  203. package/dist/no-YXPBPSGF.mjs.map +0 -1
  204. package/dist/pl-P4AZ2QME.mjs.map +0 -1
  205. package/dist/pt-LHWUS6U6.mjs.map +0 -1
  206. package/dist/ro-EA5J2ZON.mjs.map +0 -1
  207. package/dist/sv-DATBS3UQ.mjs.map +0 -1
  208. package/dist/th-WTFJRWPT.mjs.map +0 -1
  209. package/dist/tr-IKO3RXOX.mjs.map +0 -1
  210. package/dist/uk-CF6CTTRK.mjs.map +0 -1
  211. package/dist/vi-AJLTXPZQ.mjs.map +0 -1
  212. package/dist/zh-U3ORHHYH.mjs.map +0 -1
  213. /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
@@ -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 → useResolutionFlow → 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 useResolutionFlow 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, 'detectReferences').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
+ });