@semiont/react-ui 0.4.14 → 0.4.16
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 +18 -12
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +175 -0
- package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs → PdfAnnotationCanvas.client-CHDCGQBR.mjs} +3 -3
- package/dist/{ar-R4CRNXEF.mjs → ar-3W37O3R3.mjs} +9 -3
- package/dist/ar-3W37O3R3.mjs.map +1 -0
- package/dist/{bn-CZKGRHTA.mjs → bn-JZTJLMVE.mjs} +9 -3
- package/dist/bn-JZTJLMVE.mjs.map +1 -0
- package/dist/chunk-FAI3S4BM.mjs +865 -0
- package/dist/chunk-FAI3S4BM.mjs.map +1 -0
- package/dist/{chunk-HVMAGUFA.mjs → chunk-NOD3NCXE.mjs} +3 -1
- package/dist/chunk-NOD3NCXE.mjs.map +1 -0
- package/dist/{chunk-HNZOXH4L.mjs → chunk-OZICDVH7.mjs} +5 -3
- package/dist/chunk-OZICDVH7.mjs.map +1 -0
- package/dist/{chunk-BQJWOK4C.mjs → chunk-VN5NY4SN.mjs} +9 -8
- package/dist/chunk-VN5NY4SN.mjs.map +1 -0
- package/dist/{cs-4WIB2IHH.mjs → cs-XYHH7HNE.mjs} +9 -3
- package/dist/cs-XYHH7HNE.mjs.map +1 -0
- package/dist/{da-JWYEUYPX.mjs → da-MZKIECVT.mjs} +9 -3
- package/dist/da-MZKIECVT.mjs.map +1 -0
- package/dist/{de-GWUQZGER.mjs → de-AYXTMRQW.mjs} +9 -3
- package/dist/de-AYXTMRQW.mjs.map +1 -0
- package/dist/{el-DM2GT7P5.mjs → el-A6CVQWAW.mjs} +9 -3
- package/dist/el-A6CVQWAW.mjs.map +1 -0
- package/dist/{en-IUV4ZXKH.mjs → en-YPQQBI4T.mjs} +2 -2
- package/dist/{es-6LVQIM3D.mjs → es-M2HXLJGT.mjs} +9 -3
- package/dist/es-M2HXLJGT.mjs.map +1 -0
- package/dist/{fa-IRUJY3QI.mjs → fa-V6JZJDYP.mjs} +9 -3
- package/dist/fa-V6JZJDYP.mjs.map +1 -0
- package/dist/{fi-53FBOEVT.mjs → fi-ONDTZ5H7.mjs} +9 -3
- package/dist/fi-ONDTZ5H7.mjs.map +1 -0
- package/dist/{fr-Q5KY7QL6.mjs → fr-PAPV4H4G.mjs} +9 -3
- package/dist/fr-PAPV4H4G.mjs.map +1 -0
- package/dist/{he-HJNKULBY.mjs → he-F6VTLJLW.mjs} +9 -3
- package/dist/he-F6VTLJLW.mjs.map +1 -0
- package/dist/{hi-UYZ4X6CR.mjs → hi-CFUAV4BF.mjs} +9 -3
- package/dist/hi-CFUAV4BF.mjs.map +1 -0
- package/dist/{id-UAQMH6U2.mjs → id-NBKLCCI7.mjs} +9 -3
- package/dist/id-NBKLCCI7.mjs.map +1 -0
- package/dist/index.d.mts +141 -169
- package/dist/index.mjs +2394 -2116
- package/dist/index.mjs.map +1 -1
- package/dist/{it-C7QEBNFA.mjs → it-SLSOWVVU.mjs} +9 -3
- package/dist/it-SLSOWVVU.mjs.map +1 -0
- package/dist/{ja-THS6AOSJ.mjs → ja-L5IG4ECE.mjs} +9 -3
- package/dist/ja-L5IG4ECE.mjs.map +1 -0
- package/dist/{ko-XKK3TWQG.mjs → ko-QYMTULKK.mjs} +9 -3
- package/dist/ko-QYMTULKK.mjs.map +1 -0
- package/dist/{ms-GSK7LIF7.mjs → ms-5DGSFKM2.mjs} +9 -3
- package/dist/ms-5DGSFKM2.mjs.map +1 -0
- package/dist/{nl-KUBWITGY.mjs → nl-VZPCGONO.mjs} +9 -3
- package/dist/nl-VZPCGONO.mjs.map +1 -0
- package/dist/{no-ECWZUHT6.mjs → no-MF6F352I.mjs} +9 -3
- package/dist/no-MF6F352I.mjs.map +1 -0
- package/dist/{pl-PLVWSZWS.mjs → pl-WIK72JUO.mjs} +9 -3
- package/dist/pl-WIK72JUO.mjs.map +1 -0
- package/dist/{pt-AL74ZTKB.mjs → pt-RRP5ZF6A.mjs} +9 -3
- package/dist/pt-RRP5ZF6A.mjs.map +1 -0
- package/dist/{ro-WTPHLHGS.mjs → ro-XHQLC3T7.mjs} +9 -3
- package/dist/ro-XHQLC3T7.mjs.map +1 -0
- package/dist/{sv-QCLI7SG4.mjs → sv-EWULDN6E.mjs} +9 -3
- package/dist/sv-EWULDN6E.mjs.map +1 -0
- package/dist/test-utils.d.mts +13 -62
- package/dist/test-utils.mjs +41 -22
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-WCKVZU6U.mjs → th-TGOBHFG4.mjs} +9 -3
- package/dist/th-TGOBHFG4.mjs.map +1 -0
- package/dist/{tr-2CAFS2XS.mjs → tr-LMMPBMV7.mjs} +9 -3
- package/dist/tr-LMMPBMV7.mjs.map +1 -0
- package/dist/{uk-TDE4JLCY.mjs → uk-IPGRRJY6.mjs} +9 -3
- package/dist/uk-IPGRRJY6.mjs.map +1 -0
- package/dist/{vi-KKXZ4PCX.mjs → vi-Q676OJQS.mjs} +9 -3
- package/dist/vi-Q676OJQS.mjs.map +1 -0
- package/dist/{zh-VH4XN5PV.mjs → zh-F3MTWQDX.mjs} +9 -3
- package/dist/zh-F3MTWQDX.mjs.map +1 -0
- package/package.json +5 -3
- package/src/components/ProtectedErrorBoundary.tsx +95 -0
- package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +197 -0
- package/src/components/modals/PermissionDeniedModal.tsx +140 -0
- package/src/components/modals/ReferenceWizardModal.tsx +3 -2
- package/src/components/modals/SessionExpiredModal.tsx +101 -0
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +150 -0
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +115 -0
- package/src/components/resource/AnnotationHistory.tsx +5 -6
- package/src/components/resource/HistoryEvent.tsx +9 -8
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +33 -34
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +18 -19
- package/src/components/resource/__tests__/event-formatting.test.ts +70 -94
- package/src/components/resource/event-formatting.ts +92 -56
- package/src/components/resource/panels/ReferenceEntry.tsx +7 -5
- package/src/components/resource/panels/ResourceInfoPanel.tsx +18 -6
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +12 -12
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +24 -0
- package/src/features/resource-compose/components/ResourceComposePage.tsx +10 -1
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +4 -4
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +5 -10
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +23 -54
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +6 -6
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +7 -19
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +18 -44
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +6 -6
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +26 -26
- package/src/styles/features/compose.css +63 -0
- package/translations/ar.json +6 -2
- package/translations/bn.json +6 -2
- package/translations/cs.json +6 -2
- package/translations/da.json +6 -2
- package/translations/de.json +6 -2
- package/translations/el.json +6 -2
- package/translations/en.json +2 -0
- package/translations/es.json +6 -2
- package/translations/fa.json +6 -2
- package/translations/fi.json +6 -2
- package/translations/fr.json +6 -2
- package/translations/he.json +6 -2
- package/translations/hi.json +6 -2
- package/translations/id.json +6 -2
- package/translations/it.json +6 -2
- package/translations/ja.json +6 -2
- package/translations/ko.json +6 -2
- package/translations/ms.json +6 -2
- package/translations/nl.json +6 -2
- package/translations/no.json +6 -2
- package/translations/pl.json +6 -2
- package/translations/pt.json +6 -2
- package/translations/ro.json +6 -2
- package/translations/sv.json +6 -2
- package/translations/th.json +6 -2
- package/translations/tr.json +6 -2
- package/translations/uk.json +6 -2
- package/translations/vi.json +6 -2
- package/translations/zh.json +6 -2
- package/dist/TranslationManager-CudgH3gw.d.mts +0 -107
- package/dist/ar-R4CRNXEF.mjs.map +0 -1
- package/dist/bn-CZKGRHTA.mjs.map +0 -1
- package/dist/chunk-BQJWOK4C.mjs.map +0 -1
- package/dist/chunk-HNZOXH4L.mjs.map +0 -1
- package/dist/chunk-HVMAGUFA.mjs.map +0 -1
- package/dist/chunk-OL5UST25.mjs +0 -413
- package/dist/chunk-OL5UST25.mjs.map +0 -1
- package/dist/cs-4WIB2IHH.mjs.map +0 -1
- package/dist/da-JWYEUYPX.mjs.map +0 -1
- package/dist/de-GWUQZGER.mjs.map +0 -1
- package/dist/el-DM2GT7P5.mjs.map +0 -1
- package/dist/es-6LVQIM3D.mjs.map +0 -1
- package/dist/fa-IRUJY3QI.mjs.map +0 -1
- package/dist/fi-53FBOEVT.mjs.map +0 -1
- package/dist/fr-Q5KY7QL6.mjs.map +0 -1
- package/dist/he-HJNKULBY.mjs.map +0 -1
- package/dist/hi-UYZ4X6CR.mjs.map +0 -1
- package/dist/id-UAQMH6U2.mjs.map +0 -1
- package/dist/it-C7QEBNFA.mjs.map +0 -1
- package/dist/ja-THS6AOSJ.mjs.map +0 -1
- package/dist/ko-XKK3TWQG.mjs.map +0 -1
- package/dist/ms-GSK7LIF7.mjs.map +0 -1
- package/dist/nl-KUBWITGY.mjs.map +0 -1
- package/dist/no-ECWZUHT6.mjs.map +0 -1
- package/dist/pl-PLVWSZWS.mjs.map +0 -1
- package/dist/pt-AL74ZTKB.mjs.map +0 -1
- package/dist/ro-WTPHLHGS.mjs.map +0 -1
- package/dist/sv-QCLI7SG4.mjs.map +0 -1
- package/dist/th-WCKVZU6U.mjs.map +0 -1
- package/dist/tr-2CAFS2XS.mjs.map +0 -1
- package/dist/uk-TDE4JLCY.mjs.map +0 -1
- package/dist/vi-KKXZ4PCX.mjs.map +0 -1
- package/dist/zh-VH4XN5PV.mjs.map +0 -1
- /package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs.map → PdfAnnotationCanvas.client-CHDCGQBR.mjs.map} +0 -0
- /package/dist/{en-IUV4ZXKH.mjs.map → en-YPQQBI4T.mjs.map} +0 -0
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
4
4
|
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
5
5
|
import { formatLocaleDisplay } from '@semiont/api-client';
|
|
6
|
-
import type
|
|
6
|
+
import { resourceId as makeResourceId, type components } from '@semiont/core';
|
|
7
7
|
import './ResourceInfoPanel.css';
|
|
8
8
|
|
|
9
9
|
type Agent = components['schemas']['Agent'];
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
+
resourceId: string;
|
|
12
13
|
documentEntityTypes: string[];
|
|
13
14
|
documentLocale?: string | undefined;
|
|
14
15
|
primaryMediaType?: string | undefined;
|
|
15
16
|
primaryByteSize?: number | undefined;
|
|
17
|
+
storageUri?: string | undefined;
|
|
16
18
|
isArchived?: boolean;
|
|
17
19
|
dateCreated?: string | undefined;
|
|
18
20
|
dateModified?: string | undefined;
|
|
@@ -25,15 +27,17 @@ interface Props {
|
|
|
25
27
|
/**
|
|
26
28
|
* Panel for displaying resource metadata and management actions
|
|
27
29
|
*
|
|
28
|
-
* @emits yield:clone - Clone this resource
|
|
29
|
-
* @emits mark:unarchive - Unarchive this resource
|
|
30
|
-
* @emits mark:archive - Archive this resource
|
|
30
|
+
* @emits yield:clone - Clone this resource
|
|
31
|
+
* @emits mark:unarchive - Unarchive this resource
|
|
32
|
+
* @emits mark:archive - Archive this resource
|
|
31
33
|
*/
|
|
32
34
|
export function ResourceInfoPanel({
|
|
35
|
+
resourceId,
|
|
33
36
|
documentEntityTypes,
|
|
34
37
|
documentLocale,
|
|
35
38
|
primaryMediaType,
|
|
36
39
|
primaryByteSize,
|
|
40
|
+
storageUri,
|
|
37
41
|
isArchived = false,
|
|
38
42
|
dateCreated,
|
|
39
43
|
dateModified,
|
|
@@ -87,6 +91,14 @@ export function ResourceInfoPanel({
|
|
|
87
91
|
</span>
|
|
88
92
|
</div>
|
|
89
93
|
)}
|
|
94
|
+
{storageUri && (
|
|
95
|
+
<div>
|
|
96
|
+
<span className="semiont-resource-info-panel__label">{t('storageUri')}</span>
|
|
97
|
+
<span className="semiont-resource-info-panel__value">
|
|
98
|
+
{storageUri}
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
90
102
|
</div>
|
|
91
103
|
</div>
|
|
92
104
|
)}
|
|
@@ -194,7 +206,7 @@ export function ResourceInfoPanel({
|
|
|
194
206
|
{isArchived ? (
|
|
195
207
|
<>
|
|
196
208
|
<button
|
|
197
|
-
onClick={() => eventBus.get('mark:unarchive').next(
|
|
209
|
+
onClick={() => eventBus.get('mark:unarchive').next({ resourceId: makeResourceId(resourceId) })}
|
|
198
210
|
className="semiont-resource-button semiont-resource-button--secondary"
|
|
199
211
|
>
|
|
200
212
|
📤 {t('unarchive')}
|
|
@@ -206,7 +218,7 @@ export function ResourceInfoPanel({
|
|
|
206
218
|
) : (
|
|
207
219
|
<>
|
|
208
220
|
<button
|
|
209
|
-
onClick={() => eventBus.get('mark:archive').next(
|
|
221
|
+
onClick={() => eventBus.get('mark:archive').next({ resourceId: makeResourceId(resourceId) })}
|
|
210
222
|
className="semiont-resource-button semiont-resource-button--archive"
|
|
211
223
|
>
|
|
212
224
|
📦 {t('archive')}
|
|
@@ -5,6 +5,7 @@ import '@testing-library/jest-dom';
|
|
|
5
5
|
import { renderWithProviders } from '../../../../test-utils';
|
|
6
6
|
import userEvent from '@testing-library/user-event';
|
|
7
7
|
import type { components } from '@semiont/core';
|
|
8
|
+
import { SemiontApiClient } from '@semiont/api-client';
|
|
8
9
|
import type { RouteBuilder } from '../../../../contexts/RoutingContext';
|
|
9
10
|
|
|
10
11
|
type Annotation = components['schemas']['Annotation'];
|
|
@@ -303,28 +304,27 @@ describe('ReferenceEntry', () => {
|
|
|
303
304
|
expect(unlinkButton).not.toBeInTheDocument();
|
|
304
305
|
});
|
|
305
306
|
|
|
306
|
-
it('should
|
|
307
|
+
it('should call client.bind.body on unlink click', async () => {
|
|
307
308
|
mockIsBodyResolved.mockReturnValue(true);
|
|
308
309
|
mockGetBodySource.mockReturnValue('linked-doc');
|
|
309
|
-
const unlinkHandler = vi.fn();
|
|
310
310
|
|
|
311
|
-
const
|
|
311
|
+
const bindSpy = vi.spyOn(SemiontApiClient.prototype, 'bindAnnotation').mockResolvedValue({ correlationId: 'c1' });
|
|
312
|
+
|
|
313
|
+
const { container } = renderWithProviders(
|
|
312
314
|
<ReferenceEntry {...defaultProps} annotateMode={true} />,
|
|
313
|
-
{ returnEventBus: true }
|
|
314
315
|
);
|
|
315
316
|
|
|
316
|
-
const subscription = eventBus!.get('bind:update-body').subscribe(unlinkHandler);
|
|
317
|
-
|
|
318
317
|
const unlinkButton = container.querySelector('.semiont-reference-unlink')!;
|
|
319
318
|
await userEvent.click(unlinkButton);
|
|
320
319
|
|
|
321
|
-
expect(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
operations: [{ op: 'remove', item: { type: 'SpecificResource', source: 'linked-doc' } }],
|
|
325
|
-
|
|
320
|
+
expect(bindSpy).toHaveBeenCalledWith(
|
|
321
|
+
'resource-1',
|
|
322
|
+
'ref-1',
|
|
323
|
+
{ operations: [{ op: 'remove', item: { type: 'SpecificResource', source: 'linked-doc' } }] },
|
|
324
|
+
expect.anything(),
|
|
325
|
+
);
|
|
326
326
|
|
|
327
|
-
|
|
327
|
+
bindSpy.mockRestore();
|
|
328
328
|
});
|
|
329
329
|
});
|
|
330
330
|
|
|
@@ -15,6 +15,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
15
15
|
representation: 'Representation',
|
|
16
16
|
mediaType: 'Media Type',
|
|
17
17
|
byteSize: 'Size',
|
|
18
|
+
storageUri: 'Storage',
|
|
18
19
|
clone: 'Clone',
|
|
19
20
|
cloneDescription: 'Generate a shareable clone link for this resource',
|
|
20
21
|
archive: 'Archive',
|
|
@@ -122,6 +123,7 @@ const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<
|
|
|
122
123
|
|
|
123
124
|
describe('ResourceInfoPanel Component', () => {
|
|
124
125
|
const defaultProps = {
|
|
126
|
+
resourceId: 'test-resource-id',
|
|
125
127
|
documentEntityTypes: [],
|
|
126
128
|
documentLocale: undefined,
|
|
127
129
|
primaryMediaType: undefined,
|
|
@@ -194,6 +196,28 @@ describe('ResourceInfoPanel Component', () => {
|
|
|
194
196
|
expect(screen.getByText('1,024 bytes')).toBeInTheDocument();
|
|
195
197
|
});
|
|
196
198
|
|
|
199
|
+
it('should render storageUri when provided', () => {
|
|
200
|
+
renderWithEventBus(
|
|
201
|
+
<ResourceInfoPanel
|
|
202
|
+
{...defaultProps}
|
|
203
|
+
primaryMediaType="text/markdown"
|
|
204
|
+
storageUri="file://docs/overview.md"
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
expect(screen.getByText('Storage')).toBeInTheDocument();
|
|
208
|
+
expect(screen.getByText('file://docs/overview.md')).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should not render storageUri when absent', () => {
|
|
212
|
+
renderWithEventBus(
|
|
213
|
+
<ResourceInfoPanel
|
|
214
|
+
{...defaultProps}
|
|
215
|
+
primaryMediaType="text/markdown"
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
expect(screen.queryByText('Storage')).not.toBeInTheDocument();
|
|
219
|
+
});
|
|
220
|
+
|
|
197
221
|
it('should not render representation section when neither media type nor byte size provided', () => {
|
|
198
222
|
renderWithEventBus(
|
|
199
223
|
<ResourceInfoPanel
|
|
@@ -339,7 +339,9 @@ export function ResourceComposePage({
|
|
|
339
339
|
|
|
340
340
|
{/* Create Form */}
|
|
341
341
|
<div className="semiont-form">
|
|
342
|
-
<form onSubmit={handleSaveResource} className="semiont-form__fields">
|
|
342
|
+
<form onSubmit={handleSaveResource} className="semiont-form__fields semiont-compose-grid">
|
|
343
|
+
{/* Left column: metadata */}
|
|
344
|
+
<div className="semiont-compose-grid__meta">
|
|
343
345
|
{/* Name */}
|
|
344
346
|
<div className="semiont-form__field">
|
|
345
347
|
<label htmlFor="docName" className="semiont-form__label">
|
|
@@ -468,6 +470,11 @@ export function ResourceComposePage({
|
|
|
468
470
|
</select>
|
|
469
471
|
</div>
|
|
470
472
|
|
|
473
|
+
</div>{/* end semiont-compose-grid__meta */}
|
|
474
|
+
|
|
475
|
+
{/* Right column: content */}
|
|
476
|
+
<div className="semiont-compose-grid__content">
|
|
477
|
+
|
|
471
478
|
{/* Content Source Toggle - only show for new resources */}
|
|
472
479
|
{!isClone && !isReferenceCompletion && (
|
|
473
480
|
<div className="semiont-form__field">
|
|
@@ -657,6 +664,8 @@ export function ResourceComposePage({
|
|
|
657
664
|
</div>
|
|
658
665
|
)}
|
|
659
666
|
|
|
667
|
+
</div>{/* end semiont-compose-grid__content */}
|
|
668
|
+
|
|
660
669
|
{/* Action Buttons */}
|
|
661
670
|
<div className="semiont-form__actions">
|
|
662
671
|
<button
|
|
@@ -248,7 +248,7 @@ describe('Annotation creation clears pendingAnnotation', () => {
|
|
|
248
248
|
const createdListener = vi.fn();
|
|
249
249
|
// Set listener after first render so eventBus is captured
|
|
250
250
|
await waitFor(() => expect(getEventBus()).toBeDefined());
|
|
251
|
-
const subscription = getEventBus().get('mark:
|
|
251
|
+
const subscription = getEventBus().get('mark:create-ok').subscribe(createdListener);
|
|
252
252
|
|
|
253
253
|
act(() => {
|
|
254
254
|
emit('mark:requested', { selector: TEXT_SELECTOR, motivation: 'linking' });
|
|
@@ -27,6 +27,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
27
27
|
import { render, waitFor } from '@testing-library/react';
|
|
28
28
|
import { act } from 'react';
|
|
29
29
|
import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
30
|
+
import { useStoreTokenSync } from '../../../hooks/useStoreTokenSync';
|
|
30
31
|
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
31
32
|
|
|
32
33
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
@@ -69,8 +70,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
69
70
|
|
|
70
71
|
function TestComponent() {
|
|
71
72
|
eventBusInstance = useEventBus();
|
|
72
|
-
//
|
|
73
|
-
// (handles mark:delete mark:create annotate:detect-request, etc.)
|
|
73
|
+
useStoreTokenSync(); // Syncs auth token to namespace getToken
|
|
74
74
|
useMarkFlow(testId);
|
|
75
75
|
return null;
|
|
76
76
|
}
|
|
@@ -138,7 +138,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
138
138
|
const deletedListener = vi.fn();
|
|
139
139
|
|
|
140
140
|
// Subscribe to success event
|
|
141
|
-
eventBus.get('mark:
|
|
141
|
+
eventBus.get('mark:delete-ok').subscribe(deletedListener);
|
|
142
142
|
|
|
143
143
|
emitDelete('annotation-789');
|
|
144
144
|
|
|
@@ -175,7 +175,7 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
175
175
|
// Verify failure event was emitted
|
|
176
176
|
await waitFor(() => {
|
|
177
177
|
expect(failedListener).toHaveBeenCalledWith({
|
|
178
|
-
|
|
178
|
+
message: expect.any(String),
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
181
|
});
|
|
@@ -25,7 +25,7 @@ import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
|
25
25
|
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
26
26
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
27
27
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
28
|
-
import {
|
|
28
|
+
import { SemiontApiClient } from '@semiont/api-client';
|
|
29
29
|
import { resourceId } from '@semiont/core';
|
|
30
30
|
|
|
31
31
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
@@ -39,20 +39,15 @@ vi.mock('../../../components/Toast', () => ({
|
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
41
|
describe('Detection Progress Dismissal Bug', () => {
|
|
42
|
-
let mockStream: any;
|
|
43
42
|
const rUri = resourceId('test');
|
|
44
43
|
|
|
45
44
|
beforeEach(() => {
|
|
46
45
|
vi.clearAllMocks();
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
vi.spyOn(SSEClient.prototype, 'markReferences').mockReturnValue(mockStream);
|
|
53
|
-
vi.spyOn(SSEClient.prototype, 'markHighlights').mockReturnValue(mockStream);
|
|
54
|
-
vi.spyOn(SSEClient.prototype, 'markComments').mockReturnValue(mockStream);
|
|
55
|
-
vi.spyOn(SSEClient.prototype, 'markAssessments').mockReturnValue(mockStream);
|
|
47
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateReferences').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
48
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateHighlights').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
49
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateComments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
50
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateAssessments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
56
51
|
});
|
|
57
52
|
|
|
58
53
|
afterEach(() => {
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Layer 3: Feature Integration Test - Bind Flow (body update)
|
|
3
3
|
*
|
|
4
4
|
* Tests the write side of useBindFlow:
|
|
5
|
-
* - bind:update-body → calls bindAnnotation API
|
|
6
|
-
* - bind:update-body → emits bind:body-updated on success
|
|
5
|
+
* - bind:update-body → calls http.bindAnnotation API (plain POST)
|
|
7
6
|
* - bind:update-body → emits bind:body-update-failed on error
|
|
8
7
|
* - auth token passed to bindAnnotation
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* After the UNIFIED-STREAM migration, bind is a plain POST returning
|
|
10
|
+
* {correlationId}. The state change arrives on the events-stream as
|
|
11
|
+
* mark:body-updated. These tests focus on the POST call, not the
|
|
12
|
+
* events-stream delivery (which is tested in AnnotationStore tests).
|
|
13
13
|
*
|
|
14
14
|
* Uses real providers (EventBus, ApiClient, AuthToken) with mocked API boundary.
|
|
15
15
|
*/
|
|
@@ -21,14 +21,15 @@ import { useBindFlow } from '../../../hooks/useBindFlow';
|
|
|
21
21
|
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
22
22
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
23
23
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
24
|
-
import {
|
|
25
|
-
import { resourceId,
|
|
24
|
+
import { SemiontApiClient } from '@semiont/api-client';
|
|
25
|
+
import { resourceId, annotationId } from '@semiont/core';
|
|
26
|
+
|
|
27
|
+
const mockShowError = vi.fn();
|
|
26
28
|
|
|
27
|
-
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
28
29
|
vi.mock('../../../components/Toast', () => ({
|
|
29
30
|
useToast: () => ({
|
|
30
31
|
showSuccess: vi.fn(),
|
|
31
|
-
showError:
|
|
32
|
+
showError: mockShowError,
|
|
32
33
|
showInfo: vi.fn(),
|
|
33
34
|
showWarning: vi.fn(),
|
|
34
35
|
}),
|
|
@@ -43,11 +44,9 @@ describe('Bind Flow - Body Update Integration', () => {
|
|
|
43
44
|
beforeEach(() => {
|
|
44
45
|
vi.clearAllMocks();
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
vi.spyOn(SSEClient.prototype, 'bindAnnotation').mockImplementation(bindAnnotationSpy as any);
|
|
47
|
+
// Mock the HTTP bindAnnotation method (plain POST, returns {correlationId})
|
|
48
|
+
bindAnnotationSpy = vi.fn().mockResolvedValue({ correlationId: 'corr-test' });
|
|
49
|
+
vi.spyOn(SemiontApiClient.prototype, 'bindAnnotation').mockImplementation(bindAnnotationSpy as any);
|
|
51
50
|
});
|
|
52
51
|
|
|
53
52
|
afterEach(() => {
|
|
@@ -82,10 +81,11 @@ describe('Bind Flow - Body Update Integration', () => {
|
|
|
82
81
|
|
|
83
82
|
// ─── bind:update-body ──────────────────────────────────────────────────
|
|
84
83
|
|
|
85
|
-
it('bind:update-body calls bindAnnotation
|
|
84
|
+
it('bind:update-body calls http.bindAnnotation (plain POST)', async () => {
|
|
86
85
|
const { getEventBus } = renderBindFlow();
|
|
87
86
|
|
|
88
87
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
88
|
+
correlationId: 'corr-1',
|
|
89
89
|
annotationId: annotationId('ann-body-1'),
|
|
90
90
|
resourceId: resourceId('linked-resource-id'),
|
|
91
91
|
operations: [{ op: 'add', item: { type: 'SpecificResource' as const, source: 'linked-resource-id' } }],
|
|
@@ -100,6 +100,7 @@ describe('Bind Flow - Body Update Integration', () => {
|
|
|
100
100
|
const { getEventBus } = renderBindFlow();
|
|
101
101
|
|
|
102
102
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
103
|
+
correlationId: 'corr-2',
|
|
103
104
|
annotationId: annotationId('ann-auth'),
|
|
104
105
|
resourceId: resourceId('resource-id'),
|
|
105
106
|
operations: [{ op: 'replace', newItem: { type: 'SpecificResource' as const, source: 'resource-id' } }],
|
|
@@ -111,57 +112,24 @@ describe('Bind Flow - Body Update Integration', () => {
|
|
|
111
112
|
|
|
112
113
|
const callArgs = bindAnnotationSpy.mock.calls[0];
|
|
113
114
|
expect(callArgs[3]).toHaveProperty('auth');
|
|
114
|
-
expect(callArgs[3].auth).toBe(accessToken(testToken));
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('bind:update-body emits bind:body-updated on success', async () => {
|
|
118
|
-
const { getEventBus } = renderBindFlow();
|
|
119
|
-
const bodyUpdatedSpy = vi.fn();
|
|
120
|
-
|
|
121
|
-
const subscription = getEventBus().get('bind:body-updated').subscribe(bodyUpdatedSpy);
|
|
122
|
-
|
|
123
|
-
act(() => { getEventBus().get('bind:update-body').next({
|
|
124
|
-
annotationId: annotationId('ann-success'),
|
|
125
|
-
resourceId: resourceId('resource-id'),
|
|
126
|
-
operations: [{ op: 'add', item: { type: 'SpecificResource' as const, source: 'resource-id' } }],
|
|
127
|
-
}); });
|
|
128
|
-
|
|
129
|
-
await waitFor(() => {
|
|
130
|
-
expect(bodyUpdatedSpy).toHaveBeenCalledTimes(1);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
subscription.unsubscribe();
|
|
134
|
-
|
|
135
|
-
expect(bodyUpdatedSpy).toHaveBeenCalledWith({
|
|
136
|
-
annotationId: annotationId('ann-success'),
|
|
137
|
-
});
|
|
138
115
|
});
|
|
139
116
|
|
|
140
|
-
it('bind:update-body
|
|
141
|
-
bindAnnotationSpy.
|
|
142
|
-
queueMicrotask(() => opts.eventBus.get('bind:failed').next({ error: new Error('Update failed') }));
|
|
143
|
-
return { close: vi.fn() };
|
|
144
|
-
});
|
|
117
|
+
it('bind:update-body shows error toast on API error', async () => {
|
|
118
|
+
bindAnnotationSpy.mockRejectedValueOnce(new Error('Update failed'));
|
|
145
119
|
|
|
146
120
|
const { getEventBus } = renderBindFlow();
|
|
147
|
-
const bodyUpdateFailedSpy = vi.fn();
|
|
148
|
-
|
|
149
|
-
const subscription = getEventBus().get('bind:body-update-failed').subscribe(bodyUpdateFailedSpy);
|
|
150
121
|
|
|
151
122
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
123
|
+
correlationId: 'corr-3',
|
|
152
124
|
annotationId: annotationId('ann-fail'),
|
|
153
125
|
resourceId: resourceId('resource-id'),
|
|
154
126
|
operations: [{ op: 'remove', item: { type: 'SpecificResource' as const, source: 'old-id' } }],
|
|
155
127
|
}); });
|
|
156
128
|
|
|
157
129
|
await waitFor(() => {
|
|
158
|
-
expect(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
subscription.unsubscribe();
|
|
162
|
-
|
|
163
|
-
expect(bodyUpdateFailedSpy).toHaveBeenCalledWith({
|
|
164
|
-
error: expect.any(Error),
|
|
130
|
+
expect(mockShowError).toHaveBeenCalledWith(
|
|
131
|
+
expect.stringContaining('Update failed'),
|
|
132
|
+
);
|
|
165
133
|
});
|
|
166
134
|
});
|
|
167
135
|
|
|
@@ -169,6 +137,7 @@ describe('Bind Flow - Body Update Integration', () => {
|
|
|
169
137
|
const { getEventBus } = renderBindFlow();
|
|
170
138
|
|
|
171
139
|
act(() => { getEventBus().get('bind:update-body').next({
|
|
140
|
+
correlationId: 'corr-4',
|
|
172
141
|
annotationId: annotationId('ann-dedup'),
|
|
173
142
|
resourceId: resourceId('resource-id'),
|
|
174
143
|
operations: [{ op: 'add', item: { type: 'SpecificResource' as const, source: 'resource-id' } }],
|
|
@@ -17,7 +17,7 @@ import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
|
17
17
|
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
18
18
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
19
19
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
20
|
-
import {
|
|
20
|
+
import { SemiontApiClient } from '@semiont/api-client';
|
|
21
21
|
|
|
22
22
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
23
23
|
vi.mock('../../../components/Toast', () => ({
|
|
@@ -33,11 +33,11 @@ describe('REPRODUCING BUG: Detection state not updating', () => {
|
|
|
33
33
|
beforeEach(() => {
|
|
34
34
|
vi.clearAllMocks();
|
|
35
35
|
|
|
36
|
-
// Minimal mock
|
|
37
|
-
vi.spyOn(
|
|
38
|
-
vi.spyOn(
|
|
39
|
-
vi.spyOn(
|
|
40
|
-
vi.spyOn(
|
|
36
|
+
// Minimal mock — namespace methods call these HTTP methods internally
|
|
37
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateReferences').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
38
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateHighlights').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
39
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateComments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
40
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateAssessments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
afterEach(() => {
|
|
@@ -29,7 +29,7 @@ import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
|
29
29
|
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
30
30
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
31
31
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
32
|
-
import {
|
|
32
|
+
import { SemiontApiClient } from '@semiont/api-client';
|
|
33
33
|
import type { Motivation } from '@semiont/core';
|
|
34
34
|
import { resourceId } from '@semiont/core';
|
|
35
35
|
import type { Emitter } from 'mitt';
|
|
@@ -45,15 +45,7 @@ vi.mock('../../../components/Toast', () => ({
|
|
|
45
45
|
}));
|
|
46
46
|
import type { EventMap } from '@semiont/core';
|
|
47
47
|
|
|
48
|
-
// Mock SSE stream - SSE now emits directly to EventBus, no callbacks
|
|
49
|
-
const createMockSSEStream = () => {
|
|
50
|
-
return {
|
|
51
|
-
close: vi.fn(),
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
|
|
55
48
|
describe('Detection Flow - Feature Integration', () => {
|
|
56
|
-
let mockStream: ReturnType<typeof createMockSSEStream>;
|
|
57
49
|
let markReferencesSpy: any;
|
|
58
50
|
let markHighlightsSpy: any;
|
|
59
51
|
let detectCommentsSpy: any;
|
|
@@ -61,14 +53,11 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
61
53
|
beforeEach(() => {
|
|
62
54
|
vi.clearAllMocks();
|
|
63
55
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
markHighlightsSpy = vi.spyOn(SSEClient.prototype, 'markHighlights').mockReturnValue(mockStream as any);
|
|
70
|
-
detectCommentsSpy = vi.spyOn(SSEClient.prototype, 'markComments').mockReturnValue(mockStream as any);
|
|
71
|
-
vi.spyOn(SSEClient.prototype, 'markAssessments').mockReturnValue(mockStream as any);
|
|
56
|
+
// Spy on SemiontApiClient prototype HTTP methods (namespace methods call these)
|
|
57
|
+
markReferencesSpy = vi.spyOn(SemiontApiClient.prototype, 'annotateReferences').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
58
|
+
markHighlightsSpy = vi.spyOn(SemiontApiClient.prototype, 'annotateHighlights').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
59
|
+
detectCommentsSpy = vi.spyOn(SemiontApiClient.prototype, 'annotateComments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
60
|
+
vi.spyOn(SemiontApiClient.prototype, 'annotateAssessments').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
72
61
|
});
|
|
73
62
|
|
|
74
63
|
afterEach(() => {
|
|
@@ -298,8 +287,7 @@ describe('Detection Flow - Feature Integration', () => {
|
|
|
298
287
|
|
|
299
288
|
// Reset for next test
|
|
300
289
|
vi.clearAllMocks();
|
|
301
|
-
|
|
302
|
-
detectCommentsSpy.mockReturnValue(mockStream);
|
|
290
|
+
detectCommentsSpy.mockResolvedValue({ correlationId: 'c2', jobId: 'j2' });
|
|
303
291
|
|
|
304
292
|
// Test commenting
|
|
305
293
|
act(() => {
|
|
@@ -158,7 +158,7 @@ describe('Toast Notifications - Verifies Toast Integration', () => {
|
|
|
158
158
|
// Emit generation failed event
|
|
159
159
|
act(() => {
|
|
160
160
|
eventBusInstance.get('yield:failed').next({
|
|
161
|
-
error:
|
|
161
|
+
error: 'Failed to generate document',
|
|
162
162
|
});
|
|
163
163
|
});
|
|
164
164
|
|