@semiont/react-ui 0.2.45 → 0.3.0
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/{PdfAnnotationCanvas.client-COQREPXU.mjs → PdfAnnotationCanvas.client-PVTVPDBQ.mjs} +3 -4
- package/dist/PdfAnnotationCanvas.client-PVTVPDBQ.mjs.map +1 -0
- package/dist/{ar-7SUXNE34.mjs → ar-APUOG2AP.mjs} +46 -6
- package/dist/ar-APUOG2AP.mjs.map +1 -0
- package/dist/{bn-XOET3DOI.mjs → bn-EFK2LJGK.mjs} +46 -6
- package/dist/bn-EFK2LJGK.mjs.map +1 -0
- package/dist/{chunk-JH7BXE2P.mjs → chunk-7DW2P4UE.mjs} +45 -5
- package/dist/chunk-7DW2P4UE.mjs.map +1 -0
- package/dist/{chunk-Q2KV6Y2J.mjs → chunk-7GEYABC6.mjs} +32 -32
- package/dist/{chunk-3JTO27MH.mjs → chunk-D4GAAQMM.mjs} +2 -9
- package/dist/{cs-X63DXX7L.mjs → cs-A26MEEQE.mjs} +46 -6
- package/dist/cs-A26MEEQE.mjs.map +1 -0
- package/dist/{da-OWTCV57A.mjs → da-U3L2FHSZ.mjs} +46 -6
- package/dist/da-U3L2FHSZ.mjs.map +1 -0
- package/dist/{de-77BMFDVF.mjs → de-Y5BHEBT7.mjs} +46 -6
- package/dist/de-Y5BHEBT7.mjs.map +1 -0
- package/dist/dist-YLEIY3JJ.mjs +547 -0
- package/dist/dist-YLEIY3JJ.mjs.map +1 -0
- package/dist/{el-FIBNLH2V.mjs → el-HU7LAWQY.mjs} +46 -6
- package/dist/el-HU7LAWQY.mjs.map +1 -0
- package/dist/{en-XWEPVTB4.mjs → en-HAKDCFKL.mjs} +5 -3
- package/dist/{es-726NTS53.mjs → es-4BN64QH5.mjs} +46 -6
- package/dist/es-4BN64QH5.mjs.map +1 -0
- package/dist/{fa-3N4CIWE6.mjs → fa-6ELTBARU.mjs} +46 -6
- package/dist/fa-6ELTBARU.mjs.map +1 -0
- package/dist/{fi-JOM3M7Z4.mjs → fi-DJ4WGIFW.mjs} +46 -6
- package/dist/fi-DJ4WGIFW.mjs.map +1 -0
- package/dist/{fr-56QSXS7E.mjs → fr-23XM6H6H.mjs} +46 -6
- package/dist/fr-23XM6H6H.mjs.map +1 -0
- package/dist/{he-SNAXPJEK.mjs → he-JSWJC2XU.mjs} +46 -6
- package/dist/he-JSWJC2XU.mjs.map +1 -0
- package/dist/{hi-CRBRD5TB.mjs → hi-BENHG3OJ.mjs} +46 -6
- package/dist/hi-BENHG3OJ.mjs.map +1 -0
- package/dist/{id-BRCVLICF.mjs → id-4HHQJQNF.mjs} +46 -6
- package/dist/id-4HHQJQNF.mjs.map +1 -0
- package/dist/index.css +108 -12
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +399 -147
- package/dist/index.mjs +3573 -2226
- package/dist/index.mjs.map +1 -1
- package/dist/{it-M2Z27BNB.mjs → it-U6I5PDKU.mjs} +46 -6
- package/dist/it-U6I5PDKU.mjs.map +1 -0
- package/dist/{ja-TZUKW7HD.mjs → ja-K3YBDWDP.mjs} +46 -6
- package/dist/ja-K3YBDWDP.mjs.map +1 -0
- package/dist/{ko-NKBGGOL6.mjs → ko-KC2HXRXG.mjs} +46 -6
- package/dist/ko-KC2HXRXG.mjs.map +1 -0
- package/dist/{magic-string.es-7FJ3LUGB.mjs → magic-string.es-K77I4ZQN.mjs} +2 -2
- package/dist/{ms-XFXPN6RX.mjs → ms-KY5QGBNN.mjs} +46 -6
- package/dist/ms-KY5QGBNN.mjs.map +1 -0
- package/dist/{nl-MVYXAS5C.mjs → nl-6PZFLGY2.mjs} +46 -6
- package/dist/nl-6PZFLGY2.mjs.map +1 -0
- package/dist/{no-XOLO4JPV.mjs → no-5QR7PLVJ.mjs} +46 -6
- package/dist/no-5QR7PLVJ.mjs.map +1 -0
- package/dist/{pl-TRWLMMC4.mjs → pl-4GV2NQXE.mjs} +46 -6
- package/dist/pl-4GV2NQXE.mjs.map +1 -0
- package/dist/{pt-M3TE24UI.mjs → pt-F3F5QD2P.mjs} +46 -6
- package/dist/pt-F3F5QD2P.mjs.map +1 -0
- package/dist/{ro-QBFG2T64.mjs → ro-TFYL2IQB.mjs} +46 -6
- package/dist/ro-TFYL2IQB.mjs.map +1 -0
- package/dist/{sv-IUECBXWX.mjs → sv-PRVF2QLR.mjs} +46 -6
- package/dist/sv-PRVF2QLR.mjs.map +1 -0
- package/dist/test-utils.mjs +16994 -22140
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-US7KIN5Q.mjs → th-SUQOQFUZ.mjs} +46 -6
- package/dist/th-SUQOQFUZ.mjs.map +1 -0
- package/dist/{tr-DWJ2FFUK.mjs → tr-AYUJZOFJ.mjs} +46 -6
- package/dist/tr-AYUJZOFJ.mjs.map +1 -0
- package/dist/{uk-M4ZE4DPZ.mjs → uk-YY5WGLBM.mjs} +46 -6
- package/dist/uk-YY5WGLBM.mjs.map +1 -0
- package/dist/{vi-FERZNPSH.mjs → vi-6RO77ITD.mjs} +46 -6
- package/dist/{vi-FERZNPSH.mjs.map → vi-6RO77ITD.mjs.map} +1 -1
- package/dist/{zh-3J2I3WYK.mjs → zh-L6GA65H6.mjs} +46 -6
- package/dist/zh-L6GA65H6.mjs.map +1 -0
- package/package.json +18 -14
- package/src/components/Button/Button.tsx +23 -25
- package/src/components/annotation/AnnotateToolbar.tsx +1 -1
- package/src/components/annotation-popups/SharedPopupElements.tsx +5 -7
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +3 -4
- package/src/components/modals/ConfigureGenerationStep.tsx +190 -0
- package/src/components/modals/ConfigureSearchStep.tsx +105 -0
- package/src/components/modals/ContextSummary.tsx +183 -0
- package/src/components/modals/GatherContextStep.tsx +89 -0
- package/src/components/modals/KeyboardShortcutsHelpModal.tsx +4 -6
- package/src/components/modals/ProposeEntitiesModal.tsx +4 -6
- package/src/components/modals/ReferenceWizardModal.tsx +326 -0
- package/src/components/modals/ResourceSearchModal.tsx +4 -6
- package/src/components/modals/SearchModal.css +43 -0
- package/src/components/modals/SearchModal.tsx +4 -6
- package/src/components/modals/SearchResultsStep.tsx +126 -0
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +3 -4
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +36 -14
- package/src/components/resource/AnnotateView.tsx +4 -4
- package/src/components/resource/AnnotationHistory.tsx +2 -2
- package/src/components/resource/BrowseView.tsx +4 -4
- package/src/components/resource/ResourceViewer.tsx +5 -8
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +16 -16
- package/src/components/resource/__tests__/BrowseView.test.tsx +2 -2
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +1 -1
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +1 -1
- package/src/components/resource/panels/AssessmentEntry.tsx +9 -11
- package/src/components/resource/panels/AssessmentPanel.tsx +1 -1
- package/src/components/resource/panels/CommentEntry.tsx +10 -12
- package/src/components/resource/panels/CommentsPanel.tsx +1 -1
- package/src/components/resource/panels/HighlightEntry.tsx +9 -11
- package/src/components/resource/panels/HighlightPanel.tsx +2 -2
- package/src/components/resource/panels/ReferenceEntry.tsx +57 -104
- package/src/components/resource/panels/ReferencesPanel.css +85 -13
- package/src/components/resource/panels/ReferencesPanel.tsx +2 -3
- package/src/components/resource/panels/TagEntry.tsx +9 -11
- package/src/components/resource/panels/TaggingPanel.tsx +1 -1
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +7 -7
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +3 -3
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +2 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +64 -101
- package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +7 -7
- package/src/components/viewers/ImageViewer.tsx +3 -6
- package/src/components/viewers/__tests__/ImageViewer.test.tsx +3 -3
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +5 -5
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +141 -0
- package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +41 -0
- package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +148 -0
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +106 -0
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +120 -0
- package/src/features/admin-exchange/components/ExportCard.tsx +35 -0
- package/src/features/admin-exchange/components/ImportCard.tsx +188 -0
- package/src/features/admin-exchange/components/ImportProgress.tsx +86 -0
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +3 -3
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +2 -2
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +4 -4
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +3 -3
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +117 -0
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +121 -0
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -5
- package/src/features/resource-compose/components/ResourceComposePage.tsx +56 -1
- package/src/features/resource-discovery/__tests__/ResourceCard.test.tsx +1 -1
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +2 -2
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +14 -14
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +12 -11
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +2 -2
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +22 -115
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +3 -3
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +20 -20
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +7 -7
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +43 -20
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +2 -2
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +45 -82
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +4 -4
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +151 -74
- package/src/integrations/tailwind-plugin.js +3 -3
- package/src/styles/core/buttons.css +31 -0
- package/src/styles/features/exchange.css +404 -0
- package/src/styles/index.css +1 -0
- package/translations/ar.json +42 -4
- package/translations/bn.json +42 -4
- package/translations/cs.json +42 -4
- package/translations/da.json +128 -90
- package/translations/de.json +122 -84
- package/translations/el.json +42 -4
- package/translations/en.json +42 -4
- package/translations/es.json +42 -4
- package/translations/fa.json +42 -4
- package/translations/fi.json +68 -30
- package/translations/fr.json +42 -4
- package/translations/he.json +42 -4
- package/translations/hi.json +42 -4
- package/translations/id.json +43 -5
- package/translations/it.json +62 -24
- package/translations/ja.json +43 -5
- package/translations/ko.json +42 -4
- package/translations/ms.json +43 -5
- package/translations/nl.json +41 -3
- package/translations/no.json +104 -66
- package/translations/pl.json +42 -4
- package/translations/pt.json +43 -5
- package/translations/ro.json +42 -4
- package/translations/sv.json +42 -4
- package/translations/th.json +42 -4
- package/translations/tr.json +42 -4
- package/translations/uk.json +42 -4
- package/translations/vi.json +42 -4
- package/translations/zh.json +42 -4
- package/dist/PdfAnnotationCanvas.client-COQREPXU.mjs.map +0 -1
- package/dist/ar-7SUXNE34.mjs.map +0 -1
- package/dist/bn-XOET3DOI.mjs.map +0 -1
- package/dist/chunk-JH7BXE2P.mjs.map +0 -1
- package/dist/cs-X63DXX7L.mjs.map +0 -1
- package/dist/da-OWTCV57A.mjs.map +0 -1
- package/dist/de-77BMFDVF.mjs.map +0 -1
- package/dist/el-FIBNLH2V.mjs.map +0 -1
- package/dist/es-726NTS53.mjs.map +0 -1
- package/dist/fa-3N4CIWE6.mjs.map +0 -1
- package/dist/fi-JOM3M7Z4.mjs.map +0 -1
- package/dist/fr-56QSXS7E.mjs.map +0 -1
- package/dist/he-SNAXPJEK.mjs.map +0 -1
- package/dist/hi-CRBRD5TB.mjs.map +0 -1
- package/dist/id-BRCVLICF.mjs.map +0 -1
- package/dist/it-M2Z27BNB.mjs.map +0 -1
- package/dist/ja-TZUKW7HD.mjs.map +0 -1
- package/dist/ko-NKBGGOL6.mjs.map +0 -1
- package/dist/ms-XFXPN6RX.mjs.map +0 -1
- package/dist/nl-MVYXAS5C.mjs.map +0 -1
- package/dist/no-XOLO4JPV.mjs.map +0 -1
- package/dist/pl-TRWLMMC4.mjs.map +0 -1
- package/dist/pt-M3TE24UI.mjs.map +0 -1
- package/dist/ro-QBFG2T64.mjs.map +0 -1
- package/dist/sv-IUECBXWX.mjs.map +0 -1
- package/dist/th-US7KIN5Q.mjs.map +0 -1
- package/dist/tr-DWJ2FFUK.mjs.map +0 -1
- package/dist/uk-M4ZE4DPZ.mjs.map +0 -1
- package/dist/zh-3J2I3WYK.mjs.map +0 -1
- package/src/examples/ButtonUsageExample.tsx +0 -242
- package/src/examples/button-css-modules.module.css +0 -164
- package/src/examples/button-styled-components.tsx +0 -215
- package/src/examples/button-tailwind.css +0 -51
- /package/dist/{chunk-Q2KV6Y2J.mjs.map → chunk-7GEYABC6.mjs.map} +0 -0
- /package/dist/{chunk-3JTO27MH.mjs.map → chunk-D4GAAQMM.mjs.map} +0 -0
- /package/dist/{en-XWEPVTB4.mjs.map → en-HAKDCFKL.mjs.map} +0 -0
- /package/dist/{magic-string.es-7FJ3LUGB.mjs.map → magic-string.es-K77I4ZQN.mjs.map} +0 -0
|
@@ -41,11 +41,11 @@ vi.mock('../../../components/Toast', () => ({
|
|
|
41
41
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
42
42
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
43
43
|
import { SemiontApiClient } from '@semiont/api-client';
|
|
44
|
-
import {
|
|
44
|
+
import { resourceId, accessToken, annotationId as toAnnotationId } from '@semiont/core';
|
|
45
45
|
|
|
46
46
|
describe('Annotation Deletion - Feature Integration', () => {
|
|
47
47
|
let deleteAnnotationSpy: ReturnType<typeof vi.fn>;
|
|
48
|
-
const
|
|
48
|
+
const testId = resourceId('test-resource');
|
|
49
49
|
const testToken = 'test-token-123';
|
|
50
50
|
const testBaseUrl = 'http://localhost:4000';
|
|
51
51
|
|
|
@@ -72,7 +72,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
72
72
|
eventBusInstance = useEventBus();
|
|
73
73
|
// useMarkFlow is the single registration point for useBindFlow
|
|
74
74
|
// (handles mark:delete mark:create annotate:detect-request, etc.)
|
|
75
|
-
useMarkFlow(
|
|
75
|
+
useMarkFlow(testId);
|
|
76
76
|
return null;
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -89,7 +89,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
89
89
|
return {
|
|
90
90
|
emitDelete: (annotationId: string) => {
|
|
91
91
|
act(() => {
|
|
92
|
-
eventBusInstance!.get('mark:delete').next({ annotationId });
|
|
92
|
+
eventBusInstance!.get('mark:delete').next({ annotationId: toAnnotationId(annotationId) });
|
|
93
93
|
});
|
|
94
94
|
},
|
|
95
95
|
eventBus: eventBusInstance!,
|
|
@@ -109,8 +109,9 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
109
109
|
expect(deleteAnnotationSpy).toHaveBeenCalledTimes(1);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
-
// Verify correct parameters (
|
|
112
|
+
// Verify correct parameters (resourceId, annotationId, opts)
|
|
113
113
|
expect(deleteAnnotationSpy).toHaveBeenCalledWith(
|
|
114
|
+
'test-resource',
|
|
114
115
|
expect.stringContaining(annotationId),
|
|
115
116
|
expect.objectContaining({
|
|
116
117
|
auth: accessToken(testToken),
|
|
@@ -127,10 +128,10 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
127
128
|
expect(deleteAnnotationSpy).toHaveBeenCalled();
|
|
128
129
|
});
|
|
129
130
|
|
|
130
|
-
// CRITICAL: Auth token must be present
|
|
131
|
+
// CRITICAL: Auth token must be present (3rd arg is opts)
|
|
131
132
|
const callArgs = deleteAnnotationSpy.mock.calls[0];
|
|
132
|
-
expect(callArgs[
|
|
133
|
-
expect(callArgs[
|
|
133
|
+
expect(callArgs[2]).toHaveProperty('auth');
|
|
134
|
+
expect(callArgs[2].auth).toBe(accessToken(testToken));
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
it('should emit mark:deleted event on successful deletion', async () => {
|
|
@@ -197,9 +198,9 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
197
198
|
expect(deleteAnnotationSpy).toHaveBeenCalledTimes(2);
|
|
198
199
|
});
|
|
199
200
|
|
|
200
|
-
// Verify each call had correct annotation ID
|
|
201
|
-
expect(deleteAnnotationSpy.mock.calls[0][
|
|
202
|
-
expect(deleteAnnotationSpy.mock.calls[1][
|
|
201
|
+
// Verify each call had correct annotation ID (2nd arg)
|
|
202
|
+
expect(deleteAnnotationSpy.mock.calls[0][1]).toContain('annotation-1');
|
|
203
|
+
expect(deleteAnnotationSpy.mock.calls[1][1]).toContain('annotation-2');
|
|
203
204
|
});
|
|
204
205
|
|
|
205
206
|
it('ARCHITECTURE: useBindFlow is called in useMarkFlow (single registration point)', async () => {
|
|
@@ -26,7 +26,7 @@ import { EventBusProvider, resetEventBusForTesting, useEventBus } from '../../..
|
|
|
26
26
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
27
27
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
28
28
|
import { SSEClient } from '@semiont/api-client';
|
|
29
|
-
import {
|
|
29
|
+
import { resourceId } from '@semiont/core';
|
|
30
30
|
|
|
31
31
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
32
32
|
vi.mock('../../../components/Toast', () => ({
|
|
@@ -40,7 +40,7 @@ vi.mock('../../../components/Toast', () => ({
|
|
|
40
40
|
|
|
41
41
|
describe('Detection Progress Dismissal Bug', () => {
|
|
42
42
|
let mockStream: any;
|
|
43
|
-
const rUri =
|
|
43
|
+
const rUri = resourceId('test');
|
|
44
44
|
|
|
45
45
|
beforeEach(() => {
|
|
46
46
|
resetEventBusForTesting();
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Layer 3: Feature Integration Test -
|
|
2
|
+
* Layer 3: Feature Integration Test - Bind Flow (body update)
|
|
3
3
|
*
|
|
4
|
-
* Tests the
|
|
5
|
-
* - bind:link → emits bind:search-requested
|
|
6
|
-
* - bind:search-requested → opens search modal with pendingReferenceId
|
|
7
|
-
* - onCloseSearchModal → closes modal
|
|
4
|
+
* Tests the write side of useBindFlow:
|
|
8
5
|
* - bind:update-body → calls updateAnnotationBody API
|
|
9
6
|
* - bind:update-body → emits bind:body-updated on success
|
|
10
7
|
* - bind:update-body → emits bind:body-update-failed on error
|
|
11
8
|
* - auth token passed to updateAnnotationBody
|
|
12
9
|
*
|
|
13
|
-
* The
|
|
10
|
+
* The wizard modal (ReferenceWizardModal) handles modal state, context
|
|
11
|
+
* gathering, search configuration, and result display. This test covers
|
|
12
|
+
* only the downstream API calls after the wizard emits bind:update-body.
|
|
14
13
|
*
|
|
15
14
|
* Uses real providers (EventBus, ApiClient, AuthToken) with mocked API boundary.
|
|
16
15
|
*/
|
|
@@ -23,7 +22,7 @@ import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../..
|
|
|
23
22
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
24
23
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
25
24
|
import { SemiontApiClient } from '@semiont/api-client';
|
|
26
|
-
import {
|
|
25
|
+
import { resourceId, accessToken, annotationId } from '@semiont/core';
|
|
27
26
|
|
|
28
27
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
29
28
|
vi.mock('../../../components/Toast', () => ({
|
|
@@ -35,9 +34,9 @@ vi.mock('../../../components/Toast', () => ({
|
|
|
35
34
|
}),
|
|
36
35
|
}));
|
|
37
36
|
|
|
38
|
-
describe('
|
|
37
|
+
describe('Bind Flow - Body Update Integration', () => {
|
|
39
38
|
let updateAnnotationBodySpy: ReturnType<typeof vi.fn>;
|
|
40
|
-
const
|
|
39
|
+
const testId = resourceId('test-resource');
|
|
41
40
|
const testToken = 'test-resolution-token';
|
|
42
41
|
const testBaseUrl = 'http://localhost:4000';
|
|
43
42
|
|
|
@@ -57,11 +56,10 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
57
56
|
|
|
58
57
|
function renderBindFlow() {
|
|
59
58
|
let eventBusInstance: ReturnType<typeof useEventBus> | null = null;
|
|
60
|
-
let lastState: ReturnType<typeof useBindFlow> | null = null;
|
|
61
59
|
|
|
62
60
|
function TestComponent() {
|
|
63
61
|
eventBusInstance = useEventBus();
|
|
64
|
-
|
|
62
|
+
useBindFlow(testId);
|
|
65
63
|
return null;
|
|
66
64
|
}
|
|
67
65
|
|
|
@@ -76,109 +74,18 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
76
74
|
);
|
|
77
75
|
|
|
78
76
|
return {
|
|
79
|
-
getState: () => lastState!,
|
|
80
77
|
getEventBus: () => eventBusInstance!,
|
|
81
78
|
};
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
// ─── Initial state ──────────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
it('starts with search modal closed and no pending reference', () => {
|
|
87
|
-
const { getState } = renderBindFlow();
|
|
88
|
-
expect(getState().searchModalOpen).toBe(false);
|
|
89
|
-
expect(getState().pendingReferenceId).toBeNull();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// ─── bind:link ─────────────────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
it('bind:link emits bind:search-requested with referenceId and searchTerm', () => {
|
|
95
|
-
const { getEventBus } = renderBindFlow();
|
|
96
|
-
const searchRequestedSpy = vi.fn();
|
|
97
|
-
|
|
98
|
-
const subscription = getEventBus().get('bind:search-requested').subscribe(searchRequestedSpy);
|
|
99
|
-
act(() => { getEventBus().get('bind:link').next({ annotationUri: 'ann-uri-123', searchTerm: 'climate change' }); });
|
|
100
|
-
subscription.unsubscribe();
|
|
101
|
-
|
|
102
|
-
expect(searchRequestedSpy).toHaveBeenCalledTimes(1);
|
|
103
|
-
expect(searchRequestedSpy).toHaveBeenCalledWith({
|
|
104
|
-
referenceId: 'ann-uri-123',
|
|
105
|
-
searchTerm: 'climate change',
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// ─── bind:search-requested ────────────────────────────────────────────
|
|
110
|
-
|
|
111
|
-
it('bind:search-requested opens the search modal', async () => {
|
|
112
|
-
const { getState, getEventBus } = renderBindFlow();
|
|
113
|
-
|
|
114
|
-
expect(getState().searchModalOpen).toBe(false);
|
|
115
|
-
|
|
116
|
-
act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-abc', searchTerm: 'oceans' }); });
|
|
117
|
-
|
|
118
|
-
await waitFor(() => {
|
|
119
|
-
expect(getState().searchModalOpen).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('bind:search-requested sets pendingReferenceId', async () => {
|
|
124
|
-
const { getState, getEventBus } = renderBindFlow();
|
|
125
|
-
|
|
126
|
-
act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-xyz', searchTerm: 'forests' }); });
|
|
127
|
-
|
|
128
|
-
await waitFor(() => {
|
|
129
|
-
expect(getState().pendingReferenceId).toBe('ref-xyz');
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('bind:link → bind:search-requested chain opens modal end-to-end', async () => {
|
|
134
|
-
const { getState, getEventBus } = renderBindFlow();
|
|
135
|
-
|
|
136
|
-
// Simulate the full user journey: user clicks "Link Document" on a reference entry
|
|
137
|
-
act(() => { getEventBus().get('bind:link').next({ annotationUri: 'ann-full-chain', searchTerm: 'biodiversity' }); });
|
|
138
|
-
|
|
139
|
-
await waitFor(() => {
|
|
140
|
-
expect(getState().searchModalOpen).toBe(true);
|
|
141
|
-
expect(getState().pendingReferenceId).toBe('ann-full-chain');
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// ─── onCloseSearchModal ──────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
it('onCloseSearchModal closes the search modal', async () => {
|
|
148
|
-
const { getState, getEventBus } = renderBindFlow();
|
|
149
|
-
|
|
150
|
-
act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-close', searchTerm: 'test' }); });
|
|
151
|
-
|
|
152
|
-
await waitFor(() => expect(getState().searchModalOpen).toBe(true));
|
|
153
|
-
|
|
154
|
-
act(() => { getState().onCloseSearchModal(); });
|
|
155
|
-
|
|
156
|
-
await waitFor(() => {
|
|
157
|
-
expect(getState().searchModalOpen).toBe(false);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('onCloseSearchModal does not clear pendingReferenceId (preserves for re-open)', async () => {
|
|
162
|
-
const { getState, getEventBus } = renderBindFlow();
|
|
163
|
-
|
|
164
|
-
act(() => { getEventBus().get('bind:search-requested').next({ referenceId: 'ref-persist', searchTerm: 'test' }); });
|
|
165
|
-
await waitFor(() => expect(getState().searchModalOpen).toBe(true));
|
|
166
|
-
|
|
167
|
-
act(() => { getState().onCloseSearchModal(); });
|
|
168
|
-
await waitFor(() => expect(getState().searchModalOpen).toBe(false));
|
|
169
|
-
|
|
170
|
-
// pendingReferenceId remains — modal may reopen
|
|
171
|
-
expect(getState().pendingReferenceId).toBe('ref-persist');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
81
|
// ─── bind:update-body ──────────────────────────────────────────────────
|
|
175
82
|
|
|
176
83
|
it('bind:update-body calls updateAnnotationBody API', async () => {
|
|
177
84
|
const { getEventBus } = renderBindFlow();
|
|
178
85
|
|
|
179
86
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
180
|
-
|
|
181
|
-
resourceId: 'linked-resource-id',
|
|
87
|
+
annotationId: annotationId('ann-body-1'),
|
|
88
|
+
resourceId: resourceId('linked-resource-id'),
|
|
182
89
|
operations: [{ op: 'add', item: { id: 'linked-resource-id' } }],
|
|
183
90
|
}); });
|
|
184
91
|
|
|
@@ -191,8 +98,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
191
98
|
const { getEventBus } = renderBindFlow();
|
|
192
99
|
|
|
193
100
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
194
|
-
|
|
195
|
-
resourceId: 'resource-id',
|
|
101
|
+
annotationId: annotationId('ann-auth'),
|
|
102
|
+
resourceId: resourceId('resource-id'),
|
|
196
103
|
operations: [{ op: 'replace', newItem: { id: 'resource-id' } }],
|
|
197
104
|
}); });
|
|
198
105
|
|
|
@@ -201,8 +108,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
201
108
|
});
|
|
202
109
|
|
|
203
110
|
const callArgs = updateAnnotationBodySpy.mock.calls[0];
|
|
204
|
-
expect(callArgs[
|
|
205
|
-
expect(callArgs[
|
|
111
|
+
expect(callArgs[3]).toHaveProperty('auth');
|
|
112
|
+
expect(callArgs[3].auth).toBe(accessToken(testToken));
|
|
206
113
|
});
|
|
207
114
|
|
|
208
115
|
it('bind:update-body emits bind:body-updated on success', async () => {
|
|
@@ -212,8 +119,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
212
119
|
const subscription = getEventBus().get('bind:body-updated').subscribe(bodyUpdatedSpy);
|
|
213
120
|
|
|
214
121
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
215
|
-
|
|
216
|
-
resourceId: 'resource-id',
|
|
122
|
+
annotationId: annotationId('ann-success'),
|
|
123
|
+
resourceId: resourceId('resource-id'),
|
|
217
124
|
operations: [{ op: 'add', item: { id: 'resource-id' } }],
|
|
218
125
|
}); });
|
|
219
126
|
|
|
@@ -224,7 +131,7 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
224
131
|
subscription.unsubscribe();
|
|
225
132
|
|
|
226
133
|
expect(bodyUpdatedSpy).toHaveBeenCalledWith({
|
|
227
|
-
|
|
134
|
+
annotationId: annotationId('ann-success'),
|
|
228
135
|
});
|
|
229
136
|
});
|
|
230
137
|
|
|
@@ -237,8 +144,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
237
144
|
const subscription = getEventBus().get('bind:body-update-failed').subscribe(bodyUpdateFailedSpy);
|
|
238
145
|
|
|
239
146
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
240
|
-
|
|
241
|
-
resourceId: 'resource-id',
|
|
147
|
+
annotationId: annotationId('ann-fail'),
|
|
148
|
+
resourceId: resourceId('resource-id'),
|
|
242
149
|
operations: [{ op: 'remove', item: { id: 'old-id' } }],
|
|
243
150
|
}); });
|
|
244
151
|
|
|
@@ -257,8 +164,8 @@ describe('Resolution Flow - Search Modal & Body Update Integration', () => {
|
|
|
257
164
|
const { getEventBus } = renderBindFlow();
|
|
258
165
|
|
|
259
166
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
260
|
-
|
|
261
|
-
resourceId: 'resource-id',
|
|
167
|
+
annotationId: annotationId('ann-dedup'),
|
|
168
|
+
resourceId: resourceId('resource-id'),
|
|
262
169
|
operations: [{ op: 'add', item: { id: 'resource-id' } }],
|
|
263
170
|
}); });
|
|
264
171
|
|
|
@@ -52,7 +52,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
52
52
|
// Component to capture EventBus and hook state
|
|
53
53
|
function TestComponent() {
|
|
54
54
|
eventBusInstance = useEventBus();
|
|
55
|
-
const state = useMarkFlow('
|
|
55
|
+
const state = useMarkFlow('test' as any);
|
|
56
56
|
currentState = state;
|
|
57
57
|
|
|
58
58
|
console.log('[TEST] useMarkFlow state:', {
|
|
@@ -109,7 +109,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
109
109
|
|
|
110
110
|
function TestComponent() {
|
|
111
111
|
eventBusInstance = useEventBus();
|
|
112
|
-
const state = useMarkFlow('
|
|
112
|
+
const state = useMarkFlow('test' as any);
|
|
113
113
|
currentState = state;
|
|
114
114
|
|
|
115
115
|
return (
|
|
@@ -162,7 +162,7 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
162
162
|
|
|
163
163
|
function TestComponent() {
|
|
164
164
|
eventBusInstance = useEventBus();
|
|
165
|
-
const state = useMarkFlow('
|
|
165
|
+
const state = useMarkFlow('f45fd44f9cb0b0fe1b7980d3d034bc61' as any);
|
|
166
166
|
|
|
167
167
|
stateSnapshots.push({
|
|
168
168
|
assistingMotivation: state.assistingMotivation,
|
|
@@ -31,7 +31,7 @@ import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
|
31
31
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
32
32
|
import { SSEClient } from '@semiont/api-client';
|
|
33
33
|
import type { Motivation } from '@semiont/core';
|
|
34
|
-
import {
|
|
34
|
+
import { resourceId } from '@semiont/core';
|
|
35
35
|
import type { Emitter } from 'mitt';
|
|
36
36
|
|
|
37
37
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
@@ -77,10 +77,10 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('should call annotateReferences exactly ONCE when detection starts (not twice)', async () => {
|
|
80
|
-
const
|
|
80
|
+
const testId = resourceId('test-resource');
|
|
81
81
|
|
|
82
82
|
// Render with real component composition
|
|
83
|
-
const { emitDetectionStart } = renderDetectionFlow(
|
|
83
|
+
const { emitDetectionStart } = renderDetectionFlow(testId);
|
|
84
84
|
|
|
85
85
|
// Trigger detection for linking (uses annotateReferences)
|
|
86
86
|
act(() => {
|
|
@@ -98,7 +98,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
98
98
|
|
|
99
99
|
// Verify correct parameters (eventBus is passed but we don't need to verify its exact value)
|
|
100
100
|
expect(annotateReferencesSpy).toHaveBeenCalledWith(
|
|
101
|
-
|
|
101
|
+
testId,
|
|
102
102
|
{
|
|
103
103
|
entityTypes: ['Person', 'Organization'],
|
|
104
104
|
includeDescriptiveReferences: false,
|
|
@@ -108,10 +108,10 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it('should propagate SSE progress events to useMarkFlow state', async () => {
|
|
111
|
-
const
|
|
111
|
+
const testId = resourceId('test-resource');
|
|
112
112
|
|
|
113
113
|
// Render with state observer
|
|
114
|
-
const { emitDetectionStart, getEventBus } = renderDetectionFlow(
|
|
114
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
|
|
115
115
|
|
|
116
116
|
// Start detection
|
|
117
117
|
act(() => {
|
|
@@ -145,8 +145,8 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
it('should handle multiple progress updates correctly', async () => {
|
|
148
|
-
const
|
|
149
|
-
const { emitDetectionStart, getEventBus } = renderDetectionFlow(
|
|
148
|
+
const testId = resourceId('test-resource');
|
|
149
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
|
|
150
150
|
|
|
151
151
|
// Start detection
|
|
152
152
|
act(() => {
|
|
@@ -200,8 +200,8 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
it('should keep progress visible after detection completes', async () => {
|
|
203
|
-
const
|
|
204
|
-
const { emitDetectionStart, getEventBus } = renderDetectionFlow(
|
|
203
|
+
const testId = resourceId('test-resource');
|
|
204
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
|
|
205
205
|
|
|
206
206
|
// Start detection
|
|
207
207
|
act(() => {
|
|
@@ -237,8 +237,8 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
it('should clear progress on detection failure', async () => {
|
|
240
|
-
const
|
|
241
|
-
const { emitDetectionStart, getEventBus } = renderDetectionFlow(
|
|
240
|
+
const testId = resourceId('test-resource');
|
|
241
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
|
|
242
242
|
|
|
243
243
|
// Start detection
|
|
244
244
|
act(() => {
|
|
@@ -282,8 +282,8 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it('should handle different detection motivations with correct API calls', async () => {
|
|
285
|
-
const
|
|
286
|
-
const { emitDetectionStart } = renderDetectionFlow(
|
|
285
|
+
const testId = resourceId('test-resource');
|
|
286
|
+
const { emitDetectionStart } = renderDetectionFlow(testId);
|
|
287
287
|
|
|
288
288
|
// Test highlighting
|
|
289
289
|
act(() => {
|
|
@@ -292,7 +292,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
292
292
|
|
|
293
293
|
await waitFor(() => {
|
|
294
294
|
expect(annotateHighlightsSpy).toHaveBeenCalledTimes(1);
|
|
295
|
-
expect(annotateHighlightsSpy).toHaveBeenCalledWith(
|
|
295
|
+
expect(annotateHighlightsSpy).toHaveBeenCalledWith(testId, {
|
|
296
296
|
instructions: 'Find important text',
|
|
297
297
|
}, expect.objectContaining({ auth: undefined }));
|
|
298
298
|
});
|
|
@@ -312,7 +312,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
312
312
|
|
|
313
313
|
await waitFor(() => {
|
|
314
314
|
expect(detectCommentsSpy).toHaveBeenCalledTimes(1);
|
|
315
|
-
expect(detectCommentsSpy).toHaveBeenCalledWith(
|
|
315
|
+
expect(detectCommentsSpy).toHaveBeenCalledWith(testId, {
|
|
316
316
|
instructions: 'Add helpful comments',
|
|
317
317
|
tone: 'educational',
|
|
318
318
|
}, expect.objectContaining({ auth: undefined }));
|
|
@@ -320,11 +320,11 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
it('should only call API once even with multiple event listeners', async () => {
|
|
323
|
-
const
|
|
323
|
+
const testId = resourceId('test-resource');
|
|
324
324
|
|
|
325
325
|
// This test specifically catches the duplicate useBindFlow bug
|
|
326
326
|
// If multiple components call useBindFlow, we'll see multiple API calls
|
|
327
|
-
const { emitDetectionStart, getEventBus } = renderDetectionFlow(
|
|
327
|
+
const { emitDetectionStart, getEventBus } = renderDetectionFlow(testId);
|
|
328
328
|
|
|
329
329
|
// Add an additional event listener (simulating multiple subscribers)
|
|
330
330
|
const additionalListener = vi.fn();
|
|
@@ -354,7 +354,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
354
354
|
* Helper: Render useMarkFlow hook with real component composition
|
|
355
355
|
* Returns methods to interact with the rendered component
|
|
356
356
|
*/
|
|
357
|
-
function renderDetectionFlow(
|
|
357
|
+
function renderDetectionFlow(testId: string) {
|
|
358
358
|
let eventBusInstance: Emitter<EventMap>;
|
|
359
359
|
|
|
360
360
|
// Component to capture EventBus instance
|
|
@@ -365,7 +365,7 @@ function renderDetectionFlow(testUri: string) {
|
|
|
365
365
|
|
|
366
366
|
// Test harness component that uses the hook
|
|
367
367
|
function DetectionFlowTestHarness() {
|
|
368
|
-
const { progress, assistingMotivation } = useMarkFlow(
|
|
368
|
+
const { progress, assistingMotivation } = useMarkFlow(testId as any);
|
|
369
369
|
return (
|
|
370
370
|
<div>
|
|
371
371
|
<div data-testid="detecting">{assistingMotivation || 'none'}</div>
|
|
@@ -26,7 +26,7 @@ import { render, waitFor } from '@testing-library/react';
|
|
|
26
26
|
import { act } from 'react';
|
|
27
27
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
28
28
|
import { SemiontApiClient } from '@semiont/api-client';
|
|
29
|
-
import {
|
|
29
|
+
import { resourceId, accessToken } from '@semiont/core';
|
|
30
30
|
import { EventBusProvider, useEventBus, resetEventBusForTesting } from '../../../contexts/EventBusContext';
|
|
31
31
|
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
32
32
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
@@ -36,7 +36,7 @@ import type { EventMap, EventBus } from '@semiont/core';
|
|
|
36
36
|
|
|
37
37
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
const TEST_URI =
|
|
39
|
+
const TEST_URI = resourceId('test-resource');
|
|
40
40
|
const TEST_TOKEN = 'test-auth-token-123';
|
|
41
41
|
const BASE_URL = 'http://localhost:4000';
|
|
42
42
|
const CLONE_TOKEN = 'generated-clone-token-xyz';
|
|
@@ -66,11 +66,11 @@ function ResourceMutationHarness({ onEventBus }: { onEventBus: (eventBus: EventB
|
|
|
66
66
|
const generateCloneTokenMutation = resources.generateCloneToken.useMutation();
|
|
67
67
|
|
|
68
68
|
const handleResourceArchive = useCallback(async () => {
|
|
69
|
-
await updateMutation.mutateAsync({
|
|
69
|
+
await updateMutation.mutateAsync({ id: TEST_URI, data: { archived: true } });
|
|
70
70
|
}, [updateMutation]);
|
|
71
71
|
|
|
72
72
|
const handleResourceUnarchive = useCallback(async () => {
|
|
73
|
-
await updateMutation.mutateAsync({
|
|
73
|
+
await updateMutation.mutateAsync({ id: TEST_URI, data: { archived: false } });
|
|
74
74
|
}, [updateMutation]);
|
|
75
75
|
|
|
76
76
|
const handleResourceClone = useCallback(async () => {
|
|
@@ -175,7 +175,7 @@ describe('Resource mutations — hooks hoisted to top level', () => {
|
|
|
175
175
|
await waitFor(() => {
|
|
176
176
|
expect(generateCloneTokenSpy).toHaveBeenCalledWith(
|
|
177
177
|
TEST_URI,
|
|
178
|
-
expect.anything()
|
|
178
|
+
expect.anything(),
|
|
179
179
|
);
|
|
180
180
|
});
|
|
181
181
|
});
|
|
@@ -241,7 +241,7 @@ describe('Resource mutations — hooks hoisted to top level', () => {
|
|
|
241
241
|
expect(updateResourceSpy).toHaveBeenCalledWith(
|
|
242
242
|
TEST_URI,
|
|
243
243
|
expect.objectContaining({ archived: true }),
|
|
244
|
-
expect.anything()
|
|
244
|
+
expect.anything(),
|
|
245
245
|
);
|
|
246
246
|
});
|
|
247
247
|
|
|
@@ -275,7 +275,7 @@ describe('Resource mutations — hooks hoisted to top level', () => {
|
|
|
275
275
|
expect(updateResourceSpy).toHaveBeenCalledWith(
|
|
276
276
|
TEST_URI,
|
|
277
277
|
expect.objectContaining({ archived: false }),
|
|
278
|
-
expect.anything()
|
|
278
|
+
expect.anything(),
|
|
279
279
|
);
|
|
280
280
|
});
|
|
281
281
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
-
import { render, screen } from '@testing-library/react';
|
|
9
|
+
import { render, screen, act } from '@testing-library/react';
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { ResourceViewerPage } from '../components/ResourceViewerPage';
|
|
12
12
|
import type { ResourceViewerPageProps } from '../components/ResourceViewerPage';
|
|
@@ -118,23 +118,22 @@ vi.mock('../../../contexts/ResourceAnnotationsContext', () => ({
|
|
|
118
118
|
ResourceAnnotationsProvider: ({ children }: any) => children,
|
|
119
119
|
}));
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
SearchResourcesModal: () => <div data-testid="search-modal">Search Modal</div>,
|
|
121
|
+
// Mock useEventSubscription at the direct path used by ResourceViewerPage
|
|
122
|
+
// (the barrel export mock doesn't intercept direct context imports)
|
|
123
|
+
const mockUseEventSubscriptions = vi.fn();
|
|
124
|
+
vi.mock('../../../contexts/useEventSubscription', () => ({
|
|
125
|
+
useEventSubscriptions: (...args: unknown[]) => mockUseEventSubscriptions(...args),
|
|
127
126
|
}));
|
|
128
127
|
|
|
129
|
-
vi.mock('@/components/
|
|
130
|
-
|
|
128
|
+
vi.mock('@/components/toolbar/ToolbarPanels', () => ({
|
|
129
|
+
ToolbarPanels: ({ children }: any) => <div data-testid="toolbar-panels">{children}</div>,
|
|
131
130
|
}));
|
|
132
131
|
|
|
133
132
|
// Create mock props matching the current ResourceViewerPageProps
|
|
134
133
|
const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): ResourceViewerPageProps => ({
|
|
135
134
|
resource: {
|
|
136
135
|
'@context': 'https://www.w3.org/ns/anno.jsonld',
|
|
137
|
-
'@id': '
|
|
136
|
+
'@id': 'test-123',
|
|
138
137
|
'@type': 'schema:DigitalDocument',
|
|
139
138
|
name: 'Test Resource',
|
|
140
139
|
description: 'A test resource for unit testing',
|
|
@@ -148,7 +147,7 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
|
|
|
148
147
|
},
|
|
149
148
|
],
|
|
150
149
|
},
|
|
151
|
-
rUri: '
|
|
150
|
+
rUri: 'test-123' as any,
|
|
152
151
|
locale: 'en',
|
|
153
152
|
cacheManager: {},
|
|
154
153
|
Link: ({ children }: any) => <a>{children}</a>,
|
|
@@ -156,8 +155,6 @@ const createMockProps = (overrides?: Partial<ResourceViewerPageProps>): Resource
|
|
|
156
155
|
refetchDocument: vi.fn().mockResolvedValue(undefined),
|
|
157
156
|
ToolbarPanels: ({ children, activePanel }: any) =>
|
|
158
157
|
!activePanel ? null : <div data-testid="toolbar-panels">{children}</div>,
|
|
159
|
-
SearchResourcesModal: () => <div data-testid="search-modal">Search Modal</div>,
|
|
160
|
-
GenerationConfigModal: () => <div data-testid="generation-modal">Generation Modal</div>,
|
|
161
158
|
...overrides,
|
|
162
159
|
});
|
|
163
160
|
|
|
@@ -281,21 +278,47 @@ describe('ResourceViewerPage', () => {
|
|
|
281
278
|
// Archived badge only shows in annotate mode, which defaults to false
|
|
282
279
|
expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
|
|
283
280
|
});
|
|
284
|
-
});
|
|
285
281
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
282
|
+
it('shows archived badge after mark:mode-toggled event fires', () => {
|
|
283
|
+
localStorage.setItem('annotateMode', 'false');
|
|
284
|
+
localStorage.setItem('activeToolbarPanel', 'annotations');
|
|
285
|
+
|
|
286
|
+
const props = createMockProps({
|
|
287
|
+
resource: {
|
|
288
|
+
...createMockProps().resource,
|
|
289
|
+
archived: true,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
289
293
|
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
290
294
|
|
|
291
|
-
|
|
295
|
+
// Before toggle: annotateMode is false, so archived badge is hidden
|
|
296
|
+
expect(screen.queryByText('📦 Archived')).not.toBeInTheDocument();
|
|
297
|
+
|
|
298
|
+
// Get the handler map that ResourceViewerPage passed to useEventSubscriptions
|
|
299
|
+
const handlerMap = mockUseEventSubscriptions.mock.calls[mockUseEventSubscriptions.mock.calls.length - 1]?.[0] as Record<string, () => void>;
|
|
300
|
+
expect(handlerMap).toBeDefined();
|
|
301
|
+
expect(handlerMap['mark:mode-toggled']).toBeDefined();
|
|
302
|
+
|
|
303
|
+
// Fire the mode toggle — this is what the toolbar emits
|
|
304
|
+
act(() => {
|
|
305
|
+
handlerMap['mark:mode-toggled']();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// After toggle: annotateMode is true, so archived badge should appear
|
|
309
|
+
expect(screen.getByText('📦 Archived')).toBeInTheDocument();
|
|
310
|
+
|
|
311
|
+
localStorage.clear();
|
|
292
312
|
});
|
|
313
|
+
});
|
|
293
314
|
|
|
294
|
-
|
|
315
|
+
describe('Modals', () => {
|
|
316
|
+
it('renders reference wizard modal', () => {
|
|
295
317
|
const props = createMockProps();
|
|
296
318
|
renderWithProviders(<ResourceViewerPage {...props} />);
|
|
297
319
|
|
|
298
|
-
|
|
320
|
+
// Wizard modal is rendered but closed by default
|
|
321
|
+
// It opens when bind:initiate is emitted from ReferenceEntry
|
|
299
322
|
});
|
|
300
323
|
});
|
|
301
324
|
|