@semiont/react-ui 0.5.5 → 0.5.7

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 (218) hide show
  1. package/README.md +59 -55
  2. package/dist/{PdfAnnotationCanvas.client-CN3C3S55.js → PdfAnnotationCanvas.client-NIMALXNZ.js} +7 -27
  3. package/dist/PdfAnnotationCanvas.client-NIMALXNZ.js.map +1 -0
  4. package/dist/{ar-U2EXWUMQ.js → ar-SONK6MON.js} +3 -7
  5. package/dist/ar-SONK6MON.js.map +1 -0
  6. package/dist/{bn-DRJGV772.js → bn-ZKPRITNG.js} +3 -7
  7. package/dist/bn-ZKPRITNG.js.map +1 -0
  8. package/dist/{chunk-3Q3TUKWP.js → chunk-Y2EEAOMZ.js} +29 -29
  9. package/dist/{cs-PTWDM23V.js → cs-LPXQ7NHQ.js} +3 -7
  10. package/dist/cs-LPXQ7NHQ.js.map +1 -0
  11. package/dist/{da-KSNIKYSS.js → da-6TKY7MCY.js} +6 -10
  12. package/dist/da-6TKY7MCY.js.map +1 -0
  13. package/dist/{de-F2XBEWFY.js → de-C3GNII74.js} +3 -7
  14. package/dist/de-C3GNII74.js.map +1 -0
  15. package/dist/{el-DLD2GWAP.js → el-UBCXQDJ7.js} +3 -7
  16. package/dist/el-UBCXQDJ7.js.map +1 -0
  17. package/dist/{es-WLPYWGB5.js → es-BQ23TRI7.js} +11 -15
  18. package/dist/es-BQ23TRI7.js.map +1 -0
  19. package/dist/{fa-BAXHSDZG.js → fa-AFTBZB77.js} +3 -7
  20. package/dist/fa-AFTBZB77.js.map +1 -0
  21. package/dist/{fi-FCHSYVOT.js → fi-WOYNLZC2.js} +3 -7
  22. package/dist/fi-WOYNLZC2.js.map +1 -0
  23. package/dist/{fr-3UERBSL6.js → fr-NDSMIFJM.js} +3 -7
  24. package/dist/fr-NDSMIFJM.js.map +1 -0
  25. package/dist/{he-F6F3FV2K.js → he-VJXVRDOY.js} +3 -7
  26. package/dist/he-VJXVRDOY.js.map +1 -0
  27. package/dist/{hi-4BK6IK7Q.js → hi-BF6PHIE2.js} +3 -7
  28. package/dist/hi-BF6PHIE2.js.map +1 -0
  29. package/dist/{id-7ECCWP3J.js → id-GXG5QCZY.js} +3 -7
  30. package/dist/id-GXG5QCZY.js.map +1 -0
  31. package/dist/index.css +103 -0
  32. package/dist/index.css.map +1 -1
  33. package/dist/index.d.ts +271 -120
  34. package/dist/index.js +877 -698
  35. package/dist/index.js.map +1 -1
  36. package/dist/{it-234Z6XK6.js → it-XKHHCBAF.js} +3 -7
  37. package/dist/it-XKHHCBAF.js.map +1 -0
  38. package/dist/{ja-PJWQI4OQ.js → ja-TX7VM4XD.js} +3 -7
  39. package/dist/ja-TX7VM4XD.js.map +1 -0
  40. package/dist/{ko-APUEW2RS.js → ko-DNC7EQ7J.js} +3 -7
  41. package/dist/ko-DNC7EQ7J.js.map +1 -0
  42. package/dist/{ms-PJBZWZWD.js → ms-POZGBKPH.js} +3 -7
  43. package/dist/ms-POZGBKPH.js.map +1 -0
  44. package/dist/{nl-L4C3ZBCU.js → nl-IRMTKI7Z.js} +4 -11
  45. package/dist/nl-IRMTKI7Z.js.map +1 -0
  46. package/dist/{no-QE5N5KNG.js → no-ZUDJA4S6.js} +20 -24
  47. package/dist/no-ZUDJA4S6.js.map +1 -0
  48. package/dist/{pl-5Q2D23PD.js → pl-2NGAXL5U.js} +3 -7
  49. package/dist/pl-2NGAXL5U.js.map +1 -0
  50. package/dist/{pt-AIGUOIOC.js → pt-ABMCXZUM.js} +118 -122
  51. package/dist/pt-ABMCXZUM.js.map +1 -0
  52. package/dist/{ro-T56CSHTY.js → ro-VOJP6O5X.js} +3 -7
  53. package/dist/ro-VOJP6O5X.js.map +1 -0
  54. package/dist/{sv-L4TJQ2UH.js → sv-4HVFIIE5.js} +43 -47
  55. package/dist/sv-4HVFIIE5.js.map +1 -0
  56. package/dist/test-utils.js +2 -2
  57. package/dist/test-utils.js.map +1 -1
  58. package/dist/{th-6O7Y6O2Q.js → th-IFPZP3HQ.js} +3 -7
  59. package/dist/th-IFPZP3HQ.js.map +1 -0
  60. package/dist/{tr-D4CQCSNO.js → tr-2GYEAMJ4.js} +3 -7
  61. package/dist/tr-2GYEAMJ4.js.map +1 -0
  62. package/dist/{uk-2HMQG6ND.js → uk-XCJBVLLD.js} +3 -7
  63. package/dist/uk-XCJBVLLD.js.map +1 -0
  64. package/dist/{vi-XVJ4RUEJ.js → vi-4FR7CB2F.js} +3 -7
  65. package/dist/vi-4FR7CB2F.js.map +1 -0
  66. package/dist/{zh-K2KDPGHK.js → zh-NSKFOINB.js} +3 -7
  67. package/dist/zh-NSKFOINB.js.map +1 -0
  68. package/package.json +17 -13
  69. package/src/components/Button/__tests__/Button.test.tsx +0 -2
  70. package/src/components/CodeMirrorRenderer.tsx +2 -0
  71. package/src/components/ErrorBoundary.tsx +0 -9
  72. package/src/components/ProtectedErrorBoundary.css +119 -0
  73. package/src/components/ProtectedErrorBoundary.tsx +24 -15
  74. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +0 -1
  75. package/src/components/__tests__/ErrorBoundary.test.tsx +20 -13
  76. package/src/components/__tests__/LiveRegion.hooks.test.tsx +1 -1
  77. package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +2 -1
  78. package/src/components/__tests__/ResizeHandle.test.tsx +0 -1
  79. package/src/components/__tests__/SessionExpiryBanner.test.tsx +0 -1
  80. package/src/components/__tests__/StatusDisplay.test.tsx +0 -1
  81. package/src/components/__tests__/Toast.test.tsx +2 -3
  82. package/src/components/__tests__/Toolbar.test.tsx +0 -1
  83. package/src/components/annotation/annotations.css +14 -0
  84. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +3 -5
  85. package/src/components/annotation-popups/__tests__/SharedPopupElements.test.tsx +0 -1
  86. package/src/components/branding/__tests__/SemiontBranding.test.tsx +1 -2
  87. package/src/components/layout/__tests__/LeftSidebar.test.tsx +5 -6
  88. package/src/components/layout/__tests__/PageLayout.test.tsx +1 -3
  89. package/src/components/layout/__tests__/SkipLinks.a11y.test.tsx +8 -8
  90. package/src/components/layout/__tests__/UnifiedHeader.test.tsx +12 -1
  91. package/src/components/modals/__tests__/KeyboardShortcutsHelpModal.test.tsx +0 -1
  92. package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +3 -4
  93. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +1 -2
  94. package/src/components/modals/__tests__/SearchModal.basic.test.tsx +1 -1
  95. package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +0 -5
  96. package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +1 -2
  97. package/src/components/modals/__tests__/SearchModal.visual.test.tsx +2 -2
  98. package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +0 -1
  99. package/src/components/navigation/NavigationMenu.tsx +1 -1
  100. package/src/components/navigation/__tests__/Footer.a11y.test.tsx +4 -0
  101. package/src/components/navigation/__tests__/Footer.test.tsx +3 -6
  102. package/src/components/navigation/__tests__/NavigationMenu.a11y.test.tsx +1 -1
  103. package/src/components/navigation/__tests__/NavigationMenu.test.tsx +7 -9
  104. package/src/components/navigation/__tests__/ObservableLink.test.tsx +0 -1
  105. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +1 -2
  106. package/src/components/navigation/__tests__/SortableResourceTab.test.tsx +0 -1
  107. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +6 -4
  108. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +10 -19
  109. package/src/components/resource/AnnotateView.tsx +35 -37
  110. package/src/components/resource/BrowseView.tsx +31 -31
  111. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +0 -1
  112. package/src/components/resource/__tests__/BrowseView.test.tsx +12 -14
  113. package/src/components/resource/__tests__/HistoryEvent.test.tsx +0 -5
  114. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +4 -6
  115. package/src/components/resource/__tests__/event-formatting.test.ts +1 -1
  116. package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
  117. package/src/components/resource/panels/JsonLdPanel.tsx +33 -16
  118. package/src/components/resource/panels/ReferencesPanel.tsx +1 -1
  119. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -5
  120. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +8 -7
  121. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +14 -10
  122. package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +0 -1
  123. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +31 -18
  124. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +7 -6
  125. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +5 -6
  126. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +19 -13
  127. package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +95 -426
  128. package/src/components/resource/panels/__tests__/PanelHeader.test.tsx +0 -1
  129. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +5 -5
  130. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +40 -7
  131. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +4 -4
  132. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +30 -32
  133. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +6 -6
  134. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +7 -6
  135. package/src/components/settings/__tests__/SettingsPanel.test.tsx +0 -1
  136. package/src/components/viewers/__tests__/ImageViewer.test.tsx +0 -1
  137. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +7 -10
  138. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +38 -27
  139. package/src/features/admin-exchange/components/ImportProgress.tsx +28 -34
  140. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +2 -0
  141. package/src/features/auth/__tests__/SignUpForm.a11y.test.tsx +11 -12
  142. package/src/features/auth/__tests__/SignUpForm.test.tsx +3 -3
  143. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -0
  144. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +11 -9
  145. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +2 -1
  146. package/src/features/resource-compose/components/ResourceComposePage.tsx +36 -9
  147. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -8
  148. package/src/features/resource-discovery/__tests__/ResourceCard.test.tsx +0 -1
  149. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +33 -35
  150. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +12 -11
  151. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +204 -11
  152. package/src/features/resource-discovery/state/discover-state-unit.ts +70 -11
  153. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +2 -2
  154. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +10 -7
  155. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +37 -1
  156. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +14 -7
  157. package/src/integrations/__tests__/css-modules-helper.test.tsx +2 -3
  158. package/src/integrations/__tests__/styled-components-theme.test.ts +1 -3
  159. package/src/styles/features/exchange.css +0 -30
  160. package/src/styles/index.css +1 -0
  161. package/translations/ar.json +1 -3
  162. package/translations/bn.json +1 -3
  163. package/translations/cs.json +1 -3
  164. package/translations/da.json +4 -6
  165. package/translations/de.json +1 -3
  166. package/translations/el.json +1 -3
  167. package/translations/es.json +9 -11
  168. package/translations/fa.json +1 -3
  169. package/translations/fi.json +1 -3
  170. package/translations/fr.json +1 -3
  171. package/translations/he.json +1 -3
  172. package/translations/hi.json +1 -3
  173. package/translations/id.json +1 -3
  174. package/translations/it.json +1 -3
  175. package/translations/ja.json +1 -3
  176. package/translations/ko.json +1 -3
  177. package/translations/ms.json +1 -3
  178. package/translations/nl.json +2 -7
  179. package/translations/no.json +18 -20
  180. package/translations/pl.json +1 -3
  181. package/translations/pt.json +116 -118
  182. package/translations/ro.json +1 -3
  183. package/translations/sv.json +41 -43
  184. package/translations/th.json +1 -3
  185. package/translations/tr.json +1 -3
  186. package/translations/uk.json +1 -3
  187. package/translations/vi.json +1 -3
  188. package/translations/zh.json +1 -3
  189. package/dist/PdfAnnotationCanvas.client-CN3C3S55.js.map +0 -1
  190. package/dist/ar-U2EXWUMQ.js.map +0 -1
  191. package/dist/bn-DRJGV772.js.map +0 -1
  192. package/dist/cs-PTWDM23V.js.map +0 -1
  193. package/dist/da-KSNIKYSS.js.map +0 -1
  194. package/dist/de-F2XBEWFY.js.map +0 -1
  195. package/dist/el-DLD2GWAP.js.map +0 -1
  196. package/dist/es-WLPYWGB5.js.map +0 -1
  197. package/dist/fa-BAXHSDZG.js.map +0 -1
  198. package/dist/fi-FCHSYVOT.js.map +0 -1
  199. package/dist/fr-3UERBSL6.js.map +0 -1
  200. package/dist/he-F6F3FV2K.js.map +0 -1
  201. package/dist/hi-4BK6IK7Q.js.map +0 -1
  202. package/dist/id-7ECCWP3J.js.map +0 -1
  203. package/dist/it-234Z6XK6.js.map +0 -1
  204. package/dist/ja-PJWQI4OQ.js.map +0 -1
  205. package/dist/ko-APUEW2RS.js.map +0 -1
  206. package/dist/ms-PJBZWZWD.js.map +0 -1
  207. package/dist/nl-L4C3ZBCU.js.map +0 -1
  208. package/dist/no-QE5N5KNG.js.map +0 -1
  209. package/dist/pl-5Q2D23PD.js.map +0 -1
  210. package/dist/pt-AIGUOIOC.js.map +0 -1
  211. package/dist/ro-T56CSHTY.js.map +0 -1
  212. package/dist/sv-L4TJQ2UH.js.map +0 -1
  213. package/dist/th-6O7Y6O2Q.js.map +0 -1
  214. package/dist/tr-D4CQCSNO.js.map +0 -1
  215. package/dist/uk-2HMQG6ND.js.map +0 -1
  216. package/dist/vi-XVJ4RUEJ.js.map +0 -1
  217. package/dist/zh-K2KDPGHK.js.map +0 -1
  218. /package/dist/{chunk-3Q3TUKWP.js.map → chunk-Y2EEAOMZ.js.map} +0 -0
@@ -1,504 +1,173 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import React from 'react';
3
2
  import { render, screen, waitFor } from '@testing-library/react';
4
3
  import userEvent from '@testing-library/user-event';
5
4
  import '@testing-library/jest-dom';
6
5
  import { JsonLdPanel } from '../JsonLdPanel';
6
+ import { resourceId } from '@semiont/core';
7
7
  import type { components } from '@semiont/core';
8
8
 
9
- type SemiontResource = components['schemas']['ResourceDescriptor'];
9
+ type GetResourceResponse = components['schemas']['GetResourceResponse'];
10
10
 
11
- // Mock CodeMirror modules
11
+ // Mock CodeMirror modules (the panel still renders the graph in CodeMirror)
12
12
  vi.mock('@codemirror/view', () => {
13
13
  class MockEditorView {
14
14
  destroy = vi.fn();
15
- constructor(config: any) {
16
- // Store config if needed for assertions
17
- }
18
15
  static editable = { of: vi.fn() };
19
16
  static theme = vi.fn(() => ({}));
20
17
  }
21
-
22
- return {
23
- EditorView: MockEditorView,
24
- lineNumbers: vi.fn(),
25
- };
18
+ return { EditorView: MockEditorView, lineNumbers: vi.fn() };
26
19
  });
27
-
28
20
  vi.mock('@codemirror/state', () => ({
29
- EditorState: {
30
- create: vi.fn(() => ({})),
31
- readOnly: { of: vi.fn() },
32
- },
33
- }));
34
-
35
- vi.mock('@codemirror/lang-json', () => ({
36
- json: vi.fn(),
21
+ EditorState: { create: vi.fn(() => ({})), readOnly: { of: vi.fn() } },
37
22
  }));
38
-
39
- vi.mock('@codemirror/theme-one-dark', () => ({
40
- oneDark: {},
41
- }));
42
-
23
+ vi.mock('@codemirror/lang-json', () => ({ json: vi.fn() }));
24
+ vi.mock('@codemirror/theme-one-dark', () => ({ oneDark: {} }));
43
25
  vi.mock('@codemirror/language', () => ({
44
26
  syntaxHighlighting: vi.fn(),
45
- HighlightStyle: {
46
- define: vi.fn(() => ({})),
47
- },
27
+ HighlightStyle: { define: vi.fn(() => ({})) },
48
28
  }));
49
-
50
29
  vi.mock('../../../lib/codemirror-json-theme', () => ({
51
30
  jsonLightTheme: {},
52
31
  jsonLightHighlightStyle: {},
53
32
  }));
54
33
 
55
- // Mock useLineNumbers hook (must be before import)
34
+ // Mock the hooks the panel consumes — NOT the session/client stack.
56
35
  vi.mock('@/hooks/useLineNumbers');
57
-
58
- import { EditorView } from '@codemirror/view';
36
+ vi.mock('@/hooks/useResourceGraph');
59
37
  import { useLineNumbers } from '@/hooks/useLineNumbers';
60
-
61
- // Test data fixtures
62
- const createMockResource = (overrides?: Partial<SemiontResource>): SemiontResource => ({
63
- '@context': 'http://www.w3.org/ns/anno.jsonld',
64
- '@id': 'test-resource-1',
65
- id: 'test-resource-1',
66
- name: 'Test Resource',
67
- content: 'This is test content',
68
- format: 'text/plain',
69
- archived: false,
70
- entityTypes: ['Person', 'Organization'],
71
- locale: 'en-US',
72
- representations: [],
73
- created: '2024-01-01T10:00:00Z',
74
- modified: '2024-01-01T10:00:00Z',
75
- ...overrides,
76
- });
38
+ import { useResourceGraph } from '@/hooks/useResourceGraph';
39
+
40
+ const RID = resourceId('test-resource-1');
41
+
42
+ const MOCK_GRAPH: GetResourceResponse = {
43
+ resource: {
44
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
45
+ '@id': 'test-resource-1',
46
+ id: 'test-resource-1',
47
+ name: 'Test Resource',
48
+ content: 'This is test content',
49
+ format: 'text/plain',
50
+ archived: false,
51
+ entityTypes: ['Person'],
52
+ locale: 'en-US',
53
+ representations: [],
54
+ created: '2024-01-01T10:00:00Z',
55
+ modified: '2024-01-01T10:00:00Z',
56
+ },
57
+ annotations: [],
58
+ entityReferences: [],
59
+ };
77
60
 
78
61
  describe('JsonLdPanel Component', () => {
79
- let mockClipboard: any;
62
+ let mockClipboard: { writeText: ReturnType<typeof vi.fn> };
80
63
 
81
64
  beforeEach(() => {
82
65
  vi.clearAllMocks();
83
66
 
84
- // Mock clipboard API
85
- mockClipboard = {
86
- writeText: vi.fn().mockResolvedValue(undefined),
87
- };
67
+ mockClipboard = { writeText: vi.fn().mockResolvedValue(undefined) };
88
68
  Object.defineProperty(navigator, 'clipboard', {
89
- value: mockClipboard,
90
- writable: true,
91
- configurable: true,
69
+ value: mockClipboard, writable: true, configurable: true,
92
70
  });
93
-
94
- // Mock document.documentElement for dark mode detection
95
71
  Object.defineProperty(document.documentElement, 'classList', {
96
- value: {
97
- contains: vi.fn().mockReturnValue(false),
98
- },
99
- writable: true,
100
- configurable: true,
72
+ value: { contains: vi.fn().mockReturnValue(false) },
73
+ writable: true, configurable: true,
101
74
  });
102
75
 
103
- // Mock useLineNumbers hook implementation
104
76
  vi.mocked(useLineNumbers).mockReturnValue({
105
- showLineNumbers: true,
106
- toggleLineNumbers: vi.fn()
77
+ showLineNumbers: true, toggleLineNumbers: vi.fn(),
78
+ });
79
+ // Default: graph loaded successfully.
80
+ vi.mocked(useResourceGraph).mockReturnValue({
81
+ graph: MOCK_GRAPH, loading: false, error: null,
107
82
  });
108
83
  });
109
84
 
110
- afterEach(() => {
111
- vi.restoreAllMocks();
112
- });
85
+ afterEach(() => { vi.restoreAllMocks(); });
113
86
 
114
87
  describe('Rendering', () => {
115
- it('should render panel with header', () => {
116
- const resource = createMockResource();
117
- render(<JsonLdPanel resource={resource} />);
118
-
88
+ it('renders the header, copy button, and editor container', () => {
89
+ const { container } = render(<JsonLdPanel resourceId={RID} />);
119
90
  expect(screen.getByText('JSON-LD')).toBeInTheDocument();
120
- });
121
-
122
- it('should render copy button', () => {
123
- const resource = createMockResource();
124
- render(<JsonLdPanel resource={resource} />);
125
-
126
91
  expect(screen.getByText(/Copy/)).toBeInTheDocument();
92
+ expect(container.querySelector('.semiont-jsonld-panel__editor')).toBeInTheDocument();
127
93
  });
128
94
 
129
- it('should render editor container', () => {
130
- const resource = createMockResource();
131
- const { container } = render(<JsonLdPanel resource={resource} />);
132
-
133
- const editorDiv = container.querySelector('.semiont-jsonld-panel__editor');
134
- expect(editorDiv).toBeInTheDocument();
95
+ it('fetches the graph for the given resourceId', () => {
96
+ render(<JsonLdPanel resourceId={RID} />);
97
+ expect(useResourceGraph).toHaveBeenCalledWith(RID);
135
98
  });
136
99
  });
137
100
 
138
- describe('CodeMirror Integration', () => {
139
- it('should initialize CodeMirror editor', () => {
140
- const resource = createMockResource();
141
- const { container } = render(<JsonLdPanel resource={resource} />);
142
-
143
- // Verify editor container is rendered
144
- const editorDiv = container.querySelector('.semiont-jsonld-panel__editor');
145
- expect(editorDiv).toBeInTheDocument();
146
- });
147
-
148
- it('should pass resource as JSON to editor', () => {
149
- const resource = createMockResource();
150
- const { container } = render(<JsonLdPanel resource={resource} />);
151
-
152
- // Verify component renders without errors
153
- expect(container.firstChild).toBeInTheDocument();
154
- });
155
-
156
- it('should format JSON with indentation', () => {
157
- const resource = createMockResource();
158
- const { container } = render(<JsonLdPanel resource={resource} />);
159
-
160
- // Verify component renders without errors
161
- expect(container.firstChild).toBeInTheDocument();
101
+ describe('Loading / error states', () => {
102
+ it('shows a loading state and disables Copy while loading', () => {
103
+ vi.mocked(useResourceGraph).mockReturnValue({ graph: null, loading: true, error: null });
104
+ render(<JsonLdPanel resourceId={RID} />);
105
+ expect(screen.getByText(/Loading JSON-LD/i)).toBeInTheDocument();
106
+ expect(screen.getByText(/Copy/).closest('button')).toBeDisabled();
162
107
  });
163
108
 
164
- it('should configure editor as read-only', () => {
165
- const resource = createMockResource();
166
- const { container } = render(<JsonLdPanel resource={resource} />);
167
-
168
- // Verify component renders without errors (read-only is configured internally)
169
- expect(container.firstChild).toBeInTheDocument();
170
- });
171
-
172
- it('should include line numbers when enabled', () => {
173
- vi.mocked(useLineNumbers).mockReturnValue({ showLineNumbers: true, toggleLineNumbers: vi.fn() });
174
-
175
- const resource = createMockResource();
176
- const { container } = render(<JsonLdPanel resource={resource} />);
177
-
178
- // Verify component renders without errors (line numbers configured internally)
179
- expect(container.firstChild).toBeInTheDocument();
180
- });
181
-
182
- it('should not include line numbers when disabled', () => {
183
- vi.mocked(useLineNumbers).mockReturnValue({ showLineNumbers: false, toggleLineNumbers: vi.fn() });
184
-
185
- const resource = createMockResource();
186
- const { container } = render(<JsonLdPanel resource={resource} />);
187
-
188
- // Verify component renders without errors (line numbers not included)
189
- expect(container.firstChild).toBeInTheDocument();
190
- });
191
-
192
- it('should cleanup editor on unmount', () => {
193
- const resource = createMockResource();
194
- const { unmount } = render(<JsonLdPanel resource={resource} />);
195
-
196
- // Should unmount without errors (cleanup happens internally)
197
- expect(() => unmount()).not.toThrow();
198
- });
199
- });
200
-
201
- describe('Dark Mode Support', () => {
202
- it('should use dark theme when dark mode is active', () => {
203
- Object.defineProperty(document.documentElement, 'classList', {
204
- value: {
205
- contains: vi.fn().mockReturnValue(true), // Dark mode active
206
- },
207
- writable: true,
208
- configurable: true,
209
- });
210
-
211
- const resource = createMockResource();
212
- const { container } = render(<JsonLdPanel resource={resource} />);
213
-
214
- // Verify component renders without errors (dark theme configured internally)
215
- expect(container.firstChild).toBeInTheDocument();
216
- });
217
-
218
- it('should use light theme when dark mode is not active', () => {
219
- Object.defineProperty(document.documentElement, 'classList', {
220
- value: {
221
- contains: vi.fn().mockReturnValue(false), // Light mode
222
- },
223
- writable: true,
224
- configurable: true,
109
+ it('shows an error state and disables Copy on failure', () => {
110
+ vi.mocked(useResourceGraph).mockReturnValue({
111
+ graph: null, loading: false, error: new Error('boom'),
225
112
  });
226
-
227
- const resource = createMockResource();
228
- const { container } = render(<JsonLdPanel resource={resource} />);
229
-
230
- // Verify component renders without errors (light theme configured internally)
231
- expect(container.firstChild).toBeInTheDocument();
113
+ render(<JsonLdPanel resourceId={RID} />);
114
+ expect(screen.getByText(/Failed to load JSON-LD/i)).toBeInTheDocument();
115
+ expect(screen.getByText(/Copy/).closest('button')).toBeDisabled();
232
116
  });
233
117
  });
234
118
 
235
- describe('Copy to Clipboard', () => {
236
- it('should copy JSON to clipboard when button clicked', async () => {
237
- const resource = createMockResource();
238
- render(<JsonLdPanel resource={resource} />);
239
-
240
- const copyButton = screen.getByText(/Copy/);
241
- await userEvent.click(copyButton);
119
+ describe('Copy to clipboard', () => {
120
+ it('copies the full graph not the bare descriptor', async () => {
121
+ render(<JsonLdPanel resourceId={RID} />);
122
+ await userEvent.click(screen.getByText(/Copy/));
242
123
 
243
124
  expect(mockClipboard.writeText).toHaveBeenCalledOnce();
125
+ const parsed = JSON.parse(mockClipboard.writeText.mock.calls[0][0]);
244
126
 
245
- const copiedText = mockClipboard.writeText.mock.calls[0][0];
246
- expect(copiedText).toContain('test-resource-1');
247
- expect(copiedText).toContain('Test Resource');
127
+ // The graph wraps the descriptor and adds annotation collections; a bare
128
+ // ResourceDescriptor would have `id` at the top level and no `resource`.
129
+ expect(parsed.resource.id).toBe('test-resource-1');
130
+ expect(Array.isArray(parsed.annotations)).toBe(true);
131
+ expect(Array.isArray(parsed.entityReferences)).toBe(true);
132
+ expect(parsed.id).toBeUndefined();
248
133
  });
249
134
 
250
- it('should copy formatted JSON', async () => {
251
- const resource = createMockResource();
252
- render(<JsonLdPanel resource={resource} />);
253
-
254
- const copyButton = screen.getByText(/Copy/);
255
- await userEvent.click(copyButton);
256
-
257
- const copiedText = mockClipboard.writeText.mock.calls[0][0];
258
-
259
- // Should be formatted with indentation
260
- expect(copiedText).toContain('\n');
261
- expect(copiedText).toContain(' ');
135
+ it('copies pretty-printed, valid JSON', async () => {
136
+ render(<JsonLdPanel resourceId={RID} />);
137
+ await userEvent.click(screen.getByText(/Copy/));
138
+ const copied = mockClipboard.writeText.mock.calls[0][0];
139
+ expect(copied).toContain('\n');
140
+ expect(copied).toContain(' ');
141
+ expect(() => JSON.parse(copied)).not.toThrow();
142
+ });
262
143
 
263
- // Should be valid JSON
264
- expect(() => JSON.parse(copiedText)).not.toThrow();
144
+ it('does not copy when there is no graph yet', async () => {
145
+ vi.mocked(useResourceGraph).mockReturnValue({ graph: null, loading: true, error: null });
146
+ render(<JsonLdPanel resourceId={RID} />);
147
+ const button = screen.getByText(/Copy/).closest('button')!;
148
+ await userEvent.click(button);
149
+ expect(mockClipboard.writeText).not.toHaveBeenCalled();
265
150
  });
266
151
 
267
- it('should handle clipboard API errors gracefully', async () => {
152
+ it('handles clipboard errors gracefully', async () => {
268
153
  const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
269
154
  mockClipboard.writeText.mockRejectedValue(new Error('Clipboard error'));
270
-
271
- const resource = createMockResource();
272
- render(<JsonLdPanel resource={resource} />);
273
-
274
- const copyButton = screen.getByText(/Copy/);
275
-
276
- await expect(async () => {
277
- await userEvent.click(copyButton);
278
- }).not.toThrow();
279
-
155
+ render(<JsonLdPanel resourceId={RID} />);
156
+ await userEvent.click(screen.getByText(/Copy/));
280
157
  await waitFor(() => {
281
- expect(consoleErrorSpy).toHaveBeenCalledWith(
282
- 'Failed to copy JSON-LD:',
283
- expect.any(Error)
284
- );
158
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to copy JSON-LD:', expect.any(Error));
285
159
  });
286
-
287
160
  consoleErrorSpy.mockRestore();
288
161
  });
289
162
  });
290
163
 
291
- describe('Resource Updates', () => {
292
- it('should reinitialize editor when resource changes', () => {
293
- const resource1 = createMockResource({ id: 'resource-1', name: 'Resource 1' });
294
- const { rerender, container } = render(<JsonLdPanel resource={resource1} />);
295
-
296
- expect(container.firstChild).toBeInTheDocument();
297
-
298
- const resource2 = createMockResource({ id: 'resource-2', name: 'Resource 2' });
299
-
300
- // Should rerender without errors
301
- expect(() => rerender(<JsonLdPanel resource={resource2} />)).not.toThrow();
302
- });
303
-
304
- it('should reinitialize editor when line numbers setting changes', () => {
305
- vi.mocked(useLineNumbers).mockReturnValue({ showLineNumbers: true, toggleLineNumbers: vi.fn() });
306
-
307
- const resource = createMockResource();
308
- const { rerender } = render(<JsonLdPanel resource={resource} />);
309
-
310
- vi.mocked(useLineNumbers).mockReturnValue({ showLineNumbers: false, toggleLineNumbers: vi.fn() });
311
-
312
- // Should rerender without errors
313
- expect(() => rerender(<JsonLdPanel resource={resource} />)).not.toThrow();
314
- });
315
- });
316
-
317
- describe('Edge Cases', () => {
318
- it('should handle resource with minimal data', () => {
319
- const minimalResource: SemiontResource = {
320
- '@context': 'http://www.w3.org/ns/anno.jsonld',
321
- '@id': 'minimal',
322
- id: 'minimal',
323
- name: 'Minimal',
324
- content: 'Test',
325
- format: 'text/plain',
326
- archived: false,
327
- representations: [],
328
- created: '2024-01-01T00:00:00Z',
329
- modified: '2024-01-01T00:00:00Z',
330
- };
331
-
332
- expect(() => {
333
- render(<JsonLdPanel resource={minimalResource} />);
334
- }).not.toThrow();
335
- });
336
-
337
- it('should handle resource with special characters', () => {
338
- const resource = createMockResource({
339
- name: 'Test "Resource" with \'quotes\' & special <chars>',
340
- content: 'Content with\nnewlines\tand\ttabs',
341
- });
342
-
343
- expect(() => {
344
- render(<JsonLdPanel resource={resource} />);
345
- }).not.toThrow();
346
- });
347
-
348
- it('should handle very large resource data', () => {
349
- const largeContent = 'A'.repeat(100000);
350
- const resource = createMockResource({
351
- content: largeContent,
352
- });
353
-
354
- expect(() => {
355
- render(<JsonLdPanel resource={resource} />);
356
- }).not.toThrow();
357
- });
358
-
359
- it('should handle resource with unicode characters', () => {
360
- const resource = createMockResource({
361
- name: '测试资源 テストリソース ресурс тест',
362
- content: '🎉🔥💯 Unicode content',
363
- });
364
-
365
- expect(() => {
366
- render(<JsonLdPanel resource={resource} />);
367
- }).not.toThrow();
368
- });
369
-
370
- it('should handle undefined optional fields', () => {
371
- const resource: SemiontResource = {
372
- '@context': 'http://www.w3.org/ns/anno.jsonld',
373
- '@id': 'test',
374
- id: 'test',
375
- name: 'Test',
376
- content: 'Content',
377
- format: 'text/plain',
378
- archived: false,
379
- representations: [],
380
- created: '2024-01-01T00:00:00Z',
381
- modified: '2024-01-01T00:00:00Z',
382
- // Optional fields omitted
383
- };
384
-
385
- expect(() => {
386
- render(<JsonLdPanel resource={resource} />);
387
- }).not.toThrow();
388
- });
389
- });
390
-
391
- describe('Styling and Appearance', () => {
392
- it('should have proper panel structure', () => {
393
- const resource = createMockResource();
394
- const { container } = render(<JsonLdPanel resource={resource} />);
395
-
396
- const panel = container.firstChild as HTMLElement;
397
- expect(panel).toHaveClass('semiont-jsonld-panel');
398
- });
399
-
400
- it('should have proper header styling', () => {
401
- const resource = createMockResource();
402
- render(<JsonLdPanel resource={resource} />);
403
-
404
- const header = screen.getByText('JSON-LD').parentElement;
405
- expect(header).toHaveClass('semiont-jsonld-panel__header');
406
- });
407
-
408
- it('should have proper button styling', () => {
409
- const resource = createMockResource();
410
- render(<JsonLdPanel resource={resource} />);
411
-
412
- const copyButton = screen.getByText(/Copy/).closest('button');
413
- expect(copyButton).toHaveClass('semiont-button', 'semiont-button--icon');
414
- });
415
-
416
- it('should have proper editor container styling', () => {
417
- const resource = createMockResource();
418
- const { container } = render(<JsonLdPanel resource={resource} />);
419
-
420
- const editorContainer = container.querySelector('.semiont-jsonld-panel__editor');
421
- expect(editorContainer).toBeInTheDocument();
422
- });
423
-
424
- it('should support dark mode styling', () => {
425
- const resource = createMockResource();
426
- const { container } = render(<JsonLdPanel resource={resource} />);
427
-
428
- const panel = container.firstChild as HTMLElement;
429
- expect(panel).toHaveClass('semiont-jsonld-panel');
430
- });
431
- });
432
-
433
- describe('Accessibility', () => {
434
- it('should have title attribute on copy button', () => {
435
- const resource = createMockResource();
436
- render(<JsonLdPanel resource={resource} />);
437
-
164
+ describe('Accessibility / structure', () => {
165
+ it('has the panel structure and a titled copy button', () => {
166
+ const { container } = render(<JsonLdPanel resourceId={RID} />);
167
+ expect(container.firstChild).toHaveClass('semiont-jsonld-panel');
438
168
  const copyButton = screen.getByText(/Copy/).closest('button');
439
169
  expect(copyButton).toHaveAttribute('title', 'Copy to clipboard');
440
- });
441
-
442
- it('should have semantic heading', () => {
443
- const resource = createMockResource();
444
- render(<JsonLdPanel resource={resource} />);
445
-
446
- const heading = screen.getByText('JSON-LD');
447
- expect(heading).toHaveClass('semiont-jsonld-panel__title');
448
- });
449
-
450
- it('should have proper button structure', () => {
451
- const resource = createMockResource();
452
- render(<JsonLdPanel resource={resource} />);
453
-
454
- const copyButton = screen.getByText(/Copy/).closest('button');
455
- expect(copyButton?.tagName).toBe('BUTTON');
456
- });
457
- });
458
-
459
- describe('JSON-LD Format', () => {
460
- it('should produce valid JSON', async () => {
461
- const resource = createMockResource();
462
- render(<JsonLdPanel resource={resource} />);
463
-
464
- const copyButton = screen.getByText(/Copy/);
465
- await userEvent.click(copyButton);
466
-
467
- const copiedText = mockClipboard.writeText.mock.calls[0][0];
468
-
469
- expect(() => {
470
- const parsed = JSON.parse(copiedText);
471
- expect(parsed).toHaveProperty('id');
472
- expect(parsed).toHaveProperty('name');
473
- expect(parsed).toHaveProperty('content');
474
- }).not.toThrow();
475
- });
476
-
477
- it('should include all resource fields', async () => {
478
- const resource = createMockResource({
479
- id: 'full-resource',
480
- name: 'Full Resource',
481
- content: 'Complete content',
482
- format: 'text/markdown',
483
- archived: true,
484
- entityTypes: ['Person', 'Organization'],
485
- locale: 'en-US',
486
- });
487
-
488
- render(<JsonLdPanel resource={resource} />);
489
-
490
- const copyButton = screen.getByText(/Copy/);
491
- await userEvent.click(copyButton);
492
-
493
- const copiedText = mockClipboard.writeText.mock.calls[0][0];
494
- const parsed = JSON.parse(copiedText);
495
-
496
- expect(parsed.id).toBe('full-resource');
497
- expect(parsed.name).toBe('Full Resource');
498
- expect(parsed.format).toBe('text/markdown');
499
- expect(parsed.archived).toBe(true);
500
- expect(parsed.entityTypes).toEqual(['Person', 'Organization']);
501
- expect(parsed.locale).toBe('en-US');
170
+ expect(screen.getByText('JSON-LD')).toHaveClass('semiont-jsonld-panel__title');
502
171
  });
503
172
  });
504
173
  });
@@ -1,5 +1,4 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import React from 'react';
3
2
  import { screen } from '@testing-library/react';
4
3
  import '@testing-library/jest-dom';
5
4
  import { renderWithProviders } from '../../../../test-utils';
@@ -1,14 +1,12 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import React from 'react';
3
2
  import { screen } from '@testing-library/react';
4
3
  import '@testing-library/jest-dom';
5
4
  import { renderWithProviders } from '../../../../test-utils';
6
5
  import userEvent from '@testing-library/user-event';
7
- import type { components } from '@semiont/core';
8
6
  import { BindNamespace } from '@semiont/sdk';
9
7
  import type { RouteBuilder } from '../../../../contexts/RoutingContext';
10
8
 
11
- import type { Annotation } from '@semiont/core';
9
+ import type { Annotation, AnnotationId } from '@semiont/core';
12
10
 
13
11
  // Stable mock functions defined outside vi.mock to avoid re-render loops
14
12
  const mockGetAnnotationExactText = vi.fn();
@@ -54,7 +52,7 @@ import { ReferenceEntry } from '../ReferenceEntry';
54
52
 
55
53
  const createMockReference = (overrides?: Partial<Annotation>): Annotation => ({
56
54
  '@context': 'http://www.w3.org/ns/anno.jsonld',
57
- id: 'ref-1',
55
+ id: 'ref-1' as AnnotationId,
58
56
  type: 'Annotation',
59
57
  motivation: 'linking',
60
58
  created: '2024-06-15T12:00:00Z',
@@ -75,7 +73,9 @@ const createMockReference = (overrides?: Partial<Annotation>): Annotation => ({
75
73
 
76
74
  const mockRoutes: RouteBuilder = {
77
75
  resourceDetail: vi.fn((id: string) => `/resources/${id}`),
78
- resourceList: vi.fn(() => '/resources'),
76
+ userProfile: vi.fn((id: string) => `/users/${id}`),
77
+ search: vi.fn((query: string) => `/search?q=${query}`),
78
+ home: vi.fn(() => '/'),
79
79
  };
80
80
 
81
81
  describe('ReferenceEntry', () => {