@semiont/react-ui 0.4.20 → 0.4.22
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/README.md +8 -5
- package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-5QESNO5H.mjs} +13 -16
- package/dist/PdfAnnotationCanvas.client-5QESNO5H.mjs.map +1 -0
- package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
- package/dist/chunk-4NOUO3W6.mjs +7788 -0
- package/dist/chunk-4NOUO3W6.mjs.map +1 -0
- package/dist/index.d.mts +212 -1206
- package/dist/index.mjs +3332 -13712
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +48 -21
- package/dist/test-utils.mjs +2505 -87
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
- package/src/components/CodeMirrorRenderer.tsx +12 -12
- package/src/components/LiveRegion.tsx +1 -2
- package/src/components/StatusDisplay.tsx +42 -16
- package/src/components/Toolbar.tsx +4 -4
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
- package/src/components/__tests__/StatusDisplay.test.tsx +50 -65
- package/src/components/__tests__/Toolbar.test.tsx +4 -4
- package/src/components/annotation/AnnotateToolbar.tsx +8 -9
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
- package/src/components/annotation-popups/JsonLdView.tsx +1 -2
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +1 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +15 -18
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +12 -17
- package/src/components/modals/ConfigureGenerationStep.tsx +1 -2
- package/src/components/modals/PermissionDeniedModal.tsx +11 -11
- package/src/components/modals/ReferenceWizardModal.tsx +14 -18
- package/src/components/modals/ResourceSearchModal.tsx +12 -8
- package/src/components/modals/SearchModal.tsx +11 -6
- package/src/components/modals/SearchResultsStep.tsx +1 -3
- package/src/components/modals/SessionExpiredModal.tsx +11 -11
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
- package/src/components/modals/__tests__/SearchModal.accessibility.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.basic.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
- package/src/components/modals/__tests__/SearchModal.visual.test.tsx +6 -2
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
- package/src/components/navigation/ObservableLink.tsx +6 -6
- package/src/components/navigation/SimpleNavigation.tsx +4 -4
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +15 -18
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +1 -2
- package/src/components/resource/AnnotateView.tsx +8 -10
- package/src/components/resource/AnnotationHistory.tsx +9 -12
- package/src/components/resource/BrowseView.tsx +11 -8
- package/src/components/resource/ResourceViewer.tsx +22 -34
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
- package/src/components/resource/__tests__/BrowseView.test.tsx +38 -87
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +41 -31
- package/src/components/resource/__tests__/event-formatting.test.ts +6 -2
- package/src/components/resource/event-formatting.ts +2 -3
- package/src/components/resource/panels/AssessmentEntry.tsx +7 -8
- package/src/components/resource/panels/AssessmentPanel.tsx +21 -17
- package/src/components/resource/panels/AssistSection.tsx +15 -21
- package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
- package/src/components/resource/panels/CommentEntry.tsx +7 -8
- package/src/components/resource/panels/CommentsPanel.tsx +11 -13
- package/src/components/resource/panels/HighlightEntry.tsx +7 -8
- package/src/components/resource/panels/HighlightPanel.tsx +12 -13
- package/src/components/resource/panels/ReferenceEntry.tsx +13 -15
- package/src/components/resource/panels/ReferencesPanel.tsx +17 -19
- package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -7
- package/src/components/resource/panels/StatisticsPanel.tsx +2 -3
- package/src/components/resource/panels/TagEntry.tsx +7 -8
- package/src/components/resource/panels/TaggingPanel.tsx +14 -23
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +4 -3
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +22 -57
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +22 -61
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +7 -8
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +28 -53
- package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +3 -3
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +19 -52
- package/src/components/settings/SettingsPanel.tsx +9 -9
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +15 -15
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -2
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
- package/src/features/admin-exchange/components/ImportCard.tsx +2 -7
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -2
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -2
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -2
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
- package/src/features/resource-compose/components/ResourceComposePage.tsx +6 -22
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
- package/src/features/resource-discovery/components/ResourceCard.tsx +1 -2
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +3 -4
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +37 -45
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +129 -197
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
- package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
- package/dist/chunk-OZICDVH7.mjs +0 -62
- package/dist/chunk-OZICDVH7.mjs.map +0 -1
- package/dist/chunk-R4CCMFJH.mjs +0 -877
- package/dist/chunk-R4CCMFJH.mjs.map +0 -1
- package/dist/chunk-VN5NY4SN.mjs +0 -200
- package/dist/chunk-VN5NY4SN.mjs.map +0 -1
- package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
- package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
|
@@ -4,7 +4,8 @@ import { screen } from '@testing-library/react';
|
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { AnnotationHistory } from '../AnnotationHistory';
|
|
6
6
|
import { renderWithProviders } from '../../../test-utils';
|
|
7
|
-
import type {
|
|
7
|
+
import type { ResourceId } from '@semiont/core';
|
|
8
|
+
import { BehaviorSubject } from 'rxjs';
|
|
8
9
|
|
|
9
10
|
// Mock @semiont/core - must use importOriginal to preserve EventBus etc.
|
|
10
11
|
vi.mock('@semiont/core', async (importOriginal) => {
|
|
@@ -27,16 +28,26 @@ vi.mock('../../../contexts/TranslationContext', () => ({
|
|
|
27
28
|
TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
28
29
|
}));
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
31
|
+
const eventsSubject = new BehaviorSubject<any[] | undefined>(undefined);
|
|
32
|
+
const annotationsSubject = new BehaviorSubject<any[] | undefined>(undefined);
|
|
33
|
+
|
|
34
|
+
const stableMockClient = {
|
|
35
|
+
browse: {
|
|
36
|
+
events: () => eventsSubject.asObservable(),
|
|
37
|
+
annotations: () => annotationsSubject.asObservable(),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const stableMockSession = { client: stableMockClient };
|
|
41
|
+
const stableActiveSession$ = new BehaviorSubject<any>(stableMockSession);
|
|
42
|
+
const stableMockBrowser = { activeSession$: stableActiveSession$ };
|
|
43
|
+
|
|
44
|
+
vi.mock('../../../session/SemiontProvider', async (importOriginal) => {
|
|
45
|
+
const actual = await importOriginal<typeof import('../../../session/SemiontProvider')>();
|
|
46
|
+
return {
|
|
47
|
+
...actual,
|
|
48
|
+
useSemiont: () => stableMockBrowser,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
40
51
|
|
|
41
52
|
// Mock HistoryEvent to avoid deep rendering and mocking all its dependencies
|
|
42
53
|
const MockHistoryEvent = vi.fn(({ event }: any) => (
|
|
@@ -81,11 +92,12 @@ describe('AnnotationHistory', () => {
|
|
|
81
92
|
beforeEach(() => {
|
|
82
93
|
vi.clearAllMocks();
|
|
83
94
|
mockGetAnnotationUri.mockReturnValue(null);
|
|
84
|
-
|
|
95
|
+
eventsSubject.next(undefined);
|
|
96
|
+
annotationsSubject.next([]);
|
|
85
97
|
});
|
|
86
98
|
|
|
87
99
|
it('renders loading state', () => {
|
|
88
|
-
|
|
100
|
+
eventsSubject.next(undefined);
|
|
89
101
|
|
|
90
102
|
renderWithProviders(
|
|
91
103
|
<AnnotationHistory
|
|
@@ -99,22 +111,8 @@ describe('AnnotationHistory', () => {
|
|
|
99
111
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
100
112
|
});
|
|
101
113
|
|
|
102
|
-
it('renders null on error', () => {
|
|
103
|
-
mockEventsUseQuery.mockReturnValue({ data: undefined, isLoading: false, isError: true });
|
|
104
|
-
|
|
105
|
-
const { container } = renderWithProviders(
|
|
106
|
-
<AnnotationHistory
|
|
107
|
-
rUri={testRId}
|
|
108
|
-
Link={MockLink}
|
|
109
|
-
routes={mockRoutes}
|
|
110
|
-
/>
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(container.innerHTML).toBe('');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
114
|
it('renders null when no events', () => {
|
|
117
|
-
|
|
115
|
+
eventsSubject.next([]);
|
|
118
116
|
|
|
119
117
|
const { container } = renderWithProviders(
|
|
120
118
|
<AnnotationHistory
|
|
@@ -129,11 +127,11 @@ describe('AnnotationHistory', () => {
|
|
|
129
127
|
|
|
130
128
|
it('renders events sorted by sequence number', () => {
|
|
131
129
|
const events = [
|
|
132
|
-
makeStoredEvent('
|
|
133
|
-
makeStoredEvent('
|
|
134
|
-
makeStoredEvent('
|
|
130
|
+
makeStoredEvent('e3', 'mark:added', 3),
|
|
131
|
+
makeStoredEvent('e1', 'mark:added', 1),
|
|
132
|
+
makeStoredEvent('e2', 'mark:added', 2),
|
|
135
133
|
];
|
|
136
|
-
|
|
134
|
+
eventsSubject.next(events);
|
|
137
135
|
|
|
138
136
|
renderWithProviders(
|
|
139
137
|
<AnnotationHistory
|
|
@@ -143,195 +141,59 @@ describe('AnnotationHistory', () => {
|
|
|
143
141
|
/>
|
|
144
142
|
);
|
|
145
143
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
expect(
|
|
149
|
-
expect(
|
|
150
|
-
expect(
|
|
151
|
-
|
|
152
|
-
// Verify HistoryEvent was called with events in sequence order
|
|
153
|
-
const calls = MockHistoryEvent.mock.calls;
|
|
154
|
-
expect(calls[0][0].event.id).toBe('evt-1'); // .event is the React prop name
|
|
155
|
-
expect(calls[1][0].event.id).toBe('evt-2');
|
|
156
|
-
expect(calls[2][0].event.id).toBe('evt-3');
|
|
144
|
+
const renderedEvents = screen.getAllByTestId(/^history-event-/);
|
|
145
|
+
expect(renderedEvents).toHaveLength(3);
|
|
146
|
+
expect(renderedEvents[0]).toHaveAttribute('data-testid', 'history-event-e1');
|
|
147
|
+
expect(renderedEvents[1]).toHaveAttribute('data-testid', 'history-event-e2');
|
|
148
|
+
expect(renderedEvents[2]).toHaveAttribute('data-testid', 'history-event-e3');
|
|
157
149
|
});
|
|
158
150
|
|
|
159
151
|
it('filters out job events', () => {
|
|
160
152
|
const events = [
|
|
161
|
-
makeStoredEvent('
|
|
162
|
-
makeStoredEvent('
|
|
163
|
-
makeStoredEvent('
|
|
164
|
-
makeStoredEvent('
|
|
165
|
-
makeStoredEvent('
|
|
166
|
-
];
|
|
167
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
168
|
-
|
|
169
|
-
renderWithProviders(
|
|
170
|
-
<AnnotationHistory
|
|
171
|
-
rUri={testRId}
|
|
172
|
-
Link={MockLink}
|
|
173
|
-
routes={mockRoutes}
|
|
174
|
-
/>
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
// Only non-job events should render
|
|
178
|
-
expect(screen.getByTestId('history-event-evt-1')).toBeInTheDocument();
|
|
179
|
-
expect(screen.getByTestId('history-event-evt-5')).toBeInTheDocument();
|
|
180
|
-
expect(screen.queryByTestId('history-event-evt-2')).not.toBeInTheDocument();
|
|
181
|
-
expect(screen.queryByTestId('history-event-evt-3')).not.toBeInTheDocument();
|
|
182
|
-
expect(screen.queryByTestId('history-event-evt-4')).not.toBeInTheDocument();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('passes isRelated=true when hoveredAnnotationId matches event', () => {
|
|
186
|
-
const annotationUri = 'http://localhost/annotations/ann-1';
|
|
187
|
-
mockGetAnnotationUri.mockReturnValue(annotationUri);
|
|
188
|
-
|
|
189
|
-
const events = [
|
|
190
|
-
makeStoredEvent('evt-1', 'mark:added', 1),
|
|
153
|
+
makeStoredEvent('e1', 'mark:added', 1),
|
|
154
|
+
makeStoredEvent('e2', 'job:started', 2),
|
|
155
|
+
makeStoredEvent('e3', 'job:progress', 3),
|
|
156
|
+
makeStoredEvent('e4', 'job:completed', 4),
|
|
157
|
+
makeStoredEvent('e5', 'mark:body-updated', 5),
|
|
191
158
|
];
|
|
192
|
-
|
|
159
|
+
eventsSubject.next(events);
|
|
193
160
|
|
|
194
161
|
renderWithProviders(
|
|
195
162
|
<AnnotationHistory
|
|
196
163
|
rUri={testRId}
|
|
197
|
-
hoveredAnnotationId={annotationUri}
|
|
198
164
|
Link={MockLink}
|
|
199
165
|
routes={mockRoutes}
|
|
200
166
|
/>
|
|
201
167
|
);
|
|
202
168
|
|
|
203
|
-
const
|
|
204
|
-
expect(
|
|
169
|
+
const renderedEvents = screen.getAllByTestId(/^history-event-/);
|
|
170
|
+
expect(renderedEvents).toHaveLength(2);
|
|
171
|
+
expect(renderedEvents[0]).toHaveAttribute('data-testid', 'history-event-e1');
|
|
172
|
+
expect(renderedEvents[1]).toHaveAttribute('data-testid', 'history-event-e5');
|
|
205
173
|
});
|
|
206
174
|
|
|
207
|
-
it('passes isRelated
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
makeStoredEvent('evt-1', 'mark:added', 1),
|
|
212
|
-
];
|
|
213
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
175
|
+
it('passes isRelated when hovered annotation matches event', () => {
|
|
176
|
+
const events = [makeStoredEvent('e1', 'mark:added', 1)];
|
|
177
|
+
eventsSubject.next(events);
|
|
178
|
+
mockGetAnnotationUri.mockReturnValue('ann-1');
|
|
214
179
|
|
|
215
180
|
renderWithProviders(
|
|
216
181
|
<AnnotationHistory
|
|
217
182
|
rUri={testRId}
|
|
218
|
-
hoveredAnnotationId="
|
|
183
|
+
hoveredAnnotationId="ann-1"
|
|
219
184
|
Link={MockLink}
|
|
220
185
|
routes={mockRoutes}
|
|
221
186
|
/>
|
|
222
187
|
);
|
|
223
188
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('passes isRelated=false when no hoveredAnnotationId', () => {
|
|
229
|
-
const events = [
|
|
230
|
-
makeStoredEvent('evt-1', 'mark:added', 1),
|
|
231
|
-
];
|
|
232
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
233
|
-
|
|
234
|
-
renderWithProviders(
|
|
235
|
-
<AnnotationHistory
|
|
236
|
-
rUri={testRId}
|
|
237
|
-
Link={MockLink}
|
|
238
|
-
routes={mockRoutes}
|
|
239
|
-
/>
|
|
189
|
+
expect(MockHistoryEvent).toHaveBeenCalledWith(
|
|
190
|
+
expect.objectContaining({ isRelated: true })
|
|
240
191
|
);
|
|
241
|
-
|
|
242
|
-
const call = MockHistoryEvent.mock.calls[0][0];
|
|
243
|
-
expect(call.isRelated).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('passes onEventClick and onEventHover to HistoryEvent', () => {
|
|
247
|
-
const onEventClick = vi.fn();
|
|
248
|
-
const onEventHover = vi.fn();
|
|
249
|
-
|
|
250
|
-
const events = [
|
|
251
|
-
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
252
|
-
];
|
|
253
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
254
|
-
|
|
255
|
-
renderWithProviders(
|
|
256
|
-
<AnnotationHistory
|
|
257
|
-
rUri={testRId}
|
|
258
|
-
onEventClick={onEventClick}
|
|
259
|
-
onEventHover={onEventHover}
|
|
260
|
-
Link={MockLink}
|
|
261
|
-
routes={mockRoutes}
|
|
262
|
-
/>
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
const call = MockHistoryEvent.mock.calls[0][0];
|
|
266
|
-
expect(call.onEventClick).toBe(onEventClick);
|
|
267
|
-
expect(call.onEventHover).toBe(onEventHover);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('does not pass onEventClick/onEventHover when not provided', () => {
|
|
271
|
-
const events = [
|
|
272
|
-
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
273
|
-
];
|
|
274
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
275
|
-
|
|
276
|
-
renderWithProviders(
|
|
277
|
-
<AnnotationHistory
|
|
278
|
-
rUri={testRId}
|
|
279
|
-
Link={MockLink}
|
|
280
|
-
routes={mockRoutes}
|
|
281
|
-
/>
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
const call = MockHistoryEvent.mock.calls[0][0];
|
|
285
|
-
expect(call.onEventClick).toBeUndefined();
|
|
286
|
-
expect(call.onEventHover).toBeUndefined();
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it('passes annotations from useQuery to HistoryEvent', () => {
|
|
290
|
-
const mockAnnotations = [{ id: 'ann-1', body: [] }];
|
|
291
|
-
mockAnnotationsUseQuery.mockReturnValue({ data: { annotations: mockAnnotations } });
|
|
292
|
-
|
|
293
|
-
const events = [
|
|
294
|
-
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
295
|
-
];
|
|
296
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
297
|
-
|
|
298
|
-
renderWithProviders(
|
|
299
|
-
<AnnotationHistory
|
|
300
|
-
rUri={testRId}
|
|
301
|
-
Link={MockLink}
|
|
302
|
-
routes={mockRoutes}
|
|
303
|
-
/>
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const call = MockHistoryEvent.mock.calls[0][0];
|
|
307
|
-
expect(call.annotations).toEqual(mockAnnotations);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('defaults annotations to empty array when no data', () => {
|
|
311
|
-
mockAnnotationsUseQuery.mockReturnValue({ data: undefined });
|
|
312
|
-
|
|
313
|
-
const events = [
|
|
314
|
-
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
315
|
-
];
|
|
316
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
317
|
-
|
|
318
|
-
renderWithProviders(
|
|
319
|
-
<AnnotationHistory
|
|
320
|
-
rUri={testRId}
|
|
321
|
-
Link={MockLink}
|
|
322
|
-
routes={mockRoutes}
|
|
323
|
-
/>
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
const call = MockHistoryEvent.mock.calls[0][0];
|
|
327
|
-
expect(call.annotations).toEqual([]);
|
|
328
192
|
});
|
|
329
193
|
|
|
330
194
|
it('renders history panel structure with title and list', () => {
|
|
331
|
-
const events = [
|
|
332
|
-
|
|
333
|
-
];
|
|
334
|
-
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
195
|
+
const events = [makeStoredEvent('e1', 'mark:added', 1)];
|
|
196
|
+
eventsSubject.next(events);
|
|
335
197
|
|
|
336
198
|
const { container } = renderWithProviders(
|
|
337
199
|
<AnnotationHistory
|
|
@@ -2,10 +2,10 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
4
4
|
import { BrowseView } from '../BrowseView';
|
|
5
|
-
import type { components } from '@semiont/core';
|
|
6
|
-
import {
|
|
5
|
+
import type { components, EventBus } from '@semiont/core';
|
|
6
|
+
import { createTestSemiontWrapper } from '../../../test-utils';
|
|
7
7
|
|
|
8
|
-
type Annotation
|
|
8
|
+
import type { Annotation } from '@semiont/core';
|
|
9
9
|
|
|
10
10
|
// Mock ResourceAnnotationsContext - keep this simple
|
|
11
11
|
let mockNewAnnotationIds = new Set<string>();
|
|
@@ -15,9 +15,9 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
|
|
|
15
15
|
})),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
// Mock @semiont/
|
|
19
|
-
vi.mock('@semiont/
|
|
20
|
-
const actual = await vi.importActual('@semiont/
|
|
18
|
+
// Mock @semiont/core utilities (these helpers and `resourceId` all live in core).
|
|
19
|
+
vi.mock('@semiont/core', async () => {
|
|
20
|
+
const actual = await vi.importActual('@semiont/core');
|
|
21
21
|
return {
|
|
22
22
|
...actual,
|
|
23
23
|
getMimeCategory: vi.fn((mimeType: string) => {
|
|
@@ -97,68 +97,43 @@ interface TrackedEvent {
|
|
|
97
97
|
function createEventTracker() {
|
|
98
98
|
const events: TrackedEvent[] = [];
|
|
99
99
|
const subscriptions: Set<string> = new Set();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const trackEvent = (eventName: string) => (payload: any) => {
|
|
109
|
-
events.push({ event: eventName, payload });
|
|
110
|
-
};
|
|
111
|
-
|
|
100
|
+
return {
|
|
101
|
+
events,
|
|
102
|
+
subscriptions,
|
|
103
|
+
clear: () => {
|
|
104
|
+
events.length = 0;
|
|
105
|
+
subscriptions.clear();
|
|
106
|
+
},
|
|
107
|
+
_attach(eventBus: EventBus) {
|
|
112
108
|
const annotationEvents = [
|
|
113
109
|
'beckon:hover',
|
|
114
110
|
'browse:click',
|
|
115
111
|
'beckon:focus',
|
|
116
112
|
] as const;
|
|
117
|
-
|
|
118
|
-
annotationEvents.forEach(eventName => {
|
|
113
|
+
annotationEvents.forEach((eventName) => {
|
|
119
114
|
subscriptions.add(eventName);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
eventBus.get(eventName).subscribe((payload: any) => {
|
|
116
|
+
events.push({ event: eventName, payload });
|
|
117
|
+
});
|
|
123
118
|
});
|
|
124
|
-
|
|
125
|
-
return () => {
|
|
126
|
-
handlers.forEach(sub => sub.unsubscribe());
|
|
127
|
-
};
|
|
128
|
-
}, [eventBus]);
|
|
129
|
-
|
|
130
|
-
return <>{children}</>;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
EventTrackingWrapper,
|
|
135
|
-
events,
|
|
136
|
-
subscriptions,
|
|
137
|
-
clear: () => {
|
|
138
|
-
events.length = 0;
|
|
139
|
-
subscriptions.clear();
|
|
140
119
|
},
|
|
141
120
|
};
|
|
142
121
|
}
|
|
143
122
|
|
|
144
|
-
// Helper to render with providers - simple composition, no spy wrappers
|
|
145
123
|
const renderWithProviders = (
|
|
146
124
|
component: React.ReactElement,
|
|
147
125
|
options: { newAnnotationIds?: Set<string> } = {}
|
|
148
126
|
) => {
|
|
149
|
-
// Update the mock if new annotation IDs are provided
|
|
150
127
|
if (options.newAnnotationIds) {
|
|
151
128
|
mockNewAnnotationIds = options.newAnnotationIds;
|
|
152
129
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<
|
|
156
|
-
{component}
|
|
157
|
-
</EventBusProvider>
|
|
130
|
+
const { SemiontWrapper } = createTestSemiontWrapper();
|
|
131
|
+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
132
|
+
<SemiontWrapper>{children}</SemiontWrapper>
|
|
158
133
|
);
|
|
134
|
+
return render(component, { wrapper: Wrapper });
|
|
159
135
|
};
|
|
160
136
|
|
|
161
|
-
// Helper to render with event tracking
|
|
162
137
|
const renderWithEventTracking = (
|
|
163
138
|
component: React.ReactElement,
|
|
164
139
|
tracker: ReturnType<typeof createEventTracker>,
|
|
@@ -167,14 +142,12 @@ const renderWithEventTracking = (
|
|
|
167
142
|
if (options.newAnnotationIds) {
|
|
168
143
|
mockNewAnnotationIds = options.newAnnotationIds;
|
|
169
144
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
{component}
|
|
175
|
-
</tracker.EventTrackingWrapper>
|
|
176
|
-
</EventBusProvider>
|
|
145
|
+
const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
|
|
146
|
+
tracker._attach(eventBus);
|
|
147
|
+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
148
|
+
<SemiontWrapper>{children}</SemiontWrapper>
|
|
177
149
|
);
|
|
150
|
+
return render(component, { wrapper: Wrapper });
|
|
178
151
|
};
|
|
179
152
|
|
|
180
153
|
// Test data fixtures
|
|
@@ -193,7 +166,6 @@ const createMockAnnotation = (motivation: string, id: string): Annotation => ({
|
|
|
193
166
|
end: 10,
|
|
194
167
|
},
|
|
195
168
|
},
|
|
196
|
-
body: [],
|
|
197
169
|
});
|
|
198
170
|
|
|
199
171
|
describe('BrowseView Component', () => {
|
|
@@ -515,35 +487,8 @@ describe('BrowseView Component', () => {
|
|
|
515
487
|
|
|
516
488
|
describe('Performance - Event Listener Efficiency', () => {
|
|
517
489
|
it('should handle many annotations efficiently through event delegation', async () => {
|
|
518
|
-
// Create a composition-based event tracker that subscribes like a real consumer
|
|
519
490
|
const eventTracker: Array<{ event: string; annotationId: string | null }> = [];
|
|
520
491
|
|
|
521
|
-
function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
|
|
522
|
-
const eventBus = useEventBus();
|
|
523
|
-
|
|
524
|
-
React.useEffect(() => {
|
|
525
|
-
// Subscribe to events like a real component would
|
|
526
|
-
const handleHover = (payload: any) => {
|
|
527
|
-
eventTracker.push({ event: 'beckon:hover', annotationId: payload?.annotationId ?? null });
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
const handleClick = (payload: any) => {
|
|
531
|
-
eventTracker.push({ event: 'browse:click', annotationId: payload?.annotationId ?? null });
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const subscription1 = eventBus.get('beckon:hover').subscribe(handleHover);
|
|
535
|
-
const subscription2 = eventBus.get('browse:click').subscribe(handleClick);
|
|
536
|
-
|
|
537
|
-
return () => {
|
|
538
|
-
subscription1.unsubscribe();
|
|
539
|
-
subscription2.unsubscribe();
|
|
540
|
-
};
|
|
541
|
-
}, [eventBus]);
|
|
542
|
-
|
|
543
|
-
return <>{children}</>;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Create many annotations
|
|
547
492
|
const manyAnnotations = {
|
|
548
493
|
highlights: Array.from({ length: 50 }, (_, i) =>
|
|
549
494
|
createMockAnnotation('highlighting', `highlight-${i}`)
|
|
@@ -556,12 +501,18 @@ describe('BrowseView Component', () => {
|
|
|
556
501
|
tags: [],
|
|
557
502
|
};
|
|
558
503
|
|
|
504
|
+
const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
|
|
505
|
+
eventBus.get('beckon:hover').subscribe((payload: any) => {
|
|
506
|
+
eventTracker.push({ event: 'beckon:hover', annotationId: payload?.annotationId ?? null });
|
|
507
|
+
});
|
|
508
|
+
eventBus.get('browse:click').subscribe((payload: any) => {
|
|
509
|
+
eventTracker.push({ event: 'browse:click', annotationId: payload?.annotationId ?? null });
|
|
510
|
+
});
|
|
511
|
+
|
|
559
512
|
const { container } = render(
|
|
560
|
-
<
|
|
561
|
-
<
|
|
562
|
-
|
|
563
|
-
</EventTrackingWrapper>
|
|
564
|
-
</EventBusProvider>
|
|
513
|
+
<SemiontWrapper>
|
|
514
|
+
<BrowseView {...defaultProps} annotations={manyAnnotations} />
|
|
515
|
+
</SemiontWrapper>
|
|
565
516
|
);
|
|
566
517
|
|
|
567
518
|
const browseContainer = container.querySelector('.semiont-browse-view__content');
|
|
@@ -12,14 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
14
14
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
15
|
-
import {
|
|
15
|
+
import { BehaviorSubject } from 'rxjs';
|
|
16
16
|
import { ResourceViewer } from '../ResourceViewer';
|
|
17
|
-
import {
|
|
18
|
-
import { CacheProvider } from '../../../contexts/CacheContext';
|
|
17
|
+
import { createTestSemiontWrapper } from '../../../test-utils';
|
|
19
18
|
import { TranslationProvider } from '../../../contexts/TranslationContext';
|
|
20
19
|
import { ResourceAnnotationsProvider } from '../../../contexts/ResourceAnnotationsContext';
|
|
21
|
-
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
22
|
-
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
23
20
|
import type { components } from '@semiont/core';
|
|
24
21
|
|
|
25
22
|
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
@@ -29,6 +26,39 @@ vi.mock('../../../hooks/useObservableBrowse', () => ({
|
|
|
29
26
|
useObservableExternalNavigation: () => vi.fn(),
|
|
30
27
|
}));
|
|
31
28
|
|
|
29
|
+
// ResourceViewer (and ResourceAnnotationsContext) now resolve the client via
|
|
30
|
+
// useSemiont(). Mock useSemiont to emit a minimal session carrying a stub
|
|
31
|
+
// client with the methods the subject component touches. The session also
|
|
32
|
+
// exposes `on` and `emit` stubs so useEventSubscription(s) don't explode.
|
|
33
|
+
const stubClient = {
|
|
34
|
+
browse: { invalidateAnnotationList: vi.fn() },
|
|
35
|
+
markAnnotation: vi.fn(),
|
|
36
|
+
on: vi.fn(() => () => {}),
|
|
37
|
+
emit: vi.fn(),
|
|
38
|
+
stream: vi.fn(() => ({ subscribe: () => ({ unsubscribe: () => {} }) })),
|
|
39
|
+
};
|
|
40
|
+
const stubSession = {
|
|
41
|
+
client: stubClient,
|
|
42
|
+
subscribe: vi.fn(() => () => {}),
|
|
43
|
+
};
|
|
44
|
+
const stubActiveSession$ = new BehaviorSubject<any>(stubSession);
|
|
45
|
+
const stubBrowser = {
|
|
46
|
+
activeSession$: stubActiveSession$,
|
|
47
|
+
emit: vi.fn(),
|
|
48
|
+
on: vi.fn(() => () => {}),
|
|
49
|
+
stream: vi.fn(() => ({ subscribe: () => ({ unsubscribe: () => {} }) })),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
vi.mock('../../../session/SemiontProvider', async () => {
|
|
53
|
+
const actual = await vi.importActual<typeof import('../../../session/SemiontProvider')>(
|
|
54
|
+
'../../../session/SemiontProvider'
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
...actual,
|
|
58
|
+
useSemiont: () => stubBrowser,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
32
62
|
const mockResource: SemiontResource & { content: string } = {
|
|
33
63
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
34
64
|
'@id': 'test-123',
|
|
@@ -53,19 +83,6 @@ const mockAnnotations = {
|
|
|
53
83
|
tags: [],
|
|
54
84
|
};
|
|
55
85
|
|
|
56
|
-
const mockCacheManager = {
|
|
57
|
-
invalidateAnnotations: vi.fn(),
|
|
58
|
-
invalidate: vi.fn(),
|
|
59
|
-
invalidateEvents: vi.fn(),
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const queryClient = new QueryClient({
|
|
63
|
-
defaultOptions: {
|
|
64
|
-
queries: { retry: false },
|
|
65
|
-
mutations: { retry: false },
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
86
|
const mockTranslationManager = {
|
|
70
87
|
t: (namespace: string, key: string, params?: Record<string, any>) => {
|
|
71
88
|
return `${namespace}.${key}`;
|
|
@@ -73,21 +90,14 @@ const mockTranslationManager = {
|
|
|
73
90
|
};
|
|
74
91
|
|
|
75
92
|
function TestWrapper({ children }: { children: React.ReactNode }) {
|
|
93
|
+
const { SemiontWrapper } = createTestSemiontWrapper();
|
|
76
94
|
return (
|
|
77
95
|
<TranslationProvider translationManager={mockTranslationManager}>
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<CacheProvider cacheManager={mockCacheManager}>
|
|
84
|
-
{children}
|
|
85
|
-
</CacheProvider>
|
|
86
|
-
</ResourceAnnotationsProvider>
|
|
87
|
-
</QueryClientProvider>
|
|
88
|
-
</ApiClientProvider>
|
|
89
|
-
</AuthTokenProvider>
|
|
90
|
-
</EventBusProvider>
|
|
96
|
+
<SemiontWrapper>
|
|
97
|
+
<ResourceAnnotationsProvider>
|
|
98
|
+
{children}
|
|
99
|
+
</ResourceAnnotationsProvider>
|
|
100
|
+
</SemiontWrapper>
|
|
91
101
|
</TranslationProvider>
|
|
92
102
|
);
|
|
93
103
|
}
|
|
@@ -9,10 +9,14 @@ import {
|
|
|
9
9
|
} from '../event-formatting';
|
|
10
10
|
|
|
11
11
|
// Mock api-client functions
|
|
12
|
-
vi.mock('@semiont/
|
|
12
|
+
vi.mock('@semiont/core', async (importOriginal) => {
|
|
13
|
+
const actual = await importOriginal<typeof import('@semiont/core')>();
|
|
14
|
+
return {
|
|
15
|
+
...actual,
|
|
13
16
|
getExactText: vi.fn((selector: any) => selector?.exact ?? null),
|
|
14
17
|
getTargetSelector: vi.fn((target: any) => target?.selector ?? null),
|
|
15
|
-
}
|
|
18
|
+
};
|
|
19
|
+
});
|
|
16
20
|
|
|
17
21
|
const t = vi.fn((key: string, params?: Record<string, string | number>) => {
|
|
18
22
|
if (params) return `${key}(${JSON.stringify(params)})`;
|
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { StoredEventLike, PersistedEventType } from '@semiont/core';
|
|
9
|
-
import
|
|
10
|
-
import { getExactText, getTargetSelector } from '@semiont/api-client';
|
|
9
|
+
import { getExactText, getTargetSelector } from '@semiont/core';
|
|
11
10
|
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
12
11
|
|
|
13
|
-
type Annotation
|
|
12
|
+
import type { Annotation } from '@semiont/core';
|
|
14
13
|
type TranslateFn = (key: string, params?: Record<string, string | number>) => string;
|
|
15
14
|
|
|
16
15
|
// =============================================================================
|