@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.
Files changed (213) hide show
  1. package/dist/EventBusContext-7GvDyO0d.d.mts +414 -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-ZR4ZV2LY.mjs} +206 -146
  11. package/dist/chunk-ZR4ZV2LY.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 +645 -471
  38. package/dist/index.mjs +3461 -3025
  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/LiveRegion.tsx +18 -18
  77. package/src/components/ResizeHandle.tsx +10 -4
  78. package/src/components/SessionTimer.tsx +2 -2
  79. package/src/components/Toolbar.tsx +18 -9
  80. package/src/components/__tests__/SessionTimer.test.tsx +9 -9
  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 +38 -10
  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 +119 -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 +231 -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 +504 -0
  149. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +135 -88
  150. package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
  151. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +308 -528
  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,318 @@
1
+ /**
2
+ * Bug Reproduction: Detection Progress Doesn't Dismiss
3
+ *
4
+ * USER SCENARIO:
5
+ * 1. User clicks "Detect References" button
6
+ * 2. Progress modal appears showing "Processing: Location"
7
+ * 3. Detection completes successfully
8
+ * 4. BUG: Progress modal stays visible showing "Processing: Location" indefinitely
9
+ *
10
+ * ROOT CAUSE:
11
+ * - useDetectionFlow.ts (line 54-62): detection:complete clears `detectingMotivation` but keeps `detectionProgress`
12
+ * - DetectSection.tsx (line 214): Shows progress UI whenever `detectionProgress` is not null
13
+ * - No mechanism to auto-dismiss or manually close the progress display
14
+ *
15
+ * FIX OPTIONS:
16
+ * A) Auto-dismiss after timeout (3s after completion)
17
+ * B) Add "Close" button to progress display
18
+ * C) Clear progress on next detection:start (already works but not ideal UX)
19
+ */
20
+
21
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22
+ import { render, screen, waitFor } from '@testing-library/react';
23
+ import { act } from 'react';
24
+ import { useDetectionFlow } from '../../../hooks/useDetectionFlow';
25
+ import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../../contexts/EventBusContext';
26
+ import { ApiClientProvider } from '../../../contexts/ApiClientContext';
27
+ import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
28
+ import { SSEClient, resourceUri } from '@semiont/api-client';
29
+
30
+ describe('Detection Progress Dismissal Bug', () => {
31
+ let mockStream: any;
32
+ const rUri = resourceUri('https://example.com/resources/test');
33
+
34
+ beforeEach(() => {
35
+ resetEventBusForTesting();
36
+ vi.clearAllMocks();
37
+
38
+ mockStream = {
39
+ onProgress: vi.fn().mockReturnThis(),
40
+ onComplete: vi.fn().mockReturnThis(),
41
+ onError: vi.fn().mockReturnThis(),
42
+ close: vi.fn(),
43
+ };
44
+
45
+ vi.spyOn(SSEClient.prototype, 'detectAnnotations').mockReturnValue(mockStream);
46
+ vi.spyOn(SSEClient.prototype, 'detectHighlights').mockReturnValue(mockStream);
47
+ vi.spyOn(SSEClient.prototype, 'detectComments').mockReturnValue(mockStream);
48
+ vi.spyOn(SSEClient.prototype, 'detectAssessments').mockReturnValue(mockStream);
49
+ });
50
+
51
+ afterEach(() => {
52
+ vi.restoreAllMocks();
53
+ });
54
+
55
+ it('FIXED: Progress auto-dismisses after detection completes', { timeout: 7000 }, async () => {
56
+ let eventBusInstance: any;
57
+
58
+ function TestHarness() {
59
+ eventBusInstance = useEventBus();
60
+ const { detectingMotivation, detectionProgress } = useDetectionFlow(rUri);
61
+
62
+ return (
63
+ <div>
64
+ <div data-testid="detecting">{detectingMotivation || 'none'}</div>
65
+ <div data-testid="progress">
66
+ {detectionProgress ? detectionProgress.message || 'has progress' : 'no progress'}
67
+ </div>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ render(
73
+ <EventBusProvider>
74
+ <AuthTokenProvider token={null}>
75
+ <ApiClientProvider baseUrl="http://localhost:4000">
76
+ <TestHarness />
77
+ </ApiClientProvider>
78
+ </AuthTokenProvider>
79
+ </EventBusProvider>
80
+ );
81
+
82
+ // Initial state
83
+ expect(screen.getByTestId('detecting')).toHaveTextContent('none');
84
+ expect(screen.getByTestId('progress')).toHaveTextContent('no progress');
85
+
86
+ // User clicks detect button (emits detection:start)
87
+ act(() => {
88
+ eventBusInstance.emit('detection:start', {
89
+ motivation: 'linking',
90
+ options: { entityTypes: ['Location'] }
91
+ });
92
+ });
93
+
94
+ // Detection started
95
+ await waitFor(() => {
96
+ expect(screen.getByTestId('detecting')).toHaveTextContent('linking');
97
+ });
98
+
99
+ // SSE sends progress update
100
+ act(() => {
101
+ eventBusInstance.emit('detection:progress', {
102
+ status: 'scanning',
103
+ message: 'Processing: Location',
104
+ currentEntityType: 'Location',
105
+ });
106
+ });
107
+
108
+ // Progress is visible
109
+ await waitFor(() => {
110
+ expect(screen.getByTestId('progress')).toHaveTextContent('Processing: Location');
111
+ });
112
+
113
+ // Detection completes (SSE finishes, backend emits detection:complete)
114
+ act(() => {
115
+ eventBusInstance.emit('detection:complete', { motivation: 'linking' });
116
+ });
117
+
118
+ // detectingMotivation cleared immediately
119
+ await waitFor(() => {
120
+ expect(screen.getByTestId('detecting')).toHaveTextContent('none');
121
+ });
122
+
123
+ // Wait 5 seconds - progress auto-dismissed after completion
124
+ await new Promise(resolve => setTimeout(resolve, 5100));
125
+ expect(screen.getByTestId('progress')).toHaveTextContent('no progress');
126
+ });
127
+
128
+ it('WORKAROUND: Starting new detection clears old progress', async () => {
129
+ let eventBusInstance: any;
130
+
131
+ function TestHarness() {
132
+ eventBusInstance = useEventBus();
133
+ const { detectionProgress } = useDetectionFlow(rUri);
134
+
135
+ return (
136
+ <div data-testid="progress">
137
+ {detectionProgress ? detectionProgress.message || 'has progress' : 'no progress'}
138
+ </div>
139
+ );
140
+ }
141
+
142
+ render(
143
+ <EventBusProvider>
144
+ <AuthTokenProvider token={null}>
145
+ <ApiClientProvider baseUrl="http://localhost:4000">
146
+ <TestHarness />
147
+ </ApiClientProvider>
148
+ </AuthTokenProvider>
149
+ </EventBusProvider>
150
+ );
151
+
152
+ // First detection with stuck progress
153
+ act(() => {
154
+ eventBusInstance.emit('detection:start', { motivation: 'linking', options: {} });
155
+ });
156
+
157
+ act(() => {
158
+ eventBusInstance.emit('detection:progress', { message: 'Old progress stuck here' });
159
+ });
160
+
161
+ act(() => {
162
+ eventBusInstance.emit('detection:complete', { motivation: 'linking' });
163
+ });
164
+
165
+ await waitFor(() => {
166
+ expect(screen.getByTestId('progress')).toHaveTextContent('Old progress stuck here');
167
+ });
168
+
169
+ // WORKAROUND: Start new detection clears old progress
170
+ act(() => {
171
+ eventBusInstance.emit('detection:start', { motivation: 'highlighting', options: {} });
172
+ });
173
+
174
+ await waitFor(() => {
175
+ expect(screen.getByTestId('progress')).toHaveTextContent('no progress');
176
+ });
177
+ });
178
+
179
+ it('NEW FEATURE: Progress should auto-dismiss after 5 seconds', { timeout: 7000 }, async () => {
180
+ // This test verifies the new auto-dismiss feature
181
+
182
+ let eventBusInstance: any;
183
+
184
+ function TestHarness() {
185
+ eventBusInstance = useEventBus();
186
+ const { detectionProgress } = useDetectionFlow(rUri);
187
+
188
+ return (
189
+ <div data-testid="progress">
190
+ {detectionProgress ? 'visible' : 'dismissed'}
191
+ </div>
192
+ );
193
+ }
194
+
195
+ render(
196
+ <EventBusProvider>
197
+ <AuthTokenProvider token={null}>
198
+ <ApiClientProvider baseUrl="http://localhost:4000">
199
+ <TestHarness />
200
+ </ApiClientProvider>
201
+ </AuthTokenProvider>
202
+ </EventBusProvider>
203
+ );
204
+
205
+ // Show progress
206
+ act(() => {
207
+ eventBusInstance.emit('detection:start', { motivation: 'linking', options: {} });
208
+ });
209
+
210
+ act(() => {
211
+ eventBusInstance.emit('detection:progress', {
212
+ status: 'complete',
213
+ message: 'Complete! Created 5 annotations'
214
+ });
215
+ });
216
+
217
+ act(() => {
218
+ eventBusInstance.emit('detection:complete', { motivation: 'linking' });
219
+ });
220
+
221
+ // Progress visible initially
222
+ await waitFor(() => {
223
+ expect(screen.getByTestId('progress')).toHaveTextContent('visible');
224
+ });
225
+
226
+ // Auto-dismiss after 5 seconds
227
+ await new Promise(resolve => setTimeout(resolve, 5100));
228
+
229
+ await waitFor(() => {
230
+ expect(screen.getByTestId('progress')).toHaveTextContent('dismissed');
231
+ });
232
+ });
233
+
234
+ it('FIXED: useEventOperations now forwards final completion chunk data', async () => {
235
+ /**
236
+ * This test verifies the fix for the useEventOperations bug.
237
+ *
238
+ * FIX: useEventOperations.ts stream.onComplete(finalChunk) now emits detection:progress
239
+ * with the final chunk data BEFORE emitting detection:complete.
240
+ *
241
+ * This ensures the UI can display the final completion message with status:'complete'.
242
+ */
243
+
244
+ let eventBusInstance: any;
245
+
246
+ function TestHarness() {
247
+ eventBusInstance = useEventBus();
248
+ const { detectionProgress } = useDetectionFlow(rUri);
249
+
250
+ return (
251
+ <div>
252
+ <div data-testid="progress-status">{detectionProgress?.status || 'none'}</div>
253
+ <div data-testid="progress-message">{detectionProgress?.message || 'no message'}</div>
254
+ </div>
255
+ );
256
+ }
257
+
258
+ // Mock SSE stream to simulate backend behavior
259
+ let onProgressCallback: any;
260
+ let onCompleteCallback: any;
261
+
262
+ mockStream.onProgress.mockImplementation((cb: any) => {
263
+ onProgressCallback = cb;
264
+ return mockStream;
265
+ });
266
+
267
+ mockStream.onComplete.mockImplementation((cb: any) => {
268
+ onCompleteCallback = cb;
269
+ return mockStream;
270
+ });
271
+
272
+ render(
273
+ <EventBusProvider>
274
+ <AuthTokenProvider token={null}>
275
+ <ApiClientProvider baseUrl="http://localhost:4000">
276
+ <TestHarness />
277
+ </ApiClientProvider>
278
+ </AuthTokenProvider>
279
+ </EventBusProvider>
280
+ );
281
+
282
+ // Start detection (triggers SSE stream creation)
283
+ act(() => {
284
+ eventBusInstance.emit('detection:start', {
285
+ motivation: 'linking',
286
+ options: { entityTypes: ['Location'] }
287
+ });
288
+ });
289
+
290
+ // Simulate SSE scanning chunk via stream.onProgress()
291
+ act(() => {
292
+ onProgressCallback?.({
293
+ status: 'scanning',
294
+ message: 'Processing: Location',
295
+ });
296
+ });
297
+
298
+ await waitFor(() => {
299
+ expect(screen.getByTestId('progress-status')).toHaveTextContent('scanning');
300
+ });
301
+
302
+ // Simulate backend sending final chunk to stream.onComplete(finalChunk)
303
+ // useEventOperations should forward this as detection:progress
304
+ act(() => {
305
+ onCompleteCallback?.({
306
+ status: 'complete',
307
+ message: 'Complete! Found 5 entities',
308
+ foundCount: 5,
309
+ });
310
+ });
311
+
312
+ // Verify final chunk is now visible
313
+ await waitFor(() => {
314
+ expect(screen.getByTestId('progress-status')).toHaveTextContent('complete');
315
+ expect(screen.getByTestId('progress-message')).toHaveTextContent('Complete! Found 5 entities');
316
+ });
317
+ });
318
+ });