@semiont/react-ui 0.5.11 → 0.5.13

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 (53) hide show
  1. package/dist/{PdfAnnotationCanvas.client-KWQ7XDWT.js → PdfAnnotationCanvas.client-75GY2EDR.js} +12 -6
  2. package/dist/PdfAnnotationCanvas.client-75GY2EDR.js.map +1 -0
  3. package/dist/{chunk-O2MD7TGE.js → chunk-VXASXU4K.js} +10 -2
  4. package/dist/chunk-VXASXU4K.js.map +1 -0
  5. package/dist/index.d.ts +54 -16
  6. package/dist/index.js +395 -343
  7. package/dist/index.js.map +1 -1
  8. package/dist/test-utils.d.ts +5 -1
  9. package/dist/test-utils.js +3 -2
  10. package/dist/test-utils.js.map +1 -1
  11. package/package.json +5 -5
  12. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  13. package/src/components/image-annotation/SvgDrawingCanvas.tsx +12 -4
  14. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +137 -0
  15. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +108 -0
  16. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +12 -4
  17. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +137 -1
  18. package/src/components/resource/BrowseView.tsx +14 -6
  19. package/src/components/resource/ResourceViewer.tsx +6 -4
  20. package/src/components/resource/__tests__/BrowseView.test.tsx +98 -31
  21. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +40 -1
  22. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +113 -0
  23. package/src/components/resource/browse-renderers.tsx +26 -4
  24. package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
  25. package/src/components/resource/panels/AssessmentPanel.tsx +8 -5
  26. package/src/components/resource/panels/AssistSection.tsx +4 -3
  27. package/src/components/resource/panels/CommentEntry.tsx +5 -4
  28. package/src/components/resource/panels/CommentsPanel.tsx +8 -5
  29. package/src/components/resource/panels/HighlightEntry.tsx +5 -4
  30. package/src/components/resource/panels/HighlightPanel.tsx +8 -5
  31. package/src/components/resource/panels/ReferenceEntry.tsx +9 -10
  32. package/src/components/resource/panels/ReferencesPanel.tsx +11 -6
  33. package/src/components/resource/panels/ResourceInfoPanel.tsx +4 -3
  34. package/src/components/resource/panels/TagEntry.tsx +5 -3
  35. package/src/components/resource/panels/TaggingPanel.tsx +7 -4
  36. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +10 -0
  37. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +28 -20
  38. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +38 -28
  39. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +46 -17
  40. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +65 -57
  41. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +53 -24
  42. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +27 -17
  43. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +23 -17
  44. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +48 -77
  45. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +87 -0
  46. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +15 -9
  47. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +77 -59
  48. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +11 -4
  49. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +33 -19
  50. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -7
  51. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +3 -0
  52. package/dist/PdfAnnotationCanvas.client-KWQ7XDWT.js.map +0 -1
  53. package/dist/chunk-O2MD7TGE.js.map +0 -1
@@ -3,12 +3,13 @@
3
3
  import type { Ref } from 'react';
4
4
  import type { Annotation } from '@semiont/core';
5
5
  import { getAnnotationExactText } from '@semiont/core';
6
- import { useSemiont } from '../../../session/SemiontProvider';
7
- import { useObservable } from '../../../hooks/useObservable';
6
+ import type { SemiontSession } from '@semiont/sdk';
8
7
  import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
9
8
  import { renderAgentLabel } from './agent-label';
10
9
 
11
10
  interface HighlightEntryProps {
11
+ /** Session for interaction routing (browse.click etc.); the panel threads it. */
12
+ session: SemiontSession | null;
12
13
  highlight: Annotation;
13
14
  isFocused: boolean;
14
15
  isHovered?: boolean;
@@ -33,13 +34,13 @@ function formatRelativeTime(isoString: string): string {
33
34
  }
34
35
 
35
36
  export function HighlightEntry({
37
+ session,
36
38
  highlight,
37
39
  isFocused,
38
40
  isHovered = false,
39
41
  ref,
40
42
  }: HighlightEntryProps) {
41
- const session = useObservable(useSemiont().activeSession$);
42
- const hoverProps = useHoverEmitter(highlight.id);
43
+ const hoverProps = useHoverEmitter(session, highlight.id);
43
44
 
44
45
  const selectedText = getAnnotationExactText(highlight);
45
46
 
@@ -2,9 +2,8 @@
2
2
 
3
3
  import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
4
4
  import { useTranslations } from '../../../contexts/TranslationContext';
5
- import { useSemiont } from '../../../session/SemiontProvider';
6
- import { useObservable } from '../../../hooks/useObservable';
7
- import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
5
+ import type { SemiontSession } from '@semiont/sdk';
6
+ import { useSessionEventSubscriptions } from '../../../hooks/useSessionEventSubscriptions';
8
7
  import type { components, Selector } from '@semiont/core';
9
8
  import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
10
9
  import { HighlightEntry } from './HighlightEntry';
@@ -23,6 +22,8 @@ interface PendingAnnotation {
23
22
  }
24
23
 
25
24
  interface HighlightPanelProps {
25
+ /** Session carrying the client and event bus; null renders inert. */
26
+ session: SemiontSession | null;
26
27
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
27
28
  resourceId: string;
28
29
  annotations: Annotation[];
@@ -44,6 +45,7 @@ interface HighlightPanelProps {
44
45
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
45
46
  */
46
47
  export function HighlightPanel({
48
+ session,
47
49
  resourceId,
48
50
  annotations,
49
51
  pendingAnnotation,
@@ -57,7 +59,6 @@ export function HighlightPanel({
57
59
  }: HighlightPanelProps) {
58
60
 
59
61
  const t = useTranslations('HighlightPanel');
60
- const session = useObservable(useSemiont().activeSession$);
61
62
  const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
62
63
  const containerRef = useRef<HTMLDivElement>(null);
63
64
 
@@ -128,7 +129,7 @@ export function HighlightPanel({
128
129
  setTimeout(() => setFocusedAnnotationId(null), 3000);
129
130
  }, []);
130
131
 
131
- useEventSubscriptions({
132
+ useSessionEventSubscriptions(session, {
132
133
  'browse:click': handleAnnotationClick,
133
134
  });
134
135
 
@@ -155,6 +156,7 @@ export function HighlightPanel({
155
156
  {/* Assist Section - only in Annotate mode and for text resources */}
156
157
  {annotateMode && (
157
158
  <AssistSection
159
+ session={session}
158
160
  annotationType="highlight"
159
161
  isAssisting={isAssisting}
160
162
  progress={progress}
@@ -171,6 +173,7 @@ export function HighlightPanel({
171
173
  ) : (
172
174
  sortedAnnotations.map((highlight) => (
173
175
  <HighlightEntry
176
+ session={session}
174
177
  key={highlight.id}
175
178
  highlight={highlight}
176
179
  isFocused={highlight.id === focusedAnnotationId}
@@ -1,17 +1,14 @@
1
1
  'use client';
2
2
 
3
3
  import type { Ref } from 'react';
4
- import type { RouteBuilder } from '../../../contexts/RoutingContext';
5
4
  import { useTranslations } from '../../../contexts/TranslationContext';
6
5
  import type { Annotation } from '@semiont/core';
7
6
  import { resourceId } from '@semiont/core';
8
7
  import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelector, getSvgSelector, getTargetSelector } from '@semiont/core';
9
8
  import { getEntityTypes } from '@semiont/ontology';
10
9
  import { getResourceIcon } from '../../../lib/resource-utils';
11
- import { useSemiont } from '../../../session/SemiontProvider';
12
- import { useObservable } from '../../../hooks/useObservable';
10
+ import type { SemiontSession } from '@semiont/sdk';
13
11
  import { renderAgentLabel } from './agent-label';
14
- import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
15
12
  import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
16
13
 
17
14
  // Extended annotation type with runtime properties added by backend enrichment
@@ -21,29 +18,31 @@ interface EnrichedAnnotation extends Annotation {
21
18
  }
22
19
 
23
20
  interface ReferenceEntryProps {
21
+ /** Session for interaction routing (browse.click etc.); the panel threads it. */
22
+ session: SemiontSession | null;
24
23
  reference: Annotation;
25
24
  isFocused: boolean;
26
25
  isHovered?: boolean;
27
- routes: RouteBuilder;
26
+ /** Host-owned navigation: called with the resolved resource id when the entry's link opens. */
27
+ onOpenResource?: (resourceId: string) => void;
28
28
  annotateMode?: boolean;
29
29
  isGenerating?: boolean;
30
30
  ref?: Ref<HTMLDivElement>;
31
31
  }
32
32
 
33
33
  export function ReferenceEntry({
34
+ session,
34
35
  reference,
35
36
  isFocused,
36
37
  isHovered = false,
37
- routes,
38
+ onOpenResource,
38
39
  annotateMode = true,
39
40
  isGenerating = false,
40
41
  ref,
41
42
  }: ReferenceEntryProps) {
42
43
  const t = useTranslations('ReferencesPanel');
43
- const session = useObservable(useSemiont().activeSession$);
44
44
  const semiont = session?.client;
45
- const navigate = useObservableExternalNavigation();
46
- const hoverProps = useHoverEmitter(reference.id);
45
+ const hoverProps = useHoverEmitter(session, reference.id);
47
46
 
48
47
  const selectedText = getAnnotationExactText(reference) || '';
49
48
  const isResolved = isBodyResolved(reference.body);
@@ -65,7 +64,7 @@ export function ReferenceEntry({
65
64
  const handleOpen = () => {
66
65
  if (resolvedResourceUri) {
67
66
  // resolvedResourceUri is already a bare resource ID
68
- navigate(routes.resourceDetail(resolvedResourceUri), { resourceId: resolvedResourceUri });
67
+ onOpenResource?.(resolvedResourceUri);
69
68
  }
70
69
  };
71
70
 
@@ -2,9 +2,8 @@
2
2
 
3
3
  import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
4
4
  import { useTranslations } from '../../../contexts/TranslationContext';
5
- import { useSemiont } from '../../../session/SemiontProvider';
6
- import { useObservable } from '../../../hooks/useObservable';
7
- import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
5
+ import type { SemiontSession } from '@semiont/sdk';
6
+ import { useSessionEventSubscriptions } from '../../../hooks/useSessionEventSubscriptions';
8
7
  import type { RouteBuilder, LinkComponentProps } from '../../../contexts/RoutingContext';
9
8
  import { AnnotateReferencesProgressWidget } from '../../AnnotateReferencesProgressWidget';
10
9
  import { ReferenceEntry } from './ReferenceEntry';
@@ -43,6 +42,10 @@ function getSelectorDisplayText(selector: Selector | Selector[]): string | null
43
42
  }
44
43
 
45
44
  interface Props {
45
+ /** Session carrying the client and event bus; null renders inert. */
46
+ session: SemiontSession | null;
47
+ /** Host-owned navigation: called with the resolved resource id when a reference entry opens. */
48
+ onOpenResource?: (resourceId: string) => void;
46
49
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
47
50
  resourceId: string;
48
51
  // Generic panel props
@@ -78,6 +81,8 @@ interface Props {
78
81
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
79
82
  */
80
83
  export function ReferencesPanel({
84
+ session,
85
+ onOpenResource,
81
86
  resourceId,
82
87
  annotations = [],
83
88
  isAssisting,
@@ -97,7 +102,6 @@ export function ReferencesPanel({
97
102
  sourceLanguage,
98
103
  }: Props) {
99
104
  const t = useTranslations('ReferencesPanel');
100
- const session = useObservable(useSemiont().activeSession$);
101
105
  const [selectedEntityTypes, setSelectedEntityTypes] = useState<string[]>([]);
102
106
  const [lastAnnotationLog, setLastDetectionLog] = useState<Array<{ entityType: string; foundCount: number }> | null>(null);
103
107
  const [pendingEntityTypes, setPendingEntityTypes] = useState<string[]>([]);
@@ -206,7 +210,7 @@ export function ReferencesPanel({
206
210
  setTimeout(() => setFocusedAnnotationId(null), 3000);
207
211
  }, []);
208
212
 
209
- useEventSubscriptions({
213
+ useSessionEventSubscriptions(session, {
210
214
  'browse:click': handleAnnotationClick,
211
215
  });
212
216
 
@@ -492,11 +496,12 @@ export function ReferencesPanel({
492
496
  ) : (
493
497
  sortedAnnotations.map((reference) => (
494
498
  <ReferenceEntry
499
+ session={session}
495
500
  key={reference.id}
496
501
  reference={reference}
497
502
  isFocused={reference.id === focusedAnnotationId}
498
503
  isHovered={reference.id === hoveredAnnotationId}
499
- routes={routes}
504
+ onOpenResource={onOpenResource}
500
505
  annotateMode={annotateMode}
501
506
  isGenerating={reference.id === generatingReferenceId}
502
507
  ref={(el) => setEntryRef(reference.id, el)}
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useTranslations } from '../../../contexts/TranslationContext';
4
- import { useSemiont } from '../../../session/SemiontProvider';
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 session = useObservable(useSemiont().activeSession$);
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 { useEventSubscriptions } from '../../../contexts/useEventSubscription';
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,8 @@ 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;
42
44
  /** The '@id' of the panel's resource — stamped as `source` on mark:submit (multi-viewer routing). */
43
45
  resourceId: string;
44
46
  annotations: Annotation[];
@@ -64,6 +66,7 @@ interface TaggingPanelProps {
64
66
  * @subscribes browse:click - Annotation clicked. Payload: { annotationId: string }
65
67
  */
66
68
  export function TaggingPanel({
69
+ session,
67
70
  resourceId,
68
71
  annotations,
69
72
  annotateMode = true,
@@ -77,7 +80,6 @@ export function TaggingPanel({
77
80
  sourceLanguage,
78
81
  }: TaggingPanelProps) {
79
82
  const t = useTranslations('TaggingPanel');
80
- const session = useObservable(useSemiont().activeSession$);
81
83
 
82
84
  // Subscribe to the per-KB tag-schema registry. Schemas are runtime-
83
85
  // registered by the KB at session start (see frame.addTagSchema).
@@ -129,7 +131,7 @@ export function TaggingPanel({
129
131
  setTimeout(() => setFocusedAnnotationId(null), 3000);
130
132
  }, []);
131
133
 
132
- useEventSubscriptions({
134
+ useSessionEventSubscriptions(session, {
133
135
  'browse:click': handleAnnotationClick,
134
136
  });
135
137
 
@@ -528,6 +530,7 @@ export function TaggingPanel({
528
530
  ) : (
529
531
  sortedAnnotations.map((tag) => (
530
532
  <TagEntry
533
+ session={session}
531
534
  key={tag.id}
532
535
  tag={tag}
533
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
 
@@ -245,6 +252,7 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
245
252
 
246
253
  // Common props for all annotation panels
247
254
  const commonProps = {
255
+ session: props.session,
248
256
  resourceId: props.resourceId,
249
257
  annotations,
250
258
  pendingAnnotation: props.pendingAnnotation,
@@ -270,6 +278,8 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
270
278
  if (activeTab === 'reference') {
271
279
  return (
272
280
  <ReferencesPanel
281
+ session={commonProps.session}
282
+ onOpenResource={props.onOpenResource}
273
283
  resourceId={commonProps.resourceId}
274
284
  annotations={commonProps.annotations}
275
285
  pendingAnnotation={commonProps.pendingAnnotation}
@@ -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, eventBus } = renderWithProviders(
169
- <AssessmentEntry {...defaultProps} />,
170
- { returnEventBus: true }
177
+ const { container } = renderWithProviders(
178
+ <AssessmentEntry {...defaultProps} session={session} />
171
179
  );
172
180
 
173
- const subscription = eventBus!.get('browse:click').subscribe(clickHandler);
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');