@semiont/react-ui 0.2.33-build.78 → 0.2.33-build.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EventBusContext-7GvDyO0d.d.mts +414 -0
- package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
- package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
- package/dist/{ar-RNNSPLQB.mjs → ar-4ZEORRW2.mjs} +8 -4
- package/dist/ar-4ZEORRW2.mjs.map +1 -0
- package/dist/{bn-S2CDL7EC.mjs → bn-SEDE5BQJ.mjs} +8 -4
- package/dist/bn-SEDE5BQJ.mjs.map +1 -0
- package/dist/{chunk-UDX2Q35T.mjs → chunk-D7NBW4RV.mjs} +8 -4
- package/dist/chunk-D7NBW4RV.mjs.map +1 -0
- package/dist/{chunk-35LLVRFK.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
- package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
- package/dist/{cs-RSV675WU.mjs → cs-7W4WF5WD.mjs} +8 -4
- package/dist/cs-7W4WF5WD.mjs.map +1 -0
- package/dist/{da-CHXNPWJC.mjs → da-75XGBCBK.mjs} +8 -4
- package/dist/da-75XGBCBK.mjs.map +1 -0
- package/dist/{de-KPEZ53D4.mjs → de-ODJVFLHM.mjs} +8 -4
- package/dist/de-ODJVFLHM.mjs.map +1 -0
- package/dist/{el-MW2BME5T.mjs → el-C4PM4WB3.mjs} +8 -4
- package/dist/el-C4PM4WB3.mjs.map +1 -0
- package/dist/{en-EVMIX24Y.mjs → en-KJCJQ4OO.mjs} +2 -2
- package/dist/{es-HQ24NYS3.mjs → es-WD33R7QL.mjs} +8 -4
- package/dist/es-WD33R7QL.mjs.map +1 -0
- package/dist/{fa-W34LRLHG.mjs → fa-2BP6V56P.mjs} +8 -4
- package/dist/fa-2BP6V56P.mjs.map +1 -0
- package/dist/{fi-3U44IGOA.mjs → fi-USRRW24J.mjs} +8 -4
- package/dist/fi-USRRW24J.mjs.map +1 -0
- package/dist/{fr-N7DKX6NN.mjs → fr-EC5S6WVF.mjs} +8 -4
- package/dist/fr-EC5S6WVF.mjs.map +1 -0
- package/dist/{he-CS4WRXN3.mjs → he-7TBVIKAA.mjs} +8 -4
- package/dist/he-7TBVIKAA.mjs.map +1 -0
- package/dist/{hi-GJDY46KA.mjs → hi-FO4VIZLA.mjs} +8 -4
- package/dist/hi-FO4VIZLA.mjs.map +1 -0
- package/dist/{id-WAEZJK2Y.mjs → id-7U7GGVWY.mjs} +8 -4
- package/dist/id-7U7GGVWY.mjs.map +1 -0
- package/dist/index.css +123 -85
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +699 -529
- package/dist/index.mjs +4291 -3491
- package/dist/index.mjs.map +1 -1
- package/dist/{it-VDNDMZPU.mjs → it-Y4OPL6I2.mjs} +8 -4
- package/dist/it-Y4OPL6I2.mjs.map +1 -0
- package/dist/{ja-5PEH56J5.mjs → ja-PK7SQL55.mjs} +8 -4
- package/dist/ja-PK7SQL55.mjs.map +1 -0
- package/dist/{ko-JYPL3WVA.mjs → ko-L25PXMYD.mjs} +8 -4
- package/dist/ko-L25PXMYD.mjs.map +1 -0
- package/dist/{ms-5PZVW76T.mjs → ms-STH777QM.mjs} +8 -4
- package/dist/ms-STH777QM.mjs.map +1 -0
- package/dist/{nl-YXES36KM.mjs → nl-Y7LECDDR.mjs} +8 -4
- package/dist/nl-Y7LECDDR.mjs.map +1 -0
- package/dist/{no-XRA2UCQD.mjs → no-KEKCEWU6.mjs} +8 -4
- package/dist/no-KEKCEWU6.mjs.map +1 -0
- package/dist/{pl-WH6LJA5G.mjs → pl-7A7OC75O.mjs} +8 -4
- package/dist/pl-7A7OC75O.mjs.map +1 -0
- package/dist/{pt-7GAG57BM.mjs → pt-35HTM7RA.mjs} +8 -4
- package/dist/pt-35HTM7RA.mjs.map +1 -0
- package/dist/{ro-BTDDRB7N.mjs → ro-VAWL5KQA.mjs} +8 -4
- package/dist/ro-VAWL5KQA.mjs.map +1 -0
- package/dist/{sv-7V5C2IT4.mjs → sv-7ZK5EQEB.mjs} +8 -4
- package/dist/sv-7ZK5EQEB.mjs.map +1 -0
- package/dist/test-utils.d.mts +18 -8
- package/dist/test-utils.mjs +36 -14
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-LPKYLBX5.mjs → th-UDWZ4X34.mjs} +8 -4
- package/dist/th-UDWZ4X34.mjs.map +1 -0
- package/dist/{tr-DU4RQL4M.mjs → tr-4WMPK3UX.mjs} +8 -4
- package/dist/tr-4WMPK3UX.mjs.map +1 -0
- package/dist/{uk-36UHTDDI.mjs → uk-SSLASQYJ.mjs} +8 -4
- package/dist/uk-SSLASQYJ.mjs.map +1 -0
- package/dist/{vi-GDHOUZDH.mjs → vi-IF42Z5PU.mjs} +8 -4
- package/dist/vi-IF42Z5PU.mjs.map +1 -0
- package/dist/{zh-TYUID4XZ.mjs → zh-HRQTNTAI.mjs} +8 -4
- package/dist/zh-HRQTNTAI.mjs.map +1 -0
- package/package.json +8 -2
- package/src/components/CodeMirrorRenderer.tsx +66 -93
- package/src/components/DetectionProgressWidget.tsx +16 -5
- package/src/components/LiveRegion.tsx +18 -18
- package/src/components/ResizeHandle.tsx +10 -4
- package/src/components/SessionTimer.tsx +2 -2
- package/src/components/Toolbar.tsx +18 -9
- package/src/components/__tests__/SessionTimer.test.tsx +9 -9
- package/src/components/annotation/AnnotateToolbar.tsx +17 -15
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
- package/src/components/annotation/annotation-entries.css +10 -0
- package/src/components/annotation-popups/JsonLdView.tsx +8 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
- package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
- package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
- package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
- package/src/components/modals/ResourceSearchModal.tsx +2 -2
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
- package/src/components/navigation/NavigationTabs.css +36 -24
- package/src/components/navigation/ObservableLink.tsx +91 -0
- package/src/components/navigation/SimpleNavigation.tsx +20 -16
- package/src/components/navigation/SortableResourceTab.tsx +11 -5
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
- package/src/components/resource/AnnotateView.tsx +64 -138
- package/src/components/resource/AnnotationHistory.tsx +12 -13
- package/src/components/resource/BrowseView.tsx +89 -177
- package/src/components/resource/HistoryEvent.tsx +16 -11
- package/src/components/resource/ResourceViewer.tsx +201 -370
- package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
- package/src/components/resource/event-formatting.ts +316 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
- package/src/components/resource/panels/AssessmentPanel.tsx +137 -31
- package/src/components/resource/panels/CollaborationPanel.tsx +20 -13
- package/src/components/resource/panels/CommentEntry.tsx +38 -32
- package/src/components/resource/panels/CommentsPanel.tsx +153 -31
- package/src/components/resource/panels/DetectSection.css +36 -1
- package/src/components/resource/panels/DetectSection.tsx +38 -10
- package/src/components/resource/panels/HighlightEntry.tsx +25 -33
- package/src/components/resource/panels/HighlightPanel.tsx +100 -25
- package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
- package/src/components/resource/panels/ReferencesPanel.tsx +166 -49
- package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
- package/src/components/resource/panels/StatisticsPanel.tsx +9 -19
- package/src/components/resource/panels/TagEntry.tsx +25 -33
- package/src/components/resource/panels/TaggingPanel.tsx +141 -25
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +46 -101
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +566 -0
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +146 -141
- package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
- package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +228 -103
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +586 -0
- package/src/components/settings/SettingsPanel.tsx +15 -12
- package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
- package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
- package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
- package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
- package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
- package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
- package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
- package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
- package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +16 -27
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
- package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
- package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +504 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +145 -91
- package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +325 -476
- package/src/styles/motivations/motivation-assessment.css +28 -0
- package/src/styles/patterns/panel-helpers.css +26 -0
- package/translations/ar.json +7 -3
- package/translations/bn.json +7 -3
- package/translations/cs.json +7 -3
- package/translations/da.json +7 -3
- package/translations/de.json +7 -3
- package/translations/el.json +7 -3
- package/translations/en.json +7 -3
- package/translations/es.json +7 -3
- package/translations/fa.json +7 -3
- package/translations/fi.json +7 -3
- package/translations/fr.json +7 -3
- package/translations/he.json +7 -3
- package/translations/hi.json +7 -3
- package/translations/id.json +7 -3
- package/translations/it.json +7 -3
- package/translations/ja.json +7 -3
- package/translations/ko.json +7 -3
- package/translations/ms.json +7 -3
- package/translations/nl.json +7 -3
- package/translations/no.json +7 -3
- package/translations/pl.json +7 -3
- package/translations/pt.json +7 -3
- package/translations/ro.json +7 -3
- package/translations/sv.json +7 -3
- package/translations/th.json +7 -3
- package/translations/tr.json +7 -3
- package/translations/uk.json +7 -3
- package/translations/vi.json +7 -3
- package/translations/zh.json +7 -3
- package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
- package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
- package/dist/ar-RNNSPLQB.mjs.map +0 -1
- package/dist/bn-S2CDL7EC.mjs.map +0 -1
- package/dist/chunk-35LLVRFK.mjs.map +0 -1
- package/dist/chunk-UDX2Q35T.mjs.map +0 -1
- package/dist/cs-RSV675WU.mjs.map +0 -1
- package/dist/da-CHXNPWJC.mjs.map +0 -1
- package/dist/de-KPEZ53D4.mjs.map +0 -1
- package/dist/el-MW2BME5T.mjs.map +0 -1
- package/dist/es-HQ24NYS3.mjs.map +0 -1
- package/dist/fa-W34LRLHG.mjs.map +0 -1
- package/dist/fi-3U44IGOA.mjs.map +0 -1
- package/dist/fr-N7DKX6NN.mjs.map +0 -1
- package/dist/he-CS4WRXN3.mjs.map +0 -1
- package/dist/hi-GJDY46KA.mjs.map +0 -1
- package/dist/id-WAEZJK2Y.mjs.map +0 -1
- package/dist/it-VDNDMZPU.mjs.map +0 -1
- package/dist/ja-5PEH56J5.mjs.map +0 -1
- package/dist/ko-JYPL3WVA.mjs.map +0 -1
- package/dist/ms-5PZVW76T.mjs.map +0 -1
- package/dist/nl-YXES36KM.mjs.map +0 -1
- package/dist/no-XRA2UCQD.mjs.map +0 -1
- package/dist/pl-WH6LJA5G.mjs.map +0 -1
- package/dist/pt-7GAG57BM.mjs.map +0 -1
- package/dist/ro-BTDDRB7N.mjs.map +0 -1
- package/dist/sv-7V5C2IT4.mjs.map +0 -1
- package/dist/th-LPKYLBX5.mjs.map +0 -1
- package/dist/tr-DU4RQL4M.mjs.map +0 -1
- package/dist/uk-36UHTDDI.mjs.map +0 -1
- package/dist/vi-GDHOUZDH.mjs.map +0 -1
- package/dist/zh-TYUID4XZ.mjs.map +0 -1
- /package/dist/{en-EVMIX24Y.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useRef } from 'react';
|
|
3
4
|
import type { components } from '@semiont/api-client';
|
|
4
5
|
import { getSvgSelector, isHighlight, isReference, isAssessment, isComment, isTag, isBodyResolved, isResolvedReference } from '@semiont/api-client';
|
|
5
6
|
import { parseSvgSelector } from '@semiont/api-client';
|
|
7
|
+
import type { EventBus } from '../../contexts/EventBusContext';
|
|
6
8
|
|
|
7
9
|
type Annotation = components['schemas']['Annotation'];
|
|
8
10
|
|
|
@@ -12,8 +14,7 @@ interface AnnotationOverlayProps {
|
|
|
12
14
|
imageHeight: number;
|
|
13
15
|
displayWidth: number;
|
|
14
16
|
displayHeight: number;
|
|
15
|
-
|
|
16
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
17
|
+
eventBus?: EventBus;
|
|
17
18
|
hoveredAnnotationId?: string | null;
|
|
18
19
|
selectedAnnotationId?: string | null;
|
|
19
20
|
}
|
|
@@ -59,6 +60,9 @@ function getAnnotationTooltip(annotation: Annotation): string {
|
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* Render annotation overlay - displays existing annotations as SVG shapes
|
|
63
|
+
*
|
|
64
|
+
* @emits annotation:hover - Annotation hovered or unhovered. Payload: { annotationId: string | null }
|
|
65
|
+
* @emits annotation:click - Annotation clicked. Payload: { annotationId: string, motivation: Motivation }
|
|
62
66
|
*/
|
|
63
67
|
export function AnnotationOverlay({
|
|
64
68
|
annotations,
|
|
@@ -66,14 +70,30 @@ export function AnnotationOverlay({
|
|
|
66
70
|
imageHeight,
|
|
67
71
|
displayWidth,
|
|
68
72
|
displayHeight,
|
|
69
|
-
|
|
70
|
-
onAnnotationHover,
|
|
73
|
+
eventBus,
|
|
71
74
|
hoveredAnnotationId,
|
|
72
75
|
selectedAnnotationId
|
|
73
76
|
}: AnnotationOverlayProps) {
|
|
74
77
|
const scaleX = displayWidth / imageWidth;
|
|
75
78
|
const scaleY = displayHeight / imageHeight;
|
|
76
79
|
|
|
80
|
+
// Track current hover state to prevent redundant emissions
|
|
81
|
+
const currentHover = useRef<string | null>(null);
|
|
82
|
+
|
|
83
|
+
const handleMouseEnter = (annotationId: string) => {
|
|
84
|
+
if (currentHover.current !== annotationId) {
|
|
85
|
+
currentHover.current = annotationId;
|
|
86
|
+
eventBus?.emit('annotation:hover', { annotationId });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleMouseLeave = () => {
|
|
91
|
+
if (currentHover.current !== null) {
|
|
92
|
+
currentHover.current = null;
|
|
93
|
+
eventBus?.emit('annotation:hover', { annotationId: null });
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
77
97
|
return (
|
|
78
98
|
<svg
|
|
79
99
|
className="semiont-annotation-overlay"
|
|
@@ -120,9 +140,9 @@ export function AnnotationOverlay({
|
|
|
120
140
|
className="semiont-annotation-overlay__shape"
|
|
121
141
|
data-hovered={isHovered ? 'true' : 'false'}
|
|
122
142
|
data-selected={isSelected ? 'true' : 'false'}
|
|
123
|
-
onClick={() =>
|
|
124
|
-
onMouseEnter={() =>
|
|
125
|
-
onMouseLeave={
|
|
143
|
+
onClick={() => eventBus?.emit('annotation:click', { annotationId: annotation.id, motivation: annotation.motivation })}
|
|
144
|
+
onMouseEnter={() => handleMouseEnter(annotation.id)}
|
|
145
|
+
onMouseLeave={handleMouseLeave}
|
|
126
146
|
/>
|
|
127
147
|
{statusEmoji && (
|
|
128
148
|
<text
|
|
@@ -133,10 +153,10 @@ export function AnnotationOverlay({
|
|
|
133
153
|
style={{ userSelect: 'none' }}
|
|
134
154
|
onClick={(e) => {
|
|
135
155
|
e.stopPropagation();
|
|
136
|
-
|
|
156
|
+
eventBus?.emit('annotation:click', { annotationId: annotation.id, motivation: annotation.motivation });
|
|
137
157
|
}}
|
|
138
|
-
onMouseEnter={() =>
|
|
139
|
-
onMouseLeave={
|
|
158
|
+
onMouseEnter={() => handleMouseEnter(annotation.id)}
|
|
159
|
+
onMouseLeave={handleMouseLeave}
|
|
140
160
|
>
|
|
141
161
|
{statusEmoji}
|
|
142
162
|
</text>
|
|
@@ -167,9 +187,9 @@ export function AnnotationOverlay({
|
|
|
167
187
|
className="semiont-annotation-overlay__shape"
|
|
168
188
|
data-hovered={isHovered ? 'true' : 'false'}
|
|
169
189
|
data-selected={isSelected ? 'true' : 'false'}
|
|
170
|
-
onClick={() =>
|
|
171
|
-
onMouseEnter={() =>
|
|
172
|
-
onMouseLeave={
|
|
190
|
+
onClick={() => eventBus?.emit('annotation:click', { annotationId: annotation.id, motivation: annotation.motivation })}
|
|
191
|
+
onMouseEnter={() => handleMouseEnter(annotation.id)}
|
|
192
|
+
onMouseLeave={handleMouseLeave}
|
|
173
193
|
/>
|
|
174
194
|
{statusEmoji && (
|
|
175
195
|
<text
|
|
@@ -180,10 +200,10 @@ export function AnnotationOverlay({
|
|
|
180
200
|
style={{ userSelect: 'none' }}
|
|
181
201
|
onClick={(e) => {
|
|
182
202
|
e.stopPropagation();
|
|
183
|
-
|
|
203
|
+
eventBus?.emit('annotation:click', { annotationId: annotation.id, motivation: annotation.motivation });
|
|
184
204
|
}}
|
|
185
|
-
onMouseEnter={() =>
|
|
186
|
-
onMouseLeave={
|
|
205
|
+
onMouseEnter={() => handleMouseEnter(annotation.id)}
|
|
206
|
+
onMouseLeave={handleMouseLeave}
|
|
187
207
|
>
|
|
188
208
|
{statusEmoji}
|
|
189
209
|
</text>
|
|
@@ -227,9 +247,9 @@ export function AnnotationOverlay({
|
|
|
227
247
|
className="semiont-annotation-overlay__shape"
|
|
228
248
|
data-hovered={isHovered ? 'true' : 'false'}
|
|
229
249
|
data-selected={isSelected ? 'true' : 'false'}
|
|
230
|
-
onClick={() =>
|
|
231
|
-
onMouseEnter={() =>
|
|
232
|
-
onMouseLeave={
|
|
250
|
+
onClick={() => eventBus?.emit('annotation:click', { annotationId: annotation.id, motivation: annotation.motivation })}
|
|
251
|
+
onMouseEnter={() => handleMouseEnter(annotation.id)}
|
|
252
|
+
onMouseLeave={handleMouseLeave}
|
|
233
253
|
/>
|
|
234
254
|
{statusEmoji && (
|
|
235
255
|
<text
|
|
@@ -240,10 +260,10 @@ export function AnnotationOverlay({
|
|
|
240
260
|
style={{ userSelect: 'none' }}
|
|
241
261
|
onClick={(e) => {
|
|
242
262
|
e.stopPropagation();
|
|
243
|
-
|
|
263
|
+
eventBus?.emit('annotation:click', { annotationId: annotation.id, motivation: annotation.motivation });
|
|
244
264
|
}}
|
|
245
|
-
onMouseEnter={() =>
|
|
246
|
-
onMouseLeave={
|
|
265
|
+
onMouseEnter={() => handleMouseEnter(annotation.id)}
|
|
266
|
+
onMouseLeave={handleMouseLeave}
|
|
247
267
|
>
|
|
248
268
|
{statusEmoji}
|
|
249
269
|
</text>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import React, { useRef, useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import type { components, ResourceUri } from '@semiont/api-client';
|
|
5
5
|
import { createRectangleSvg, createCircleSvg, createPolygonSvg, scaleSvgToNative, parseSvgSelector, type Point } from '@semiont/api-client';
|
|
6
6
|
import { AnnotationOverlay } from './AnnotationOverlay';
|
|
7
7
|
import type { SelectionMotivation } from '../annotation/AnnotateToolbar';
|
|
8
|
+
import type { EventBus } from '../../contexts/EventBusContext';
|
|
8
9
|
|
|
9
10
|
type Annotation = components['schemas']['Annotation'];
|
|
10
11
|
|
|
@@ -38,30 +39,30 @@ interface SvgDrawingCanvasProps {
|
|
|
38
39
|
existingAnnotations?: Annotation[];
|
|
39
40
|
drawingMode: DrawingMode;
|
|
40
41
|
selectedMotivation?: SelectionMotivation | null;
|
|
41
|
-
|
|
42
|
-
onAnnotationClick?: (annotation: Annotation) => void;
|
|
43
|
-
onAnnotationHover?: (annotationId: string | null) => void;
|
|
42
|
+
eventBus?: EventBus;
|
|
44
43
|
hoveredAnnotationId?: string | null;
|
|
45
44
|
selectedAnnotationId?: string | null;
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
/**
|
|
48
|
+
* SVG-based drawing canvas for creating image annotations with shapes
|
|
49
|
+
*
|
|
50
|
+
* @emits annotation:click - Annotation clicked on canvas. Payload: { annotationId: string, motivation: Motivation }
|
|
51
|
+
* @emits annotation:requested - New annotation drawn on canvas. Payload: { selector: SvgSelector, motivation: SelectionMotivation }
|
|
52
|
+
*/
|
|
48
53
|
export function SvgDrawingCanvas({
|
|
49
54
|
resourceUri,
|
|
50
55
|
existingAnnotations = [],
|
|
51
56
|
drawingMode,
|
|
52
57
|
selectedMotivation,
|
|
53
|
-
|
|
54
|
-
onAnnotationClick,
|
|
55
|
-
onAnnotationHover,
|
|
58
|
+
eventBus,
|
|
56
59
|
hoveredAnnotationId,
|
|
57
60
|
selectedAnnotationId
|
|
58
61
|
}: SvgDrawingCanvasProps) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// This allows us to add authentication headers which <img> tags can't send
|
|
64
|
-
const imageUrl = `/api/resources/${resourceId}`;
|
|
62
|
+
const imageUrl = useMemo(() => {
|
|
63
|
+
const resourceId = resourceUri.split('/').pop();
|
|
64
|
+
return `/api/resources/${resourceId}`;
|
|
65
|
+
}, [resourceUri]);
|
|
65
66
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
66
67
|
const imageRef = useRef<HTMLImageElement>(null);
|
|
67
68
|
const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null);
|
|
@@ -172,7 +173,7 @@ export function SvgDrawingCanvas({
|
|
|
172
173
|
|
|
173
174
|
if (dragDistance < MIN_DRAG_DISTANCE) {
|
|
174
175
|
// This was a click, not a drag - check if we clicked an existing annotation
|
|
175
|
-
if (
|
|
176
|
+
if (existingAnnotations.length > 0) {
|
|
176
177
|
// Find annotation at click point
|
|
177
178
|
// Note: We're checking in display coordinates
|
|
178
179
|
const clickedAnnotation = existingAnnotations.find(ann => {
|
|
@@ -212,7 +213,7 @@ export function SvgDrawingCanvas({
|
|
|
212
213
|
});
|
|
213
214
|
|
|
214
215
|
if (clickedAnnotation) {
|
|
215
|
-
|
|
216
|
+
eventBus?.emit('annotation:click', { annotationId: clickedAnnotation.id, motivation: clickedAnnotation.motivation });
|
|
216
217
|
setIsDrawing(false);
|
|
217
218
|
setStartPoint(null);
|
|
218
219
|
setCurrentPoint(null);
|
|
@@ -273,25 +274,22 @@ export function SvgDrawingCanvas({
|
|
|
273
274
|
imageDimensions.height
|
|
274
275
|
);
|
|
275
276
|
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// Notify parent
|
|
286
|
-
if (onAnnotationCreate) {
|
|
287
|
-
onAnnotationCreate(nativeSvg, screenPosition);
|
|
277
|
+
// Emit annotation:requested event with SvgSelector
|
|
278
|
+
if (eventBus && selectedMotivation) {
|
|
279
|
+
eventBus.emit('annotation:requested', {
|
|
280
|
+
selector: {
|
|
281
|
+
type: 'SvgSelector',
|
|
282
|
+
value: nativeSvg
|
|
283
|
+
},
|
|
284
|
+
motivation: selectedMotivation
|
|
285
|
+
});
|
|
288
286
|
}
|
|
289
287
|
|
|
290
288
|
// Reset drawing state
|
|
291
289
|
setIsDrawing(false);
|
|
292
290
|
setStartPoint(null);
|
|
293
291
|
setCurrentPoint(null);
|
|
294
|
-
}, [isDrawing, startPoint, drawingMode, displayDimensions, imageDimensions, getRelativeCoordinates,
|
|
292
|
+
}, [isDrawing, startPoint, drawingMode, displayDimensions, imageDimensions, getRelativeCoordinates, selectedMotivation, existingAnnotations]);
|
|
295
293
|
|
|
296
294
|
// Cancel drawing on mouse leave
|
|
297
295
|
const handleMouseLeave = useCallback(() => {
|
|
@@ -340,8 +338,7 @@ export function SvgDrawingCanvas({
|
|
|
340
338
|
imageHeight={imageDimensions.height}
|
|
341
339
|
displayWidth={displayDimensions.width}
|
|
342
340
|
displayHeight={displayDimensions.height}
|
|
343
|
-
{...(
|
|
344
|
-
{...(onAnnotationHover && { onAnnotationHover })}
|
|
341
|
+
{...(eventBus && { eventBus })}
|
|
345
342
|
{...(hoveredAnnotationId !== undefined && { hoveredAnnotationId })}
|
|
346
343
|
{...(selectedAnnotationId !== undefined && { selectedAnnotationId })}
|
|
347
344
|
/>
|
|
@@ -4,32 +4,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { LeftSidebar } from '../LeftSidebar';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
vi.mock('../../navigation/NavigationMenu', () => ({
|
|
9
|
-
NavigationMenu: ({ t, onItemClick }: any) => (
|
|
10
|
-
<div data-testid="navigation-menu">
|
|
11
|
-
<button onClick={onItemClick}>Menu Item</button>
|
|
12
|
-
<span>{t('home')}</span>
|
|
13
|
-
</div>
|
|
14
|
-
),
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
vi.mock('../../branding/SemiontBranding', () => ({
|
|
18
|
-
SemiontBranding: ({ t, size, showTagline }: any) => (
|
|
19
|
-
<div data-testid="semiont-branding">
|
|
20
|
-
<span>Semiont {size}</span>
|
|
21
|
-
{showTagline && <span>Tagline</span>}
|
|
22
|
-
</div>
|
|
23
|
-
),
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
vi.mock('@/hooks/useAuth', () => ({
|
|
27
|
-
useAuth: vi.fn(() => ({
|
|
28
|
-
isAuthenticated: true,
|
|
29
|
-
isAdmin: false,
|
|
30
|
-
isModerator: false,
|
|
31
|
-
})),
|
|
32
|
-
}));
|
|
7
|
+
// No mocks - using real components via composition
|
|
33
8
|
|
|
34
9
|
// Mock Link component
|
|
35
10
|
const MockLink = ({ href, children, ...props }: any) => (
|
|
@@ -98,7 +73,8 @@ describe('LeftSidebar Component', () => {
|
|
|
98
73
|
</LeftSidebar>
|
|
99
74
|
);
|
|
100
75
|
|
|
101
|
-
|
|
76
|
+
// Real SemiontBranding renders "Semiont" text
|
|
77
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
102
78
|
});
|
|
103
79
|
|
|
104
80
|
it('should render branding link', () => {
|
|
@@ -276,7 +252,9 @@ describe('LeftSidebar Component', () => {
|
|
|
276
252
|
);
|
|
277
253
|
|
|
278
254
|
expect(mockChildren).toHaveBeenCalled();
|
|
279
|
-
|
|
255
|
+
// Real NavigationMenu renders navigation links
|
|
256
|
+
const childrenContent = screen.getByTestId('children-content');
|
|
257
|
+
expect(childrenContent.querySelector('nav.semiont-navigation-menu')).toBeInTheDocument();
|
|
280
258
|
});
|
|
281
259
|
|
|
282
260
|
it('should pass onClose callback to NavigationMenu', () => {
|
|
@@ -297,8 +275,8 @@ describe('LeftSidebar Component', () => {
|
|
|
297
275
|
</LeftSidebar>
|
|
298
276
|
);
|
|
299
277
|
|
|
300
|
-
//
|
|
301
|
-
const menuItem = screen.getByText('
|
|
278
|
+
// Real NavigationMenu renders a link (translated 'know' key)
|
|
279
|
+
const menuItem = screen.getByText('nav.know');
|
|
302
280
|
fireEvent.click(menuItem);
|
|
303
281
|
|
|
304
282
|
expect(mockOnClose).toHaveBeenCalledOnce();
|
|
@@ -355,7 +333,8 @@ describe('LeftSidebar Component', () => {
|
|
|
355
333
|
);
|
|
356
334
|
|
|
357
335
|
expect(screen.getByText('S')).toBeInTheDocument();
|
|
358
|
-
|
|
336
|
+
// When collapsed, full "Semiont" branding is not shown
|
|
337
|
+
expect(screen.queryByText(/^Semiont$/)).not.toBeInTheDocument();
|
|
359
338
|
});
|
|
360
339
|
});
|
|
361
340
|
|
|
@@ -408,7 +387,7 @@ describe('LeftSidebar Component', () => {
|
|
|
408
387
|
</LeftSidebar>
|
|
409
388
|
);
|
|
410
389
|
|
|
411
|
-
expect(screen.
|
|
390
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
412
391
|
});
|
|
413
392
|
|
|
414
393
|
it('should default branding link to /', () => {
|
|
@@ -423,7 +402,7 @@ describe('LeftSidebar Component', () => {
|
|
|
423
402
|
</LeftSidebar>
|
|
424
403
|
);
|
|
425
404
|
|
|
426
|
-
expect(screen.
|
|
405
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
427
406
|
});
|
|
428
407
|
});
|
|
429
408
|
});
|
|
@@ -4,22 +4,15 @@ import { render, screen } from '@testing-library/react';
|
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { PageLayout } from '../PageLayout';
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Mock Footer
|
|
17
|
-
vi.mock('../../navigation/Footer', () => ({
|
|
18
|
-
Footer: ({ t }: any) => (
|
|
19
|
-
<div data-testid="footer">
|
|
20
|
-
<span>{t('copyright')}</span>
|
|
21
|
-
</div>
|
|
22
|
-
),
|
|
7
|
+
// No mocks - using real components via composition
|
|
8
|
+
// Need to mock useDropdown hook used by UnifiedHeader
|
|
9
|
+
vi.mock('@/hooks/useUI', () => ({
|
|
10
|
+
useDropdown: vi.fn(() => ({
|
|
11
|
+
isOpen: false,
|
|
12
|
+
toggle: vi.fn(),
|
|
13
|
+
close: vi.fn(),
|
|
14
|
+
dropdownRef: { current: null },
|
|
15
|
+
})),
|
|
23
16
|
}));
|
|
24
17
|
|
|
25
18
|
// Mock Link component
|
|
@@ -69,7 +62,19 @@ describe('PageLayout Component', () => {
|
|
|
69
62
|
</PageLayout>
|
|
70
63
|
);
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
// Real UnifiedHeader renders a header element
|
|
66
|
+
const { container } = render(
|
|
67
|
+
<PageLayout
|
|
68
|
+
Link={MockLink}
|
|
69
|
+
routes={mockRoutes}
|
|
70
|
+
t={mockT}
|
|
71
|
+
tNav={mockTNav}
|
|
72
|
+
tHome={mockTHome}
|
|
73
|
+
>
|
|
74
|
+
<div>Content</div>
|
|
75
|
+
</PageLayout>
|
|
76
|
+
);
|
|
77
|
+
expect(container.querySelector('header')).toBeInTheDocument();
|
|
73
78
|
});
|
|
74
79
|
|
|
75
80
|
it('should render footer', () => {
|
|
@@ -85,7 +90,8 @@ describe('PageLayout Component', () => {
|
|
|
85
90
|
</PageLayout>
|
|
86
91
|
);
|
|
87
92
|
|
|
88
|
-
|
|
93
|
+
// Real Footer renders contentinfo role
|
|
94
|
+
expect(screen.getByRole('contentinfo')).toBeInTheDocument();
|
|
89
95
|
});
|
|
90
96
|
|
|
91
97
|
it('should render children in main element', () => {
|
|
@@ -180,7 +186,7 @@ describe('PageLayout Component', () => {
|
|
|
180
186
|
|
|
181
187
|
describe('Props Handling', () => {
|
|
182
188
|
it('should pass showAuthLinks to UnifiedHeader', () => {
|
|
183
|
-
const { rerender } = render(
|
|
189
|
+
const { rerender, container } = render(
|
|
184
190
|
<PageLayout
|
|
185
191
|
Link={MockLink}
|
|
186
192
|
routes={mockRoutes}
|
|
@@ -193,7 +199,7 @@ describe('PageLayout Component', () => {
|
|
|
193
199
|
</PageLayout>
|
|
194
200
|
);
|
|
195
201
|
|
|
196
|
-
expect(
|
|
202
|
+
expect(container.querySelector('header')).toBeInTheDocument();
|
|
197
203
|
|
|
198
204
|
rerender(
|
|
199
205
|
<PageLayout
|
|
@@ -208,11 +214,11 @@ describe('PageLayout Component', () => {
|
|
|
208
214
|
</PageLayout>
|
|
209
215
|
);
|
|
210
216
|
|
|
211
|
-
expect(
|
|
217
|
+
expect(container.querySelector('header')).toBeInTheDocument();
|
|
212
218
|
});
|
|
213
219
|
|
|
214
220
|
it('should default showAuthLinks to true', () => {
|
|
215
|
-
render(
|
|
221
|
+
const { container } = render(
|
|
216
222
|
<PageLayout
|
|
217
223
|
Link={MockLink}
|
|
218
224
|
routes={mockRoutes}
|
|
@@ -224,7 +230,7 @@ describe('PageLayout Component', () => {
|
|
|
224
230
|
</PageLayout>
|
|
225
231
|
);
|
|
226
232
|
|
|
227
|
-
expect(
|
|
233
|
+
expect(container.querySelector('header')).toBeInTheDocument();
|
|
228
234
|
});
|
|
229
235
|
|
|
230
236
|
it('should pass translation functions to components', () => {
|
|
@@ -240,11 +246,9 @@ describe('PageLayout Component', () => {
|
|
|
240
246
|
</PageLayout>
|
|
241
247
|
);
|
|
242
248
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// Footer should receive t
|
|
247
|
-
expect(screen.getByText('translated.copyright')).toBeInTheDocument();
|
|
249
|
+
// Real Footer renders copyright with dynamic year
|
|
250
|
+
const currentYear = new Date().getFullYear();
|
|
251
|
+
expect(screen.getByText(`translated.copyright`)).toBeInTheDocument();
|
|
248
252
|
});
|
|
249
253
|
});
|
|
250
254
|
|
|
@@ -283,7 +287,8 @@ describe('PageLayout Component', () => {
|
|
|
283
287
|
</PageLayout>
|
|
284
288
|
);
|
|
285
289
|
|
|
286
|
-
|
|
290
|
+
// Real Footer renders contentinfo role
|
|
291
|
+
expect(screen.getByRole('contentinfo')).toBeInTheDocument();
|
|
287
292
|
});
|
|
288
293
|
|
|
289
294
|
it('should render with onOpenKeyboardHelp handler', () => {
|
|
@@ -302,7 +307,7 @@ describe('PageLayout Component', () => {
|
|
|
302
307
|
</PageLayout>
|
|
303
308
|
);
|
|
304
309
|
|
|
305
|
-
expect(screen.
|
|
310
|
+
expect(screen.getByRole('contentinfo')).toBeInTheDocument();
|
|
306
311
|
});
|
|
307
312
|
});
|
|
308
313
|
|
|
@@ -355,14 +360,14 @@ describe('PageLayout Component', () => {
|
|
|
355
360
|
const wrapper = container.firstChild as HTMLElement;
|
|
356
361
|
const header = wrapper.querySelector('header');
|
|
357
362
|
const main = wrapper.querySelector('main');
|
|
358
|
-
const footer = wrapper.querySelector('footer')
|
|
363
|
+
const footer = wrapper.querySelector('footer');
|
|
359
364
|
|
|
360
365
|
// Header should come before main
|
|
361
366
|
expect(header?.compareDocumentPosition(main!)).toBe(
|
|
362
367
|
Node.DOCUMENT_POSITION_FOLLOWING
|
|
363
368
|
);
|
|
364
369
|
|
|
365
|
-
// Main should come before footer
|
|
370
|
+
// Main should come before footer (real Footer component has footer element)
|
|
366
371
|
if (footer) {
|
|
367
372
|
expect(main?.compareDocumentPosition(footer)).toBe(
|
|
368
373
|
Node.DOCUMENT_POSITION_FOLLOWING
|
|
@@ -4,32 +4,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { UnifiedHeader } from '../UnifiedHeader';
|
|
6
6
|
|
|
7
|
-
// Mock
|
|
8
|
-
vi.mock('../../branding/SemiontBranding', () => ({
|
|
9
|
-
SemiontBranding: ({ t, size, showTagline, compactTagline }: any) => (
|
|
10
|
-
<div data-testid="semiont-branding">
|
|
11
|
-
Semiont {size} {showTagline && '- Tagline'} {compactTagline && '(compact)'}
|
|
12
|
-
</div>
|
|
13
|
-
),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
vi.mock('../../navigation/NavigationMenu', () => ({
|
|
17
|
-
NavigationMenu: ({ t, onItemClick }: any) => (
|
|
18
|
-
<div data-testid="navigation-menu">
|
|
19
|
-
<button onClick={onItemClick}>Menu Item</button>
|
|
20
|
-
<span>{t('home')}</span>
|
|
21
|
-
</div>
|
|
22
|
-
),
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock('@/hooks/useAuth', () => ({
|
|
26
|
-
useAuth: vi.fn(() => ({
|
|
27
|
-
isAuthenticated: true,
|
|
28
|
-
isAdmin: false,
|
|
29
|
-
isModerator: false,
|
|
30
|
-
})),
|
|
31
|
-
}));
|
|
32
|
-
|
|
7
|
+
// Mock only hooks - using real components via composition
|
|
33
8
|
vi.mock('@/hooks/useUI', () => ({
|
|
34
9
|
useDropdown: vi.fn(() => ({
|
|
35
10
|
isOpen: false,
|
|
@@ -95,7 +70,8 @@ describe('UnifiedHeader Component', () => {
|
|
|
95
70
|
/>
|
|
96
71
|
);
|
|
97
72
|
|
|
98
|
-
|
|
73
|
+
// Real SemiontBranding renders "Semiont" text
|
|
74
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
99
75
|
});
|
|
100
76
|
|
|
101
77
|
it('should default to standalone variant', () => {
|
|
@@ -140,7 +116,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
140
116
|
/>
|
|
141
117
|
);
|
|
142
118
|
|
|
143
|
-
expect(screen.
|
|
119
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
144
120
|
});
|
|
145
121
|
});
|
|
146
122
|
|
|
@@ -171,7 +147,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
171
147
|
/>
|
|
172
148
|
);
|
|
173
149
|
|
|
174
|
-
expect(screen.
|
|
150
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
175
151
|
});
|
|
176
152
|
|
|
177
153
|
it('should not render floating variant when showBranding is false', () => {
|
|
@@ -202,7 +178,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
202
178
|
/>
|
|
203
179
|
);
|
|
204
180
|
|
|
205
|
-
expect(screen.
|
|
181
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
206
182
|
});
|
|
207
183
|
|
|
208
184
|
it('should hide branding when showBranding is false', () => {
|
|
@@ -216,7 +192,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
216
192
|
/>
|
|
217
193
|
);
|
|
218
194
|
|
|
219
|
-
expect(screen.
|
|
195
|
+
expect(screen.queryByText('Semiont')).not.toBeInTheDocument();
|
|
220
196
|
});
|
|
221
197
|
|
|
222
198
|
it('should render navigation button for branding', () => {
|
|
@@ -277,7 +253,8 @@ describe('UnifiedHeader Component', () => {
|
|
|
277
253
|
/>
|
|
278
254
|
);
|
|
279
255
|
|
|
280
|
-
|
|
256
|
+
// Real NavigationMenu renders nav.know link
|
|
257
|
+
expect(screen.getByText('nav.know')).toBeInTheDocument();
|
|
281
258
|
});
|
|
282
259
|
|
|
283
260
|
it('should not show dropdown menu when not authenticated', () => {
|
|
@@ -298,7 +275,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
298
275
|
/>
|
|
299
276
|
);
|
|
300
277
|
|
|
301
|
-
expect(screen.
|
|
278
|
+
expect(screen.queryByText('nav.know')).not.toBeInTheDocument();
|
|
302
279
|
});
|
|
303
280
|
|
|
304
281
|
it('should not show dropdown menu when closed', () => {
|
|
@@ -319,7 +296,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
319
296
|
/>
|
|
320
297
|
);
|
|
321
298
|
|
|
322
|
-
expect(screen.
|
|
299
|
+
expect(screen.queryByText('nav.know')).not.toBeInTheDocument();
|
|
323
300
|
});
|
|
324
301
|
|
|
325
302
|
it('should close dropdown when menu item clicked', () => {
|
|
@@ -341,7 +318,8 @@ describe('UnifiedHeader Component', () => {
|
|
|
341
318
|
/>
|
|
342
319
|
);
|
|
343
320
|
|
|
344
|
-
|
|
321
|
+
// Real NavigationMenu renders nav.know link
|
|
322
|
+
const menuItem = screen.getByText('nav.know');
|
|
345
323
|
fireEvent.click(menuItem);
|
|
346
324
|
|
|
347
325
|
expect(mockClose).toHaveBeenCalledOnce();
|
|
@@ -368,7 +346,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
368
346
|
);
|
|
369
347
|
|
|
370
348
|
// Should render branding with custom link
|
|
371
|
-
expect(screen.
|
|
349
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
372
350
|
});
|
|
373
351
|
|
|
374
352
|
it('should default branding link to /', () => {
|
|
@@ -381,7 +359,7 @@ describe('UnifiedHeader Component', () => {
|
|
|
381
359
|
/>
|
|
382
360
|
);
|
|
383
361
|
|
|
384
|
-
expect(screen.
|
|
362
|
+
expect(screen.getByText('Semiont')).toBeInTheDocument();
|
|
385
363
|
});
|
|
386
364
|
|
|
387
365
|
it('should pass admin status to NavigationMenu', () => {
|
|
@@ -403,7 +381,8 @@ describe('UnifiedHeader Component', () => {
|
|
|
403
381
|
/>
|
|
404
382
|
);
|
|
405
383
|
|
|
406
|
-
|
|
384
|
+
// Real NavigationMenu renders admin link
|
|
385
|
+
expect(screen.getByText('nav.administer')).toBeInTheDocument();
|
|
407
386
|
});
|
|
408
387
|
|
|
409
388
|
it('should pass moderator status to NavigationMenu', () => {
|
|
@@ -425,7 +404,8 @@ describe('UnifiedHeader Component', () => {
|
|
|
425
404
|
/>
|
|
426
405
|
);
|
|
427
406
|
|
|
428
|
-
|
|
407
|
+
// Real NavigationMenu renders moderate link
|
|
408
|
+
expect(screen.getByText('nav.moderate')).toBeInTheDocument();
|
|
429
409
|
});
|
|
430
410
|
});
|
|
431
411
|
|
|
@@ -567,7 +547,8 @@ describe('UnifiedHeader Component', () => {
|
|
|
567
547
|
const standaloneContent = standaloneContainer.querySelector('.semiont-unified-header');
|
|
568
548
|
expect(standaloneContent).toBeInTheDocument();
|
|
569
549
|
|
|
570
|
-
|
|
550
|
+
// Embedded variant has branding directly in container
|
|
551
|
+
const embeddedContent = embeddedContainer.querySelector('.semiont-branding');
|
|
571
552
|
expect(embeddedContent).toBeInTheDocument();
|
|
572
553
|
});
|
|
573
554
|
});
|