@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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/dist/{PdfAnnotationCanvas.client-P37SXBWQ.js → PdfAnnotationCanvas.client-75GY2EDR.js} +15 -6
  3. package/dist/PdfAnnotationCanvas.client-75GY2EDR.js.map +1 -0
  4. package/dist/{chunk-5LFFAVDI.js → chunk-PFUXQPWJ.js} +96 -96
  5. package/dist/{chunk-5LFFAVDI.js.map → chunk-PFUXQPWJ.js.map} +1 -1
  6. package/dist/{chunk-O2MD7TGE.js → chunk-VXASXU4K.js} +10 -2
  7. package/dist/chunk-VXASXU4K.js.map +1 -0
  8. package/dist/index.css +150 -112
  9. package/dist/index.css.map +1 -1
  10. package/dist/index.d.ts +334 -48
  11. package/dist/index.js +2494 -2201
  12. package/dist/index.js.map +1 -1
  13. package/dist/test-utils.d.ts +5 -1
  14. package/dist/test-utils.js +10 -4
  15. package/dist/test-utils.js.map +1 -1
  16. package/package.json +7 -7
  17. package/src/components/ResourceTagsInline.tsx +94 -11
  18. package/src/components/StatusDisplay.css +36 -0
  19. package/src/components/__tests__/ResourceTagsInline.test.tsx +101 -3
  20. package/src/components/annotation/AnnotateToolbar.tsx +53 -28
  21. package/src/components/annotation/__tests__/AnnotateToolbar.display.test.tsx +76 -0
  22. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +124 -360
  23. package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
  24. package/src/components/image-annotation/SvgDrawingCanvas.tsx +17 -5
  25. package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +137 -0
  26. package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +108 -0
  27. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +17 -4
  28. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +143 -7
  29. package/src/components/resource/AnnotateView.tsx +59 -38
  30. package/src/components/resource/BrowseView.tsx +172 -138
  31. package/src/components/resource/ResourceViewer.tsx +123 -130
  32. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +55 -0
  33. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +49 -0
  34. package/src/components/resource/__tests__/BrowseView.links.test.tsx +60 -0
  35. package/src/components/resource/__tests__/BrowseView.overlay-async.test.tsx +113 -0
  36. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +161 -0
  37. package/src/components/resource/__tests__/BrowseView.registry.test.tsx +43 -0
  38. package/src/components/resource/__tests__/BrowseView.test.tsx +133 -53
  39. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +102 -0
  40. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +12 -7
  41. package/src/components/resource/__tests__/ResourceViewer.toolbar-opt-out.test.tsx +135 -0
  42. package/src/components/resource/__tests__/ResourceViewer.toolbar-prefs.test.tsx +200 -0
  43. package/src/components/resource/__tests__/browse-renderers.dispatch.test.tsx +113 -0
  44. package/src/components/resource/__tests__/browse-renderers.test.tsx +34 -0
  45. package/src/components/resource/__tests__/inline-embedding.test.tsx +81 -0
  46. package/src/components/resource/__tests__/media-completeness.test.tsx +88 -0
  47. package/src/components/resource/browse-renderers.tsx +85 -0
  48. package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
  49. package/src/components/resource/panels/AssessmentPanel.tsx +12 -5
  50. package/src/components/resource/panels/AssistSection.tsx +4 -3
  51. package/src/components/resource/panels/CommentEntry.tsx +5 -4
  52. package/src/components/resource/panels/CommentsPanel.tsx +12 -5
  53. package/src/components/resource/panels/HighlightEntry.tsx +5 -4
  54. package/src/components/resource/panels/HighlightPanel.tsx +12 -5
  55. package/src/components/resource/panels/ReferenceEntry.tsx +9 -10
  56. package/src/components/resource/panels/ReferencesPanel.tsx +15 -6
  57. package/src/components/resource/panels/ResourceInfoPanel.tsx +4 -3
  58. package/src/components/resource/panels/TagEntry.tsx +5 -3
  59. package/src/components/resource/panels/TaggingPanel.tsx +11 -4
  60. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +15 -2
  61. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +28 -20
  62. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +39 -28
  63. package/src/components/resource/panels/__tests__/AssistSection.test.tsx +46 -17
  64. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +65 -57
  65. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +54 -24
  66. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +27 -17
  67. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +23 -17
  68. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +48 -77
  69. package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +87 -0
  70. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +15 -9
  71. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +78 -59
  72. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +11 -4
  73. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +33 -19
  74. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +16 -7
  75. package/src/components/toolbar/Toolbar.css +14 -0
  76. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +7 -17
  77. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +43 -17
  78. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +2 -14
  79. package/src/styles/features/resource-viewer.css +19 -0
  80. package/dist/PdfAnnotationCanvas.client-P37SXBWQ.js.map +0 -1
  81. 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>
@@ -141,6 +152,7 @@ const createPendingAnnotation = (exact: string) => ({
141
152
 
142
153
  describe('CommentsPanel Component', () => {
143
154
  const defaultProps = {
155
+ resourceId: 'res-1',
144
156
  annotations: mockComments.empty,
145
157
  pendingAnnotation: null,
146
158
  };
@@ -167,7 +179,7 @@ describe('CommentsPanel Component', () => {
167
179
 
168
180
  describe('Rendering', () => {
169
181
  it('should render panel header with title and count', () => {
170
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
182
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
171
183
 
172
184
  const headings = screen.getAllByText(/Comments/);
173
185
  expect(headings.length).toBeGreaterThan(0);
@@ -175,13 +187,13 @@ describe('CommentsPanel Component', () => {
175
187
  });
176
188
 
177
189
  it('should show empty state when no comments', () => {
178
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
190
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
179
191
 
180
192
  expect(screen.getByText(/No comments yet/)).toBeInTheDocument();
181
193
  });
182
194
 
183
195
  it('should render all comments', () => {
184
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
196
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
185
197
 
186
198
  expect(screen.getByTestId('comment-1')).toBeInTheDocument();
187
199
  expect(screen.getByTestId('comment-2')).toBeInTheDocument();
@@ -189,7 +201,7 @@ describe('CommentsPanel Component', () => {
189
201
  });
190
202
 
191
203
  it('should have proper panel structure', () => {
192
- const { container } = renderWithEventBus(<CommentsPanel {...defaultProps} />);
204
+ const { container } = renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
193
205
 
194
206
  // Find the root panel div (first child of the container)
195
207
  const panel = container.firstChild as HTMLElement;
@@ -198,7 +210,7 @@ describe('CommentsPanel Component', () => {
198
210
 
199
211
  it('should have scrollable comments list', () => {
200
212
  const { container } = renderWithEventBus(
201
- <CommentsPanel {...defaultProps} annotations={mockComments.many} />
213
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.many} />
202
214
  );
203
215
 
204
216
  const commentsList = container.querySelector('.semiont-panel__list');
@@ -208,7 +220,7 @@ describe('CommentsPanel Component', () => {
208
220
 
209
221
  describe('Comment Sorting', () => {
210
222
  it('should sort comments by position in resource', () => {
211
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
223
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
212
224
 
213
225
  const comments = screen.getAllByTestId(/comment-/);
214
226
 
@@ -222,13 +234,13 @@ describe('CommentsPanel Component', () => {
222
234
  mockGetTextPositionSelector.mockReturnValue(null);
223
235
 
224
236
  expect(() => {
225
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={mockComments.multiple} />);
237
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />);
226
238
  }).not.toThrow();
227
239
  });
228
240
 
229
241
  it('should maintain sort order when comments update', () => {
230
242
  const { rerender } = renderWithEventBus(
231
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
243
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
232
244
  );
233
245
 
234
246
  // Add a new comment at position 25
@@ -238,7 +250,7 @@ describe('CommentsPanel Component', () => {
238
250
  ];
239
251
 
240
252
  rerender(
241
- <CommentsPanel {...defaultProps} annotations={updatedComments} />
253
+ <CommentsPanel session={session} {...defaultProps} annotations={updatedComments} />
242
254
  );
243
255
 
244
256
  const comments = screen.getAllByTestId(/comment-/);
@@ -253,7 +265,7 @@ describe('CommentsPanel Component', () => {
253
265
 
254
266
  describe('New Comment Creation', () => {
255
267
  it('should not show new comment input by default', () => {
256
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
268
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
257
269
 
258
270
  expect(screen.queryByPlaceholderText(/Add your comment/)).not.toBeInTheDocument();
259
271
  });
@@ -263,6 +275,7 @@ describe('CommentsPanel Component', () => {
263
275
 
264
276
  renderWithEventBus(
265
277
  <CommentsPanel
278
+ session={session}
266
279
  {...defaultProps}
267
280
  pendingAnnotation={pendingAnnotation}
268
281
  />
@@ -276,6 +289,7 @@ describe('CommentsPanel Component', () => {
276
289
 
277
290
  renderWithEventBus(
278
291
  <CommentsPanel
292
+ session={session}
279
293
  {...defaultProps}
280
294
  pendingAnnotation={pendingAnnotation}
281
295
  />
@@ -290,6 +304,7 @@ describe('CommentsPanel Component', () => {
290
304
 
291
305
  renderWithEventBus(
292
306
  <CommentsPanel
307
+ session={session}
293
308
  {...defaultProps}
294
309
  pendingAnnotation={pendingAnnotation}
295
310
  />
@@ -304,6 +319,7 @@ describe('CommentsPanel Component', () => {
304
319
 
305
320
  renderWithEventBus(
306
321
  <CommentsPanel
322
+ session={session}
307
323
  {...defaultProps}
308
324
  pendingAnnotation={pendingAnnotation}
309
325
  />
@@ -320,6 +336,7 @@ describe('CommentsPanel Component', () => {
320
336
 
321
337
  renderWithEventBus(
322
338
  <CommentsPanel
339
+ session={session}
323
340
  {...defaultProps}
324
341
  pendingAnnotation={pendingAnnotation}
325
342
  />
@@ -338,6 +355,7 @@ describe('CommentsPanel Component', () => {
338
355
 
339
356
  renderWithEventBus(
340
357
  <CommentsPanel
358
+ session={session}
341
359
  {...defaultProps}
342
360
  pendingAnnotation={pendingAnnotation}
343
361
  />
@@ -352,6 +370,7 @@ describe('CommentsPanel Component', () => {
352
370
 
353
371
  renderWithEventBus(
354
372
  <CommentsPanel
373
+ session={session}
355
374
  {...defaultProps}
356
375
  pendingAnnotation={pendingAnnotation}
357
376
  />
@@ -367,6 +386,7 @@ describe('CommentsPanel Component', () => {
367
386
 
368
387
  renderWithEventBus(
369
388
  <CommentsPanel
389
+ session={session}
370
390
  {...defaultProps}
371
391
  pendingAnnotation={pendingAnnotation}
372
392
  />,
@@ -393,6 +413,7 @@ describe('CommentsPanel Component', () => {
393
413
 
394
414
  renderWithEventBus(
395
415
  <CommentsPanel
416
+ session={session}
396
417
  {...defaultProps}
397
418
  pendingAnnotation={pendingAnnotation}
398
419
  />
@@ -410,6 +431,7 @@ describe('CommentsPanel Component', () => {
410
431
 
411
432
  renderWithEventBus(
412
433
  <CommentsPanel
434
+ session={session}
413
435
  {...defaultProps}
414
436
  pendingAnnotation={pendingAnnotation}
415
437
  />
@@ -424,6 +446,7 @@ describe('CommentsPanel Component', () => {
424
446
 
425
447
  renderWithEventBus(
426
448
  <CommentsPanel
449
+ session={session}
427
450
  {...defaultProps}
428
451
  pendingAnnotation={pendingAnnotation}
429
452
  />
@@ -441,6 +464,7 @@ describe('CommentsPanel Component', () => {
441
464
 
442
465
  renderWithEventBus(
443
466
  <CommentsPanel
467
+ session={session}
444
468
  {...defaultProps}
445
469
  pendingAnnotation={pendingAnnotation}
446
470
  />
@@ -458,6 +482,7 @@ describe('CommentsPanel Component', () => {
458
482
 
459
483
  const { container } = renderWithEventBus(
460
484
  <CommentsPanel
485
+ session={session}
461
486
  {...defaultProps}
462
487
  pendingAnnotation={pendingAnnotation}
463
488
  />
@@ -473,6 +498,7 @@ describe('CommentsPanel Component', () => {
473
498
  it('should render comment entries', () => {
474
499
  renderWithEventBus(
475
500
  <CommentsPanel
501
+ session={session}
476
502
  {...defaultProps}
477
503
  annotations={mockComments.single}
478
504
  />
@@ -489,6 +515,7 @@ describe('CommentsPanel Component', () => {
489
515
  expect(() => {
490
516
  renderWithEventBus(
491
517
  <CommentsPanel
518
+ session={session}
492
519
  {...defaultProps}
493
520
  annotations={mockComments.single}
494
521
  />
@@ -501,6 +528,7 @@ describe('CommentsPanel Component', () => {
501
528
  it('should render comments', () => {
502
529
  renderWithEventBus(
503
530
  <CommentsPanel
531
+ session={session}
504
532
  {...defaultProps}
505
533
  annotations={mockComments.multiple}
506
534
  />
@@ -514,7 +542,7 @@ describe('CommentsPanel Component', () => {
514
542
  describe('Panel Structure and Styling', () => {
515
543
  it('should have fixed header that does not scroll', () => {
516
544
  renderWithEventBus(
517
- <CommentsPanel {...defaultProps} annotations={mockComments.many} />
545
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.many} />
518
546
  );
519
547
 
520
548
  const headers = screen.getAllByText(/Comments/);
@@ -523,7 +551,7 @@ describe('CommentsPanel Component', () => {
523
551
  });
524
552
 
525
553
  it('should support dark mode', () => {
526
- const { container } = renderWithEventBus(<CommentsPanel {...defaultProps} />);
554
+ const { container } = renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
527
555
 
528
556
  const panel = container.firstChild as HTMLElement;
529
557
  expect(panel).toHaveClass('semiont-panel');
@@ -531,7 +559,7 @@ describe('CommentsPanel Component', () => {
531
559
 
532
560
  it('should have proper spacing between comments', () => {
533
561
  const { container } = renderWithEventBus(
534
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
562
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
535
563
  );
536
564
 
537
565
  const commentsList = container.querySelector('.semiont-panel__list');
@@ -539,7 +567,7 @@ describe('CommentsPanel Component', () => {
539
567
  });
540
568
 
541
569
  it('should have proper border styling', () => {
542
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
570
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
543
571
 
544
572
  const headers = screen.getAllByText(/Comments/);
545
573
  const header = headers[0].closest('div');
@@ -550,7 +578,7 @@ describe('CommentsPanel Component', () => {
550
578
  describe('Edge Cases', () => {
551
579
  it('should handle empty comments array', () => {
552
580
  expect(() => {
553
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={[]} />);
581
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={[]} />);
554
582
  }).not.toThrow();
555
583
 
556
584
  expect(screen.getByText(/No comments yet/)).toBeInTheDocument();
@@ -558,7 +586,7 @@ describe('CommentsPanel Component', () => {
558
586
 
559
587
  it('should handle rapid comment additions', () => {
560
588
  const { rerender } = renderWithEventBus(
561
- <CommentsPanel {...defaultProps} annotations={mockComments.empty} />
589
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.empty} />
562
590
  );
563
591
 
564
592
  for (let i = 1; i <= 5; i++) {
@@ -566,7 +594,7 @@ describe('CommentsPanel Component', () => {
566
594
  createMockComment(`${j + 1}`, j * 10, (j + 1) * 10)
567
595
  );
568
596
  rerender(
569
- <CommentsPanel {...defaultProps} annotations={comments} />
597
+ <CommentsPanel session={session} {...defaultProps} annotations={comments} />
570
598
  );
571
599
  }
572
600
 
@@ -575,13 +603,13 @@ describe('CommentsPanel Component', () => {
575
603
 
576
604
  it('should handle comment removal', () => {
577
605
  const { rerender } = renderWithEventBus(
578
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
606
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
579
607
  );
580
608
 
581
609
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(3);
582
610
 
583
611
  rerender(
584
- <CommentsPanel {...defaultProps} annotations={mockComments.single} />
612
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.single} />
585
613
  );
586
614
 
587
615
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(1);
@@ -592,6 +620,7 @@ describe('CommentsPanel Component', () => {
592
620
 
593
621
  renderWithEventBus(
594
622
  <CommentsPanel
623
+ session={session}
595
624
  {...defaultProps}
596
625
  pendingAnnotation={pendingAnnotation}
597
626
  />
@@ -607,7 +636,7 @@ describe('CommentsPanel Component', () => {
607
636
  );
608
637
 
609
638
  expect(() => {
610
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={manyComments} />);
639
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={manyComments} />);
611
640
  }).not.toThrow();
612
641
 
613
642
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(100);
@@ -621,7 +650,7 @@ describe('CommentsPanel Component', () => {
621
650
  ];
622
651
 
623
652
  expect(() => {
624
- renderWithEventBus(<CommentsPanel {...defaultProps} annotations={commentsAtSamePosition} />);
653
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} annotations={commentsAtSamePosition} />);
625
654
  }).not.toThrow();
626
655
 
627
656
  expect(screen.getAllByTestId(/comment-/)).toHaveLength(3);
@@ -630,7 +659,7 @@ describe('CommentsPanel Component', () => {
630
659
 
631
660
  describe('Accessibility', () => {
632
661
  it('should have proper heading structure', () => {
633
- renderWithEventBus(<CommentsPanel {...defaultProps} />);
662
+ renderWithEventBus(<CommentsPanel session={session} {...defaultProps} />);
634
663
 
635
664
  const headings = screen.getAllByText(/Comments/);
636
665
  expect(headings[0]).toHaveClass('semiont-panel-header__text');
@@ -647,6 +676,7 @@ describe('CommentsPanel Component', () => {
647
676
 
648
677
  renderWithEventBus(
649
678
  <CommentsPanel
679
+ session={session}
650
680
  {...defaultProps}
651
681
  pendingAnnotation={pendingAnnotation}
652
682
  />
@@ -658,7 +688,7 @@ describe('CommentsPanel Component', () => {
658
688
 
659
689
  it('should have semantic HTML structure', () => {
660
690
  const { container } = renderWithEventBus(
661
- <CommentsPanel {...defaultProps} annotations={mockComments.multiple} />
691
+ <CommentsPanel session={session} {...defaultProps} annotations={mockComments.multiple} />
662
692
  );
663
693
 
664
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
359
+ <HighlightPanel session={session} resourceId="res-1"
354
360
  annotations={[]}
355
361
  pendingAnnotation={null}
356
362
  isAssisting={false}