@semiont/react-ui 0.5.28 → 0.5.30

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.
Files changed (113) hide show
  1. package/README.md +3 -3
  2. package/dist/{PdfAnnotationCanvas.client-C2IGG4SW.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +4 -4
  3. package/dist/{ar-FABPTJVB.js → ar-AZBRADEB.js} +5 -1
  4. package/dist/{bn-LKP6BDMH.js → bn-CCEW6AOS.js} +5 -1
  5. package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
  6. package/dist/{chunk-2YHEE63S.js → chunk-G4VHL4UA.js} +31 -31
  7. package/dist/{chunk-2GOHTTHW.js → chunk-SDKFZLEX.js} +5 -1
  8. package/dist/{cs-YQYNWHRG.js → cs-HXPF2CTE.js} +5 -1
  9. package/dist/{da-QHZOYKRY.js → da-K5NTK7LH.js} +5 -1
  10. package/dist/{de-GKA7RD7J.js → de-GMRQEDZI.js} +5 -1
  11. package/dist/{el-45T257AL.js → el-EUB4CI4G.js} +5 -1
  12. package/dist/{en-RVW5EQ66.js → en-R5XA26NF.js} +2 -2
  13. package/dist/{es-AVER7RU4.js → es-Z32DAZ6W.js} +5 -1
  14. package/dist/{fa-K3VPK3O2.js → fa-G7HN6B32.js} +5 -1
  15. package/dist/{fi-5NEKF7EB.js → fi-3YBLZO7I.js} +5 -1
  16. package/dist/{fr-FUMNDLBZ.js → fr-VUBH7BBS.js} +5 -1
  17. package/dist/{he-3JGNA3PR.js → he-WQQEY26B.js} +5 -1
  18. package/dist/{hi-IJY375DF.js → hi-HJZ6W4JB.js} +5 -1
  19. package/dist/{id-LWHKJ7ZD.js → id-ZOGZ37MI.js} +5 -1
  20. package/dist/index.d.ts +157 -257
  21. package/dist/index.js +763 -1209
  22. package/dist/{it-EBVOV4RW.js → it-DT6UW66F.js} +5 -1
  23. package/dist/{ja-KWRIX2NF.js → ja-RUKQCOLO.js} +5 -1
  24. package/dist/{ko-L5CJMFDS.js → ko-ZH5ADKZM.js} +5 -1
  25. package/dist/{ms-LSNMJDR2.js → ms-Y2AFPZTU.js} +5 -1
  26. package/dist/{nl-ZB32MB7U.js → nl-OQEWIXNA.js} +5 -1
  27. package/dist/{no-4GTNY7M5.js → no-KPAYJZWY.js} +5 -1
  28. package/dist/{pl-LUHBLTHB.js → pl-WSXK2UUD.js} +5 -1
  29. package/dist/{pt-C4H3UNQ6.js → pt-NWRJRUCK.js} +5 -1
  30. package/dist/{ro-GGITS5V4.js → ro-3CGVMOC6.js} +5 -1
  31. package/dist/{sv-T56RTLBP.js → sv-HGSEBA7S.js} +5 -1
  32. package/dist/test-utils.js +16 -7
  33. package/dist/{th-22X2IWCB.js → th-NZ4G352Z.js} +5 -1
  34. package/dist/{tr-77TFG62H.js → tr-LGGS4DLW.js} +5 -1
  35. package/dist/{uk-MIIL6NXT.js → uk-AIX536L4.js} +5 -1
  36. package/dist/{vi-7JYOIGNV.js → vi-V6CVQZLQ.js} +5 -1
  37. package/dist/{zh-ID6DRYU4.js → zh-AGKPGI3L.js} +5 -1
  38. package/package.json +13 -13
  39. package/src/components/StatusDisplay.tsx +13 -13
  40. package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
  41. package/src/components/modals/ComposeStep.tsx +19 -4
  42. package/src/components/modals/ConfigureGenerationStep.tsx +143 -15
  43. package/src/components/modals/ReferenceWizardModal.tsx +14 -9
  44. package/src/components/modals/ResourceGenerateModal.tsx +10 -5
  45. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +171 -8
  46. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +25 -1
  47. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +24 -1
  48. package/src/components/modals/__tests__/WizardFooter.test.tsx +3 -5
  49. package/src/components/resource/BrowseView.tsx +3 -3
  50. package/src/components/resource/DownloadFileLink.tsx +1 -1
  51. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
  52. package/src/components/resource/__tests__/BrowseView.test.tsx +2 -2
  53. package/src/components/resource/__tests__/download-link.test.tsx +6 -6
  54. package/src/components/resource/panels/ReferenceEntry.tsx +2 -2
  55. package/src/components/resource/panels/ReferencesPanel.tsx +5 -1
  56. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
  57. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -2
  58. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -1
  59. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
  60. package/src/features/auth/components/SignInForm.tsx +53 -53
  61. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
  62. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +2 -2
  63. package/src/features/resource-compose/components/ResourceComposePage.tsx +18 -3
  64. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
  65. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
  66. package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
  67. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +2 -2
  68. package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
  69. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +16 -25
  70. package/src/features/resource-viewer/generation-options.ts +29 -0
  71. package/src/styles/index.css +0 -1
  72. package/translations/ar.json +4 -0
  73. package/translations/bn.json +4 -0
  74. package/translations/cs.json +4 -0
  75. package/translations/da.json +4 -0
  76. package/translations/de.json +4 -0
  77. package/translations/el.json +4 -0
  78. package/translations/en.json +4 -0
  79. package/translations/es.json +4 -0
  80. package/translations/fa.json +4 -0
  81. package/translations/fi.json +4 -0
  82. package/translations/fr.json +4 -0
  83. package/translations/he.json +4 -0
  84. package/translations/hi.json +4 -0
  85. package/translations/id.json +4 -0
  86. package/translations/it.json +4 -0
  87. package/translations/ja.json +4 -0
  88. package/translations/ko.json +4 -0
  89. package/translations/ms.json +4 -0
  90. package/translations/nl.json +4 -0
  91. package/translations/no.json +4 -0
  92. package/translations/pl.json +4 -0
  93. package/translations/pt.json +4 -0
  94. package/translations/ro.json +4 -0
  95. package/translations/sv.json +4 -0
  96. package/translations/th.json +4 -0
  97. package/translations/tr.json +4 -0
  98. package/translations/uk.json +4 -0
  99. package/translations/vi.json +4 -0
  100. package/translations/zh.json +4 -0
  101. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
  102. package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
  103. package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
  104. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
  105. package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
  106. package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
  107. package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
  108. package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
  109. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
  110. package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
  111. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
  112. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
  113. package/src/styles/features/exchange.css +0 -374
@@ -1,137 +0,0 @@
1
- /**
2
- * Tests for AdminExchangePage component
3
- */
4
-
5
- import { describe, it, expect, vi } from 'vitest';
6
- import { render, screen } from '@testing-library/react';
7
- import { AdminExchangePage } from '../components/AdminExchangePage';
8
- import type { AdminExchangePageProps } from '../components/AdminExchangePage';
9
-
10
- const createProps = (overrides?: Partial<AdminExchangePageProps>): AdminExchangePageProps => ({
11
- onExport: vi.fn(),
12
- isExporting: false,
13
- onFileSelected: vi.fn(),
14
- onImport: vi.fn(),
15
- onCancelImport: vi.fn(),
16
- selectedFile: null,
17
- preview: null,
18
- isImporting: false,
19
- importPhase: null,
20
- theme: 'light',
21
- activePanel: null,
22
- translations: {
23
- title: 'Backup & Restore',
24
- subtitle: 'Back up and restore your knowledge base',
25
- export: {
26
- title: 'Backup',
27
- description: 'Create a lossless backup',
28
- exportButton: 'Create Backup',
29
- exporting: 'Creating backup…',
30
- },
31
- import: {
32
- title: 'Restore',
33
- description: 'Restore from a backup',
34
- dropzoneLabel: 'Drop a file here',
35
- dropzoneActive: 'Drop to upload',
36
- detectedFormat: 'Format',
37
- statsPreview: 'Preview',
38
- importButton: 'Restore',
39
- importing: 'Restoring…',
40
- importConfirmTitle: 'Confirm',
41
- importConfirmMessage: 'Cannot undo.',
42
- confirmImport: 'Proceed',
43
- cancelImport: 'Cancel',
44
- },
45
- progress: {
46
- phaseStarted: 'Starting…',
47
- phaseComplete: 'Complete',
48
- phaseError: 'Failed',
49
- statsEventsReplayed: 'Events replayed',
50
- statsResourcesCreated: 'Resources created',
51
- statsAnnotationsCreated: 'Annotations created',
52
- statsEntityTypesAdded: 'Entity types added',
53
- },
54
- },
55
- ToolbarPanels: () => <div data-testid="toolbar-panels" />,
56
- Toolbar: () => <div data-testid="toolbar" />,
57
- ...overrides,
58
- });
59
-
60
- describe('AdminExchangePage', () => {
61
- it('renders page title and subtitle', () => {
62
- render(<AdminExchangePage {...createProps()} />);
63
- expect(screen.getByText('Backup & Restore')).toBeInTheDocument();
64
- expect(screen.getByText('Back up and restore your knowledge base')).toBeInTheDocument();
65
- });
66
-
67
- it('renders ExportCard', () => {
68
- render(<AdminExchangePage {...createProps()} />);
69
- expect(screen.getByRole('heading', { name: 'Backup' })).toBeInTheDocument();
70
- expect(screen.getByText('Create a lossless backup')).toBeInTheDocument();
71
- });
72
-
73
- it('renders ImportCard', () => {
74
- render(<AdminExchangePage {...createProps()} />);
75
- expect(screen.getByRole('heading', { name: 'Restore' })).toBeInTheDocument();
76
- expect(screen.getByText('Restore from a backup')).toBeInTheDocument();
77
- });
78
-
79
- it('renders toolbar components', () => {
80
- render(<AdminExchangePage {...createProps()} />);
81
- expect(screen.getByTestId('toolbar-panels')).toBeInTheDocument();
82
- expect(screen.getByTestId('toolbar')).toBeInTheDocument();
83
- });
84
-
85
- it('does not render ImportProgress when importPhase is null', () => {
86
- const { container } = render(<AdminExchangePage {...createProps()} />);
87
- expect(container.querySelector('.semiont-exchange__progress')).not.toBeInTheDocument();
88
- });
89
-
90
- it('renders ImportProgress when importPhase is set', () => {
91
- render(<AdminExchangePage {...createProps({ importPhase: 'started' })} />);
92
- expect(screen.getByText('Starting…')).toBeInTheDocument();
93
- });
94
-
95
- it('renders ImportProgress with backup result on completion', () => {
96
- render(<AdminExchangePage {...createProps({
97
- importPhase: 'complete',
98
- importResult: { stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } },
99
- })} />);
100
- expect(screen.getByText('Complete')).toBeInTheDocument();
101
- expect(screen.getByText('42')).toBeInTheDocument();
102
- expect(screen.getByText('Events replayed')).toBeInTheDocument();
103
- });
104
-
105
- it('applies panel-open class when common panel is active', () => {
106
- const { container } = render(<AdminExchangePage {...createProps({ activePanel: 'user' })} />);
107
- expect(container.querySelector('.semiont-page--panel-open')).toBeInTheDocument();
108
- });
109
-
110
- it('does not apply panel-open class when no panel is active', () => {
111
- const { container } = render(<AdminExchangePage {...createProps({ activePanel: null })} />);
112
- expect(container.querySelector('.semiont-page--panel-open')).not.toBeInTheDocument();
113
- });
114
-
115
- it('passes theme to ToolbarPanels', () => {
116
- const ToolbarPanels = vi.fn(() => <div data-testid="toolbar-panels" />);
117
- render(<AdminExchangePage {...createProps({ theme: 'dark', ToolbarPanels })} />);
118
- expect(ToolbarPanels).toHaveBeenCalledWith(
119
- expect.objectContaining({ theme: 'dark' }),
120
- undefined,
121
- );
122
- });
123
-
124
- it('passes context to Toolbar', () => {
125
- const Toolbar = vi.fn(() => <div data-testid="toolbar" />);
126
- render(<AdminExchangePage {...createProps({ Toolbar })} />);
127
- expect(Toolbar).toHaveBeenCalledWith(
128
- expect.objectContaining({ context: 'simple' }),
129
- undefined,
130
- );
131
- });
132
-
133
- it('renders cards in grid layout', () => {
134
- const { container } = render(<AdminExchangePage {...createProps()} />);
135
- expect(container.querySelector('.semiont-exchange__cards')).toBeInTheDocument();
136
- });
137
- });
@@ -1,41 +0,0 @@
1
- /**
2
- * Tests for ExportCard component
3
- */
4
-
5
- import { describe, it, expect, vi } from 'vitest';
6
- import { render, screen, fireEvent } from '@testing-library/react';
7
- import { ExportCard } from '../components/ExportCard';
8
- import type { ExportCardProps } from '../components/ExportCard';
9
-
10
- const createProps = (overrides?: Partial<ExportCardProps>): ExportCardProps => ({
11
- onExport: vi.fn(),
12
- isExporting: false,
13
- translations: {
14
- title: 'Backup',
15
- description: 'Create a lossless backup',
16
- exportButton: 'Create Backup',
17
- exporting: 'Creating backup…',
18
- },
19
- ...overrides,
20
- });
21
-
22
- describe('ExportCard', () => {
23
- it('renders title and description', () => {
24
- render(<ExportCard {...createProps()} />);
25
- expect(screen.getByRole('heading', { name: 'Backup' })).toBeInTheDocument();
26
- expect(screen.getByText('Create a lossless backup')).toBeInTheDocument();
27
- });
28
-
29
- it('calls onExport when button is clicked', () => {
30
- const onExport = vi.fn();
31
- render(<ExportCard {...createProps({ onExport })} />);
32
- fireEvent.click(screen.getByRole('button', { name: 'Create Backup' }));
33
- expect(onExport).toHaveBeenCalled();
34
- });
35
-
36
- it('disables button and shows exporting text when isExporting', () => {
37
- render(<ExportCard {...createProps({ isExporting: true })} />);
38
- const button = screen.getByRole('button', { name: 'Creating backup…' });
39
- expect(button).toBeDisabled();
40
- });
41
- });
@@ -1,148 +0,0 @@
1
- /**
2
- * Tests for ImportCard component
3
- */
4
-
5
- import { describe, it, expect, vi } from 'vitest';
6
- import { render, screen, fireEvent } from '@testing-library/react';
7
- import { ImportCard } from '../components/ImportCard';
8
- import type { ImportCardProps } from '../components/ImportCard';
9
-
10
- const createProps = (overrides?: Partial<ImportCardProps>): ImportCardProps => ({
11
- onFileSelected: vi.fn(),
12
- onImport: vi.fn(),
13
- onCancel: vi.fn(),
14
- selectedFile: null,
15
- preview: null,
16
- isImporting: false,
17
- translations: {
18
- title: 'Import',
19
- description: 'Restore from a file',
20
- dropzoneLabel: 'Drop a file here',
21
- dropzoneActive: 'Drop to upload',
22
- detectedFormat: 'Format',
23
- statsPreview: 'Preview',
24
- importButton: 'Import',
25
- importing: 'Importing…',
26
- importConfirmTitle: 'Confirm Import',
27
- importConfirmMessage: 'This cannot be undone.',
28
- confirmImport: 'Proceed',
29
- cancelImport: 'Cancel',
30
- },
31
- ...overrides,
32
- });
33
-
34
- describe('ImportCard', () => {
35
- it('renders title and description', () => {
36
- render(<ImportCard {...createProps()} />);
37
- expect(screen.getByRole('heading', { name: 'Import' })).toBeInTheDocument();
38
- expect(screen.getByText('Restore from a file')).toBeInTheDocument();
39
- });
40
-
41
- it('renders dropzone with label', () => {
42
- render(<ImportCard {...createProps()} />);
43
- expect(screen.getByText('Drop a file here')).toBeInTheDocument();
44
- });
45
-
46
- it('disables import button when no preview', () => {
47
- render(<ImportCard {...createProps()} />);
48
- expect(screen.getByRole('button', { name: 'Import' })).toBeDisabled();
49
- });
50
-
51
- it('enables import button when preview is available', () => {
52
- render(<ImportCard {...createProps({
53
- selectedFile: new File([''], 'test.jsonl'),
54
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
55
- })} />);
56
- expect(screen.getByRole('button', { name: 'Import' })).not.toBeDisabled();
57
- });
58
-
59
- it('shows file info when a file is selected', () => {
60
- const file = new File(['test'], 'backup.tar.gz');
61
- render(<ImportCard {...createProps({ selectedFile: file })} />);
62
- expect(screen.getByText(/backup\.tar\.gz/)).toBeInTheDocument();
63
- });
64
-
65
- it('shows preview details', () => {
66
- render(<ImportCard {...createProps({
67
- selectedFile: new File([''], 'test.jsonl'),
68
- preview: {
69
- format: 'semiont-backup',
70
- version: 1,
71
- sourceUrl: 'http://example.com',
72
- stats: { streams: 5 },
73
- },
74
- })} />);
75
- expect(screen.getByText('semiont-backup v1')).toBeInTheDocument();
76
- expect(screen.getByText('http://example.com')).toBeInTheDocument();
77
- expect(screen.getByText('5 streams')).toBeInTheDocument();
78
- });
79
-
80
- it('shows confirmation dialog on import click', () => {
81
- render(<ImportCard {...createProps({
82
- selectedFile: new File([''], 'test.jsonl'),
83
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
84
- })} />);
85
-
86
- fireEvent.click(screen.getByRole('button', { name: 'Import' }));
87
- expect(screen.getByText('Confirm Import')).toBeInTheDocument();
88
- expect(screen.getByText('This cannot be undone.')).toBeInTheDocument();
89
- });
90
-
91
- it('calls onImport when confirmation is confirmed', () => {
92
- const onImport = vi.fn();
93
- render(<ImportCard {...createProps({
94
- onImport,
95
- selectedFile: new File([''], 'test.jsonl'),
96
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
97
- })} />);
98
-
99
- fireEvent.click(screen.getByRole('button', { name: 'Import' }));
100
- fireEvent.click(screen.getByRole('button', { name: 'Proceed' }));
101
- expect(onImport).toHaveBeenCalled();
102
- });
103
-
104
- it('hides confirmation when cancel is clicked in confirm dialog', () => {
105
- render(<ImportCard {...createProps({
106
- selectedFile: new File([''], 'test.jsonl'),
107
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
108
- })} />);
109
-
110
- fireEvent.click(screen.getByRole('button', { name: 'Import' }));
111
- expect(screen.getByText('Confirm Import')).toBeInTheDocument();
112
-
113
- // In confirm dialog, the Cancel button
114
- const cancelButtons = screen.getAllByRole('button', { name: 'Cancel' });
115
- fireEvent.click(cancelButtons[0]);
116
- expect(screen.queryByText('Confirm Import')).not.toBeInTheDocument();
117
- });
118
-
119
- it('shows cancel button when file is selected', () => {
120
- render(<ImportCard {...createProps({
121
- selectedFile: new File([''], 'test.jsonl'),
122
- })} />);
123
- expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
124
- });
125
-
126
- it('disables import button when importing', () => {
127
- render(<ImportCard {...createProps({
128
- isImporting: true,
129
- selectedFile: new File([''], 'test.jsonl'),
130
- preview: { format: 'semiont-backup', version: 1, sourceUrl: '', stats: {} },
131
- })} />);
132
- expect(screen.getByRole('button', { name: 'Importing…' })).toBeDisabled();
133
- });
134
-
135
- it('handles file drop', () => {
136
- const onFileSelected = vi.fn();
137
- render(<ImportCard {...createProps({ onFileSelected })} />);
138
-
139
- const dropzone = screen.getByText('Drop a file here').closest('.semiont-exchange__dropzone')!;
140
- const file = new File(['content'], 'test.tar.gz');
141
-
142
- fireEvent.drop(dropzone, {
143
- dataTransfer: { files: [file] },
144
- });
145
-
146
- expect(onFileSelected).toHaveBeenCalledWith(file);
147
- });
148
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * Tests for ImportProgress component
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { render, screen } from '@testing-library/react';
7
- import { ImportProgress } from '../components/ImportProgress';
8
- import type { ImportProgressProps } from '../components/ImportProgress';
9
-
10
- const translations: ImportProgressProps['translations'] = {
11
- phaseStarted: 'Starting restore…',
12
- phaseComplete: 'Restore complete',
13
- phaseError: 'Restore failed',
14
- statsEventsReplayed: 'Events replayed',
15
- statsResourcesCreated: 'Resources created',
16
- statsAnnotationsCreated: 'Annotations created',
17
- statsEntityTypesAdded: 'Entity types added',
18
- };
19
-
20
- describe('ImportProgress', () => {
21
- it('renders phase label for known phases', () => {
22
- render(<ImportProgress phase="started" translations={translations} />);
23
- expect(screen.getByText('Starting restore…')).toBeInTheDocument();
24
- });
25
-
26
- it('renders raw phase string for unknown phases', () => {
27
- render(<ImportProgress phase="custom-phase" translations={translations} />);
28
- expect(screen.getByText('custom-phase')).toBeInTheDocument();
29
- });
30
-
31
- it('renders message during active phases', () => {
32
- render(<ImportProgress phase="started" message="Restoring backup..." translations={translations} />);
33
- expect(screen.getByText('Restoring backup...')).toBeInTheDocument();
34
- });
35
-
36
- it('does not render message during complete phase', () => {
37
- render(<ImportProgress phase="complete" message="some message" translations={translations} />);
38
- expect(screen.queryByText('some message')).not.toBeInTheDocument();
39
- });
40
-
41
- it('renders complete phase with correct class', () => {
42
- const { container } = render(<ImportProgress phase="complete" translations={translations} />);
43
- const label = container.querySelector('.semiont-exchange__phase-label--complete');
44
- expect(label).toBeInTheDocument();
45
- expect(label).toHaveTextContent('Restore complete');
46
- });
47
-
48
- it('renders error phase with correct class', () => {
49
- const { container } = render(<ImportProgress phase="error" translations={translations} />);
50
- const label = container.querySelector('.semiont-exchange__phase-label--error');
51
- expect(label).toBeInTheDocument();
52
- expect(label).toHaveTextContent('Restore failed');
53
- });
54
-
55
- it('renders error message', () => {
56
- render(<ImportProgress phase="error" message="Connection failed" translations={translations} />);
57
- expect(screen.getByText('Connection failed')).toBeInTheDocument();
58
- });
59
-
60
- it('renders backup restore stats nested under result.stats', () => {
61
- render(<ImportProgress
62
- phase="complete"
63
- result={{ stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
64
- translations={translations}
65
- />);
66
- expect(screen.getByText('42')).toBeInTheDocument();
67
- expect(screen.getByText('Events replayed')).toBeInTheDocument();
68
- expect(screen.getByText('5')).toBeInTheDocument();
69
- expect(screen.getByText('Resources created')).toBeInTheDocument();
70
- expect(screen.getByText('12')).toBeInTheDocument();
71
- expect(screen.getByText('Annotations created')).toBeInTheDocument();
72
- expect(screen.getByText('3')).toBeInTheDocument();
73
- expect(screen.getByText('Entity types added')).toBeInTheDocument();
74
- });
75
-
76
- it('renders linked-data import stats nested under result.stats', () => {
77
- render(<ImportProgress
78
- phase="complete"
79
- result={{ stats: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
80
- translations={translations}
81
- />);
82
- expect(screen.getByText('5')).toBeInTheDocument();
83
- expect(screen.getByText('Resources created')).toBeInTheDocument();
84
- expect(screen.getByText('12')).toBeInTheDocument();
85
- expect(screen.getByText('Annotations created')).toBeInTheDocument();
86
- expect(screen.getByText('3')).toBeInTheDocument();
87
- expect(screen.getByText('Entity types added')).toBeInTheDocument();
88
- });
89
-
90
- it('does not render result section when result lacks a stats object', () => {
91
- const { container } = render(<ImportProgress
92
- phase="complete"
93
- result={{ resourcesCreated: 5 }}
94
- translations={translations}
95
- />);
96
- expect(container.querySelector('.semiont-exchange__result')).not.toBeInTheDocument();
97
- });
98
-
99
- it('renders raw key for unknown stats', () => {
100
- render(<ImportProgress
101
- phase="complete"
102
- result={{ stats: { somethingNew: 7 } }}
103
- translations={translations}
104
- />);
105
- expect(screen.getByText('7')).toBeInTheDocument();
106
- expect(screen.getByText('somethingNew')).toBeInTheDocument();
107
- });
108
-
109
- it('does not render result section during non-complete phases', () => {
110
- const { container } = render(<ImportProgress
111
- phase="started"
112
- result={{ stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
113
- translations={translations}
114
- />);
115
- expect(container.querySelector('.semiont-exchange__result')).not.toBeInTheDocument();
116
- });
117
- });
@@ -1,117 +0,0 @@
1
- /**
2
- * AdminExchangePage — Backup/Restore admin page
3
- *
4
- * Pure React component. All state and handlers passed as props.
5
- */
6
-
7
- import React from 'react';
8
- import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
9
- import { ExportCard, type ExportCardTranslations } from './ExportCard';
10
- import { ImportCard, type ImportCardProps, type ImportCardTranslations } from './ImportCard';
11
- import { ImportProgress, type ImportProgressTranslations } from './ImportProgress';
12
-
13
- export interface AdminExchangePageTranslations {
14
- title: string;
15
- subtitle: string;
16
- export: ExportCardTranslations;
17
- import: ImportCardTranslations;
18
- progress: ImportProgressTranslations;
19
- }
20
-
21
- export interface AdminExchangePageProps {
22
- // Backup
23
- onExport: () => void;
24
- isExporting: boolean;
25
-
26
- // Import
27
- onFileSelected: (file: File) => void;
28
- onImport: () => void;
29
- onCancelImport: () => void;
30
- selectedFile: File | null;
31
- preview: ImportCardProps['preview'];
32
- isImporting: boolean;
33
-
34
- // Progress
35
- importPhase: string | null;
36
- importMessage?: string | undefined;
37
- importResult?: Record<string, unknown> | undefined;
38
-
39
- // UI state
40
- theme: 'light' | 'dark' | 'system';
41
- activePanel: string | null;
42
-
43
- // Translations
44
- translations: AdminExchangePageTranslations;
45
-
46
- // Component dependencies
47
- ToolbarPanels: React.ComponentType<any>;
48
- Toolbar: React.ComponentType<any>;
49
- }
50
-
51
- export function AdminExchangePage({
52
- onExport,
53
- isExporting,
54
- onFileSelected,
55
- onImport,
56
- onCancelImport,
57
- selectedFile,
58
- preview,
59
- isImporting,
60
- importPhase,
61
- importMessage,
62
- importResult,
63
- theme,
64
- activePanel,
65
- translations: t,
66
- ToolbarPanels,
67
- Toolbar,
68
- }: AdminExchangePageProps) {
69
- return (
70
- <div className={`semiont-page${activePanel && COMMON_PANELS.includes(activePanel as ToolbarPanelType) ? ' semiont-page--panel-open' : ''}`}>
71
- <div className="semiont-page__content">
72
- <div className="semiont-page__header">
73
- <h1 className="semiont-page__title">{t.title}</h1>
74
- <p className="semiont-page__subtitle">{t.subtitle}</p>
75
- </div>
76
-
77
- <div className="semiont-exchange__cards">
78
- <ExportCard
79
- onExport={onExport}
80
- isExporting={isExporting}
81
- translations={t.export}
82
- />
83
-
84
- <ImportCard
85
- onFileSelected={onFileSelected}
86
- onImport={onImport}
87
- onCancel={onCancelImport}
88
- selectedFile={selectedFile}
89
- preview={preview}
90
- isImporting={isImporting}
91
- translations={t.import}
92
- />
93
- </div>
94
-
95
- {importPhase && (
96
- <ImportProgress
97
- phase={importPhase}
98
- message={importMessage}
99
- result={importResult}
100
- translations={t.progress}
101
- />
102
- )}
103
- </div>
104
-
105
- <div className="semiont-page__sidebar">
106
- <ToolbarPanels
107
- activePanel={activePanel}
108
- theme={theme}
109
- />
110
- <Toolbar
111
- context="simple"
112
- activePanel={activePanel}
113
- />
114
- </div>
115
- </div>
116
- );
117
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * ExportCard — Backup trigger
3
- *
4
- * Pure React component. All state and handlers passed as props.
5
- */
6
-
7
- export interface ExportCardTranslations {
8
- title: string;
9
- description: string;
10
- exportButton: string;
11
- exporting: string;
12
- }
13
-
14
- export interface ExportCardProps {
15
- onExport: () => void;
16
- isExporting: boolean;
17
- translations: ExportCardTranslations;
18
- }
19
-
20
- export function ExportCard({ onExport, isExporting, translations: t }: ExportCardProps) {
21
- return (
22
- <div className="semiont-admin__card">
23
- <h2 className="semiont-admin__section-title">{t.title}</h2>
24
- <p className="semiont-admin__section-description">{t.description}</p>
25
-
26
- <button
27
- className="semiont-exchange__button semiont-exchange__button--primary"
28
- onClick={onExport}
29
- disabled={isExporting}
30
- >
31
- {isExporting ? t.exporting : t.exportButton}
32
- </button>
33
- </div>
34
- );
35
- }