@semiont/react-ui 0.4.20 → 0.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-5QESNO5H.mjs} +13 -16
- package/dist/PdfAnnotationCanvas.client-5QESNO5H.mjs.map +1 -0
- package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
- package/dist/chunk-4NOUO3W6.mjs +7788 -0
- package/dist/chunk-4NOUO3W6.mjs.map +1 -0
- package/dist/index.d.mts +212 -1206
- package/dist/index.mjs +3332 -13712
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +48 -21
- package/dist/test-utils.mjs +2505 -87
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
- package/src/components/CodeMirrorRenderer.tsx +12 -12
- package/src/components/LiveRegion.tsx +1 -2
- package/src/components/StatusDisplay.tsx +42 -16
- package/src/components/Toolbar.tsx +4 -4
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
- package/src/components/__tests__/StatusDisplay.test.tsx +50 -65
- package/src/components/__tests__/Toolbar.test.tsx +4 -4
- package/src/components/annotation/AnnotateToolbar.tsx +8 -9
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
- package/src/components/annotation-popups/JsonLdView.tsx +1 -2
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +1 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +15 -18
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +12 -17
- package/src/components/modals/ConfigureGenerationStep.tsx +1 -2
- package/src/components/modals/PermissionDeniedModal.tsx +11 -11
- package/src/components/modals/ReferenceWizardModal.tsx +14 -18
- package/src/components/modals/ResourceSearchModal.tsx +12 -8
- package/src/components/modals/SearchModal.tsx +11 -6
- package/src/components/modals/SearchResultsStep.tsx +1 -3
- package/src/components/modals/SessionExpiredModal.tsx +11 -11
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
- package/src/components/modals/__tests__/SearchModal.accessibility.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.basic.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
- package/src/components/modals/__tests__/SearchModal.visual.test.tsx +6 -2
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
- package/src/components/navigation/ObservableLink.tsx +6 -6
- package/src/components/navigation/SimpleNavigation.tsx +4 -4
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +15 -18
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +1 -2
- package/src/components/resource/AnnotateView.tsx +8 -10
- package/src/components/resource/AnnotationHistory.tsx +9 -12
- package/src/components/resource/BrowseView.tsx +11 -8
- package/src/components/resource/ResourceViewer.tsx +22 -34
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
- package/src/components/resource/__tests__/BrowseView.test.tsx +38 -87
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +41 -31
- package/src/components/resource/__tests__/event-formatting.test.ts +6 -2
- package/src/components/resource/event-formatting.ts +2 -3
- package/src/components/resource/panels/AssessmentEntry.tsx +7 -8
- package/src/components/resource/panels/AssessmentPanel.tsx +21 -17
- package/src/components/resource/panels/AssistSection.tsx +15 -21
- package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
- package/src/components/resource/panels/CommentEntry.tsx +7 -8
- package/src/components/resource/panels/CommentsPanel.tsx +11 -13
- package/src/components/resource/panels/HighlightEntry.tsx +7 -8
- package/src/components/resource/panels/HighlightPanel.tsx +12 -13
- package/src/components/resource/panels/ReferenceEntry.tsx +13 -15
- package/src/components/resource/panels/ReferencesPanel.tsx +17 -19
- package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -7
- package/src/components/resource/panels/StatisticsPanel.tsx +2 -3
- package/src/components/resource/panels/TagEntry.tsx +7 -8
- package/src/components/resource/panels/TaggingPanel.tsx +14 -23
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +4 -3
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +22 -57
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +22 -61
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +7 -8
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +28 -53
- package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +3 -3
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +19 -52
- package/src/components/settings/SettingsPanel.tsx +9 -9
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +15 -15
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -2
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
- package/src/features/admin-exchange/components/ImportCard.tsx +2 -7
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -2
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -2
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -2
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
- package/src/features/resource-compose/components/ResourceComposePage.tsx +6 -22
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
- package/src/features/resource-discovery/components/ResourceCard.tsx +1 -2
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +3 -4
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +37 -45
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +129 -197
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
- package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
- package/dist/chunk-OZICDVH7.mjs +0 -62
- package/dist/chunk-OZICDVH7.mjs.map +0 -1
- package/dist/chunk-R4CCMFJH.mjs +0 -877
- package/dist/chunk-R4CCMFJH.mjs.map +0 -1
- package/dist/chunk-VN5NY4SN.mjs +0 -200
- package/dist/chunk-VN5NY4SN.mjs.map +0 -1
- package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
- package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
|
@@ -4,7 +4,8 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
5
|
import '@testing-library/jest-dom';
|
|
6
6
|
import { ReferencesPanel } from '../ReferencesPanel';
|
|
7
|
-
import {
|
|
7
|
+
import type { EventBus } from '@semiont/core';
|
|
8
|
+
import { createTestSemiontWrapper } from '../../../../test-utils';
|
|
8
9
|
|
|
9
10
|
// Composition-based event tracker
|
|
10
11
|
interface TrackedEvent {
|
|
@@ -14,59 +15,27 @@ interface TrackedEvent {
|
|
|
14
15
|
|
|
15
16
|
function createEventTracker() {
|
|
16
17
|
const events: TrackedEvent[] = [];
|
|
17
|
-
|
|
18
|
-
function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
|
|
19
|
-
const eventBus = useEventBus();
|
|
20
|
-
|
|
21
|
-
React.useEffect(() => {
|
|
22
|
-
const handlers: Array<() => void> = [];
|
|
23
|
-
|
|
24
|
-
const trackEvent = (eventName: string) => (payload: any) => {
|
|
25
|
-
events.push({ event: eventName, payload });
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const panelEvents = ['mark:assist-request'] as const;
|
|
29
|
-
|
|
30
|
-
panelEvents.forEach(eventName => {
|
|
31
|
-
const handler = trackEvent(eventName);
|
|
32
|
-
const subscription = eventBus.get(eventName).subscribe(handler);
|
|
33
|
-
handlers.push(subscription);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return () => {
|
|
37
|
-
handlers.forEach(sub => sub.unsubscribe());
|
|
38
|
-
};
|
|
39
|
-
}, [eventBus]);
|
|
40
|
-
|
|
41
|
-
return <>{children}</>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
18
|
return {
|
|
45
|
-
EventTrackingWrapper,
|
|
46
19
|
events,
|
|
47
|
-
clear: () => {
|
|
48
|
-
|
|
20
|
+
clear: () => { events.length = 0; },
|
|
21
|
+
_attach(eventBus: EventBus) {
|
|
22
|
+
const panelEvents = ['mark:assist-request'] as const;
|
|
23
|
+
panelEvents.forEach((eventName) => {
|
|
24
|
+
eventBus.get(eventName).subscribe((payload: any) => {
|
|
25
|
+
events.push({ event: eventName, payload });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
49
28
|
},
|
|
50
29
|
};
|
|
51
30
|
}
|
|
52
31
|
|
|
53
|
-
// Helper to render with EventBusProvider
|
|
54
32
|
const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{component}
|
|
60
|
-
</tracker.EventTrackingWrapper>
|
|
61
|
-
</EventBusProvider>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return render(
|
|
66
|
-
<EventBusProvider>
|
|
67
|
-
{component}
|
|
68
|
-
</EventBusProvider>
|
|
33
|
+
const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
|
|
34
|
+
if (tracker) tracker._attach(eventBus);
|
|
35
|
+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
36
|
+
<SemiontWrapper>{children}</SemiontWrapper>
|
|
69
37
|
);
|
|
38
|
+
return render(component, { wrapper: Wrapper });
|
|
70
39
|
};
|
|
71
40
|
|
|
72
41
|
// Mock TranslationContext
|
|
@@ -344,26 +313,22 @@ describe('ReferencesPanel Component', () => {
|
|
|
344
313
|
|
|
345
314
|
// Simulate detection starting
|
|
346
315
|
rerender(
|
|
347
|
-
<
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
/>
|
|
353
|
-
</EventBusProvider>
|
|
316
|
+
<ReferencesPanel
|
|
317
|
+
{...defaultProps}
|
|
318
|
+
isAssisting={true}
|
|
319
|
+
progress={{ completedEntityTypes: [] }}
|
|
320
|
+
/>
|
|
354
321
|
);
|
|
355
322
|
|
|
356
323
|
// Simulate detection completing
|
|
357
324
|
rerender(
|
|
358
|
-
<
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
/>
|
|
366
|
-
</EventBusProvider>
|
|
325
|
+
<ReferencesPanel
|
|
326
|
+
{...defaultProps}
|
|
327
|
+
isAssisting={false}
|
|
328
|
+
progress={{
|
|
329
|
+
completedEntityTypes: [{ entityType: 'Person', foundCount: 5 }],
|
|
330
|
+
}}
|
|
331
|
+
/>
|
|
367
332
|
);
|
|
368
333
|
|
|
369
334
|
// UI should reset but we can't directly test internal state
|
|
@@ -473,13 +438,11 @@ describe('ReferencesPanel Component', () => {
|
|
|
473
438
|
|
|
474
439
|
// Parent clears progress after completion
|
|
475
440
|
rerender(
|
|
476
|
-
<
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
/>
|
|
482
|
-
</EventBusProvider>
|
|
441
|
+
<ReferencesPanel
|
|
442
|
+
{...defaultProps}
|
|
443
|
+
isAssisting={false}
|
|
444
|
+
progress={null}
|
|
445
|
+
/>
|
|
483
446
|
);
|
|
484
447
|
|
|
485
448
|
expect(screen.getByText('Person:')).toBeInTheDocument();
|
|
@@ -498,9 +461,7 @@ describe('ReferencesPanel Component', () => {
|
|
|
498
461
|
);
|
|
499
462
|
|
|
500
463
|
rerender(
|
|
501
|
-
<
|
|
502
|
-
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
503
|
-
</EventBusProvider>
|
|
464
|
+
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
504
465
|
);
|
|
505
466
|
expect(screen.getByText(/Found.*5/i)).toBeInTheDocument();
|
|
506
467
|
});
|
|
@@ -517,9 +478,7 @@ describe('ReferencesPanel Component', () => {
|
|
|
517
478
|
);
|
|
518
479
|
|
|
519
480
|
rerender(
|
|
520
|
-
<
|
|
521
|
-
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
522
|
-
</EventBusProvider>
|
|
481
|
+
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
523
482
|
);
|
|
524
483
|
expect(screen.getByText('✓')).toBeInTheDocument();
|
|
525
484
|
});
|
|
@@ -536,9 +495,7 @@ describe('ReferencesPanel Component', () => {
|
|
|
536
495
|
);
|
|
537
496
|
|
|
538
497
|
rerender(
|
|
539
|
-
<
|
|
540
|
-
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
541
|
-
</EventBusProvider>
|
|
498
|
+
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
542
499
|
);
|
|
543
500
|
|
|
544
501
|
// Should show both the completed log AND the selection UI
|
|
@@ -558,9 +515,7 @@ describe('ReferencesPanel Component', () => {
|
|
|
558
515
|
);
|
|
559
516
|
|
|
560
517
|
rerender(
|
|
561
|
-
<
|
|
562
|
-
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
563
|
-
</EventBusProvider>
|
|
518
|
+
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
564
519
|
);
|
|
565
520
|
|
|
566
521
|
// Selection UI should be immediately available (no button click needed)
|
|
@@ -594,13 +549,11 @@ describe('ReferencesPanel Component', () => {
|
|
|
594
549
|
|
|
595
550
|
// Start detecting
|
|
596
551
|
rerender(
|
|
597
|
-
<
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
/>
|
|
603
|
-
</EventBusProvider>
|
|
552
|
+
<ReferencesPanel
|
|
553
|
+
{...defaultProps}
|
|
554
|
+
isAssisting={true}
|
|
555
|
+
progress={{ completedEntityTypes: [] }}
|
|
556
|
+
/>
|
|
604
557
|
);
|
|
605
558
|
|
|
606
559
|
// Detecting state
|
|
@@ -622,22 +575,18 @@ describe('ReferencesPanel Component', () => {
|
|
|
622
575
|
|
|
623
576
|
// Complete - first trigger useEffect to copy to lastDetectionLog
|
|
624
577
|
rerender(
|
|
625
|
-
<
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
/>
|
|
633
|
-
</EventBusProvider>
|
|
578
|
+
<ReferencesPanel
|
|
579
|
+
{...defaultProps}
|
|
580
|
+
isAssisting={false}
|
|
581
|
+
progress={{
|
|
582
|
+
completedEntityTypes: [{ entityType: 'Person', foundCount: 5 }],
|
|
583
|
+
}}
|
|
584
|
+
/>
|
|
634
585
|
);
|
|
635
586
|
|
|
636
587
|
// Then clear progress to show the log
|
|
637
588
|
rerender(
|
|
638
|
-
<
|
|
639
|
-
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
640
|
-
</EventBusProvider>
|
|
589
|
+
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
641
590
|
);
|
|
642
591
|
|
|
643
592
|
expect(screen.queryByTestId('annotation-progress-widget')).not.toBeInTheDocument();
|
|
@@ -659,18 +608,14 @@ describe('ReferencesPanel Component', () => {
|
|
|
659
608
|
|
|
660
609
|
// Clear progress to show the log
|
|
661
610
|
rerender(
|
|
662
|
-
<
|
|
663
|
-
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
664
|
-
</EventBusProvider>
|
|
611
|
+
<ReferencesPanel {...defaultProps} isAssisting={false} progress={null} />
|
|
665
612
|
);
|
|
666
613
|
|
|
667
614
|
// Selection UI should be immediately available
|
|
668
615
|
expect(screen.getByText('Select entity types')).toBeInTheDocument();
|
|
669
616
|
|
|
670
617
|
rerender(
|
|
671
|
-
<
|
|
672
|
-
<ReferencesPanel {...defaultProps} />
|
|
673
|
-
</EventBusProvider>
|
|
618
|
+
<ReferencesPanel {...defaultProps} />
|
|
674
619
|
);
|
|
675
620
|
|
|
676
621
|
expect(screen.getByText('Select entity types')).toBeInTheDocument();
|
|
@@ -2,8 +2,10 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
+
import type { EventBus } from '@semiont/core';
|
|
6
|
+
import type { SemiontClient } from '@semiont/sdk';
|
|
5
7
|
import { ResourceInfoPanel } from '../ResourceInfoPanel';
|
|
6
|
-
import {
|
|
8
|
+
import { createTestSemiontWrapper } from '../../../../test-utils';
|
|
7
9
|
|
|
8
10
|
// Mock TranslationContext
|
|
9
11
|
vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
@@ -36,8 +38,8 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
36
38
|
}));
|
|
37
39
|
|
|
38
40
|
// Mock @semiont/api-client utilities
|
|
39
|
-
vi.mock('@semiont/
|
|
40
|
-
const actual = await vi.importActual('@semiont/
|
|
41
|
+
vi.mock('@semiont/core', async () => {
|
|
42
|
+
const actual = await vi.importActual('@semiont/core');
|
|
41
43
|
return {
|
|
42
44
|
...actual,
|
|
43
45
|
formatLocaleDisplay: vi.fn((locale: string) => `Language: ${locale}`),
|
|
@@ -61,64 +63,37 @@ interface TrackedEvent {
|
|
|
61
63
|
|
|
62
64
|
function createEventTracker() {
|
|
63
65
|
const events: TrackedEvent[] = [];
|
|
64
|
-
|
|
65
|
-
function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
|
|
66
|
-
const eventBus = useEventBus();
|
|
67
|
-
|
|
68
|
-
React.useEffect(() => {
|
|
69
|
-
const handlers: Array<() => void> = [];
|
|
70
|
-
|
|
71
|
-
// Track resource-related events
|
|
72
|
-
const trackEvent = (eventName: string) => (payload: any) => {
|
|
73
|
-
events.push({ event: eventName, payload });
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const resourceEvents = [
|
|
77
|
-
'yield:clone',
|
|
78
|
-
'mark:archive',
|
|
79
|
-
'mark:unarchive',
|
|
80
|
-
] as const;
|
|
81
|
-
|
|
82
|
-
resourceEvents.forEach(eventName => {
|
|
83
|
-
const handler = trackEvent(eventName);
|
|
84
|
-
const subscription = eventBus.get(eventName).subscribe(handler);
|
|
85
|
-
handlers.push(subscription);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
return () => {
|
|
89
|
-
handlers.forEach(sub => sub.unsubscribe());
|
|
90
|
-
};
|
|
91
|
-
}, [eventBus]);
|
|
92
|
-
|
|
93
|
-
return <>{children}</>;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
66
|
return {
|
|
97
|
-
EventTrackingWrapper,
|
|
98
67
|
events,
|
|
99
|
-
clear: () => {
|
|
100
|
-
|
|
68
|
+
clear: () => { events.length = 0; },
|
|
69
|
+
_attach(eventBus: EventBus, client: SemiontClient) {
|
|
70
|
+
// `yield:clone` is a local-bus UI signal emitted by `client.yield.clone()`.
|
|
71
|
+
eventBus.get('yield:clone').subscribe((payload: any) => {
|
|
72
|
+
events.push({ event: 'yield:clone', payload });
|
|
73
|
+
});
|
|
74
|
+
// `mark:archive` / `mark:unarchive` are backend-routed via `actor.emit`;
|
|
75
|
+
// spy on the namespace methods instead of subscribing to a local bus.
|
|
76
|
+
const origArchive = client.mark.archive.bind(client.mark);
|
|
77
|
+
const origUnarchive = client.mark.unarchive.bind(client.mark);
|
|
78
|
+
client.mark.archive = vi.fn(async (rid: any) => {
|
|
79
|
+
events.push({ event: 'mark:archive', payload: { resourceId: rid } });
|
|
80
|
+
return origArchive(rid);
|
|
81
|
+
}) as typeof client.mark.archive;
|
|
82
|
+
client.mark.unarchive = vi.fn(async (rid: any) => {
|
|
83
|
+
events.push({ event: 'mark:unarchive', payload: { resourceId: rid } });
|
|
84
|
+
return origUnarchive(rid);
|
|
85
|
+
}) as typeof client.mark.unarchive;
|
|
101
86
|
},
|
|
102
87
|
};
|
|
103
88
|
}
|
|
104
89
|
|
|
105
|
-
// Helper to render with EventBusProvider
|
|
106
90
|
const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{component}
|
|
112
|
-
</tracker.EventTrackingWrapper>
|
|
113
|
-
</EventBusProvider>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return render(
|
|
118
|
-
<EventBusProvider>
|
|
119
|
-
{component}
|
|
120
|
-
</EventBusProvider>
|
|
91
|
+
const { SemiontWrapper, eventBus, client } = createTestSemiontWrapper();
|
|
92
|
+
if (tracker) tracker._attach(eventBus, client);
|
|
93
|
+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
94
|
+
<SemiontWrapper>{children}</SemiontWrapper>
|
|
121
95
|
);
|
|
96
|
+
return render(component, { wrapper: Wrapper });
|
|
122
97
|
};
|
|
123
98
|
|
|
124
99
|
describe('ResourceInfoPanel Component', () => {
|
|
@@ -5,14 +5,14 @@ import '@testing-library/jest-dom';
|
|
|
5
5
|
import { renderWithProviders } from '../../../../test-utils';
|
|
6
6
|
import type { components } from '@semiont/core';
|
|
7
7
|
|
|
8
|
-
type Annotation
|
|
8
|
+
import type { Annotation } from '@semiont/core';
|
|
9
9
|
|
|
10
10
|
// Stable mock functions defined outside vi.mock to avoid re-render loops
|
|
11
11
|
const mockIsBodyResolved = vi.fn();
|
|
12
12
|
const mockGetEntityTypes = vi.fn();
|
|
13
13
|
|
|
14
|
-
vi.mock('@semiont/
|
|
15
|
-
const actual = await vi.importActual('@semiont/
|
|
14
|
+
vi.mock('@semiont/core', async () => {
|
|
15
|
+
const actual = await vi.importActual('@semiont/core');
|
|
16
16
|
return {
|
|
17
17
|
...actual,
|
|
18
18
|
isBodyResolved: (...args: unknown[]) => mockIsBodyResolved(...args),
|
|
@@ -6,11 +6,11 @@ import { renderWithProviders } from '../../../../test-utils';
|
|
|
6
6
|
import userEvent from '@testing-library/user-event';
|
|
7
7
|
import type { components } from '@semiont/core';
|
|
8
8
|
|
|
9
|
-
type Annotation
|
|
9
|
+
import type { Annotation } from '@semiont/core';
|
|
10
10
|
|
|
11
11
|
// Mock @semiont/api-client
|
|
12
|
-
vi.mock('@semiont/
|
|
13
|
-
const actual = await vi.importActual('@semiont/
|
|
12
|
+
vi.mock('@semiont/core', async () => {
|
|
13
|
+
const actual = await vi.importActual('@semiont/core');
|
|
14
14
|
return {
|
|
15
15
|
...actual,
|
|
16
16
|
getAnnotationExactText: vi.fn(),
|
|
@@ -28,7 +28,7 @@ vi.mock('../../../../lib/tag-schemas', () => ({
|
|
|
28
28
|
getTagSchema: vi.fn(),
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
|
-
import { getAnnotationExactText } from '@semiont/
|
|
31
|
+
import { getAnnotationExactText } from '@semiont/core';
|
|
32
32
|
import { getTagCategory, getTagSchemaId } from '@semiont/ontology';
|
|
33
33
|
import { getTagSchema } from '../../../../lib/tag-schemas';
|
|
34
34
|
import type { MockedFunction } from 'vitest';
|
|
@@ -5,10 +5,10 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import '@testing-library/jest-dom';
|
|
7
7
|
import { TaggingPanel } from '../TaggingPanel';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import type { components, EventBus } from '@semiont/core';
|
|
9
|
+
import { createTestSemiontWrapper } from '../../../../test-utils';
|
|
10
10
|
|
|
11
|
-
type Annotation
|
|
11
|
+
import type { Annotation } from '@semiont/core';
|
|
12
12
|
|
|
13
13
|
// Composition-based event tracker
|
|
14
14
|
interface TrackedEvent {
|
|
@@ -18,59 +18,27 @@ interface TrackedEvent {
|
|
|
18
18
|
|
|
19
19
|
function createEventTracker() {
|
|
20
20
|
const events: TrackedEvent[] = [];
|
|
21
|
-
|
|
22
|
-
function EventTrackingWrapper({ children }: { children: React.ReactNode }) {
|
|
23
|
-
const eventBus = useEventBus();
|
|
24
|
-
|
|
25
|
-
React.useEffect(() => {
|
|
26
|
-
const handlers: Array<() => void> = [];
|
|
27
|
-
|
|
28
|
-
const trackEvent = (eventName: string) => (payload: any) => {
|
|
29
|
-
events.push({ event: eventName, payload });
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const panelEvents = ['mark:submit', 'mark:assist-request'] as const;
|
|
33
|
-
|
|
34
|
-
panelEvents.forEach(eventName => {
|
|
35
|
-
const handler = trackEvent(eventName);
|
|
36
|
-
const subscription = eventBus.get(eventName).subscribe(handler);
|
|
37
|
-
handlers.push(subscription);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return () => {
|
|
41
|
-
handlers.forEach(sub => sub.unsubscribe());
|
|
42
|
-
};
|
|
43
|
-
}, [eventBus]);
|
|
44
|
-
|
|
45
|
-
return <>{children}</>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
21
|
return {
|
|
49
|
-
EventTrackingWrapper,
|
|
50
22
|
events,
|
|
51
|
-
clear: () => {
|
|
52
|
-
|
|
23
|
+
clear: () => { events.length = 0; },
|
|
24
|
+
_attach(eventBus: EventBus) {
|
|
25
|
+
const panelEvents = ['mark:submit', 'mark:assist-request'] as const;
|
|
26
|
+
panelEvents.forEach((eventName) => {
|
|
27
|
+
eventBus.get(eventName).subscribe((payload: any) => {
|
|
28
|
+
events.push({ event: eventName, payload });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
53
31
|
},
|
|
54
32
|
};
|
|
55
33
|
}
|
|
56
34
|
|
|
57
|
-
// Helper to render with EventBusProvider
|
|
58
35
|
const renderWithEventBus = (component: React.ReactElement, tracker?: ReturnType<typeof createEventTracker>) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
{component}
|
|
64
|
-
</tracker.EventTrackingWrapper>
|
|
65
|
-
</EventBusProvider>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return render(
|
|
70
|
-
<EventBusProvider>
|
|
71
|
-
{component}
|
|
72
|
-
</EventBusProvider>
|
|
36
|
+
const { SemiontWrapper, eventBus } = createTestSemiontWrapper();
|
|
37
|
+
if (tracker) tracker._attach(eventBus);
|
|
38
|
+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
39
|
+
<SemiontWrapper>{children}</SemiontWrapper>
|
|
73
40
|
);
|
|
41
|
+
return render(component, { wrapper: Wrapper });
|
|
74
42
|
};
|
|
75
43
|
|
|
76
44
|
// Mock TranslationContext
|
|
@@ -109,8 +77,8 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
|
109
77
|
}));
|
|
110
78
|
|
|
111
79
|
// Mock @semiont/api-client utilities
|
|
112
|
-
vi.mock('@semiont/
|
|
113
|
-
const actual = await vi.importActual('@semiont/
|
|
80
|
+
vi.mock('@semiont/core', async () => {
|
|
81
|
+
const actual = await vi.importActual('@semiont/core');
|
|
114
82
|
return {
|
|
115
83
|
...actual,
|
|
116
84
|
getTextPositionSelector: vi.fn(),
|
|
@@ -144,8 +112,7 @@ vi.mock('../../../../lib/tag-schemas', () => ({
|
|
|
144
112
|
]),
|
|
145
113
|
}));
|
|
146
114
|
|
|
147
|
-
import { getTextPositionSelector, getTargetSelector } from '@semiont/
|
|
148
|
-
|
|
115
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
|
|
149
116
|
const mockGetTextPositionSelector = getTextPositionSelector as MockedFunction<typeof getTextPositionSelector>;
|
|
150
117
|
const mockGetTargetSelector = getTargetSelector as MockedFunction<typeof getTargetSelector>;
|
|
151
118
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, { useEffect } from 'react';
|
|
4
|
-
import { LOCALES } from '@semiont/
|
|
4
|
+
import { LOCALES } from '@semiont/core';
|
|
5
5
|
import { useTranslations } from '../../contexts/TranslationContext';
|
|
6
6
|
import { useLanguageChangeAnnouncements } from '../LiveRegion';
|
|
7
|
-
import {
|
|
7
|
+
import { useSemiont } from '../../session/SemiontProvider';
|
|
8
8
|
import './SettingsPanel.css';
|
|
9
9
|
|
|
10
10
|
interface SettingsPanelProps {
|
|
@@ -31,7 +31,7 @@ export function SettingsPanel({
|
|
|
31
31
|
hoverDelayMs
|
|
32
32
|
}: SettingsPanelProps) {
|
|
33
33
|
const t = useTranslations('Settings');
|
|
34
|
-
const
|
|
34
|
+
const semiont = useSemiont();
|
|
35
35
|
const { announceLanguageChanging, announceLanguageChanged } = useLanguageChangeAnnouncements();
|
|
36
36
|
|
|
37
37
|
// Track previous locale to detect changes
|
|
@@ -41,7 +41,7 @@ export function SettingsPanel({
|
|
|
41
41
|
const handleLocaleChange = (newLocale: string) => {
|
|
42
42
|
const localeName = LOCALES.find(l => l.code === newLocale)?.nativeName || newLocale;
|
|
43
43
|
announceLanguageChanging(localeName);
|
|
44
|
-
|
|
44
|
+
semiont.emit('settings:locale-changed', { locale: newLocale });
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
// Announce when language has successfully changed
|
|
@@ -70,7 +70,7 @@ export function SettingsPanel({
|
|
|
70
70
|
type="button"
|
|
71
71
|
role="switch"
|
|
72
72
|
aria-checked={showLineNumbers}
|
|
73
|
-
onClick={() =>
|
|
73
|
+
onClick={() => semiont.emit('settings:line-numbers-toggled', undefined)}
|
|
74
74
|
className={`semiont-toggle ${
|
|
75
75
|
showLineNumbers ? 'semiont-toggle--active' : ''
|
|
76
76
|
}`}
|
|
@@ -94,7 +94,7 @@ export function SettingsPanel({
|
|
|
94
94
|
</label>
|
|
95
95
|
<div className="semiont-settings-panel__button-group">
|
|
96
96
|
<button
|
|
97
|
-
onClick={() =>
|
|
97
|
+
onClick={() => semiont.emit('settings:theme-changed', { theme: 'light' })}
|
|
98
98
|
className={`semiont-panel-button ${
|
|
99
99
|
theme === 'light' ? 'semiont-panel-button-active' : ''
|
|
100
100
|
}`}
|
|
@@ -103,7 +103,7 @@ export function SettingsPanel({
|
|
|
103
103
|
☀️ {t('themeLight')}
|
|
104
104
|
</button>
|
|
105
105
|
<button
|
|
106
|
-
onClick={() =>
|
|
106
|
+
onClick={() => semiont.emit('settings:theme-changed', { theme: 'dark' })}
|
|
107
107
|
className={`semiont-panel-button ${
|
|
108
108
|
theme === 'dark' ? 'semiont-panel-button-active' : ''
|
|
109
109
|
}`}
|
|
@@ -112,7 +112,7 @@ export function SettingsPanel({
|
|
|
112
112
|
🌙 {t('themeDark')}
|
|
113
113
|
</button>
|
|
114
114
|
<button
|
|
115
|
-
onClick={() =>
|
|
115
|
+
onClick={() => semiont.emit('settings:theme-changed', { theme: 'system' })}
|
|
116
116
|
className={`semiont-panel-button ${
|
|
117
117
|
theme === 'system' ? 'semiont-panel-button-active' : ''
|
|
118
118
|
}`}
|
|
@@ -170,7 +170,7 @@ export function SettingsPanel({
|
|
|
170
170
|
max="500"
|
|
171
171
|
step="50"
|
|
172
172
|
value={hoverDelayMs}
|
|
173
|
-
onChange={(e) =>
|
|
173
|
+
onChange={(e) => semiont.emit('settings:hover-delay-changed', { hoverDelayMs: Number(e.target.value) })}
|
|
174
174
|
className="semiont-slider"
|
|
175
175
|
aria-describedby="hover-delay-description"
|
|
176
176
|
/>
|
|
@@ -13,9 +13,9 @@ vi.mock('../../LiveRegion', () => ({
|
|
|
13
13
|
})),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
// Mock LOCALES
|
|
17
|
-
vi.mock('@semiont/
|
|
18
|
-
const actual = await vi.importActual('@semiont/
|
|
16
|
+
// Mock LOCALES (lives in @semiont/core)
|
|
17
|
+
vi.mock('@semiont/core', async () => {
|
|
18
|
+
const actual = await vi.importActual('@semiont/core');
|
|
19
19
|
return {
|
|
20
20
|
...actual,
|
|
21
21
|
LOCALES: [
|
|
@@ -63,12 +63,12 @@ describe('SettingsPanel', () => {
|
|
|
63
63
|
|
|
64
64
|
it('emits settings:line-numbers-toggled on toggle click', () => {
|
|
65
65
|
const handler = vi.fn();
|
|
66
|
-
const {
|
|
66
|
+
const { shellBus } = renderWithProviders(
|
|
67
67
|
<SettingsPanel {...defaultProps} />,
|
|
68
|
-
{
|
|
68
|
+
{ returnShellBus: true }
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
-
const sub =
|
|
71
|
+
const sub = shellBus!.get('settings:line-numbers-toggled').subscribe(handler);
|
|
72
72
|
fireEvent.click(screen.getByRole('switch'));
|
|
73
73
|
expect(handler).toHaveBeenCalled();
|
|
74
74
|
sub.unsubscribe();
|
|
@@ -94,12 +94,12 @@ describe('SettingsPanel', () => {
|
|
|
94
94
|
|
|
95
95
|
it('emits settings:theme-changed on theme button click', () => {
|
|
96
96
|
const handler = vi.fn();
|
|
97
|
-
const {
|
|
97
|
+
const { shellBus } = renderWithProviders(
|
|
98
98
|
<SettingsPanel {...defaultProps} />,
|
|
99
|
-
{
|
|
99
|
+
{ returnShellBus: true }
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
-
const sub =
|
|
102
|
+
const sub = shellBus!.get('settings:theme-changed').subscribe(handler);
|
|
103
103
|
fireEvent.click(screen.getByText(/Settings.themeDark/));
|
|
104
104
|
expect(handler).toHaveBeenCalledWith({ theme: 'dark' });
|
|
105
105
|
sub.unsubscribe();
|
|
@@ -125,12 +125,12 @@ describe('SettingsPanel', () => {
|
|
|
125
125
|
|
|
126
126
|
it('emits settings:locale-changed on language change', () => {
|
|
127
127
|
const handler = vi.fn();
|
|
128
|
-
const {
|
|
128
|
+
const { shellBus } = renderWithProviders(
|
|
129
129
|
<SettingsPanel {...defaultProps} />,
|
|
130
|
-
{
|
|
130
|
+
{ returnShellBus: true }
|
|
131
131
|
);
|
|
132
132
|
|
|
133
|
-
const sub =
|
|
133
|
+
const sub = shellBus!.get('settings:locale-changed').subscribe(handler);
|
|
134
134
|
fireEvent.change(screen.getByLabelText('Settings.language'), {
|
|
135
135
|
target: { value: 'fr' },
|
|
136
136
|
});
|
|
@@ -173,12 +173,12 @@ describe('SettingsPanel', () => {
|
|
|
173
173
|
|
|
174
174
|
it('emits settings:hover-delay-changed on slider change', () => {
|
|
175
175
|
const handler = vi.fn();
|
|
176
|
-
const {
|
|
176
|
+
const { shellBus } = renderWithProviders(
|
|
177
177
|
<SettingsPanel {...defaultProps} />,
|
|
178
|
-
{
|
|
178
|
+
{ returnShellBus: true }
|
|
179
179
|
);
|
|
180
180
|
|
|
181
|
-
const sub =
|
|
181
|
+
const sub = shellBus!.get('settings:hover-delay-changed').subscribe(handler);
|
|
182
182
|
fireEvent.change(screen.getByLabelText('Settings.hoverDelay'), {
|
|
183
183
|
target: { value: '300' },
|
|
184
184
|
});
|