@semiont/react-ui 0.5.10 → 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{PdfAnnotationCanvas.client-P37SXBWQ.js → PdfAnnotationCanvas.client-75GY2EDR.js} +15 -6
- package/dist/PdfAnnotationCanvas.client-75GY2EDR.js.map +1 -0
- package/dist/{chunk-5LFFAVDI.js → chunk-PFUXQPWJ.js} +96 -96
- package/dist/{chunk-5LFFAVDI.js.map → chunk-PFUXQPWJ.js.map} +1 -1
- package/dist/{chunk-O2MD7TGE.js → chunk-VXASXU4K.js} +10 -2
- package/dist/chunk-VXASXU4K.js.map +1 -0
- package/dist/index.css +150 -112
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +334 -48
- package/dist/index.js +2494 -2201
- package/dist/index.js.map +1 -1
- package/dist/test-utils.d.ts +5 -1
- package/dist/test-utils.js +10 -4
- package/dist/test-utils.js.map +1 -1
- package/package.json +7 -7
- package/src/components/ResourceTagsInline.tsx +94 -11
- package/src/components/StatusDisplay.css +36 -0
- package/src/components/__tests__/ResourceTagsInline.test.tsx +101 -3
- package/src/components/annotation/AnnotateToolbar.tsx +53 -28
- package/src/components/annotation/__tests__/AnnotateToolbar.display.test.tsx +76 -0
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +124 -360
- package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +17 -5
- package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +137 -0
- package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +108 -0
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +17 -4
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +143 -7
- package/src/components/resource/AnnotateView.tsx +59 -38
- package/src/components/resource/BrowseView.tsx +172 -138
- package/src/components/resource/ResourceViewer.tsx +123 -130
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +55 -0
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +49 -0
- package/src/components/resource/__tests__/BrowseView.links.test.tsx +60 -0
- package/src/components/resource/__tests__/BrowseView.overlay-async.test.tsx +113 -0
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +161 -0
- package/src/components/resource/__tests__/BrowseView.registry.test.tsx +43 -0
- package/src/components/resource/__tests__/BrowseView.test.tsx +133 -53
- package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +102 -0
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +12 -7
- package/src/components/resource/__tests__/ResourceViewer.toolbar-opt-out.test.tsx +135 -0
- package/src/components/resource/__tests__/ResourceViewer.toolbar-prefs.test.tsx +200 -0
- package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +113 -0
- package/src/components/resource/__tests__/browse-renderers.test.tsx +34 -0
- package/src/components/resource/__tests__/inline-embedding.test.tsx +81 -0
- package/src/components/resource/__tests__/media-completeness.test.tsx +88 -0
- package/src/components/resource/browse-renderers.tsx +85 -0
- package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
- package/src/components/resource/panels/AssessmentPanel.tsx +12 -5
- package/src/components/resource/panels/AssistSection.tsx +4 -3
- package/src/components/resource/panels/CommentEntry.tsx +5 -4
- package/src/components/resource/panels/CommentsPanel.tsx +12 -5
- package/src/components/resource/panels/HighlightEntry.tsx +5 -4
- package/src/components/resource/panels/HighlightPanel.tsx +12 -5
- package/src/components/resource/panels/ReferenceEntry.tsx +9 -10
- package/src/components/resource/panels/ReferencesPanel.tsx +15 -6
- package/src/components/resource/panels/ResourceInfoPanel.tsx +4 -3
- package/src/components/resource/panels/TagEntry.tsx +5 -3
- package/src/components/resource/panels/TaggingPanel.tsx +11 -4
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +15 -2
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +28 -20
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +39 -28
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +46 -17
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +65 -57
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +54 -24
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +27 -17
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +23 -17
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +48 -77
- package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +87 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +15 -9
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +78 -59
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +11 -4
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +33 -19
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +16 -7
- package/src/components/toolbar/Toolbar.css +14 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +7 -17
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +43 -17
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +2 -14
- package/src/styles/features/resource-viewer.css +19 -0
- package/dist/PdfAnnotationCanvas.client-P37SXBWQ.js.map +0 -1
- package/dist/chunk-O2MD7TGE.js.map +0 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
4
|
-
import {
|
|
5
|
-
import { useObservable } from '../../../hooks/useObservable';
|
|
4
|
+
import type { SemiontSession } from '@semiont/sdk';
|
|
6
5
|
import { formatLocaleDisplay } from '@semiont/core';
|
|
7
6
|
import { resourceId as makeResourceId, type components } from '@semiont/core';
|
|
8
7
|
import { renderAgentLabel } from './agent-label';
|
|
@@ -11,6 +10,8 @@ import './ResourceInfoPanel.css';
|
|
|
11
10
|
type Agent = components['schemas']['Agent'];
|
|
12
11
|
|
|
13
12
|
interface Props {
|
|
13
|
+
/** Session carrying the client and event bus; null renders inert. */
|
|
14
|
+
session: SemiontSession | null;
|
|
14
15
|
resourceId: string;
|
|
15
16
|
documentEntityTypes: string[];
|
|
16
17
|
documentLocale?: string | undefined;
|
|
@@ -39,6 +40,7 @@ interface Props {
|
|
|
39
40
|
* @emits mark:archive - Archive this resource
|
|
40
41
|
*/
|
|
41
42
|
export function ResourceInfoPanel({
|
|
43
|
+
session,
|
|
42
44
|
resourceId,
|
|
43
45
|
documentEntityTypes,
|
|
44
46
|
documentLocale,
|
|
@@ -54,7 +56,6 @@ export function ResourceInfoPanel({
|
|
|
54
56
|
onGenerate,
|
|
55
57
|
}: Props) {
|
|
56
58
|
const t = useTranslations('ResourceInfoPanel');
|
|
57
|
-
const session = useObservable(useSemiont().activeSession$);
|
|
58
59
|
|
|
59
60
|
// Single attribution surface. `wasAttributedTo` is the canonical list
|
|
60
61
|
// of responsible parties; if a producer set only `generator` we
|
|
@@ -5,12 +5,14 @@ import type { Ref } from 'react';
|
|
|
5
5
|
import type { Annotation } from '@semiont/core';
|
|
6
6
|
import { getAnnotationExactText } from '@semiont/core';
|
|
7
7
|
import { getTagCategory, getTagSchemaId } from '@semiont/ontology';
|
|
8
|
-
import { useSemiont } from '../../../session/SemiontProvider';
|
|
9
8
|
import { useObservable } from '../../../hooks/useObservable';
|
|
9
|
+
import type { SemiontSession } from '@semiont/sdk';
|
|
10
10
|
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
11
11
|
import { renderAgentLabel } from './agent-label';
|
|
12
12
|
|
|
13
13
|
interface TagEntryProps {
|
|
14
|
+
/** Session for interaction routing (browse.click, tag schemas); the panel threads it. */
|
|
15
|
+
session: SemiontSession | null;
|
|
14
16
|
tag: Annotation;
|
|
15
17
|
isFocused: boolean;
|
|
16
18
|
isHovered?: boolean;
|
|
@@ -18,13 +20,13 @@ interface TagEntryProps {
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export function TagEntry({
|
|
23
|
+
session,
|
|
21
24
|
tag,
|
|
22
25
|
isFocused,
|
|
23
26
|
isHovered = false,
|
|
24
27
|
ref,
|
|
25
28
|
}: TagEntryProps) {
|
|
26
|
-
const
|
|
27
|
-
const hoverProps = useHoverEmitter(tag.id);
|
|
29
|
+
const hoverProps = useHoverEmitter(session, tag.id);
|
|
28
30
|
|
|
29
31
|
const selectedText = getAnnotationExactText(tag);
|
|
30
32
|
const category = getTagCategory(tag);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import { useSemiont } from '../../../session/SemiontProvider';
|
|
6
5
|
import { useObservable } from '../../../hooks/useObservable';
|
|
7
|
-
import {
|
|
6
|
+
import type { SemiontSession } from '@semiont/sdk';
|
|
7
|
+
import { useSessionEventSubscriptions } from '../../../hooks/useSessionEventSubscriptions';
|
|
8
8
|
import type { components, Selector } from '@semiont/core';
|
|
9
9
|
import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
|
|
10
10
|
import { TagEntry } from './TagEntry';
|
|
@@ -39,6 +39,10 @@ function getSelectorDisplayText(selector: Selector | Selector[]): string | null
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
interface TaggingPanelProps {
|
|
42
|
+
/** Session carrying the client and event bus; null renders inert. */
|
|
43
|
+
session: SemiontSession | null;
|
|
44
|
+
/** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
|
|
45
|
+
resourceId: string;
|
|
42
46
|
annotations: Annotation[];
|
|
43
47
|
annotateMode?: boolean;
|
|
44
48
|
isAssisting?: boolean;
|
|
@@ -62,6 +66,8 @@ interface TaggingPanelProps {
|
|
|
62
66
|
* @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
|
|
63
67
|
*/
|
|
64
68
|
export function TaggingPanel({
|
|
69
|
+
session,
|
|
70
|
+
resourceId,
|
|
65
71
|
annotations,
|
|
66
72
|
annotateMode = true,
|
|
67
73
|
isAssisting = false,
|
|
@@ -74,7 +80,6 @@ export function TaggingPanel({
|
|
|
74
80
|
sourceLanguage,
|
|
75
81
|
}: TaggingPanelProps) {
|
|
76
82
|
const t = useTranslations('TaggingPanel');
|
|
77
|
-
const session = useObservable(useSemiont().activeSession$);
|
|
78
83
|
|
|
79
84
|
// Subscribe to the per-KB tag-schema registry. Schemas are runtime-
|
|
80
85
|
// registered by the KB at session start (see frame.addTagSchema).
|
|
@@ -126,7 +131,7 @@ export function TaggingPanel({
|
|
|
126
131
|
setTimeout(() => setFocusedAnnotationId(null), 3000);
|
|
127
132
|
}, []);
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
useSessionEventSubscriptions(session, {
|
|
130
135
|
'browse:click': handleAnnotationClick,
|
|
131
136
|
});
|
|
132
137
|
|
|
@@ -309,6 +314,7 @@ export function TaggingPanel({
|
|
|
309
314
|
onChange={(e) => {
|
|
310
315
|
if (e.target.value && pendingAnnotation) {
|
|
311
316
|
session?.client.mark.submit({
|
|
317
|
+
source: resourceId,
|
|
312
318
|
motivation: 'tagging',
|
|
313
319
|
selector: pendingAnnotation.selector,
|
|
314
320
|
body: [
|
|
@@ -524,6 +530,7 @@ export function TaggingPanel({
|
|
|
524
530
|
) : (
|
|
525
531
|
sortedAnnotations.map((tag) => (
|
|
526
532
|
<TagEntry
|
|
533
|
+
session={session}
|
|
527
534
|
key={tag.id}
|
|
528
535
|
tag={tag}
|
|
529
536
|
isFocused={tag.id === focusedAnnotationId}
|
|
@@ -5,6 +5,7 @@ import { useTranslations } from '../../../contexts/TranslationContext';
|
|
|
5
5
|
import type { components, Selector } from '@semiont/core';
|
|
6
6
|
type JobProgress = components['schemas']['JobProgress'];
|
|
7
7
|
import type { RouteBuilder, LinkComponentProps } from '../../../contexts/RoutingContext';
|
|
8
|
+
import type { SemiontSession } from '@semiont/sdk';
|
|
8
9
|
import type { Annotator } from '../../../lib/annotation-registry';
|
|
9
10
|
import { StatisticsPanel } from './StatisticsPanel';
|
|
10
11
|
import { HighlightPanel } from './HighlightPanel';
|
|
@@ -37,6 +38,12 @@ const TAB_ORDER: TabKey[] = ['statistics', 'reference', 'highlight', 'assessment
|
|
|
37
38
|
* - All operations managed via event bus (no callback props)
|
|
38
39
|
*/
|
|
39
40
|
interface UnifiedAnnotationsPanelProps {
|
|
41
|
+
/** Session carrying the client and event bus; null renders inert. */
|
|
42
|
+
session: SemiontSession | null;
|
|
43
|
+
|
|
44
|
+
/** Host-owned navigation: called with the resolved resource id when a reference entry opens. */
|
|
45
|
+
onOpenResource?: (resourceId: string) => void;
|
|
46
|
+
|
|
40
47
|
// All annotations (grouped internally by motivation)
|
|
41
48
|
annotations: Annotation[];
|
|
42
49
|
|
|
@@ -59,8 +66,9 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
59
66
|
referencedBy?: any[];
|
|
60
67
|
referencedByLoading?: boolean;
|
|
61
68
|
|
|
62
|
-
// Resource context
|
|
63
|
-
|
|
69
|
+
// Resource context — threaded to every per-motivation panel, which stamps it
|
|
70
|
+
// as `source` on mark:submit (multi-viewer routing).
|
|
71
|
+
resourceId: string;
|
|
64
72
|
initialTab?: TabKey;
|
|
65
73
|
initialTabGeneration?: number; // Generation counter for tab switching
|
|
66
74
|
|
|
@@ -244,6 +252,8 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
244
252
|
|
|
245
253
|
// Common props for all annotation panels
|
|
246
254
|
const commonProps = {
|
|
255
|
+
session: props.session,
|
|
256
|
+
resourceId: props.resourceId,
|
|
247
257
|
annotations,
|
|
248
258
|
pendingAnnotation: props.pendingAnnotation,
|
|
249
259
|
isAssisting,
|
|
@@ -268,6 +278,9 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
268
278
|
if (activeTab === 'reference') {
|
|
269
279
|
return (
|
|
270
280
|
<ReferencesPanel
|
|
281
|
+
session={commonProps.session}
|
|
282
|
+
onOpenResource={props.onOpenResource}
|
|
283
|
+
resourceId={commonProps.resourceId}
|
|
271
284
|
annotations={commonProps.annotations}
|
|
272
285
|
pendingAnnotation={commonProps.pendingAnnotation}
|
|
273
286
|
isAssisting={commonProps.isAssisting}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { screen } from '@testing-library/react';
|
|
3
3
|
import '@testing-library/jest-dom';
|
|
4
|
-
import { renderWithProviders } from '../../../../test-utils';
|
|
4
|
+
import { renderWithProviders, createTestSemiontWrapper } from '../../../../test-utils';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
|
|
7
|
-
import type { Annotation, AnnotationId } from '@semiont/core';
|
|
7
|
+
import type { Annotation, AnnotationId, EventBus } from '@semiont/core';
|
|
8
|
+
import type { SemiontSession } from '@semiont/sdk';
|
|
8
9
|
|
|
9
10
|
// Mock @semiont/http-transport
|
|
10
11
|
vi.mock('@semiont/core', async () => {
|
|
@@ -53,20 +54,28 @@ describe('AssessmentEntry', () => {
|
|
|
53
54
|
isFocused: false,
|
|
54
55
|
};
|
|
55
56
|
|
|
57
|
+
// Per-test session/bus — created in beforeEach (a module-scope factory
|
|
58
|
+
// call would hand tests a client that test-utils disposes after the
|
|
59
|
+
// first test). The `session` prop and the `eventBus` the interaction
|
|
60
|
+
// test subscribes come from the SAME factory call.
|
|
61
|
+
let session: SemiontSession;
|
|
62
|
+
let eventBus: EventBus;
|
|
63
|
+
|
|
56
64
|
beforeEach(() => {
|
|
57
65
|
vi.clearAllMocks();
|
|
66
|
+
({ session, eventBus } = createTestSemiontWrapper());
|
|
58
67
|
mockGetAnnotationExactText.mockReturnValue('Selected passage text');
|
|
59
68
|
});
|
|
60
69
|
|
|
61
70
|
describe('Rendering', () => {
|
|
62
71
|
it('should render the selected text in quotes', () => {
|
|
63
|
-
renderWithProviders(<AssessmentEntry {...defaultProps} />);
|
|
72
|
+
renderWithProviders(<AssessmentEntry {...defaultProps} session={session} />);
|
|
64
73
|
|
|
65
74
|
expect(screen.getByText(/Selected passage text/)).toBeInTheDocument();
|
|
66
75
|
});
|
|
67
76
|
|
|
68
77
|
it('should render the assessment body text', () => {
|
|
69
|
-
renderWithProviders(<AssessmentEntry {...defaultProps} />);
|
|
78
|
+
renderWithProviders(<AssessmentEntry {...defaultProps} session={session} />);
|
|
70
79
|
|
|
71
80
|
expect(screen.getByText('This passage needs clarification')).toBeInTheDocument();
|
|
72
81
|
});
|
|
@@ -80,7 +89,7 @@ describe('AssessmentEntry', () => {
|
|
|
80
89
|
});
|
|
81
90
|
|
|
82
91
|
renderWithProviders(
|
|
83
|
-
<AssessmentEntry assessment={assessment} isFocused={false} />
|
|
92
|
+
<AssessmentEntry assessment={assessment} isFocused={false} session={session} />
|
|
84
93
|
);
|
|
85
94
|
|
|
86
95
|
expect(screen.getByText('Direct body assessment')).toBeInTheDocument();
|
|
@@ -95,7 +104,7 @@ describe('AssessmentEntry', () => {
|
|
|
95
104
|
});
|
|
96
105
|
|
|
97
106
|
renderWithProviders(
|
|
98
|
-
<AssessmentEntry assessment={assessment} isFocused={false} />
|
|
107
|
+
<AssessmentEntry assessment={assessment} isFocused={false} session={session} />
|
|
99
108
|
);
|
|
100
109
|
|
|
101
110
|
expect(screen.getByText('Array body assessment')).toBeInTheDocument();
|
|
@@ -105,14 +114,14 @@ describe('AssessmentEntry', () => {
|
|
|
105
114
|
const longText = 'X'.repeat(150);
|
|
106
115
|
mockGetAnnotationExactText.mockReturnValue(longText);
|
|
107
116
|
|
|
108
|
-
renderWithProviders(<AssessmentEntry {...defaultProps} />);
|
|
117
|
+
renderWithProviders(<AssessmentEntry {...defaultProps} session={session} />);
|
|
109
118
|
|
|
110
119
|
expect(screen.getByText(new RegExp(`"${'X'.repeat(100)}`))).toBeInTheDocument();
|
|
111
120
|
expect(screen.getByText(/\.\.\./)).toBeInTheDocument();
|
|
112
121
|
});
|
|
113
122
|
|
|
114
123
|
it('should show creator name', () => {
|
|
115
|
-
renderWithProviders(<AssessmentEntry {...defaultProps} />);
|
|
124
|
+
renderWithProviders(<AssessmentEntry {...defaultProps} session={session} />);
|
|
116
125
|
|
|
117
126
|
expect(screen.getByText(/reviewer@example.com/)).toBeInTheDocument();
|
|
118
127
|
});
|
|
@@ -122,7 +131,7 @@ describe('AssessmentEntry', () => {
|
|
|
122
131
|
delete (assessment as Record<string, unknown>).creator;
|
|
123
132
|
|
|
124
133
|
renderWithProviders(
|
|
125
|
-
<AssessmentEntry assessment={assessment} isFocused={false} />
|
|
134
|
+
<AssessmentEntry assessment={assessment} isFocused={false} session={session} />
|
|
126
135
|
);
|
|
127
136
|
|
|
128
137
|
expect(screen.getByText(/Unknown/)).toBeInTheDocument();
|
|
@@ -133,7 +142,7 @@ describe('AssessmentEntry', () => {
|
|
|
133
142
|
delete (assessment as Record<string, unknown>).body;
|
|
134
143
|
|
|
135
144
|
const { container } = renderWithProviders(
|
|
136
|
-
<AssessmentEntry assessment={assessment} isFocused={false} />
|
|
145
|
+
<AssessmentEntry assessment={assessment} isFocused={false} session={session} />
|
|
137
146
|
);
|
|
138
147
|
|
|
139
148
|
// Body section should not render
|
|
@@ -143,7 +152,7 @@ describe('AssessmentEntry', () => {
|
|
|
143
152
|
it('should not render quote section when selectedText is empty', () => {
|
|
144
153
|
mockGetAnnotationExactText.mockReturnValue('');
|
|
145
154
|
|
|
146
|
-
const { container } = renderWithProviders(<AssessmentEntry {...defaultProps} />);
|
|
155
|
+
const { container } = renderWithProviders(<AssessmentEntry {...defaultProps} session={session} />);
|
|
147
156
|
|
|
148
157
|
expect(container.querySelector('.semiont-annotation-entry__quote')).not.toBeInTheDocument();
|
|
149
158
|
});
|
|
@@ -154,7 +163,7 @@ describe('AssessmentEntry', () => {
|
|
|
154
163
|
});
|
|
155
164
|
|
|
156
165
|
renderWithProviders(
|
|
157
|
-
<AssessmentEntry assessment={recentAssessment} isFocused={false} />
|
|
166
|
+
<AssessmentEntry assessment={recentAssessment} isFocused={false} session={session} />
|
|
158
167
|
);
|
|
159
168
|
|
|
160
169
|
expect(screen.getByText(/just now/)).toBeInTheDocument();
|
|
@@ -165,12 +174,11 @@ describe('AssessmentEntry', () => {
|
|
|
165
174
|
it('should emit browse:click on click', async () => {
|
|
166
175
|
const clickHandler = vi.fn();
|
|
167
176
|
|
|
168
|
-
const { container
|
|
169
|
-
<AssessmentEntry {...defaultProps}
|
|
170
|
-
{ returnEventBus: true }
|
|
177
|
+
const { container } = renderWithProviders(
|
|
178
|
+
<AssessmentEntry {...defaultProps} session={session} />
|
|
171
179
|
);
|
|
172
180
|
|
|
173
|
-
const subscription = eventBus
|
|
181
|
+
const subscription = eventBus.get('browse:click').subscribe(clickHandler);
|
|
174
182
|
|
|
175
183
|
const entry = container.firstChild as HTMLElement;
|
|
176
184
|
await userEvent.click(entry);
|
|
@@ -187,7 +195,7 @@ describe('AssessmentEntry', () => {
|
|
|
187
195
|
describe('Hover state', () => {
|
|
188
196
|
it('should apply pulse class when isHovered is true', () => {
|
|
189
197
|
const { container } = renderWithProviders(
|
|
190
|
-
<AssessmentEntry {...defaultProps} isHovered={true} />
|
|
198
|
+
<AssessmentEntry {...defaultProps} session={session} isHovered={true} />
|
|
191
199
|
);
|
|
192
200
|
|
|
193
201
|
const entry = container.firstChild as HTMLElement;
|
|
@@ -196,7 +204,7 @@ describe('AssessmentEntry', () => {
|
|
|
196
204
|
|
|
197
205
|
it('should not apply pulse class when isHovered is false', () => {
|
|
198
206
|
const { container } = renderWithProviders(
|
|
199
|
-
<AssessmentEntry {...defaultProps} isHovered={false} />
|
|
207
|
+
<AssessmentEntry {...defaultProps} session={session} isHovered={false} />
|
|
200
208
|
);
|
|
201
209
|
|
|
202
210
|
const entry = container.firstChild as HTMLElement;
|
|
@@ -207,7 +215,7 @@ describe('AssessmentEntry', () => {
|
|
|
207
215
|
describe('Focus state', () => {
|
|
208
216
|
it('should set data-focused to true when focused', () => {
|
|
209
217
|
const { container } = renderWithProviders(
|
|
210
|
-
<AssessmentEntry {...defaultProps} isFocused={true} />
|
|
218
|
+
<AssessmentEntry {...defaultProps} session={session} isFocused={true} />
|
|
211
219
|
);
|
|
212
220
|
|
|
213
221
|
const entry = container.firstChild as HTMLElement;
|
|
@@ -215,7 +223,7 @@ describe('AssessmentEntry', () => {
|
|
|
215
223
|
});
|
|
216
224
|
|
|
217
225
|
it('should set data-type to assessment', () => {
|
|
218
|
-
const { container } = renderWithProviders(<AssessmentEntry {...defaultProps} />);
|
|
226
|
+
const { container } = renderWithProviders(<AssessmentEntry {...defaultProps} session={session} />);
|
|
219
227
|
|
|
220
228
|
const entry = container.firstChild as HTMLElement;
|
|
221
229
|
expect(entry).toHaveAttribute('data-type', 'assessment');
|
|
@@ -6,6 +6,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
6
6
|
import '@testing-library/jest-dom';
|
|
7
7
|
import { AssessmentPanel } from '../AssessmentPanel';
|
|
8
8
|
import type { EventBus } from '@semiont/core';
|
|
9
|
+
import type { SemiontSession } from '@semiont/sdk';
|
|
9
10
|
import { createTestSemiontWrapper } from '../../../../test-utils';
|
|
10
11
|
|
|
11
12
|
import type { Annotation, AnnotationId } from '@semiont/core';
|
|
@@ -32,8 +33,15 @@ function createEventTracker() {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// Per-test session/bus/wrapper — created in beforeEach (a module-scope
|
|
37
|
+
// factory call would hand tests a client that test-utils disposes after
|
|
38
|
+
// the first test). The `session` passed as a prop below and the `eventBus`
|
|
39
|
+
// the tracker subscribes come from the SAME factory call.
|
|
40
|
+
let session: SemiontSession;
|
|
41
|
+
let eventBus: EventBus;
|
|
42
|
+
let SemiontWrapper: React.ComponentType<{ children: React.ReactNode }>;
|
|
43
|
+
|
|
35
44
|
const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
|
|
36
|
-
const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
|
|
37
45
|
if (tracker) tracker._attach(eventBus);
|
|
38
46
|
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
39
47
|
<SemiontWrapper>{children}</SemiontWrapper>
|
|
@@ -140,6 +148,7 @@ const createPendingAnnotation = (exact: string) => ({
|
|
|
140
148
|
|
|
141
149
|
describe('AssessmentPanel Component', () => {
|
|
142
150
|
const defaultProps = {
|
|
151
|
+
resourceId: 'res-1',
|
|
143
152
|
annotations: mockAssessments.empty,
|
|
144
153
|
pendingAnnotation: null,
|
|
145
154
|
};
|
|
@@ -147,6 +156,8 @@ describe('AssessmentPanel Component', () => {
|
|
|
147
156
|
beforeEach(() => {
|
|
148
157
|
vi.clearAllMocks();
|
|
149
158
|
|
|
159
|
+
({ session, eventBus, SemiontWrapper } = createTestSemiontWrapper());
|
|
160
|
+
|
|
150
161
|
// Mock scrollIntoView for jsdom
|
|
151
162
|
Element.prototype.scrollIntoView = vi.fn();
|
|
152
163
|
|
|
@@ -166,20 +177,20 @@ describe('AssessmentPanel Component', () => {
|
|
|
166
177
|
|
|
167
178
|
describe('Rendering', () => {
|
|
168
179
|
it('should render panel header with title and count', () => {
|
|
169
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} annotations={mockAssessments.multiple} />);
|
|
180
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} annotations={mockAssessments.multiple} />);
|
|
170
181
|
|
|
171
182
|
expect(screen.getByText(/Assessments/)).toBeInTheDocument();
|
|
172
183
|
expect(screen.getByText(/\(3\)/)).toBeInTheDocument();
|
|
173
184
|
});
|
|
174
185
|
|
|
175
186
|
it('should show empty state when no assessments', () => {
|
|
176
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} />);
|
|
187
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} />);
|
|
177
188
|
|
|
178
189
|
expect(screen.getByText(/No assessments yet/)).toBeInTheDocument();
|
|
179
190
|
});
|
|
180
191
|
|
|
181
192
|
it('should render all assessments', () => {
|
|
182
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} annotations={mockAssessments.multiple} />);
|
|
193
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} annotations={mockAssessments.multiple} />);
|
|
183
194
|
|
|
184
195
|
expect(screen.getByTestId('assessment-1')).toBeInTheDocument();
|
|
185
196
|
expect(screen.getByTestId('assessment-2')).toBeInTheDocument();
|
|
@@ -187,7 +198,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
187
198
|
});
|
|
188
199
|
|
|
189
200
|
it('should have proper panel structure', () => {
|
|
190
|
-
const { container } = renderWithEventBus(<AssessmentPanel {...defaultProps} />);
|
|
201
|
+
const { container } = renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} />);
|
|
191
202
|
|
|
192
203
|
const panel = container.firstChild as HTMLElement;
|
|
193
204
|
expect(panel).toHaveClass('semiont-panel');
|
|
@@ -196,7 +207,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
196
207
|
|
|
197
208
|
describe('Assessment Sorting', () => {
|
|
198
209
|
it('should sort assessments by position in resource', () => {
|
|
199
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} annotations={mockAssessments.multiple} />);
|
|
210
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} annotations={mockAssessments.multiple} />);
|
|
200
211
|
|
|
201
212
|
const assessments = screen.getAllByTestId(/assessment-/);
|
|
202
213
|
|
|
@@ -210,14 +221,14 @@ describe('AssessmentPanel Component', () => {
|
|
|
210
221
|
mockGetTextPositionSelector.mockReturnValue(null);
|
|
211
222
|
|
|
212
223
|
expect(() => {
|
|
213
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} annotations={mockAssessments.multiple} />);
|
|
224
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} annotations={mockAssessments.multiple} />);
|
|
214
225
|
}).not.toThrow();
|
|
215
226
|
});
|
|
216
227
|
});
|
|
217
228
|
|
|
218
229
|
describe('New Assessment Creation', () => {
|
|
219
230
|
it('should not show new assessment input by default', () => {
|
|
220
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} />);
|
|
231
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} />);
|
|
221
232
|
|
|
222
233
|
expect(screen.queryByPlaceholderText(/Type your assessment here/)).not.toBeInTheDocument();
|
|
223
234
|
});
|
|
@@ -227,7 +238,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
227
238
|
|
|
228
239
|
renderWithEventBus(
|
|
229
240
|
<AssessmentPanel
|
|
230
|
-
{...defaultProps}
|
|
241
|
+
{...defaultProps} session={session}
|
|
231
242
|
pendingAnnotation={pendingAnnotation}
|
|
232
243
|
/>
|
|
233
244
|
);
|
|
@@ -240,7 +251,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
240
251
|
|
|
241
252
|
renderWithEventBus(
|
|
242
253
|
<AssessmentPanel
|
|
243
|
-
{...defaultProps}
|
|
254
|
+
{...defaultProps} session={session}
|
|
244
255
|
pendingAnnotation={pendingAnnotation}
|
|
245
256
|
/>
|
|
246
257
|
);
|
|
@@ -254,7 +265,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
254
265
|
|
|
255
266
|
renderWithEventBus(
|
|
256
267
|
<AssessmentPanel
|
|
257
|
-
{...defaultProps}
|
|
268
|
+
{...defaultProps} session={session}
|
|
258
269
|
pendingAnnotation={pendingAnnotation}
|
|
259
270
|
/>
|
|
260
271
|
);
|
|
@@ -268,7 +279,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
268
279
|
|
|
269
280
|
renderWithEventBus(
|
|
270
281
|
<AssessmentPanel
|
|
271
|
-
{...defaultProps}
|
|
282
|
+
{...defaultProps} session={session}
|
|
272
283
|
pendingAnnotation={pendingAnnotation}
|
|
273
284
|
/>
|
|
274
285
|
);
|
|
@@ -284,7 +295,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
284
295
|
|
|
285
296
|
renderWithEventBus(
|
|
286
297
|
<AssessmentPanel
|
|
287
|
-
{...defaultProps}
|
|
298
|
+
{...defaultProps} session={session}
|
|
288
299
|
pendingAnnotation={pendingAnnotation}
|
|
289
300
|
/>
|
|
290
301
|
);
|
|
@@ -302,7 +313,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
302
313
|
|
|
303
314
|
renderWithEventBus(
|
|
304
315
|
<AssessmentPanel
|
|
305
|
-
{...defaultProps}
|
|
316
|
+
{...defaultProps} session={session}
|
|
306
317
|
pendingAnnotation={pendingAnnotation}
|
|
307
318
|
/>
|
|
308
319
|
);
|
|
@@ -316,7 +327,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
316
327
|
|
|
317
328
|
renderWithEventBus(
|
|
318
329
|
<AssessmentPanel
|
|
319
|
-
{...defaultProps}
|
|
330
|
+
{...defaultProps} session={session}
|
|
320
331
|
pendingAnnotation={pendingAnnotation}
|
|
321
332
|
/>
|
|
322
333
|
);
|
|
@@ -331,7 +342,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
331
342
|
|
|
332
343
|
renderWithEventBus(
|
|
333
344
|
<AssessmentPanel
|
|
334
|
-
{...defaultProps}
|
|
345
|
+
{...defaultProps} session={session}
|
|
335
346
|
pendingAnnotation={pendingAnnotation}
|
|
336
347
|
/>,
|
|
337
348
|
tracker
|
|
@@ -357,7 +368,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
357
368
|
|
|
358
369
|
renderWithEventBus(
|
|
359
370
|
<AssessmentPanel
|
|
360
|
-
{...defaultProps}
|
|
371
|
+
{...defaultProps} session={session}
|
|
361
372
|
pendingAnnotation={pendingAnnotation}
|
|
362
373
|
/>
|
|
363
374
|
);
|
|
@@ -375,7 +386,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
375
386
|
|
|
376
387
|
renderWithEventBus(
|
|
377
388
|
<AssessmentPanel
|
|
378
|
-
{...defaultProps}
|
|
389
|
+
{...defaultProps} session={session}
|
|
379
390
|
pendingAnnotation={pendingAnnotation}
|
|
380
391
|
/>,
|
|
381
392
|
tracker
|
|
@@ -398,7 +409,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
398
409
|
|
|
399
410
|
const { container } = renderWithEventBus(
|
|
400
411
|
<AssessmentPanel
|
|
401
|
-
{...defaultProps}
|
|
412
|
+
{...defaultProps} session={session}
|
|
402
413
|
pendingAnnotation={pendingAnnotation}
|
|
403
414
|
/>
|
|
404
415
|
);
|
|
@@ -413,7 +424,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
413
424
|
it('should render assessment entries', () => {
|
|
414
425
|
renderWithEventBus(
|
|
415
426
|
<AssessmentPanel
|
|
416
|
-
{...defaultProps}
|
|
427
|
+
{...defaultProps} session={session}
|
|
417
428
|
annotations={mockAssessments.single}
|
|
418
429
|
/>
|
|
419
430
|
);
|
|
@@ -428,7 +439,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
428
439
|
expect(() => {
|
|
429
440
|
renderWithEventBus(
|
|
430
441
|
<AssessmentPanel
|
|
431
|
-
{...defaultProps}
|
|
442
|
+
{...defaultProps} session={session}
|
|
432
443
|
annotations={mockAssessments.single}
|
|
433
444
|
/>
|
|
434
445
|
);
|
|
@@ -440,7 +451,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
440
451
|
it('should render AssistSection when annotateMode is true', () => {
|
|
441
452
|
renderWithEventBus(
|
|
442
453
|
<AssessmentPanel
|
|
443
|
-
{...defaultProps}
|
|
454
|
+
{...defaultProps} session={session}
|
|
444
455
|
annotateMode={true}
|
|
445
456
|
/>
|
|
446
457
|
);
|
|
@@ -451,7 +462,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
451
462
|
it('should not render AssistSection when annotateMode is false', () => {
|
|
452
463
|
renderWithEventBus(
|
|
453
464
|
<AssessmentPanel
|
|
454
|
-
{...defaultProps}
|
|
465
|
+
{...defaultProps} session={session}
|
|
455
466
|
annotateMode={false}
|
|
456
467
|
/>
|
|
457
468
|
);
|
|
@@ -462,7 +473,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
462
473
|
it('should render AssistSection with correct annotationType', () => {
|
|
463
474
|
renderWithEventBus(
|
|
464
475
|
<AssessmentPanel
|
|
465
|
-
{...defaultProps}
|
|
476
|
+
{...defaultProps} session={session}
|
|
466
477
|
annotateMode={true}
|
|
467
478
|
/>
|
|
468
479
|
);
|
|
@@ -478,7 +489,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
478
489
|
|
|
479
490
|
renderWithEventBus(
|
|
480
491
|
<AssessmentPanel
|
|
481
|
-
{...defaultProps}
|
|
492
|
+
{...defaultProps} session={session}
|
|
482
493
|
pendingAnnotation={pendingAnnotation}
|
|
483
494
|
/>
|
|
484
495
|
);
|
|
@@ -491,7 +502,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
491
502
|
|
|
492
503
|
renderWithEventBus(
|
|
493
504
|
<AssessmentPanel
|
|
494
|
-
{...defaultProps}
|
|
505
|
+
{...defaultProps} session={session}
|
|
495
506
|
pendingAnnotation={pendingAnnotation}
|
|
496
507
|
/>
|
|
497
508
|
);
|
|
@@ -508,7 +519,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
508
519
|
|
|
509
520
|
describe('Accessibility', () => {
|
|
510
521
|
it('should have proper heading structure', () => {
|
|
511
|
-
renderWithEventBus(<AssessmentPanel {...defaultProps} />);
|
|
522
|
+
renderWithEventBus(<AssessmentPanel {...defaultProps} session={session} />);
|
|
512
523
|
|
|
513
524
|
const heading = screen.getByText(/Assessments/);
|
|
514
525
|
expect(heading).toHaveClass('semiont-panel-header__text');
|
|
@@ -519,7 +530,7 @@ describe('AssessmentPanel Component', () => {
|
|
|
519
530
|
|
|
520
531
|
renderWithEventBus(
|
|
521
532
|
<AssessmentPanel
|
|
522
|
-
{...defaultProps}
|
|
533
|
+
{...defaultProps} session={session}
|
|
523
534
|
pendingAnnotation={pendingAnnotation}
|
|
524
535
|
/>
|
|
525
536
|
);
|