@semiont/react-ui 0.5.27 → 0.5.29
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 +3 -3
- package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +32 -5
- package/dist/{ar-OU3DQGC6.js → ar-AZBRADEB.js} +43 -23
- package/dist/{bn-PHWCR6AZ.js → bn-CCEW6AOS.js} +43 -23
- package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
- package/dist/{chunk-5VDTWRXJ.js → chunk-G4VHL4UA.js} +31 -31
- package/dist/{chunk-PCRRBU7H.js → chunk-SDKFZLEX.js} +43 -23
- package/dist/{cs-NEXYZ62I.js → cs-HXPF2CTE.js} +43 -23
- package/dist/{da-FAT43YCU.js → da-K5NTK7LH.js} +43 -23
- package/dist/{de-KRQ3WBCU.js → de-GMRQEDZI.js} +43 -23
- package/dist/{el-Q4IU3EKC.js → el-EUB4CI4G.js} +43 -23
- package/dist/{en-2KADE7N4.js → en-R5XA26NF.js} +2 -2
- package/dist/{es-2IO3R2MK.js → es-Z32DAZ6W.js} +43 -23
- package/dist/{fa-Q5GOQFXX.js → fa-G7HN6B32.js} +43 -23
- package/dist/{fi-R6NMRWBE.js → fi-3YBLZO7I.js} +43 -23
- package/dist/{fr-QY4D5E6J.js → fr-VUBH7BBS.js} +43 -23
- package/dist/{he-X6Y77XHY.js → he-WQQEY26B.js} +43 -23
- package/dist/{hi-XL7IOEQM.js → hi-HJZ6W4JB.js} +43 -23
- package/dist/{id-QZ4ZZS67.js → id-ZOGZ37MI.js} +43 -23
- package/dist/index.css +279 -74
- package/dist/index.d.ts +318 -393
- package/dist/index.js +2619 -2687
- package/dist/{it-ME2Q7OB6.js → it-DT6UW66F.js} +43 -23
- package/dist/{ja-HX7QASRI.js → ja-RUKQCOLO.js} +43 -23
- package/dist/{ko-EQO5AHAV.js → ko-ZH5ADKZM.js} +43 -23
- package/dist/{ms-UILFSF3I.js → ms-Y2AFPZTU.js} +43 -23
- package/dist/{nl-NNI45PMK.js → nl-OQEWIXNA.js} +43 -23
- package/dist/{no-MAL5F6P6.js → no-KPAYJZWY.js} +43 -23
- package/dist/{pl-4RMY6JAJ.js → pl-WSXK2UUD.js} +43 -23
- package/dist/{pt-VQH2X3QQ.js → pt-NWRJRUCK.js} +43 -23
- package/dist/{ro-XXCQO2OG.js → ro-3CGVMOC6.js} +43 -23
- package/dist/{sv-7Z6EJ6U4.js → sv-HGSEBA7S.js} +43 -23
- package/dist/test-utils.js +16 -7
- package/dist/{th-L3DXXYVQ.js → th-NZ4G352Z.js} +43 -23
- package/dist/{tr-P3DFVLFJ.js → tr-LGGS4DLW.js} +43 -23
- package/dist/{uk-7C5D3PXS.js → uk-AIX536L4.js} +43 -23
- package/dist/{vi-ZLTLEO7P.js → vi-V6CVQZLQ.js} +43 -23
- package/dist/{zh-5FHVWDIN.js → zh-AGKPGI3L.js} +43 -23
- package/package.json +16 -15
- package/src/components/AssistProgress.tsx +36 -2
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/StatusDisplay.tsx +13 -13
- package/src/components/__tests__/AssistProgress.test.tsx +59 -0
- package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
- package/src/components/modals/ComposeStep.tsx +233 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +178 -34
- package/src/components/modals/ConfigureSearchStep.tsx +19 -0
- package/src/components/modals/ContextSummary.tsx +185 -72
- package/src/components/modals/CorpusPane.tsx +75 -0
- package/src/components/modals/DiscardPrompt.tsx +38 -0
- package/src/components/modals/GatherContextStep.tsx +237 -107
- package/src/components/modals/ReferenceWizardModal.tsx +252 -59
- package/src/components/modals/ResourceGenerateModal.tsx +225 -112
- package/src/components/modals/SearchModal.css +337 -101
- package/src/components/modals/SearchResultsStep.tsx +61 -68
- package/src/components/modals/WizardFooter.tsx +7 -1
- package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
- package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +206 -8
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +442 -39
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +300 -86
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +116 -9
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
- package/src/components/resource/AnnotateView.tsx +3 -3
- package/src/components/resource/BrowseView.tsx +8 -8
- package/src/components/resource/DownloadFileLink.tsx +1 -1
- package/src/components/resource/ResourceViewer.tsx +4 -4
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +11 -11
- package/src/components/resource/__tests__/download-link.test.tsx +6 -6
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/panels/AssistSection.css +35 -0
- package/src/components/resource/panels/ReferenceEntry.tsx +11 -4
- package/src/components/resource/panels/ReferencesPanel.tsx +9 -1
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +84 -3
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +149 -7
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
- package/src/features/auth/components/SignInForm.tsx +53 -53
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +35 -133
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
- package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +73 -17
- package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +148 -83
- package/src/features/resource-viewer/generation-options.ts +29 -0
- package/src/styles/core/forms.css +35 -8
- package/src/styles/features/resource-viewer.css +0 -22
- package/src/styles/features/resource.css +0 -4
- package/src/styles/index.css +0 -1
- package/translations/ar.json +42 -22
- package/translations/bn.json +42 -22
- package/translations/cs.json +42 -22
- package/translations/da.json +42 -22
- package/translations/de.json +42 -22
- package/translations/el.json +42 -22
- package/translations/en.json +42 -22
- package/translations/es.json +42 -22
- package/translations/fa.json +42 -22
- package/translations/fi.json +42 -22
- package/translations/fr.json +42 -22
- package/translations/he.json +42 -22
- package/translations/hi.json +42 -22
- package/translations/id.json +42 -22
- package/translations/it.json +42 -22
- package/translations/ja.json +42 -22
- package/translations/ko.json +42 -22
- package/translations/ms.json +42 -22
- package/translations/nl.json +42 -22
- package/translations/no.json +42 -22
- package/translations/pl.json +42 -22
- package/translations/pt.json +42 -22
- package/translations/ro.json +42 -22
- package/translations/sv.json +42 -22
- package/translations/th.json +42 -22
- package/translations/tr.json +42 -22
- package/translations/uk.json +42 -22
- package/translations/vi.json +42 -22
- package/translations/zh.json +42 -22
- package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
- package/dist/ar-OU3DQGC6.js.map +0 -1
- package/dist/bn-PHWCR6AZ.js.map +0 -1
- package/dist/chunk-5VDTWRXJ.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-PCRRBU7H.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/cs-NEXYZ62I.js.map +0 -1
- package/dist/da-FAT43YCU.js.map +0 -1
- package/dist/de-KRQ3WBCU.js.map +0 -1
- package/dist/el-Q4IU3EKC.js.map +0 -1
- package/dist/en-2KADE7N4.js.map +0 -1
- package/dist/es-2IO3R2MK.js.map +0 -1
- package/dist/fa-Q5GOQFXX.js.map +0 -1
- package/dist/fi-R6NMRWBE.js.map +0 -1
- package/dist/fr-QY4D5E6J.js.map +0 -1
- package/dist/he-X6Y77XHY.js.map +0 -1
- package/dist/hi-XL7IOEQM.js.map +0 -1
- package/dist/id-QZ4ZZS67.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/css-modules-helper.js.map +0 -1
- package/dist/integrations/styled-components-theme.js.map +0 -1
- package/dist/it-ME2Q7OB6.js.map +0 -1
- package/dist/ja-HX7QASRI.js.map +0 -1
- package/dist/ko-EQO5AHAV.js.map +0 -1
- package/dist/ms-UILFSF3I.js.map +0 -1
- package/dist/nl-NNI45PMK.js.map +0 -1
- package/dist/no-MAL5F6P6.js.map +0 -1
- package/dist/pl-4RMY6JAJ.js.map +0 -1
- package/dist/pt-VQH2X3QQ.js.map +0 -1
- package/dist/ro-XXCQO2OG.js.map +0 -1
- package/dist/sv-7Z6EJ6U4.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-L3DXXYVQ.js.map +0 -1
- package/dist/tr-P3DFVLFJ.js.map +0 -1
- package/dist/uk-7C5D3PXS.js.map +0 -1
- package/dist/vi-ZLTLEO7P.js.map +0 -1
- package/dist/zh-5FHVWDIN.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
- package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
- package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
- package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
- package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
- package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
- package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
- package/src/styles/features/exchange.css +0 -374
|
@@ -2,8 +2,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
-
import
|
|
6
|
-
import type {
|
|
5
|
+
import { BehaviorSubject } from 'rxjs';
|
|
6
|
+
import type { EventBus, ResourceDescriptor } from '@semiont/core';
|
|
7
|
+
import { resourceId as makeResourceId } from '@semiont/core';
|
|
8
|
+
import type { CacheObservable, CacheState, SemiontClient, SemiontSession } from '@semiont/sdk';
|
|
7
9
|
import { ResourceInfoPanel } from '../ResourceInfoPanel';
|
|
8
10
|
import { createTestSemiontWrapper } from '../../../../test-utils';
|
|
9
11
|
|
|
@@ -71,7 +73,7 @@ function createEventTracker() {
|
|
|
71
73
|
eventBus.get('yield:clone').subscribe((payload: any) => {
|
|
72
74
|
events.push({ event: 'yield:clone', payload });
|
|
73
75
|
});
|
|
74
|
-
// `mark:archive` / `mark:unarchive` are
|
|
76
|
+
// `mark:archive` / `mark:unarchive` are gateway-routed via `actor.emit`;
|
|
75
77
|
// spy on the namespace methods instead of subscribing to a local bus.
|
|
76
78
|
const origArchive = client.mark.archive.bind(client.mark);
|
|
77
79
|
const origUnarchive = client.mark.unarchive.bind(client.mark);
|
|
@@ -281,17 +283,19 @@ describe('ResourceInfoPanel Component', () => {
|
|
|
281
283
|
});
|
|
282
284
|
|
|
283
285
|
describe('Generate Action', () => {
|
|
286
|
+
// The shell's collapsible header is ALSO a button named "Generate", so the
|
|
287
|
+
// form control is targeted by its exact accessible name (the ✨ is part of it).
|
|
284
288
|
it('renders the Generate button when onGenerate is provided', () => {
|
|
285
289
|
renderWithEventBus(
|
|
286
290
|
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}} />
|
|
287
291
|
);
|
|
288
|
-
expect(screen.getByRole('button', { name:
|
|
292
|
+
expect(screen.getByRole('button', { name: '✨ Generate' })).toBeInTheDocument();
|
|
289
293
|
expect(screen.getByText("Generate a new resource from this one's context")).toBeInTheDocument();
|
|
290
294
|
});
|
|
291
295
|
|
|
292
296
|
it('hides the Generate button when onGenerate is omitted', () => {
|
|
293
297
|
renderWithEventBus(<ResourceInfoPanel {...defaultProps} />);
|
|
294
|
-
expect(screen.queryByRole('button', { name:
|
|
298
|
+
expect(screen.queryByRole('button', { name: '✨ Generate' })).not.toBeInTheDocument();
|
|
295
299
|
});
|
|
296
300
|
|
|
297
301
|
it('calls onGenerate when clicked', () => {
|
|
@@ -299,9 +303,117 @@ describe('ResourceInfoPanel Component', () => {
|
|
|
299
303
|
renderWithEventBus(
|
|
300
304
|
<ResourceInfoPanel {...defaultProps} onGenerate={onGenerate} />
|
|
301
305
|
);
|
|
302
|
-
fireEvent.click(screen.getByRole('button', { name:
|
|
306
|
+
fireEvent.click(screen.getByRole('button', { name: '✨ Generate' }));
|
|
303
307
|
expect(onGenerate).toHaveBeenCalledTimes(1);
|
|
304
308
|
});
|
|
309
|
+
|
|
310
|
+
// ── GENERATE-FROM-RESOURCE P2 (D7/D8): the panel is the progress surface ──
|
|
311
|
+
|
|
312
|
+
it('the Generate control lives inside the assist shell (D7)', () => {
|
|
313
|
+
const { container } = renderWithEventBus(
|
|
314
|
+
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}}
|
|
315
|
+
isGenerating={false} generationProgress={null} />
|
|
316
|
+
);
|
|
317
|
+
const shell = container.querySelector('.semiont-assist-widget[data-type="generation"]');
|
|
318
|
+
expect(shell).not.toBeNull();
|
|
319
|
+
expect(shell!.querySelector('button')).not.toBeNull(); // the form IS the Generate control
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('progress replaces the Generate form while a generation runs', () => {
|
|
323
|
+
const { container } = renderWithEventBus(
|
|
324
|
+
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}}
|
|
325
|
+
isGenerating
|
|
326
|
+
generationProgress={{ percentage: 40, message: { code: 'generating-resource' } }} />
|
|
327
|
+
);
|
|
328
|
+
expect(container.querySelector('.semiont-assist-progress')).not.toBeNull();
|
|
329
|
+
expect(screen.queryByRole('button', { name: '✨ Generate' })).toBeNull();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('the ended frame links the generated resource by name, and dismisses (D8)', () => {
|
|
333
|
+
const onDismissProgress = vi.fn();
|
|
334
|
+
const { client } = renderWithEventBus(
|
|
335
|
+
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}}
|
|
336
|
+
isGenerating={false}
|
|
337
|
+
generationProgress={{ percentage: 100, message: { code: 'complete-generated', truncated: false } }}
|
|
338
|
+
generationOutcome={{
|
|
339
|
+
resourceId: makeResourceId('urn:semiont:resource:new1'),
|
|
340
|
+
resourceName: 'Summary of PB',
|
|
341
|
+
truncated: false,
|
|
342
|
+
}}
|
|
343
|
+
onDismissProgress={onDismissProgress} />
|
|
344
|
+
);
|
|
345
|
+
const openSpy = vi.spyOn(client.browse, 'openResource');
|
|
346
|
+
|
|
347
|
+
fireEvent.click(screen.getByRole('button', { name: 'Summary of PB' }));
|
|
348
|
+
expect(openSpy).toHaveBeenCalledTimes(1);
|
|
349
|
+
expect(String(openSpy.mock.calls[0]![0])).toBe('urn:semiont:resource:new1');
|
|
350
|
+
|
|
351
|
+
fireEvent.click(screen.getByTestId('semiont-assist-control'));
|
|
352
|
+
expect(onDismissProgress).toHaveBeenCalledTimes(1);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('a truncated completion says so; a natural stop does not (GFR P3b, D6)', () => {
|
|
356
|
+
// The mocked translator echoes unknown keys, so the assertion reads the
|
|
357
|
+
// KEY the real assistProgressCopy branch picked — structure, not copy.
|
|
358
|
+
const base = { ...defaultProps, onGenerate: () => {}, isGenerating: false };
|
|
359
|
+
const { unmount } = renderWithEventBus(
|
|
360
|
+
<ResourceInfoPanel {...base}
|
|
361
|
+
generationProgress={{ percentage: 100, message: { code: 'complete-generated', truncated: true } }} />
|
|
362
|
+
);
|
|
363
|
+
expect(screen.getByTestId('semiont-assist-status').textContent)
|
|
364
|
+
.toContain('codeCompleteGeneratedTruncated');
|
|
365
|
+
unmount();
|
|
366
|
+
|
|
367
|
+
renderWithEventBus(
|
|
368
|
+
<ResourceInfoPanel {...base}
|
|
369
|
+
generationProgress={{ percentage: 100, message: { code: 'complete-generated', truncated: false } }} />
|
|
370
|
+
);
|
|
371
|
+
expect(screen.getByTestId('semiont-assist-status').textContent)
|
|
372
|
+
.toContain('codeCompleteGenerated');
|
|
373
|
+
expect(screen.getByTestId('semiont-assist-status').textContent)
|
|
374
|
+
.not.toContain('Truncated');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('the ended frame speaks completion even when the final progress frame lost the race (GENERATION-ARRIVAL D4/D5)', () => {
|
|
378
|
+
// The screenshot case: last frame is the 95% "creating" payload, the
|
|
379
|
+
// terminal 100% frame never arrived — but the outcome did. The sentence
|
|
380
|
+
// derives from the OUTCOME, not the racing frame.
|
|
381
|
+
renderWithEventBus(
|
|
382
|
+
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}}
|
|
383
|
+
isGenerating={false}
|
|
384
|
+
generationProgress={{ percentage: 95, message: { code: 'creating-resource' } }}
|
|
385
|
+
generationOutcome={{
|
|
386
|
+
resourceId: makeResourceId('urn:semiont:resource:new1'),
|
|
387
|
+
resourceName: 'Summary of PB',
|
|
388
|
+
truncated: false,
|
|
389
|
+
}} />
|
|
390
|
+
);
|
|
391
|
+
expect(screen.getByTestId('semiont-assist-status').textContent).toBe('codeCompleteGenerated');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('a truncated outcome says so — the bit rides the outcome, not the racing frame (D5)', () => {
|
|
395
|
+
renderWithEventBus(
|
|
396
|
+
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}}
|
|
397
|
+
isGenerating={false}
|
|
398
|
+
generationProgress={{ percentage: 95, message: { code: 'creating-resource' } }}
|
|
399
|
+
generationOutcome={{
|
|
400
|
+
resourceId: makeResourceId('urn:semiont:resource:new1'),
|
|
401
|
+
resourceName: 'Summary of PB',
|
|
402
|
+
truncated: true,
|
|
403
|
+
}} />
|
|
404
|
+
);
|
|
405
|
+
expect(screen.getByTestId('semiont-assist-status').textContent).toBe('codeCompleteGeneratedTruncated');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('no outcome, no link: the ended frame renders without one until job:complete arrives', () => {
|
|
409
|
+
renderWithEventBus(
|
|
410
|
+
<ResourceInfoPanel {...defaultProps} onGenerate={() => {}}
|
|
411
|
+
isGenerating={false}
|
|
412
|
+
generationProgress={{ percentage: 100, message: { code: 'complete-generated', truncated: false } }}
|
|
413
|
+
generationOutcome={null} />
|
|
414
|
+
);
|
|
415
|
+
expect(screen.queryByText('Summary of PB')).toBeNull();
|
|
416
|
+
});
|
|
305
417
|
});
|
|
306
418
|
|
|
307
419
|
describe('Provenance Section', () => {
|
|
@@ -350,7 +462,9 @@ describe('ResourceInfoPanel Component', () => {
|
|
|
350
462
|
expect(screen.getByText('Alice, Bob')).toBeInTheDocument();
|
|
351
463
|
});
|
|
352
464
|
|
|
353
|
-
it('should render wasDerivedFrom when provided', () => {
|
|
465
|
+
it('should render wasDerivedFrom when provided — the id is the unresolved fallback', () => {
|
|
466
|
+
// The factory client's fetch fails in jsdom (no server), so the
|
|
467
|
+
// descriptor never resolves and the raw id stays as the honest label.
|
|
354
468
|
renderWithEventBus(
|
|
355
469
|
<ResourceInfoPanel
|
|
356
470
|
{...defaultProps}
|
|
@@ -382,6 +496,34 @@ describe('ResourceInfoPanel Component', () => {
|
|
|
382
496
|
expect(String(openSpy.mock.calls[0]![0])).toBe('urn:semiont:resource:abc123');
|
|
383
497
|
});
|
|
384
498
|
|
|
499
|
+
it('resolves the derived-from id to its resource NAME; clicks still navigate by id', () => {
|
|
500
|
+
// The id is wire identity, not presentation. Once `browse.resource`
|
|
501
|
+
// serves the source's descriptor, the link wears its name — the raw
|
|
502
|
+
// hash only survives while the descriptor is unresolved (pin below).
|
|
503
|
+
const { SemiontWrapper, session, client } = createTestSemiontWrapper();
|
|
504
|
+
vi.spyOn(client.browse, 'resource').mockReturnValue(
|
|
505
|
+
new BehaviorSubject<CacheState<ResourceDescriptor>>({
|
|
506
|
+
status: 'ready',
|
|
507
|
+
value: { '@id': 'urn:semiont:resource:abc123', name: 'Source Doc' } as ResourceDescriptor,
|
|
508
|
+
}) as unknown as CacheObservable<ResourceDescriptor>,
|
|
509
|
+
);
|
|
510
|
+
const openSpy = vi.spyOn(client.browse, 'openResource');
|
|
511
|
+
render(
|
|
512
|
+
<ResourceInfoPanel
|
|
513
|
+
{...defaultProps}
|
|
514
|
+
session={session}
|
|
515
|
+
wasDerivedFrom="urn:semiont:resource:abc123"
|
|
516
|
+
/>,
|
|
517
|
+
{ wrapper: ({ children }) => <SemiontWrapper>{children}</SemiontWrapper> },
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
expect(screen.getByText('Source Doc')).toBeInTheDocument();
|
|
521
|
+
expect(screen.queryByText('urn:semiont:resource:abc123')).toBeNull();
|
|
522
|
+
|
|
523
|
+
fireEvent.click(screen.getByText('Source Doc'));
|
|
524
|
+
expect(String(openSpy.mock.calls[0]![0])).toBe('urn:semiont:resource:abc123');
|
|
525
|
+
});
|
|
526
|
+
|
|
385
527
|
it('renders every id when the resource derives from several', () => {
|
|
386
528
|
renderWithEventBus(
|
|
387
529
|
<ResourceInfoPanel
|
|
@@ -29,8 +29,8 @@ const mockTranslations = {
|
|
|
29
29
|
welcomeBack: 'Welcome back to Semiont',
|
|
30
30
|
signInPrompt: 'Sign in to your knowledge workspace',
|
|
31
31
|
continueWithGoogle: 'Continue with Google',
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
gatewayUrlLabel: 'Gateway URL',
|
|
33
|
+
gatewayUrlPlaceholder: 'http://localhost:4000',
|
|
34
34
|
emailLabel: 'Email',
|
|
35
35
|
emailPlaceholder: 'your@email.com',
|
|
36
36
|
passwordLabel: 'Password',
|
|
@@ -44,9 +44,9 @@ const mockTranslations = {
|
|
|
44
44
|
signUpInstead: 'Sign Up Instead',
|
|
45
45
|
errorEmailRequired: 'Email is required',
|
|
46
46
|
errorPasswordRequired: 'Password is required',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
errorGatewayUrlRequired: 'Gateway URL is required',
|
|
48
|
+
errorGatewayUrlInvalid: 'Gateway URL is not valid',
|
|
49
|
+
errorGatewayUrlUnreachable: 'Gateway URL is unreachable',
|
|
50
50
|
tagline: 'make meaning',
|
|
51
51
|
};
|
|
52
52
|
|
|
@@ -58,7 +58,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
58
58
|
const { container } = render(
|
|
59
59
|
<SignInForm
|
|
60
60
|
onGoogleSignIn={onGoogleSignIn}
|
|
61
|
-
|
|
61
|
+
gatewayUrl="http://localhost:4000"
|
|
62
62
|
Link={MockLink}
|
|
63
63
|
translations={mockTranslations}
|
|
64
64
|
/>
|
|
@@ -206,7 +206,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
206
206
|
render(
|
|
207
207
|
<SignInForm
|
|
208
208
|
onGoogleSignIn={onGoogleSignIn}
|
|
209
|
-
|
|
209
|
+
gatewayUrl="http://localhost:4000"
|
|
210
210
|
Link={MockLink}
|
|
211
211
|
translations={mockTranslations}
|
|
212
212
|
/>
|
|
@@ -244,7 +244,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
244
244
|
render(
|
|
245
245
|
<SignInForm
|
|
246
246
|
onGoogleSignIn={onGoogleSignIn}
|
|
247
|
-
|
|
247
|
+
gatewayUrl="http://localhost:4000"
|
|
248
248
|
Link={MockLink}
|
|
249
249
|
translations={mockTranslations}
|
|
250
250
|
/>
|
|
@@ -261,7 +261,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
261
261
|
render(
|
|
262
262
|
<SignInForm
|
|
263
263
|
onGoogleSignIn={onGoogleSignIn}
|
|
264
|
-
|
|
264
|
+
gatewayUrl="http://localhost:4000"
|
|
265
265
|
Link={MockLink}
|
|
266
266
|
translations={mockTranslations}
|
|
267
267
|
/>
|
|
@@ -361,7 +361,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
361
361
|
render(
|
|
362
362
|
<SignInForm
|
|
363
363
|
onGoogleSignIn={onGoogleSignIn}
|
|
364
|
-
|
|
364
|
+
gatewayUrl="http://localhost:4000"
|
|
365
365
|
Link={MockLink}
|
|
366
366
|
translations={mockTranslations}
|
|
367
367
|
/>
|
|
@@ -379,7 +379,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
379
379
|
const { container } = render(
|
|
380
380
|
<SignInForm
|
|
381
381
|
onGoogleSignIn={onGoogleSignIn}
|
|
382
|
-
|
|
382
|
+
gatewayUrl="http://localhost:4000"
|
|
383
383
|
Link={MockLink}
|
|
384
384
|
translations={mockTranslations}
|
|
385
385
|
/>
|
|
@@ -397,7 +397,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
397
397
|
const { container } = render(
|
|
398
398
|
<SignInForm
|
|
399
399
|
onGoogleSignIn={onGoogleSignIn}
|
|
400
|
-
|
|
400
|
+
gatewayUrl="http://localhost:4000"
|
|
401
401
|
Link={MockLink}
|
|
402
402
|
translations={mockTranslations}
|
|
403
403
|
/>
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Supports both Google OAuth and email/password credentials.
|
|
5
5
|
* No Next.js dependencies - all data via props.
|
|
6
6
|
*
|
|
7
|
-
* When
|
|
8
|
-
* When
|
|
7
|
+
* When gatewayUrl is provided it is shown as a locked read-only field.
|
|
8
|
+
* When gatewayUrl is omitted the user must enter a gateway URL to connect to.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React from 'react';
|
|
@@ -15,21 +15,21 @@ import '../auth.css';
|
|
|
15
15
|
export interface SignInFormProps {
|
|
16
16
|
/**
|
|
17
17
|
* Callback when user clicks Google sign-in.
|
|
18
|
-
* Receives the
|
|
18
|
+
* Receives the gateway URL (either the locked one or what the user typed).
|
|
19
19
|
*/
|
|
20
|
-
onGoogleSignIn: (
|
|
20
|
+
onGoogleSignIn: (gatewayUrl: string) => Promise<void>;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Callback when user submits email/password credentials.
|
|
24
|
-
* Receives the
|
|
24
|
+
* Receives the gateway URL along with email and password.
|
|
25
25
|
*/
|
|
26
|
-
onCredentialsSignIn?: ((
|
|
26
|
+
onCredentialsSignIn?: ((gatewayUrl: string, email: string, password: string) => Promise<void>) | undefined;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Pre-filled
|
|
29
|
+
* Pre-filled gateway URL. When provided the field is read-only (re-auth to known workspace).
|
|
30
30
|
* When omitted the user enters the URL themselves (new connection).
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
gatewayUrl?: string;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Error message to display (if any)
|
|
@@ -59,8 +59,8 @@ export interface SignInFormProps {
|
|
|
59
59
|
welcomeBack: string;
|
|
60
60
|
signInPrompt: string;
|
|
61
61
|
continueWithGoogle: string;
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
gatewayUrlLabel: string;
|
|
63
|
+
gatewayUrlPlaceholder: string;
|
|
64
64
|
emailLabel: string;
|
|
65
65
|
emailPlaceholder: string;
|
|
66
66
|
passwordLabel: string;
|
|
@@ -72,9 +72,9 @@ export interface SignInFormProps {
|
|
|
72
72
|
backToHome: string;
|
|
73
73
|
learnMore: string;
|
|
74
74
|
signUpInstead: string;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
errorGatewayUrlRequired: string;
|
|
76
|
+
errorGatewayUrlInvalid: string;
|
|
77
|
+
errorGatewayUrlUnreachable: string;
|
|
78
78
|
errorEmailRequired: string;
|
|
79
79
|
errorPasswordRequired: string;
|
|
80
80
|
tagline: string;
|
|
@@ -117,23 +117,23 @@ function normalizeUrlForProbe(raw: string): string | null {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* CredentialsAuthForm -
|
|
121
|
-
* When
|
|
120
|
+
* CredentialsAuthForm - Gateway URL + email/password form.
|
|
121
|
+
* When lockedGatewayUrl is provided, the URL field is shown read-only.
|
|
122
122
|
*/
|
|
123
123
|
function CredentialsAuthForm({
|
|
124
|
-
|
|
124
|
+
lockedGatewayUrl,
|
|
125
125
|
onSubmit,
|
|
126
126
|
translations: t,
|
|
127
127
|
}: {
|
|
128
|
-
|
|
129
|
-
onSubmit: (
|
|
128
|
+
lockedGatewayUrl: string | undefined;
|
|
129
|
+
onSubmit: (gatewayUrl: string, email: string, password: string) => Promise<void>;
|
|
130
130
|
translations: SignInFormProps['translations'];
|
|
131
131
|
}) {
|
|
132
|
-
const [
|
|
132
|
+
const [gatewayUrl, setGatewayUrl] = React.useState(lockedGatewayUrl ?? '');
|
|
133
133
|
const [email, setEmail] = React.useState('');
|
|
134
134
|
const [password, setPassword] = React.useState('');
|
|
135
135
|
const [validationError, setValidationError] = React.useState<string | null>(null);
|
|
136
|
-
const [fieldErrors, setFieldErrors] = React.useState<{
|
|
136
|
+
const [fieldErrors, setFieldErrors] = React.useState<{ gatewayUrl?: string; email?: string; password?: string }>({});
|
|
137
137
|
const [urlProbeStatus, setUrlProbeStatus] = React.useState<UrlProbeStatus>('idle');
|
|
138
138
|
|
|
139
139
|
const probeUrl = async (raw: string) => {
|
|
@@ -141,7 +141,7 @@ function CredentialsAuthForm({
|
|
|
141
141
|
if (!url) return;
|
|
142
142
|
// Validate format
|
|
143
143
|
try { new URL(url); } catch {
|
|
144
|
-
setFieldErrors(prev => ({ ...prev,
|
|
144
|
+
setFieldErrors(prev => ({ ...prev, gatewayUrl: t.errorGatewayUrlInvalid }));
|
|
145
145
|
setUrlProbeStatus('error');
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
@@ -150,19 +150,19 @@ function CredentialsAuthForm({
|
|
|
150
150
|
const res = await fetch(`${url}/api/health`, { signal: AbortSignal.timeout(5000) });
|
|
151
151
|
setUrlProbeStatus(res.ok ? 'ok' : 'error');
|
|
152
152
|
if (!res.ok) {
|
|
153
|
-
setFieldErrors(prev => ({ ...prev,
|
|
153
|
+
setFieldErrors(prev => ({ ...prev, gatewayUrl: t.errorGatewayUrlUnreachable }));
|
|
154
154
|
}
|
|
155
155
|
} catch {
|
|
156
156
|
setUrlProbeStatus('error');
|
|
157
|
-
setFieldErrors(prev => ({ ...prev,
|
|
157
|
+
setFieldErrors(prev => ({ ...prev, gatewayUrl: t.errorGatewayUrlUnreachable }));
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
160
|
|
|
161
161
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
162
162
|
e.preventDefault();
|
|
163
|
-
const errors: {
|
|
163
|
+
const errors: { gatewayUrl?: string; email?: string; password?: string } = {};
|
|
164
164
|
|
|
165
|
-
if (!
|
|
165
|
+
if (!gatewayUrl.trim()) errors.gatewayUrl = t.errorGatewayUrlRequired;
|
|
166
166
|
if (!email) errors.email = t.errorEmailRequired;
|
|
167
167
|
if (!password) errors.password = t.errorPasswordRequired;
|
|
168
168
|
|
|
@@ -174,10 +174,10 @@ function CredentialsAuthForm({
|
|
|
174
174
|
|
|
175
175
|
setFieldErrors({});
|
|
176
176
|
setValidationError(null);
|
|
177
|
-
await onSubmit(
|
|
177
|
+
await onSubmit(gatewayUrl.trim(), email, password);
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
-
const probeIndicator = !
|
|
180
|
+
const probeIndicator = !lockedGatewayUrl && urlProbeStatus !== 'idle' ? (
|
|
181
181
|
<span
|
|
182
182
|
className={`semiont-form__url-status semiont-form__url-status--${urlProbeStatus}`}
|
|
183
183
|
aria-live="polite"
|
|
@@ -198,33 +198,33 @@ function CredentialsAuthForm({
|
|
|
198
198
|
|
|
199
199
|
<form onSubmit={handleSubmit} className="semiont-auth__form" noValidate>
|
|
200
200
|
<div className="semiont-form__field">
|
|
201
|
-
<label htmlFor="
|
|
202
|
-
{t.
|
|
201
|
+
<label htmlFor="gateway-url" className="semiont-form__label">
|
|
202
|
+
{t.gatewayUrlLabel}
|
|
203
203
|
</label>
|
|
204
204
|
<div className="semiont-form__input-row">
|
|
205
205
|
<input
|
|
206
|
-
id="
|
|
206
|
+
id="gateway-url"
|
|
207
207
|
type="url"
|
|
208
|
-
value={
|
|
208
|
+
value={gatewayUrl}
|
|
209
209
|
onChange={(e) => {
|
|
210
|
-
if (
|
|
211
|
-
|
|
210
|
+
if (lockedGatewayUrl) return;
|
|
211
|
+
setGatewayUrl(e.target.value);
|
|
212
212
|
setUrlProbeStatus('idle');
|
|
213
|
-
if (fieldErrors.
|
|
213
|
+
if (fieldErrors.gatewayUrl) setFieldErrors({ ...fieldErrors, gatewayUrl: undefined });
|
|
214
214
|
}}
|
|
215
|
-
onBlur={() => { if (!
|
|
216
|
-
readOnly={!!
|
|
217
|
-
placeholder={t.
|
|
218
|
-
className={`semiont-input${
|
|
219
|
-
aria-invalid={!!fieldErrors.
|
|
220
|
-
aria-describedby={fieldErrors.
|
|
215
|
+
onBlur={() => { if (!lockedGatewayUrl) probeUrl(gatewayUrl); }}
|
|
216
|
+
readOnly={!!lockedGatewayUrl}
|
|
217
|
+
placeholder={t.gatewayUrlPlaceholder}
|
|
218
|
+
className={`semiont-input${lockedGatewayUrl ? ' semiont-input--readonly' : ''}`}
|
|
219
|
+
aria-invalid={!!fieldErrors.gatewayUrl}
|
|
220
|
+
aria-describedby={fieldErrors.gatewayUrl ? 'gateway-url-error' : undefined}
|
|
221
221
|
required
|
|
222
222
|
/>
|
|
223
223
|
{probeIndicator}
|
|
224
224
|
</div>
|
|
225
|
-
{fieldErrors.
|
|
226
|
-
<span id="
|
|
227
|
-
{fieldErrors.
|
|
225
|
+
{fieldErrors.gatewayUrl && (
|
|
226
|
+
<span id="gateway-url-error" className="semiont-form__error" role="alert">
|
|
227
|
+
{fieldErrors.gatewayUrl}
|
|
228
228
|
</span>
|
|
229
229
|
)}
|
|
230
230
|
</div>
|
|
@@ -295,23 +295,23 @@ function CredentialsAuthForm({
|
|
|
295
295
|
/**
|
|
296
296
|
* SignInForm - Main sign-in / connect component.
|
|
297
297
|
*
|
|
298
|
-
* When
|
|
299
|
-
* When
|
|
298
|
+
* When gatewayUrl is provided (re-auth to known workspace) the URL field is locked.
|
|
299
|
+
* When gatewayUrl is omitted (new connection) the user enters the URL themselves.
|
|
300
300
|
*/
|
|
301
301
|
export function SignInForm({
|
|
302
302
|
onGoogleSignIn,
|
|
303
303
|
onCredentialsSignIn,
|
|
304
|
-
|
|
304
|
+
gatewayUrl,
|
|
305
305
|
error,
|
|
306
306
|
showCredentialsAuth = false,
|
|
307
307
|
isLoading = false,
|
|
308
308
|
translations: t,
|
|
309
309
|
}: SignInFormProps) {
|
|
310
310
|
const handleGoogleClick = () => {
|
|
311
|
-
// For Google auth we need the
|
|
312
|
-
// The caller is responsible for ensuring
|
|
313
|
-
if (
|
|
314
|
-
onGoogleSignIn(
|
|
311
|
+
// For Google auth we need the gateway URL from the locked prop or we cannot proceed.
|
|
312
|
+
// The caller is responsible for ensuring gatewayUrl is set when Google sign-in is offered.
|
|
313
|
+
if (gatewayUrl) {
|
|
314
|
+
onGoogleSignIn(gatewayUrl);
|
|
315
315
|
}
|
|
316
316
|
};
|
|
317
317
|
|
|
@@ -348,14 +348,14 @@ export function SignInForm({
|
|
|
348
348
|
<>
|
|
349
349
|
{showCredentialsAuth && onCredentialsSignIn && (
|
|
350
350
|
<CredentialsAuthForm
|
|
351
|
-
|
|
351
|
+
lockedGatewayUrl={gatewayUrl}
|
|
352
352
|
onSubmit={onCredentialsSignIn}
|
|
353
353
|
translations={t}
|
|
354
354
|
/>
|
|
355
355
|
)}
|
|
356
356
|
|
|
357
|
-
{/* Google sign-in only shown when a
|
|
358
|
-
{
|
|
357
|
+
{/* Google sign-in only shown when a gateway URL is known */}
|
|
358
|
+
{gatewayUrl && (
|
|
359
359
|
<button onClick={handleGoogleClick} className={`${buttonStyles.primary.base} semiont-button--full-width`}>
|
|
360
360
|
<GoogleIcon />
|
|
361
361
|
{t.continueWithGoogle}
|
|
@@ -3,7 +3,7 @@ import { firstValueFrom } from 'rxjs';
|
|
|
3
3
|
import { filter } from 'rxjs/operators';
|
|
4
4
|
import type { SemiontSession } from '@semiont/sdk';
|
|
5
5
|
import { AuthNamespace } from '@semiont/sdk';
|
|
6
|
-
import { createTestSession,
|
|
6
|
+
import { createTestSession, stubGateway } from '@semiont/sdk/testing';
|
|
7
7
|
import { createWelcomeStateUnit } from '../welcome-state-unit';
|
|
8
8
|
import { assertStateUnitAxioms } from '@semiont/core/testing/axioms';
|
|
9
9
|
|
|
@@ -25,7 +25,7 @@ afterEach(() => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
function makeSession(): SemiontSession {
|
|
28
|
-
return createTestSession({
|
|
28
|
+
return createTestSession({ gateway: stubGateway() }).session;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
describe('createWelcomeStateUnit', () => {
|
|
@@ -28,10 +28,7 @@ vi.mock('../../../components/CodeMirrorRenderer', () => ({
|
|
|
28
28
|
const createMockTranslations = () => ({
|
|
29
29
|
title: 'Compose Resource',
|
|
30
30
|
titleEditClone: 'Edit Cloned Resource',
|
|
31
|
-
titleCompleteReference: 'Complete Reference',
|
|
32
31
|
subtitleClone: 'Editing a cloned resource',
|
|
33
|
-
subtitleReference: 'Creating a new resource for reference',
|
|
34
|
-
linkedNoticePrefix: 'This resource will be linked',
|
|
35
32
|
resourceName: 'Resource Name',
|
|
36
33
|
resourceNamePlaceholder: 'Enter resource name',
|
|
37
34
|
entityTypes: 'Entity Types',
|
|
@@ -53,9 +50,7 @@ const createMockTranslations = () => ({
|
|
|
53
50
|
cancel: 'Cancel',
|
|
54
51
|
saving: 'Saving...',
|
|
55
52
|
creating: 'Creating...',
|
|
56
|
-
creatingAndLinking: 'Creating and linking...',
|
|
57
53
|
saveClonedResource: 'Save Cloned Resource',
|
|
58
|
-
createAndLinkResource: 'Create and Link Resource',
|
|
59
54
|
createResource: 'Create Resource',
|
|
60
55
|
});
|
|
61
56
|
|
|
@@ -207,66 +202,6 @@ describe('ResourceComposePage', () => {
|
|
|
207
202
|
});
|
|
208
203
|
});
|
|
209
204
|
|
|
210
|
-
describe('Reference Completion Mode', () => {
|
|
211
|
-
it('shows reference completion title', () => {
|
|
212
|
-
const props = createMockProps({
|
|
213
|
-
mode: 'reference',
|
|
214
|
-
referenceData: {
|
|
215
|
-
annotationUri: 'http://localhost:8080/annotations/ref-1',
|
|
216
|
-
sourceDocumentId: 'doc-1',
|
|
217
|
-
name: 'Referenced Resource',
|
|
218
|
-
entityTypes: ['Document'],
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
renderWithProviders(<ResourceComposePage {...props} />);
|
|
222
|
-
|
|
223
|
-
expect(screen.getByText('Complete Reference')).toBeInTheDocument();
|
|
224
|
-
expect(screen.getByText('Creating a new resource for reference')).toBeInTheDocument();
|
|
225
|
-
expect(screen.getByText('This resource will be linked')).toBeInTheDocument();
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('initializes with reference data', () => {
|
|
229
|
-
const props = createMockProps({
|
|
230
|
-
mode: 'reference',
|
|
231
|
-
referenceData: {
|
|
232
|
-
annotationUri: 'http://localhost:8080/annotations/ref-1',
|
|
233
|
-
sourceDocumentId: 'doc-1',
|
|
234
|
-
name: 'Referenced Resource',
|
|
235
|
-
entityTypes: ['Document', 'Article'],
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
renderWithProviders(<ResourceComposePage {...props} />);
|
|
239
|
-
|
|
240
|
-
const nameInput = screen.getByLabelText('Resource Name') as HTMLInputElement;
|
|
241
|
-
expect(nameInput.value).toBe('Referenced Resource');
|
|
242
|
-
|
|
243
|
-
expect(screen.getByText('Document')).toBeInTheDocument();
|
|
244
|
-
expect(screen.getByText('Article')).toBeInTheDocument();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('shows entity types as read-only when provided', () => {
|
|
248
|
-
const props = createMockProps({
|
|
249
|
-
mode: 'reference',
|
|
250
|
-
referenceData: {
|
|
251
|
-
annotationUri: 'http://localhost:8080/annotations/ref-1',
|
|
252
|
-
sourceDocumentId: 'doc-1',
|
|
253
|
-
name: 'Referenced Resource',
|
|
254
|
-
entityTypes: ['Document'],
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
renderWithProviders(<ResourceComposePage {...props} />);
|
|
258
|
-
|
|
259
|
-
// Should show read-only entity types
|
|
260
|
-
expect(screen.getByText('Document')).toBeInTheDocument();
|
|
261
|
-
|
|
262
|
-
// Should not show selectable buttons
|
|
263
|
-
expect(screen.queryByRole('button', { name: /Document entity type/ })).not.toBeInTheDocument();
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Entity Type Selection tests removed - functionality not present in current component
|
|
268
|
-
// The component only displays entity types in reference mode, doesn't have selection buttons
|
|
269
|
-
|
|
270
205
|
describe('Content Input Method', () => {
|
|
271
206
|
it('defaults to write mode', () => {
|
|
272
207
|
const props = createMockProps();
|