@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
@@ -6,6 +6,7 @@ import userEvent from '@testing-library/user-event';
6
6
  import '@testing-library/jest-dom';
7
7
  import { CommentsPanel } from '../CommentsPanel';
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,18 @@ function createEventTracker() {
32
33
  };
33
34
  }
34
35
 
36
+ // Per-test session/bus/wrapper — created in beforeEach because test-utils
37
+ // disposes every created client after each test (a module-scope wrapper
38
+ // would hand later tests a disposed client).
39
+ let session: SemiontSession;
40
+ let eventBus: EventBus;
41
+ let SemiontWrapper: React.ComponentType<{ children: React.ReactNode }>;
42
+
43
+ beforeEach(() => {
44
+ ({ session, eventBus, SemiontWrapper } = createTestSemiontWrapper());
45
+ });
46
+
35
47
  const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
36
- const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
37
48
  if (tracker) tracker._attach(eventBus);
38
49
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
39
50
  <SemiontWrapper>{children}</SemiontWrapper>
@@ -168,7 +179,7 @@ describe('CommentsPanel Component', () => {
168
179
 
169
180
  describe('Rendering', () => {
170
181
  it('should render panel header with title and count', () => {
171
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
182
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
172
183
 
173
184
  const headings = screen.getAllByText(/Comments/);
174
185
  expect(headings.length).toBeGreaterThan(0);
@@ -176,13 +187,13 @@ describe('CommentsPanel Component', () => {
176
187
  });
177
188
 
178
189
  it('should show empty state when no comments', () => {
179
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
190
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
180
191
 
181
192
  expect(screen.getByText(/No comments yet/)).toBeInTheDocument();
182
193
  });
183
194
 
184
195
  it('should render all comments', () => {
185
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
196
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
186
197
 
187
198
  expect(screen.getByTestId('comment-1')).toBeInTheDocument();
188
199
  expect(screen.getByTestId('comment-2')).toBeInTheDocument();
@@ -190,7 +201,7 @@ describe('CommentsPanel Component', () => {
190
201
  });
191
202
 
192
203
  it('should have proper panel structure', () => {
193
- const { container } = renderWithEventBus(<CommentsPanel {...defaultProps} />);
204
+ const { container } = renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
194
205
 
195
206
  // Find the root panel div (first child of the container)
196
207
  const panel = container.firstChild as HTMLElement;
@@ -199,7 +210,7 @@ describe('CommentsPanel Component', () => {
199
210
 
200
211
  it('should have scrollable comments list', () => {
201
212
  const { container } = renderWithEventBus(
202
- <CommentsPanel {...defaultProps} annotations={mockComments.many} />
213
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.many} />
203
214
  );
204
215
 
205
216
  const commentsList = container.querySelector('.semiont-panel__list');
@@ -209,7 +220,7 @@ describe('CommentsPanel Component', () => {
209
220
 
210
221
  describe('Comment Sorting', () => {
211
222
  it('should sort comments by position in resource', () => {
212
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
223
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
213
224
 
214
225
  const comments = screen.getAllByTestId(/comment-/);
215
226
 
@@ -223,13 +234,13 @@ describe('CommentsPanel Component', () => {
223
234
  mockGetTextPositionSelector.mockReturnValue(null);
224
235
 
225
236
  expect(() => {
226
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
237
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
227
238
  }).not.toThrow();
228
239
  });
229
240
 
230
241
  it('should maintain sort order when comments update', () => {
231
242
  const { rerender } = renderWithEventBus(
232
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
243
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
233
244
  );
234
245
 
235
246
  // Add a new comment at position 25
@@ -239,7 +250,7 @@ describe('CommentsPanel Component', () => {
239
250
  ];
240
251
 
241
252
  rerender(
242
- <CommentsPanel {...defaultProps} annotations={updatedComments} />
253
+ <CommentsPanel session={session} {...defaultProps} annotations={updatedComments} />
243
254
  );
244
255
 
245
256
  const comments = screen.getAllByTestId(/comment-/);
@@ -254,7 +265,7 @@ describe('CommentsPanel Component', () => {
254
265
 
255
266
  describe('New Comment Creation', () => {
256
267
  it('should not show new comment input by default', () => {
257
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
268
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
258
269
 
259
270
  expect(screen.queryByPlaceholderText(/Add your comment/)).not.toBeInTheDocument();
260
271
  });
@@ -264,6 +275,7 @@ describe('CommentsPanel Component', () => {
264
275
 
265
276
  renderWithEventBus(
266
277
  <CommentsPanel
278
+ session={session}
267
279
  {...defaultProps}
268
280
  pendingAnnotation={pendingAnnotation}
269
281
  />
@@ -277,6 +289,7 @@ describe('CommentsPanel Component', () => {
277
289
 
278
290
  renderWithEventBus(
279
291
  <CommentsPanel
292
+ session={session}
280
293
  {...defaultProps}
281
294
  pendingAnnotation={pendingAnnotation}
282
295
  />
@@ -291,6 +304,7 @@ describe('CommentsPanel Component', () => {
291
304
 
292
305
  renderWithEventBus(
293
306
  <CommentsPanel
307
+ session={session}
294
308
  {...defaultProps}
295
309
  pendingAnnotation={pendingAnnotation}
296
310
  />
@@ -305,6 +319,7 @@ describe('CommentsPanel Component', () => {
305
319
 
306
320
  renderWithEventBus(
307
321
  <CommentsPanel
322
+ session={session}
308
323
  {...defaultProps}
309
324
  pendingAnnotation={pendingAnnotation}
310
325
  />
@@ -321,6 +336,7 @@ describe('CommentsPanel Component', () => {
321
336
 
322
337
  renderWithEventBus(
323
338
  <CommentsPanel
339
+ session={session}
324
340
  {...defaultProps}
325
341
  pendingAnnotation={pendingAnnotation}
326
342
  />
@@ -339,6 +355,7 @@ describe('CommentsPanel Component', () => {
339
355
 
340
356
  renderWithEventBus(
341
357
  <CommentsPanel
358
+ session={session}
342
359
  {...defaultProps}
343
360
  pendingAnnotation={pendingAnnotation}
344
361
  />
@@ -353,6 +370,7 @@ describe('CommentsPanel Component', () => {
353
370
 
354
371
  renderWithEventBus(
355
372
  <CommentsPanel
373
+ session={session}
356
374
  {...defaultProps}
357
375
  pendingAnnotation={pendingAnnotation}
358
376
  />
@@ -368,6 +386,7 @@ describe('CommentsPanel Component', () => {
368
386
 
369
387
  renderWithEventBus(
370
388
  <CommentsPanel
389
+ session={session}
371
390
  {...defaultProps}
372
391
  pendingAnnotation={pendingAnnotation}
373
392
  />,
@@ -394,6 +413,7 @@ describe('CommentsPanel Component', () => {
394
413
 
395
414
  renderWithEventBus(
396
415
  <CommentsPanel
416
+ session={session}
397
417
  {...defaultProps}
398
418
  pendingAnnotation={pendingAnnotation}
399
419
  />
@@ -411,6 +431,7 @@ describe('CommentsPanel Component', () => {
411
431
 
412
432
  renderWithEventBus(
413
433
  <CommentsPanel
434
+ session={session}
414
435
  {...defaultProps}
415
436
  pendingAnnotation={pendingAnnotation}
416
437
  />
@@ -425,6 +446,7 @@ describe('CommentsPanel Component', () => {
425
446
 
426
447
  renderWithEventBus(
427
448
  <CommentsPanel
449
+ session={session}
428
450
  {...defaultProps}
429
451
  pendingAnnotation={pendingAnnotation}
430
452
  />
@@ -442,6 +464,7 @@ describe('CommentsPanel Component', () => {
442
464
 
443
465
  renderWithEventBus(
444
466
  <CommentsPanel
467
+ session={session}
445
468
  {...defaultProps}
446
469
  pendingAnnotation={pendingAnnotation}
447
470
  />
@@ -459,6 +482,7 @@ describe('CommentsPanel Component', () => {
459
482
 
460
483
  const { container } = renderWithEventBus(
461
484
  <CommentsPanel
485
+ session={session}
462
486
  {...defaultProps}
463
487
  pendingAnnotation={pendingAnnotation}
464
488
  />
@@ -474,6 +498,7 @@ describe('CommentsPanel Component', () => {
474
498
  it('should render comment entries', () => {
475
499
  renderWithEventBus(
476
500
  <CommentsPanel
501
+ session={session}
477
502
  {...defaultProps}
478
503
  annotations={mockComments.single}
479
504
  />
@@ -490,6 +515,7 @@ describe('CommentsPanel Component', () => {
490
515
  expect(() => {
491
516
  renderWithEventBus(
492
517
  <CommentsPanel
518
+ session={session}
493
519
  {...defaultProps}
494
520
  annotations={mockComments.single}
495
521
  />
@@ -502,6 +528,7 @@ describe('CommentsPanel Component', () => {
502
528
  it('should render comments', () => {
503
529
  renderWithEventBus(
504
530
  <CommentsPanel
531
+ session={session}
505
532
  {...defaultProps}
506
533
  annotations={mockComments.multiple}
507
534
  />
@@ -515,7 +542,7 @@ describe('CommentsPanel Component', () => {
515
542
  describe('Panel Structure and Styling', () => {
516
543
  it('should have fixed header that does not scroll', () => {
517
544
  renderWithEventBus(
518
- <CommentsPanel {...defaultProps} annotations={mockComments.many} />
545
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.many} />
519
546
  );
520
547
 
521
548
  const headers = screen.getAllByText(/Comments/);
@@ -524,7 +551,7 @@ describe('CommentsPanel Component', () => {
524
551
  });
525
552
 
526
553
  it('should support dark mode', () => {
527
- const { container } = renderWithEventBus(<CommentsPanel {...defaultProps} />);
554
+ const { container } = renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
528
555
 
529
556
  const panel = container.firstChild as HTMLElement;
530
557
  expect(panel).toHaveClass('semiont-panel');
@@ -532,7 +559,7 @@ describe('CommentsPanel Component', () => {
532
559
 
533
560
  it('should have proper spacing between comments', () => {
534
561
  const { container } = renderWithEventBus(
535
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
562
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
536
563
  );
537
564
 
538
565
  const commentsList = container.querySelector('.semiont-panel__list');
@@ -540,7 +567,7 @@ describe('CommentsPanel Component', () => {
540
567
  });
541
568
 
542
569
  it('should have proper border styling', () => {
543
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
570
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
544
571
 
545
572
  const headers = screen.getAllByText(/Comments/);
546
573
  const header = headers[0].closest('div');
@@ -551,7 +578,7 @@ describe('CommentsPanel Component', () => {
551
578
  describe('Edge Cases', () => {
552
579
  it('should handle empty comments array', () => {
553
580
  expect(() => {
554
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={[]} />);
581
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={[]} />);
555
582
  }).not.toThrow();
556
583
 
557
584
  expect(screen.getByText(/No comments yet/)).toBeInTheDocument();
@@ -559,7 +586,7 @@ describe('CommentsPanel Component', () => {
559
586
 
560
587
  it('should handle rapid comment additions', () => {
561
588
  const { rerender } = renderWithEventBus(
562
- <CommentsPanel {...defaultProps} annotations={mockComments.empty} />
589
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.empty} />
563
590
  );
564
591
 
565
592
  for (let i = 1; i <= 5; i++) {
@@ -567,7 +594,7 @@ describe('CommentsPanel Component', () => {
567
594
  createMockComment(`${j + 1}`, j * 10, (j + 1) * 10)
568
595
  );
569
596
  rerender(
570
- <CommentsPanel {...defaultProps} annotations={comments} />
597
+ <CommentsPanel session={session} {...defaultProps} annotations={comments} />
571
598
  );
572
599
  }
573
600
 
@@ -576,13 +603,13 @@ describe('CommentsPanel Component', () => {
576
603
 
577
604
  it('should handle comment removal', () => {
578
605
  const { rerender } = renderWithEventBus(
579
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
606
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
580
607
  );
581
608
 
582
609
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(3);
583
610
 
584
611
  rerender(
585
- <CommentsPanel {...defaultProps} annotations={mockComments.single} />
612
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.single} />
586
613
  );
587
614
 
588
615
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(1);
@@ -593,6 +620,7 @@ describe('CommentsPanel Component', () => {
593
620
 
594
621
  renderWithEventBus(
595
622
  <CommentsPanel
623
+ session={session}
596
624
  {...defaultProps}
597
625
  pendingAnnotation={pendingAnnotation}
598
626
  />
@@ -608,7 +636,7 @@ describe('CommentsPanel Component', () => {
608
636
  );
609
637
 
610
638
  expect(() => {
611
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={manyComments} />);
639
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={manyComments} />);
612
640
  }).not.toThrow();
613
641
 
614
642
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(100);
@@ -622,7 +650,7 @@ describe('CommentsPanel Component', () => {
622
650
  ];
623
651
 
624
652
  expect(() => {
625
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={commentsAtSamePosition} />);
653
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={commentsAtSamePosition} />);
626
654
  }).not.toThrow();
627
655
 
628
656
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(3);
@@ -631,7 +659,7 @@ describe('CommentsPanel Component', () => {
631
659
 
632
660
  describe('Accessibility', () => {
633
661
  it('should have proper heading structure', () => {
634
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
662
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
635
663
 
636
664
  const headings = screen.getAllByText(/Comments/);
637
665
  expect(headings[0]).toHaveClass('semiont-panel-header__text');
@@ -648,6 +676,7 @@ describe('CommentsPanel Component', () => {
648
676
 
649
677
  renderWithEventBus(
650
678
  <CommentsPanel
679
+ session={session}
651
680
  {...defaultProps}
652
681
  pendingAnnotation={pendingAnnotation}
653
682
  />
@@ -659,7 +688,7 @@ describe('CommentsPanel Component', () => {
659
688
 
660
689
  it('should have semantic HTML structure', () => {
661
690
  const { container } = renderWithEventBus(
662
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
691
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
663
692
  );
664
693
 
665
694
  // Panel should be a properly structured div hierarchy
@@ -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
+ import type { SemiontSession } from '@semiont/sdk';
6
7
 
7
- import type { Annotation, AnnotationId } from '@semiont/core';
8
+ import type { Annotation, AnnotationId, EventBus } from '@semiont/core';
8
9
 
9
10
  // Mock @semiont/http-transport
10
11
  vi.mock('@semiont/core', async () => {
@@ -49,14 +50,21 @@ describe('HighlightEntry', () => {
49
50
  isFocused: false,
50
51
  };
51
52
 
53
+ // Created per-test: test-utils disposes every created client in a
54
+ // module-scope afterEach, so a module-scope session would be dead
55
+ // after the first test.
56
+ let session: SemiontSession;
57
+ let eventBus: EventBus;
58
+
52
59
  beforeEach(() => {
53
60
  vi.clearAllMocks();
54
61
  mockGetAnnotationExactText.mockReturnValue('This is the highlighted text');
62
+ ({ session, eventBus } = createTestSemiontWrapper());
55
63
  });
56
64
 
57
65
  describe('Rendering', () => {
58
66
  it('should render the selected text in quotes', () => {
59
- renderWithProviders(<HighlightEntry {...defaultProps} />);
67
+ renderWithProviders(<HighlightEntry {...defaultProps} session={session} />);
60
68
 
61
69
  expect(screen.getByText(/This is the highlighted text/)).toBeInTheDocument();
62
70
  });
@@ -65,7 +73,7 @@ describe('HighlightEntry', () => {
65
73
  const longText = 'A'.repeat(250);
66
74
  mockGetAnnotationExactText.mockReturnValue(longText);
67
75
 
68
- renderWithProviders(<HighlightEntry {...defaultProps} />);
76
+ renderWithProviders(<HighlightEntry {...defaultProps} session={session} />);
69
77
 
70
78
  // Should show first 200 chars followed by ellipsis
71
79
  expect(screen.getByText(new RegExp(`"${'A'.repeat(200)}`))).toBeInTheDocument();
@@ -76,7 +84,7 @@ describe('HighlightEntry', () => {
76
84
  const exactText = 'B'.repeat(200);
77
85
  mockGetAnnotationExactText.mockReturnValue(exactText);
78
86
 
79
- const { container } = renderWithProviders(<HighlightEntry {...defaultProps} />);
87
+ const { container } = renderWithProviders(<HighlightEntry {...defaultProps} session={session} />);
80
88
 
81
89
  const quote = container.querySelector('.semiont-annotation-entry__quote');
82
90
  expect(quote).toBeInTheDocument();
@@ -84,7 +92,7 @@ describe('HighlightEntry', () => {
84
92
  });
85
93
 
86
94
  it('should show creator name', () => {
87
- renderWithProviders(<HighlightEntry {...defaultProps} />);
95
+ renderWithProviders(<HighlightEntry {...defaultProps} session={session} />);
88
96
 
89
97
  expect(screen.getByText(/alice@example.com/)).toBeInTheDocument();
90
98
  });
@@ -94,7 +102,7 @@ describe('HighlightEntry', () => {
94
102
  delete (highlight as Record<string, unknown>).creator;
95
103
 
96
104
  renderWithProviders(
97
- <HighlightEntry highlight={highlight} isFocused={false} />
105
+ <HighlightEntry highlight={highlight} isFocused={false} session={session} />
98
106
  );
99
107
 
100
108
  expect(screen.getByText(/Unknown/)).toBeInTheDocument();
@@ -106,7 +114,7 @@ describe('HighlightEntry', () => {
106
114
  });
107
115
 
108
116
  renderWithProviders(
109
- <HighlightEntry highlight={recentHighlight} isFocused={false} />
117
+ <HighlightEntry highlight={recentHighlight} isFocused={false} session={session} />
110
118
  );
111
119
 
112
120
  expect(screen.getByText(/just now/)).toBeInTheDocument();
@@ -115,7 +123,7 @@ describe('HighlightEntry', () => {
115
123
  it('should not render quote section when selectedText is empty', () => {
116
124
  mockGetAnnotationExactText.mockReturnValue('');
117
125
 
118
- const { container } = renderWithProviders(<HighlightEntry {...defaultProps} />);
126
+ const { container } = renderWithProviders(<HighlightEntry {...defaultProps} session={session} />);
119
127
 
120
128
  expect(container.querySelector('.semiont-annotation-entry__quote')).not.toBeInTheDocument();
121
129
  });
@@ -125,12 +133,14 @@ describe('HighlightEntry', () => {
125
133
  it('should emit browse:click on click', async () => {
126
134
  const clickHandler = vi.fn();
127
135
 
128
- const { container, eventBus } = renderWithProviders(
129
- <HighlightEntry {...defaultProps} />,
130
- { returnEventBus: true }
136
+ // The component only sees the `session` prop now, so the assertion
137
+ // must subscribe on that same session's bus — not the bus of the
138
+ // wrapper renderWithProviders creates internally.
139
+ const { container } = renderWithProviders(
140
+ <HighlightEntry {...defaultProps} session={session} />
131
141
  );
132
142
 
133
- const subscription = eventBus!.get('browse:click').subscribe(clickHandler);
143
+ const subscription = eventBus.get('browse:click').subscribe(clickHandler);
134
144
 
135
145
  const entry = container.firstChild as HTMLElement;
136
146
  await userEvent.click(entry);
@@ -147,7 +157,7 @@ describe('HighlightEntry', () => {
147
157
  describe('Hover state', () => {
148
158
  it('should apply pulse class when isHovered is true', () => {
149
159
  const { container } = renderWithProviders(
150
- <HighlightEntry {...defaultProps} isHovered={true} />
160
+ <HighlightEntry {...defaultProps} isHovered={true} session={session} />
151
161
  );
152
162
 
153
163
  const entry = container.firstChild as HTMLElement;
@@ -156,7 +166,7 @@ describe('HighlightEntry', () => {
156
166
 
157
167
  it('should not apply pulse class when isHovered is false', () => {
158
168
  const { container } = renderWithProviders(
159
- <HighlightEntry {...defaultProps} isHovered={false} />
169
+ <HighlightEntry {...defaultProps} isHovered={false} session={session} />
160
170
  );
161
171
 
162
172
  const entry = container.firstChild as HTMLElement;
@@ -167,7 +177,7 @@ describe('HighlightEntry', () => {
167
177
  describe('Focus state', () => {
168
178
  it('should set data-focused to true when focused', () => {
169
179
  const { container } = renderWithProviders(
170
- <HighlightEntry {...defaultProps} isFocused={true} />
180
+ <HighlightEntry {...defaultProps} isFocused={true} session={session} />
171
181
  );
172
182
 
173
183
  const entry = container.firstChild as HTMLElement;
@@ -176,7 +186,7 @@ describe('HighlightEntry', () => {
176
186
 
177
187
  it('should set data-focused to false when not focused', () => {
178
188
  const { container } = renderWithProviders(
179
- <HighlightEntry {...defaultProps} isFocused={false} />
189
+ <HighlightEntry {...defaultProps} isFocused={false} session={session} />
180
190
  );
181
191
 
182
192
  const entry = container.firstChild as HTMLElement;
@@ -14,8 +14,9 @@
14
14
  import { describe, it, expect, vi, beforeEach } from 'vitest';
15
15
  import React from 'react';
16
16
  import { screen } from '@testing-library/react';
17
- import { renderWithProviders } from '../../../../test-utils';
17
+ import { renderWithProviders, createTestSemiontWrapper } from '../../../../test-utils';
18
18
  import { HighlightPanel } from '../HighlightPanel';
19
+ import type { SemiontSession } from '@semiont/sdk';
19
20
 
20
21
  import type { Annotation, AnnotationId } from '@semiont/core';
21
22
 
@@ -48,9 +49,14 @@ vi.mock('../../../../contexts/useEventSubscription', () => ({
48
49
 
49
50
  describe('HighlightPanel + AssistSection Integration', () => {
50
51
  let mockAnnotations: Annotation[];
52
+ // Created per-test: test-utils disposes every created client in a
53
+ // module-scope afterEach, so a module-scope session would be dead
54
+ // after the first test.
55
+ let session: SemiontSession;
51
56
 
52
57
  beforeEach(() => {
53
58
  vi.clearAllMocks();
59
+ ({ session } = createTestSemiontWrapper());
54
60
 
55
61
  mockAnnotations = [
56
62
  {
@@ -74,7 +80,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
74
80
  describe('Detection Progress Prop Passing', () => {
75
81
  it('should pass progress to AssistSection when provided', () => {
76
82
  renderWithProviders(
77
- <HighlightPanel resourceId="res-1"
83
+ <HighlightPanel session={session} resourceId="res-1"
78
84
  annotations={mockAnnotations}
79
85
  pendingAnnotation={null}
80
86
  isAssisting={true}
@@ -93,7 +99,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
93
99
 
94
100
  it('should pass null progress to AssistSection', () => {
95
101
  renderWithProviders(
96
- <HighlightPanel resourceId="res-1"
102
+ <HighlightPanel session={session} resourceId="res-1"
97
103
  annotations={mockAnnotations}
98
104
  pendingAnnotation={null}
99
105
  isAssisting={false}
@@ -109,7 +115,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
109
115
 
110
116
  it('should pass undefined progress to AssistSection', () => {
111
117
  renderWithProviders(
112
- <HighlightPanel resourceId="res-1"
118
+ <HighlightPanel session={session} resourceId="res-1"
113
119
  annotations={mockAnnotations}
114
120
  pendingAnnotation={null}
115
121
  isAssisting={false}
@@ -125,7 +131,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
125
131
 
126
132
  it('should keep progress visible after detection completes (isAssisting=false)', () => {
127
133
  renderWithProviders(
128
- <HighlightPanel resourceId="res-1"
134
+ <HighlightPanel session={session} resourceId="res-1"
129
135
  annotations={mockAnnotations}
130
136
  pendingAnnotation={null}
131
137
  isAssisting={false}
@@ -146,7 +152,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
146
152
 
147
153
  it('should pass progress with request parameters to AssistSection', () => {
148
154
  renderWithProviders(
149
- <HighlightPanel resourceId="res-1"
155
+ <HighlightPanel session={session} resourceId="res-1"
150
156
  annotations={mockAnnotations}
151
157
  pendingAnnotation={null}
152
158
  isAssisting={true}
@@ -172,7 +178,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
172
178
  describe('Annotate Mode Toggling', () => {
173
179
  it('should render AssistSection when annotateMode is true', () => {
174
180
  renderWithProviders(
175
- <HighlightPanel resourceId="res-1"
181
+ <HighlightPanel session={session} resourceId="res-1"
176
182
  annotations={mockAnnotations}
177
183
  pendingAnnotation={null}
178
184
  isAssisting={false}
@@ -186,7 +192,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
186
192
 
187
193
  it('should NOT render AssistSection when annotateMode is false', () => {
188
194
  renderWithProviders(
189
- <HighlightPanel resourceId="res-1"
195
+ <HighlightPanel session={session} resourceId="res-1"
190
196
  annotations={mockAnnotations}
191
197
  pendingAnnotation={null}
192
198
  isAssisting={false}
@@ -200,7 +206,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
200
206
 
201
207
  it('should hide progress when switching to browse mode (annotateMode=false)', () => {
202
208
  const { rerender } = renderWithProviders(
203
- <HighlightPanel resourceId="res-1"
209
+ <HighlightPanel session={session} resourceId="res-1"
204
210
  annotations={mockAnnotations}
205
211
  pendingAnnotation={null}
206
212
  isAssisting={true}
@@ -217,7 +223,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
217
223
 
218
224
  // Switch to browse mode
219
225
  rerender(
220
- <HighlightPanel resourceId="res-1"
226
+ <HighlightPanel session={session} resourceId="res-1"
221
227
  annotations={mockAnnotations}
222
228
  pendingAnnotation={null}
223
229
  isAssisting={true}
@@ -238,7 +244,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
238
244
  describe('State Combinations', () => {
239
245
  it('should handle isAssisting=true with no progress (starting state)', () => {
240
246
  renderWithProviders(
241
- <HighlightPanel resourceId="res-1"
247
+ <HighlightPanel session={session} resourceId="res-1"
242
248
  annotations={mockAnnotations}
243
249
  pendingAnnotation={null}
244
250
  isAssisting={true}
@@ -253,7 +259,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
253
259
 
254
260
  it('should handle isAssisting=false with progress (final state)', () => {
255
261
  renderWithProviders(
256
- <HighlightPanel resourceId="res-1"
262
+ <HighlightPanel session={session} resourceId="res-1"
257
263
  annotations={mockAnnotations}
258
264
  pendingAnnotation={null}
259
265
  isAssisting={false}
@@ -274,7 +280,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
274
280
 
275
281
  it('should handle multiple progress updates', () => {
276
282
  const { rerender } = renderWithProviders(
277
- <HighlightPanel resourceId="res-1"
283
+ <HighlightPanel session={session} resourceId="res-1"
278
284
  annotations={mockAnnotations}
279
285
  pendingAnnotation={null}
280
286
  isAssisting={true}
@@ -291,7 +297,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
291
297
 
292
298
  // Update to analyzing
293
299
  rerender(
294
- <HighlightPanel resourceId="res-1"
300
+ <HighlightPanel session={session} resourceId="res-1"
295
301
  annotations={mockAnnotations}
296
302
  pendingAnnotation={null}
297
303
  isAssisting={true}
@@ -309,7 +315,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
309
315
 
310
316
  // Update to complete
311
317
  rerender(
312
- <HighlightPanel resourceId="res-1"
318
+ <HighlightPanel session={session} resourceId="res-1"
313
319
  annotations={mockAnnotations}
314
320
  pendingAnnotation={null}
315
321
  isAssisting={false}
@@ -330,7 +336,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
330
336
  describe('Highlights List Rendering', () => {
331
337
  it('should render highlights list alongside detection progress', () => {
332
338
  renderWithProviders(
333
- <HighlightPanel resourceId="res-1"
339
+ <HighlightPanel session={session} resourceId="res-1"
334
340
  annotations={mockAnnotations}
335
341
  pendingAnnotation={null}
336
342
  isAssisting={true}
@@ -350,7 +356,7 @@ describe('HighlightPanel + AssistSection Integration', () => {
350
356
 
351
357
  it('should show empty state when no highlights', () => {
352
358
  renderWithProviders(
353
- <HighlightPanel resourceId="res-1"
359
+ <HighlightPanel session={session} resourceId="res-1"
354
360
  annotations={[]}
355
361
  pendingAnnotation={null}
356
362
  isAssisting={false}