@semiont/react-ui 0.2.33-build.80 → 0.2.33-build.82
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 → EventBusContext-CJjL_cCf.d.mts} +67 -19
- package/dist/{chunk-ZR4ZV2LY.mjs → chunk-QB52Q7EQ.mjs} +7 -7
- package/dist/chunk-QB52Q7EQ.mjs.map +1 -0
- package/dist/index.d.mts +194 -208
- package/dist/index.mjs +1542 -1637
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +2 -2
- package/dist/test-utils.mjs +1 -1
- package/package.json +1 -1
- package/src/components/LiveRegion.tsx +18 -18
- package/src/components/SessionExpiryBanner.tsx +2 -3
- package/src/components/SessionTimer.tsx +2 -2
- package/src/components/__tests__/SessionTimer.test.tsx +27 -27
- package/src/components/resource/panels/AssessmentPanel.tsx +2 -2
- package/src/components/resource/panels/DetectSection.tsx +13 -7
- package/src/components/resource/panels/ReferenceEntry.tsx +1 -1
- package/src/components/resource/panels/TaggingPanel.tsx +2 -3
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +16 -13
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +13 -13
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +5 -5
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +7 -12
- package/src/features/resource-viewer/__tests__/ResolutionFlowIntegration.test.tsx +266 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +11 -12
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +3 -3
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +132 -93
- package/dist/chunk-ZR4ZV2LY.mjs.map +0 -1
|
@@ -42,7 +42,7 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
42
42
|
close: vi.fn(),
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
vi.spyOn(SSEClient.prototype, '
|
|
45
|
+
vi.spyOn(SSEClient.prototype, 'detectReferences').mockReturnValue(mockStream);
|
|
46
46
|
vi.spyOn(SSEClient.prototype, 'detectHighlights').mockReturnValue(mockStream);
|
|
47
47
|
vi.spyOn(SSEClient.prototype, 'detectComments').mockReturnValue(mockStream);
|
|
48
48
|
vi.spyOn(SSEClient.prototype, 'detectAssessments').mockReturnValue(mockStream);
|
|
@@ -231,11 +231,11 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
231
231
|
});
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
it('FIXED:
|
|
234
|
+
it('FIXED: useResolutionFlow now forwards final completion chunk data', async () => {
|
|
235
235
|
/**
|
|
236
|
-
* This test verifies the fix for the
|
|
236
|
+
* This test verifies the fix for the useResolutionFlow bug.
|
|
237
237
|
*
|
|
238
|
-
* FIX:
|
|
238
|
+
* FIX: useResolutionFlow.ts stream.onComplete(finalChunk) now emits detection:progress
|
|
239
239
|
* with the final chunk data BEFORE emitting detection:complete.
|
|
240
240
|
*
|
|
241
241
|
* This ensures the UI can display the final completion message with status:'complete'.
|
|
@@ -300,7 +300,7 @@ describe('Detection Progress Dismissal Bug', () => {
|
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
// Simulate backend sending final chunk to stream.onComplete(finalChunk)
|
|
303
|
-
//
|
|
303
|
+
// useResolutionFlow should forward this as detection:progress
|
|
304
304
|
act(() => {
|
|
305
305
|
onCompleteCallback?.({
|
|
306
306
|
status: 'complete',
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* Tests the COMPLETE generation flow with real component composition:
|
|
5
5
|
* - EventBusProvider (REAL)
|
|
6
6
|
* - ApiClientProvider (REAL, with MOCKED client)
|
|
7
|
-
* - useGenerationFlow (REAL)
|
|
8
|
-
* -
|
|
9
|
-
* - useEventOperations (REAL)
|
|
7
|
+
* - useGenerationFlow (REAL, with inlined progress state)
|
|
8
|
+
* - useResolutionFlow (REAL)
|
|
10
9
|
* - useEventSubscriptions (REAL)
|
|
11
10
|
*
|
|
12
11
|
* This test focuses on ARCHITECTURE and EVENT WIRING:
|
|
@@ -24,11 +23,11 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|
|
24
23
|
import { act } from 'react';
|
|
25
24
|
import { useGenerationFlow } from '../../../hooks/useGenerationFlow';
|
|
26
25
|
import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
27
|
-
import { ApiClientProvider
|
|
26
|
+
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
28
27
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
29
|
-
import {
|
|
28
|
+
import { useResolutionFlow } from '../../../hooks/useResolutionFlow';
|
|
30
29
|
import { SSEClient } from '@semiont/api-client';
|
|
31
|
-
import type {
|
|
30
|
+
import type { ResourceUri, AnnotationUri } from '@semiont/api-client';
|
|
32
31
|
import { resourceUri, annotationUri } from '@semiont/api-client';
|
|
33
32
|
import type { Emitter } from 'mitt';
|
|
34
33
|
import type { EventMap } from '../../../contexts/EventBusContext';
|
|
@@ -416,13 +415,9 @@ function renderGenerationFlow(
|
|
|
416
415
|
// Component to capture EventBus instance and set up event operations
|
|
417
416
|
function EventBusCapture() {
|
|
418
417
|
eventBusInstance = useEventBus();
|
|
419
|
-
const client = useApiClient();
|
|
420
418
|
|
|
421
|
-
// Set up
|
|
422
|
-
|
|
423
|
-
client: client as SemiontApiClient,
|
|
424
|
-
resourceUri: testResourceUri,
|
|
425
|
-
});
|
|
419
|
+
// Set up resolution flow (annotation:update-body, reference:link)
|
|
420
|
+
useResolutionFlow(testResourceUri);
|
|
426
421
|
|
|
427
422
|
return null;
|
|
428
423
|
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3: Feature Integration Test - Resolution Flow (search modal & body update)
|
|
3
|
+
*
|
|
4
|
+
* Tests the UNCOVERED half of useResolutionFlow:
|
|
5
|
+
* - reference:link → emits resolution:search-requested
|
|
6
|
+
* - resolution:search-requested → opens search modal with pendingReferenceId
|
|
7
|
+
* - onCloseSearchModal → closes modal
|
|
8
|
+
* - annotation:update-body → calls updateAnnotationBody API
|
|
9
|
+
* - annotation:update-body → emits annotation:body-updated on success
|
|
10
|
+
* - annotation:update-body → emits annotation:body-update-failed on error
|
|
11
|
+
* - auth token passed to updateAnnotationBody
|
|
12
|
+
*
|
|
13
|
+
* The deletion half of useResolutionFlow is covered by AnnotationDeletionIntegration.test.tsx.
|
|
14
|
+
*
|
|
15
|
+
* Uses real providers (EventBus, ApiClient, AuthToken) with mocked API boundary.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
19
|
+
import { render, waitFor } from '@testing-library/react';
|
|
20
|
+
import { act } from 'react';
|
|
21
|
+
import { useResolutionFlow } from '../../../hooks/useResolutionFlow';
|
|
22
|
+
import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
23
|
+
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
24
|
+
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
25
|
+
import { SemiontApiClient, resourceUri, accessToken } from '@semiont/api-client';
|
|
26
|
+
|
|
27
|
+
describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
28
|
+
let updateAnnotationBodySpy: ReturnType<typeof vi.fn>;
|
|
29
|
+
const testUri = resourceUri('http://localhost:4000/resources/test-resource');
|
|
30
|
+
const testToken = 'test-resolution-token';
|
|
31
|
+
const testBaseUrl = 'http://localhost:4000';
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
resetEventBusForTesting();
|
|
36
|
+
|
|
37
|
+
updateAnnotationBodySpy = vi.fn().mockResolvedValue({ success: true });
|
|
38
|
+
vi.spyOn(SemiontApiClient.prototype, 'updateAnnotationBody').mockImplementation(updateAnnotationBodySpy);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.restoreAllMocks();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ─── Render helper ──────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function renderResolutionFlow() {
|
|
48
|
+
let eventBusInstance: ReturnType<typeof useEventBus> | null = null;
|
|
49
|
+
let lastState: ReturnType<typeof useResolutionFlow> | null = null;
|
|
50
|
+
|
|
51
|
+
function TestComponent() {
|
|
52
|
+
eventBusInstance = useEventBus();
|
|
53
|
+
lastState = useResolutionFlow(testUri);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
<AuthTokenProvider token={testToken}>
|
|
59
|
+
<EventBusProvider>
|
|
60
|
+
<ApiClientProvider baseUrl={testBaseUrl}>
|
|
61
|
+
<TestComponent />
|
|
62
|
+
</ApiClientProvider>
|
|
63
|
+
</EventBusProvider>
|
|
64
|
+
</AuthTokenProvider>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
getState: () => lastState!,
|
|
69
|
+
emit: (event: Parameters<typeof eventBusInstance.emit>[0], payload: Parameters<typeof eventBusInstance.emit>[1]) => {
|
|
70
|
+
act(() => { eventBusInstance!.emit(event as any, payload as any); });
|
|
71
|
+
},
|
|
72
|
+
on: (event: Parameters<typeof eventBusInstance.on>[0], handler: (payload: any) => void) => {
|
|
73
|
+
eventBusInstance!.on(event as any, handler);
|
|
74
|
+
},
|
|
75
|
+
off: (event: Parameters<typeof eventBusInstance.off>[0], handler: (payload: any) => void) => {
|
|
76
|
+
eventBusInstance!.off(event as any, handler);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Initial state ──────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
it('starts with search modal closed and no pending reference', () => {
|
|
84
|
+
const { getState } = renderResolutionFlow();
|
|
85
|
+
expect(getState().searchModalOpen).toBe(false);
|
|
86
|
+
expect(getState().pendingReferenceId).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ─── reference:link ─────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
it('reference:link emits resolution:search-requested with referenceId and searchTerm', () => {
|
|
92
|
+
const { emit, on, off } = renderResolutionFlow();
|
|
93
|
+
const searchRequestedSpy = vi.fn();
|
|
94
|
+
|
|
95
|
+
on('resolution:search-requested', searchRequestedSpy);
|
|
96
|
+
emit('reference:link', { annotationUri: 'ann-uri-123', searchTerm: 'climate change' });
|
|
97
|
+
off('resolution:search-requested', searchRequestedSpy);
|
|
98
|
+
|
|
99
|
+
expect(searchRequestedSpy).toHaveBeenCalledTimes(1);
|
|
100
|
+
expect(searchRequestedSpy).toHaveBeenCalledWith({
|
|
101
|
+
referenceId: 'ann-uri-123',
|
|
102
|
+
searchTerm: 'climate change',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ─── resolution:search-requested ────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
it('resolution:search-requested opens the search modal', async () => {
|
|
109
|
+
const { getState, emit } = renderResolutionFlow();
|
|
110
|
+
|
|
111
|
+
expect(getState().searchModalOpen).toBe(false);
|
|
112
|
+
|
|
113
|
+
emit('resolution:search-requested', { referenceId: 'ref-abc', searchTerm: 'oceans' });
|
|
114
|
+
|
|
115
|
+
await waitFor(() => {
|
|
116
|
+
expect(getState().searchModalOpen).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('resolution:search-requested sets pendingReferenceId', async () => {
|
|
121
|
+
const { getState, emit } = renderResolutionFlow();
|
|
122
|
+
|
|
123
|
+
emit('resolution:search-requested', { referenceId: 'ref-xyz', searchTerm: 'forests' });
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(getState().pendingReferenceId).toBe('ref-xyz');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('reference:link → resolution:search-requested chain opens modal end-to-end', async () => {
|
|
131
|
+
const { getState, emit } = renderResolutionFlow();
|
|
132
|
+
|
|
133
|
+
// Simulate the full user journey: user clicks "Link Document" on a reference entry
|
|
134
|
+
emit('reference:link', { annotationUri: 'ann-full-chain', searchTerm: 'biodiversity' });
|
|
135
|
+
|
|
136
|
+
await waitFor(() => {
|
|
137
|
+
expect(getState().searchModalOpen).toBe(true);
|
|
138
|
+
expect(getState().pendingReferenceId).toBe('ann-full-chain');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ─── onCloseSearchModal ──────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
it('onCloseSearchModal closes the search modal', async () => {
|
|
145
|
+
const { getState, emit } = renderResolutionFlow();
|
|
146
|
+
|
|
147
|
+
emit('resolution:search-requested', { referenceId: 'ref-close', searchTerm: 'test' });
|
|
148
|
+
|
|
149
|
+
await waitFor(() => expect(getState().searchModalOpen).toBe(true));
|
|
150
|
+
|
|
151
|
+
act(() => { getState().onCloseSearchModal(); });
|
|
152
|
+
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
expect(getState().searchModalOpen).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('onCloseSearchModal does not clear pendingReferenceId (preserves for re-open)', async () => {
|
|
159
|
+
const { getState, emit } = renderResolutionFlow();
|
|
160
|
+
|
|
161
|
+
emit('resolution:search-requested', { referenceId: 'ref-persist', searchTerm: 'test' });
|
|
162
|
+
await waitFor(() => expect(getState().searchModalOpen).toBe(true));
|
|
163
|
+
|
|
164
|
+
act(() => { getState().onCloseSearchModal(); });
|
|
165
|
+
await waitFor(() => expect(getState().searchModalOpen).toBe(false));
|
|
166
|
+
|
|
167
|
+
// pendingReferenceId remains — modal may reopen
|
|
168
|
+
expect(getState().pendingReferenceId).toBe('ref-persist');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ─── annotation:update-body ──────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
it('annotation:update-body calls updateAnnotationBody API', async () => {
|
|
174
|
+
const { emit } = renderResolutionFlow();
|
|
175
|
+
|
|
176
|
+
emit('annotation:update-body', {
|
|
177
|
+
annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-body-1',
|
|
178
|
+
resourceId: 'linked-resource-id',
|
|
179
|
+
operations: [{ op: 'add', item: { id: 'linked-resource-id' } }],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await waitFor(() => {
|
|
183
|
+
expect(updateAnnotationBodySpy).toHaveBeenCalledTimes(1);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('annotation:update-body passes auth token to API call', async () => {
|
|
188
|
+
const { emit } = renderResolutionFlow();
|
|
189
|
+
|
|
190
|
+
emit('annotation:update-body', {
|
|
191
|
+
annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-auth',
|
|
192
|
+
resourceId: 'resource-id',
|
|
193
|
+
operations: [{ op: 'replace', newItem: { id: 'resource-id' } }],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(updateAnnotationBodySpy).toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const callArgs = updateAnnotationBodySpy.mock.calls[0];
|
|
201
|
+
expect(callArgs[2]).toHaveProperty('auth');
|
|
202
|
+
expect(callArgs[2].auth).toBe(accessToken(testToken));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('annotation:update-body emits annotation:body-updated on success', async () => {
|
|
206
|
+
const { emit, on, off } = renderResolutionFlow();
|
|
207
|
+
const bodyUpdatedSpy = vi.fn();
|
|
208
|
+
|
|
209
|
+
on('annotation:body-updated', bodyUpdatedSpy);
|
|
210
|
+
|
|
211
|
+
emit('annotation:update-body', {
|
|
212
|
+
annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-success',
|
|
213
|
+
resourceId: 'resource-id',
|
|
214
|
+
operations: [{ op: 'add', item: { id: 'resource-id' } }],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await waitFor(() => {
|
|
218
|
+
expect(bodyUpdatedSpy).toHaveBeenCalledTimes(1);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
off('annotation:body-updated', bodyUpdatedSpy);
|
|
222
|
+
|
|
223
|
+
expect(bodyUpdatedSpy).toHaveBeenCalledWith({
|
|
224
|
+
annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-success',
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('annotation:update-body emits annotation:body-update-failed on API error', async () => {
|
|
229
|
+
updateAnnotationBodySpy.mockRejectedValue(new Error('Update failed'));
|
|
230
|
+
|
|
231
|
+
const { emit, on, off } = renderResolutionFlow();
|
|
232
|
+
const bodyUpdateFailedSpy = vi.fn();
|
|
233
|
+
|
|
234
|
+
on('annotation:body-update-failed', bodyUpdateFailedSpy);
|
|
235
|
+
|
|
236
|
+
emit('annotation:update-body', {
|
|
237
|
+
annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-fail',
|
|
238
|
+
resourceId: 'resource-id',
|
|
239
|
+
operations: [{ op: 'remove', item: { id: 'old-id' } }],
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(bodyUpdateFailedSpy).toHaveBeenCalledTimes(1);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
off('annotation:body-update-failed', bodyUpdateFailedSpy);
|
|
247
|
+
|
|
248
|
+
expect(bodyUpdateFailedSpy).toHaveBeenCalledWith({
|
|
249
|
+
error: expect.any(Error),
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('annotation:update-body called ONCE — no duplicate subscriptions', async () => {
|
|
254
|
+
const { emit } = renderResolutionFlow();
|
|
255
|
+
|
|
256
|
+
emit('annotation:update-body', {
|
|
257
|
+
annotationUri: 'http://localhost:4000/resources/test-resource/annotations/ann-dedup',
|
|
258
|
+
resourceId: 'resource-id',
|
|
259
|
+
operations: [{ op: 'add', item: { id: 'resource-id' } }],
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await waitFor(() => {
|
|
263
|
+
expect(updateAnnotationBodySpy).toHaveBeenCalledTimes(1);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -15,6 +15,7 @@ import { EventBusProvider, resetEventBusForTesting } from '../../../contexts/Eve
|
|
|
15
15
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
16
16
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
17
17
|
import { ToastProvider } from '../../../components/Toast';
|
|
18
|
+
import { ThemeProvider } from '../../../contexts/ThemeContext';
|
|
18
19
|
|
|
19
20
|
// jsdom doesn't implement window.matchMedia — mock it for useTheme
|
|
20
21
|
Object.defineProperty(window, 'matchMedia', {
|
|
@@ -76,11 +77,7 @@ vi.mock('@semiont/react-ui', async () => {
|
|
|
76
77
|
JsonLdPanel: () => <div data-testid="jsonld-panel">JSON-LD</div>,
|
|
77
78
|
ErrorBoundary: ({ children }: any) => children,
|
|
78
79
|
createCancelDetectionHandler: () => vi.fn(),
|
|
79
|
-
|
|
80
|
-
progress: null,
|
|
81
|
-
clearProgress: vi.fn(),
|
|
82
|
-
}),
|
|
83
|
-
useDebouncedCallback: (fn: any) => fn,
|
|
80
|
+
useDebouncedCallback: (fn: any) => fn,
|
|
84
81
|
supportsDetection: () => false,
|
|
85
82
|
MakeMeaningEventBusProvider: ({ children }: any) => children,
|
|
86
83
|
useResourceLoadingAnnouncements: () => ({
|
|
@@ -165,13 +162,15 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
|
|
|
165
162
|
// Test wrapper to provide all required providers
|
|
166
163
|
const renderWithProviders = (ui: React.ReactElement) => {
|
|
167
164
|
return render(
|
|
168
|
-
<
|
|
169
|
-
<
|
|
170
|
-
<
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
<ThemeProvider>
|
|
166
|
+
<ToastProvider>
|
|
167
|
+
<AuthTokenProvider token={null}>
|
|
168
|
+
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
169
|
+
<EventBusProvider>{ui}</EventBusProvider>
|
|
170
|
+
</ApiClientProvider>
|
|
171
|
+
</AuthTokenProvider>
|
|
172
|
+
</ToastProvider>
|
|
173
|
+
</ThemeProvider>
|
|
175
174
|
);
|
|
176
175
|
};
|
|
177
176
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Layer 3 Integration Test: Detection Progress Flow UI/UX
|
|
3
3
|
*
|
|
4
|
-
* Tests the complete data flow from UI → EventBus →
|
|
4
|
+
* Tests the complete data flow from UI → EventBus → useResolutionFlow → SSE (mocked)
|
|
5
5
|
*
|
|
6
6
|
* This test uses COMPOSITION instead of mocking:
|
|
7
7
|
* - Real React components composed together (useDetectionFlow + HighlightPanel + DetectSection)
|
|
8
8
|
* - Real EventBus (mitt) passed via context
|
|
9
|
-
* - Real
|
|
9
|
+
* - Real useResolutionFlow hook with mock API client passed as prop
|
|
10
10
|
* - Mock SSE stream (simulated API responses) provided via composition
|
|
11
11
|
*
|
|
12
12
|
* This test focuses on USER EXPERIENCE:
|
|
@@ -145,7 +145,7 @@ describe('Detection Progress Flow Integration (Layer 3)', () => {
|
|
|
145
145
|
vi.spyOn(SSEClient.prototype, 'detectHighlights').mockReturnValue(mockStream as any);
|
|
146
146
|
vi.spyOn(SSEClient.prototype, 'detectAssessments').mockReturnValue(mockStream as any);
|
|
147
147
|
vi.spyOn(SSEClient.prototype, 'detectComments').mockReturnValue(mockStream as any);
|
|
148
|
-
vi.spyOn(SSEClient.prototype, '
|
|
148
|
+
vi.spyOn(SSEClient.prototype, 'detectReferences').mockReturnValue(mockStream as any);
|
|
149
149
|
|
|
150
150
|
mockAnnotations = [];
|
|
151
151
|
});
|