@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
@@ -5,6 +5,7 @@ import userEvent from '@testing-library/user-event';
5
5
  import '@testing-library/jest-dom';
6
6
  import { ReferencesPanel } from '../ReferencesPanel';
7
7
  import type { EventBus } from '@semiont/core';
8
+ import type { SemiontSession } from '@semiont/sdk';
8
9
  import { createTestSemiontWrapper } from '../../../../test-utils';
9
10
 
10
11
  // Composition-based event tracker
@@ -29,8 +30,20 @@ function createEventTracker() {
29
30
  };
30
31
  }
31
32
 
33
+ // Per-test session/wrapper: created in beforeEach — test-utils disposes every
34
+ // created client in a module-scope afterEach, so a module-scope factory call
35
+ // would hand tests after the first a disposed client. The component is
36
+ // provider-free: the `session` passed as a prop (same factory call as the
37
+ // eventBus the tracker attaches to) is the only session it sees.
38
+ let session: SemiontSession;
39
+ let eventBus: EventBus;
40
+ let SemiontWrapper: React.ComponentType<{ children: React.ReactNode }>;
41
+
42
+ beforeEach(() => {
43
+ ({ session, eventBus, SemiontWrapper } = createTestSemiontWrapper());
44
+ });
45
+
32
46
  const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
33
- const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
34
47
  if (tracker) tracker._attach(eventBus);
35
48
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
36
49
  <SemiontWrapper>{children}</SemiontWrapper>
@@ -96,6 +109,11 @@ describe('ReferencesPanel Component', () => {
96
109
  pendingAnnotation: null,
97
110
  };
98
111
 
112
+ // Per-test props: merges the beforeEach-created session at render time.
113
+ // (Module/describe-scope defaultProps must stay session-free — disposal
114
+ // hazard, see note above renderWithEventBus.)
115
+ const panelProps = () => ({ ...defaultProps, session });
116
+
99
117
  beforeEach(() => {
100
118
  vi.clearAllMocks();
101
119
  });
@@ -106,13 +124,13 @@ describe('ReferencesPanel Component', () => {
106
124
 
107
125
  describe('Rendering', () => {
108
126
  it('should render panel with title', () => {
109
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
127
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
110
128
 
111
129
  expect(screen.getByText('Annotate References')).toBeInTheDocument();
112
130
  });
113
131
 
114
132
  it('should render all entity type buttons', () => {
115
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
133
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
116
134
 
117
135
  expect(screen.getByText('Person')).toBeInTheDocument();
118
136
  expect(screen.getByText('Organization')).toBeInTheDocument();
@@ -121,13 +139,13 @@ describe('ReferencesPanel Component', () => {
121
139
  });
122
140
 
123
141
  it('should show message when no entity types available', () => {
124
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={[]} />);
142
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={[]} />);
125
143
 
126
144
  expect(screen.getByText('No entity types available')).toBeInTheDocument();
127
145
  });
128
146
 
129
147
  it('should render start detection button', () => {
130
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
148
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
131
149
 
132
150
  expect(screen.getByTitle('Annotate')).toBeInTheDocument();
133
151
  });
@@ -135,7 +153,7 @@ describe('ReferencesPanel Component', () => {
135
153
 
136
154
  describe('Entity Type Selection', () => {
137
155
  it('should toggle entity type selection on click', async () => {
138
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
156
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
139
157
 
140
158
  const personButton = screen.getByText('Person');
141
159
 
@@ -154,7 +172,7 @@ describe('ReferencesPanel Component', () => {
154
172
  });
155
173
 
156
174
  it('should allow multiple selections', async () => {
157
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
175
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
158
176
 
159
177
  const personButton = screen.getByText('Person');
160
178
  const orgButton = screen.getByText('Organization');
@@ -170,7 +188,7 @@ describe('ReferencesPanel Component', () => {
170
188
  });
171
189
 
172
190
  it('should deselect when clicking selected type', async () => {
173
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
191
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
174
192
 
175
193
  const personButton = screen.getByText('Person');
176
194
 
@@ -182,7 +200,7 @@ describe('ReferencesPanel Component', () => {
182
200
  });
183
201
 
184
202
  it('should show selected count', async () => {
185
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
203
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
186
204
 
187
205
  const personButton = screen.getByText('Person');
188
206
  const orgButton = screen.getByText('Organization');
@@ -199,7 +217,7 @@ describe('ReferencesPanel Component', () => {
199
217
  });
200
218
 
201
219
  it('should not show selected count when none selected', () => {
202
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
220
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
203
221
 
204
222
  expect(screen.queryByText(/selected/i)).not.toBeInTheDocument();
205
223
  });
@@ -207,7 +225,7 @@ describe('ReferencesPanel Component', () => {
207
225
 
208
226
  describe('Button Styling', () => {
209
227
  it('should style selected buttons differently', async () => {
210
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
228
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
211
229
 
212
230
  const personButton = screen.getByText('Person');
213
231
 
@@ -222,7 +240,7 @@ describe('ReferencesPanel Component', () => {
222
240
  });
223
241
 
224
242
  it('should have proper ARIA attributes', () => {
225
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
243
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
226
244
 
227
245
  const personButton = screen.getByText('Person');
228
246
 
@@ -231,7 +249,7 @@ describe('ReferencesPanel Component', () => {
231
249
  });
232
250
 
233
251
  it('should have focus styles', () => {
234
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
252
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
235
253
 
236
254
  const personButton = screen.getByText('Person');
237
255
 
@@ -241,7 +259,7 @@ describe('ReferencesPanel Component', () => {
241
259
 
242
260
  describe('Start Annotate Button', () => {
243
261
  it('should be disabled when no types selected', () => {
244
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
262
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
245
263
 
246
264
  const startButton = screen.getByTitle('Annotate');
247
265
 
@@ -249,7 +267,7 @@ describe('ReferencesPanel Component', () => {
249
267
  });
250
268
 
251
269
  it('should be enabled when types are selected', async () => {
252
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
270
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
253
271
 
254
272
  const personButton = screen.getByText('Person');
255
273
  await userEvent.click(personButton);
@@ -261,7 +279,7 @@ describe('ReferencesPanel Component', () => {
261
279
 
262
280
  it('should emit annotate:detect-request event with selected types and includeDescriptiveReferences', async () => {
263
281
  const tracker = createEventTracker();
264
- renderWithEventBus(<ReferencesPanel {...defaultProps} />, tracker);
282
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />, tracker);
265
283
 
266
284
  await userEvent.click(screen.getByText('Person'));
267
285
  await userEvent.click(screen.getByText('Organization'));
@@ -282,7 +300,7 @@ describe('ReferencesPanel Component', () => {
282
300
 
283
301
  it('should emit annotate:detect-request event with includeDescriptiveReferences when checkbox is checked', async () => {
284
302
  const tracker = createEventTracker();
285
- renderWithEventBus(<ReferencesPanel {...defaultProps} />, tracker);
303
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />, tracker);
286
304
 
287
305
  await userEvent.click(screen.getByText('Person'));
288
306
 
@@ -305,7 +323,7 @@ describe('ReferencesPanel Component', () => {
305
323
  });
306
324
 
307
325
  it('should clear selected types after detection starts', async () => {
308
- const { rerender } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
326
+ const { rerender } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
309
327
 
310
328
  await userEvent.click(screen.getByText('Person'));
311
329
 
@@ -315,7 +333,7 @@ describe('ReferencesPanel Component', () => {
315
333
  // Simulate detection starting
316
334
  rerender(
317
335
  <ReferencesPanel
318
- {...defaultProps}
336
+ {...panelProps()}
319
337
  isAssisting={true}
320
338
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
321
339
  />
@@ -324,7 +342,7 @@ describe('ReferencesPanel Component', () => {
324
342
  // Simulate detection completing
325
343
  rerender(
326
344
  <ReferencesPanel
327
- {...defaultProps}
345
+ {...panelProps()}
328
346
  isAssisting={false}
329
347
  progress={{
330
348
  stage: 'complete',
@@ -340,7 +358,7 @@ describe('ReferencesPanel Component', () => {
340
358
  });
341
359
 
342
360
  it('should have proper styling when disabled', () => {
343
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
361
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
344
362
 
345
363
  const startButton = screen.getByTitle('Annotate');
346
364
 
@@ -351,7 +369,7 @@ describe('ReferencesPanel Component', () => {
351
369
  });
352
370
 
353
371
  it('should have proper styling when enabled', async () => {
354
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
372
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
355
373
 
356
374
  await userEvent.click(screen.getByText('Person'));
357
375
 
@@ -368,7 +386,7 @@ describe('ReferencesPanel Component', () => {
368
386
  it('should show progress widget when detecting', () => {
369
387
  renderWithEventBus(
370
388
  <ReferencesPanel
371
- {...defaultProps}
389
+ {...panelProps()}
372
390
  isAssisting={true}
373
391
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
374
392
  />
@@ -390,7 +408,7 @@ describe('ReferencesPanel Component', () => {
390
408
 
391
409
  renderWithEventBus(
392
410
  <ReferencesPanel
393
- {...defaultProps}
411
+ {...panelProps()}
394
412
  isAssisting={true}
395
413
  progress={progress}
396
414
  />
@@ -404,7 +422,7 @@ describe('ReferencesPanel Component', () => {
404
422
  it('should hide entity type selection during detection', () => {
405
423
  renderWithEventBus(
406
424
  <ReferencesPanel
407
- {...defaultProps}
425
+ {...panelProps()}
408
426
  isAssisting={true}
409
427
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
410
428
  />
@@ -417,7 +435,7 @@ describe('ReferencesPanel Component', () => {
417
435
  it('should render cancel button when detecting', async () => {
418
436
  renderWithEventBus(
419
437
  <ReferencesPanel
420
- {...defaultProps}
438
+ {...panelProps()}
421
439
  isAssisting={true}
422
440
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
423
441
  />
@@ -432,7 +450,7 @@ describe('ReferencesPanel Component', () => {
432
450
  it('should show completed log after detection finishes', () => {
433
451
  const { rerender } = renderWithEventBus(
434
452
  <ReferencesPanel
435
- {...defaultProps}
453
+ {...panelProps()}
436
454
  isAssisting={false}
437
455
  progress={{
438
456
  stage: 'complete',
@@ -449,7 +467,7 @@ describe('ReferencesPanel Component', () => {
449
467
  // Parent clears progress after completion
450
468
  rerender(
451
469
  <ReferencesPanel
452
- {...defaultProps}
470
+ {...panelProps()}
453
471
  isAssisting={false}
454
472
  progress={null}
455
473
  />
@@ -462,7 +480,7 @@ describe('ReferencesPanel Component', () => {
462
480
  it('should show found counts in log', () => {
463
481
  const { rerender } = renderWithEventBus(
464
482
  <ReferencesPanel
465
- {...defaultProps}
483
+ {...panelProps()}
466
484
  isAssisting={false}
467
485
  progress={{
468
486
  stage: 'complete',
@@ -474,7 +492,7 @@ describe('ReferencesPanel Component', () => {
474
492
  );
475
493
 
476
494
  rerender(
477
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
495
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
478
496
  );
479
497
  expect(screen.getByText(/Found.*5/i)).toBeInTheDocument();
480
498
  });
@@ -482,7 +500,7 @@ describe('ReferencesPanel Component', () => {
482
500
  it('should show checkmarks for completed types', () => {
483
501
  const { rerender } = renderWithEventBus(
484
502
  <ReferencesPanel
485
- {...defaultProps}
503
+ {...panelProps()}
486
504
  isAssisting={false}
487
505
  progress={{
488
506
  stage: 'complete',
@@ -494,7 +512,7 @@ describe('ReferencesPanel Component', () => {
494
512
  );
495
513
 
496
514
  rerender(
497
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
515
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
498
516
  );
499
517
  expect(screen.getByText('✓')).toBeInTheDocument();
500
518
  });
@@ -502,7 +520,7 @@ describe('ReferencesPanel Component', () => {
502
520
  it('should show detection log and selection UI together after completion', () => {
503
521
  const { rerender } = renderWithEventBus(
504
522
  <ReferencesPanel
505
- {...defaultProps}
523
+ {...panelProps()}
506
524
  isAssisting={false}
507
525
  progress={{
508
526
  stage: 'complete',
@@ -514,7 +532,7 @@ describe('ReferencesPanel Component', () => {
514
532
  );
515
533
 
516
534
  rerender(
517
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
535
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
518
536
  );
519
537
 
520
538
  // Should show both the completed log AND the selection UI
@@ -525,7 +543,7 @@ describe('ReferencesPanel Component', () => {
525
543
  it('should show selection UI immediately after detection completes', async () => {
526
544
  const { rerender } = renderWithEventBus(
527
545
  <ReferencesPanel
528
- {...defaultProps}
546
+ {...panelProps()}
529
547
  isAssisting={false}
530
548
  progress={{
531
549
  stage: 'complete',
@@ -537,7 +555,7 @@ describe('ReferencesPanel Component', () => {
537
555
  );
538
556
 
539
557
  rerender(
540
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
558
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
541
559
  );
542
560
 
543
561
  // Selection UI should be immediately available (no button click needed)
@@ -548,7 +566,7 @@ describe('ReferencesPanel Component', () => {
548
566
  it('should not show log when empty', () => {
549
567
  renderWithEventBus(
550
568
  <ReferencesPanel
551
- {...defaultProps}
569
+ {...panelProps()}
552
570
  isAssisting={false}
553
571
  progress={{
554
572
  stage: 'complete',
@@ -567,7 +585,7 @@ describe('ReferencesPanel Component', () => {
567
585
 
568
586
  describe('State Transitions', () => {
569
587
  it('should transition from idle to detecting', () => {
570
- const { rerender } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
588
+ const { rerender } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
571
589
 
572
590
  // Idle state
573
591
  expect(screen.getByText('Select entity types')).toBeInTheDocument();
@@ -575,7 +593,7 @@ describe('ReferencesPanel Component', () => {
575
593
  // Start detecting
576
594
  rerender(
577
595
  <ReferencesPanel
578
- {...defaultProps}
596
+ {...panelProps()}
579
597
  isAssisting={true}
580
598
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
581
599
  />
@@ -589,7 +607,7 @@ describe('ReferencesPanel Component', () => {
589
607
  it('should transition from detecting to complete', () => {
590
608
  const { rerender } = renderWithEventBus(
591
609
  <ReferencesPanel
592
- {...defaultProps}
610
+ {...panelProps()}
593
611
  isAssisting={true}
594
612
  progress={{ stage: 'analyzing', percentage: 0, message: 'Detecting references...', completedEntityTypes: [] }}
595
613
  />
@@ -601,7 +619,7 @@ describe('ReferencesPanel Component', () => {
601
619
  // Complete - first trigger useEffect to copy to lastDetectionLog
602
620
  rerender(
603
621
  <ReferencesPanel
604
- {...defaultProps}
622
+ {...panelProps()}
605
623
  isAssisting={false}
606
624
  progress={{
607
625
  stage: 'complete',
@@ -614,7 +632,7 @@ describe('ReferencesPanel Component', () => {
614
632
 
615
633
  // Then clear progress to show the log
616
634
  rerender(
617
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
635
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
618
636
  );
619
637
 
620
638
  expect(screen.queryByTestId('annotation-progress-widget')).not.toBeInTheDocument();
@@ -626,7 +644,7 @@ describe('ReferencesPanel Component', () => {
626
644
  it('should show selection UI after detection completes', async () => {
627
645
  const { rerender } = renderWithEventBus(
628
646
  <ReferencesPanel
629
- {...defaultProps}
647
+ {...panelProps()}
630
648
  isAssisting={false}
631
649
  progress={{
632
650
  stage: 'complete',
@@ -639,14 +657,14 @@ describe('ReferencesPanel Component', () => {
639
657
 
640
658
  // Clear progress to show the log
641
659
  rerender(
642
- <ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
660
+ <ReferencesPanel {...panelProps()} isAssisting={false} progress={null} />
643
661
  );
644
662
 
645
663
  // Selection UI should be immediately available
646
664
  expect(screen.getByText('Select entity types')).toBeInTheDocument();
647
665
 
648
666
  rerender(
649
- <ReferencesPanel {...defaultProps} />
667
+ <ReferencesPanel {...panelProps()} />
650
668
  );
651
669
 
652
670
  expect(screen.getByText('Select entity types')).toBeInTheDocument();
@@ -656,7 +674,7 @@ describe('ReferencesPanel Component', () => {
656
674
  describe('Edge Cases', () => {
657
675
  it('should handle empty entity types array', () => {
658
676
  expect(() => {
659
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={[]} />);
677
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={[]} />);
660
678
  }).not.toThrow();
661
679
  });
662
680
 
@@ -664,7 +682,7 @@ describe('ReferencesPanel Component', () => {
664
682
  const manyTypes = Array.from({ length: 50 }, (_, i) => `Type${i}`);
665
683
 
666
684
  expect(() => {
667
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={manyTypes} />);
685
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={manyTypes} />);
668
686
  }).not.toThrow();
669
687
 
670
688
  expect(screen.getByText('Type0')).toBeInTheDocument();
@@ -674,7 +692,7 @@ describe('ReferencesPanel Component', () => {
674
692
  it('should handle entity types with special characters', () => {
675
693
  const specialTypes = ['Type-A', 'Type_B', 'Type.C', 'Type/D'];
676
694
 
677
- renderWithEventBus(<ReferencesPanel {...defaultProps} allEntityTypes={specialTypes} />);
695
+ renderWithEventBus(<ReferencesPanel {...panelProps()} allEntityTypes={specialTypes} />);
678
696
 
679
697
  specialTypes.forEach(type => {
680
698
  expect(screen.getByText(type)).toBeInTheDocument();
@@ -682,7 +700,7 @@ describe('ReferencesPanel Component', () => {
682
700
  });
683
701
 
684
702
  it('should handle selecting and deselecting all types', async () => {
685
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
703
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
686
704
 
687
705
  // Select all
688
706
  for (const type of defaultProps.allEntityTypes) {
@@ -704,7 +722,7 @@ describe('ReferencesPanel Component', () => {
704
722
  });
705
723
 
706
724
  it('should handle rapid selection changes', async () => {
707
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
725
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
708
726
 
709
727
  const personButton = screen.getByText('Person');
710
728
 
@@ -720,7 +738,7 @@ describe('ReferencesPanel Component', () => {
720
738
  it('should handle zero found count in results', () => {
721
739
  renderWithEventBus(
722
740
  <ReferencesPanel
723
- {...defaultProps}
741
+ {...panelProps()}
724
742
  isAssisting={false}
725
743
  progress={{
726
744
  stage: 'complete',
@@ -738,7 +756,7 @@ describe('ReferencesPanel Component', () => {
738
756
  expect(() => {
739
757
  renderWithEventBus(
740
758
  <ReferencesPanel
741
- {...defaultProps}
759
+ {...panelProps()}
742
760
  isAssisting={false}
743
761
  progress={undefined as any}
744
762
  />
@@ -749,21 +767,21 @@ describe('ReferencesPanel Component', () => {
749
767
 
750
768
  describe('Styling and Appearance', () => {
751
769
  it('should have proper panel structure', () => {
752
- const { container } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
770
+ const { container } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
753
771
 
754
772
  const panel = container.firstChild as HTMLElement;
755
773
  expect(panel).toHaveClass('semiont-panel');
756
774
  });
757
775
 
758
776
  it('should support dark mode', () => {
759
- const { container } = renderWithEventBus(<ReferencesPanel {...defaultProps} />);
777
+ const { container } = renderWithEventBus(<ReferencesPanel {...panelProps()} />);
760
778
 
761
779
  const panel = container.firstChild as HTMLElement;
762
780
  expect(panel).toHaveClass('semiont-panel');
763
781
  });
764
782
 
765
783
  it('should have title without emoji', () => {
766
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
784
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
767
785
 
768
786
  // The emoji is no longer in the title (it's only in the tab now)
769
787
  const title = screen.getByRole('heading', { level: 2 });
@@ -772,7 +790,7 @@ describe('ReferencesPanel Component', () => {
772
790
  });
773
791
 
774
792
  it('should have proper button layout', () => {
775
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
793
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
776
794
 
777
795
  const buttonContainer = screen.getByText('Person').parentElement;
778
796
  expect(buttonContainer).toHaveClass('semiont-assist-widget__chips');
@@ -781,7 +799,7 @@ describe('ReferencesPanel Component', () => {
781
799
 
782
800
  describe('Accessibility', () => {
783
801
  it('should have proper ARIA labels for selection', async () => {
784
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
802
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
785
803
 
786
804
  const personButton = screen.getByText('Person');
787
805
 
@@ -795,7 +813,7 @@ describe('ReferencesPanel Component', () => {
795
813
  });
796
814
 
797
815
  it('should have proper ARIA pressed states', async () => {
798
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
816
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
799
817
 
800
818
  const personButton = screen.getByText('Person');
801
819
 
@@ -807,7 +825,7 @@ describe('ReferencesPanel Component', () => {
807
825
  });
808
826
 
809
827
  it('should be keyboard navigable', () => {
810
- renderWithEventBus(<ReferencesPanel {...defaultProps} />);
828
+ renderWithEventBus(<ReferencesPanel {...panelProps()} />);
811
829
 
812
830
  const personButton = screen.getByText('Person');
813
831
  personButton.focus();
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { render, screen, fireEvent, waitFor } from '@testing-library/react';
4
4
  import '@testing-library/jest-dom';
5
5
  import type { EventBus } from '@semiont/core';
6
- import type { SemiontClient } from '@semiont/sdk';
6
+ import type { SemiontClient, SemiontSession } from '@semiont/sdk';
7
7
  import { ResourceInfoPanel } from '../ResourceInfoPanel';
8
8
  import { createTestSemiontWrapper } from '../../../../test-utils';
9
9
 
@@ -87,13 +87,15 @@ function createEventTracker() {
87
87
  };
88
88
  }
89
89
 
90
- const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
91
- const { SemiontWrapper, eventBus, client } = createTestSemiontWrapper();
90
+ const renderWithEventBus = (component: React.ReactElement<{ session: SemiontSession | null }>, tracker?: ReturnType<typeof createEventTracker>) => {
91
+ const { SemiontWrapper, eventBus, client, session } = createTestSemiontWrapper();
92
92
  if (tracker) tracker._attach(eventBus, client);
93
93
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
94
94
  <SemiontWrapper>{children}</SemiontWrapper>
95
95
  );
96
- return render(component, { wrapper: Wrapper });
96
+ // The component is provider-free: inject the factory session so it is the
97
+ // SAME session whose client the tracker spies on / whose bus it subscribes.
98
+ return render(React.cloneElement(component, { session }), { wrapper: Wrapper });
97
99
  };
98
100
 
99
101
  describe('ResourceInfoPanel Component', () => {
@@ -103,6 +105,11 @@ describe('ResourceInfoPanel Component', () => {
103
105
  documentLocale: undefined,
104
106
  primaryMediaType: undefined,
105
107
  primaryByteSize: undefined,
108
+ // Satisfies JSX at element construction only; renderWithEventBus always
109
+ // replaces it (via cloneElement) with the per-test factory session. Never
110
+ // put a live session here — module-scope clients get disposed after the
111
+ // first test.
112
+ session: null,
106
113
  };
107
114
 
108
115
  beforeEach(() => {