@semiont/react-ui 0.4.14 → 0.4.16
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 +18 -12
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +175 -0
- package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs → PdfAnnotationCanvas.client-CHDCGQBR.mjs} +3 -3
- package/dist/{ar-R4CRNXEF.mjs → ar-3W37O3R3.mjs} +9 -3
- package/dist/ar-3W37O3R3.mjs.map +1 -0
- package/dist/{bn-CZKGRHTA.mjs → bn-JZTJLMVE.mjs} +9 -3
- package/dist/bn-JZTJLMVE.mjs.map +1 -0
- package/dist/chunk-FAI3S4BM.mjs +865 -0
- package/dist/chunk-FAI3S4BM.mjs.map +1 -0
- package/dist/{chunk-HVMAGUFA.mjs → chunk-NOD3NCXE.mjs} +3 -1
- package/dist/chunk-NOD3NCXE.mjs.map +1 -0
- package/dist/{chunk-HNZOXH4L.mjs → chunk-OZICDVH7.mjs} +5 -3
- package/dist/chunk-OZICDVH7.mjs.map +1 -0
- package/dist/{chunk-BQJWOK4C.mjs → chunk-VN5NY4SN.mjs} +9 -8
- package/dist/chunk-VN5NY4SN.mjs.map +1 -0
- package/dist/{cs-4WIB2IHH.mjs → cs-XYHH7HNE.mjs} +9 -3
- package/dist/cs-XYHH7HNE.mjs.map +1 -0
- package/dist/{da-JWYEUYPX.mjs → da-MZKIECVT.mjs} +9 -3
- package/dist/da-MZKIECVT.mjs.map +1 -0
- package/dist/{de-GWUQZGER.mjs → de-AYXTMRQW.mjs} +9 -3
- package/dist/de-AYXTMRQW.mjs.map +1 -0
- package/dist/{el-DM2GT7P5.mjs → el-A6CVQWAW.mjs} +9 -3
- package/dist/el-A6CVQWAW.mjs.map +1 -0
- package/dist/{en-IUV4ZXKH.mjs → en-YPQQBI4T.mjs} +2 -2
- package/dist/{es-6LVQIM3D.mjs → es-M2HXLJGT.mjs} +9 -3
- package/dist/es-M2HXLJGT.mjs.map +1 -0
- package/dist/{fa-IRUJY3QI.mjs → fa-V6JZJDYP.mjs} +9 -3
- package/dist/fa-V6JZJDYP.mjs.map +1 -0
- package/dist/{fi-53FBOEVT.mjs → fi-ONDTZ5H7.mjs} +9 -3
- package/dist/fi-ONDTZ5H7.mjs.map +1 -0
- package/dist/{fr-Q5KY7QL6.mjs → fr-PAPV4H4G.mjs} +9 -3
- package/dist/fr-PAPV4H4G.mjs.map +1 -0
- package/dist/{he-HJNKULBY.mjs → he-F6VTLJLW.mjs} +9 -3
- package/dist/he-F6VTLJLW.mjs.map +1 -0
- package/dist/{hi-UYZ4X6CR.mjs → hi-CFUAV4BF.mjs} +9 -3
- package/dist/hi-CFUAV4BF.mjs.map +1 -0
- package/dist/{id-UAQMH6U2.mjs → id-NBKLCCI7.mjs} +9 -3
- package/dist/id-NBKLCCI7.mjs.map +1 -0
- package/dist/index.d.mts +141 -169
- package/dist/index.mjs +2394 -2116
- package/dist/index.mjs.map +1 -1
- package/dist/{it-C7QEBNFA.mjs → it-SLSOWVVU.mjs} +9 -3
- package/dist/it-SLSOWVVU.mjs.map +1 -0
- package/dist/{ja-THS6AOSJ.mjs → ja-L5IG4ECE.mjs} +9 -3
- package/dist/ja-L5IG4ECE.mjs.map +1 -0
- package/dist/{ko-XKK3TWQG.mjs → ko-QYMTULKK.mjs} +9 -3
- package/dist/ko-QYMTULKK.mjs.map +1 -0
- package/dist/{ms-GSK7LIF7.mjs → ms-5DGSFKM2.mjs} +9 -3
- package/dist/ms-5DGSFKM2.mjs.map +1 -0
- package/dist/{nl-KUBWITGY.mjs → nl-VZPCGONO.mjs} +9 -3
- package/dist/nl-VZPCGONO.mjs.map +1 -0
- package/dist/{no-ECWZUHT6.mjs → no-MF6F352I.mjs} +9 -3
- package/dist/no-MF6F352I.mjs.map +1 -0
- package/dist/{pl-PLVWSZWS.mjs → pl-WIK72JUO.mjs} +9 -3
- package/dist/pl-WIK72JUO.mjs.map +1 -0
- package/dist/{pt-AL74ZTKB.mjs → pt-RRP5ZF6A.mjs} +9 -3
- package/dist/pt-RRP5ZF6A.mjs.map +1 -0
- package/dist/{ro-WTPHLHGS.mjs → ro-XHQLC3T7.mjs} +9 -3
- package/dist/ro-XHQLC3T7.mjs.map +1 -0
- package/dist/{sv-QCLI7SG4.mjs → sv-EWULDN6E.mjs} +9 -3
- package/dist/sv-EWULDN6E.mjs.map +1 -0
- package/dist/test-utils.d.mts +13 -62
- package/dist/test-utils.mjs +41 -22
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-WCKVZU6U.mjs → th-TGOBHFG4.mjs} +9 -3
- package/dist/th-TGOBHFG4.mjs.map +1 -0
- package/dist/{tr-2CAFS2XS.mjs → tr-LMMPBMV7.mjs} +9 -3
- package/dist/tr-LMMPBMV7.mjs.map +1 -0
- package/dist/{uk-TDE4JLCY.mjs → uk-IPGRRJY6.mjs} +9 -3
- package/dist/uk-IPGRRJY6.mjs.map +1 -0
- package/dist/{vi-KKXZ4PCX.mjs → vi-Q676OJQS.mjs} +9 -3
- package/dist/vi-Q676OJQS.mjs.map +1 -0
- package/dist/{zh-VH4XN5PV.mjs → zh-F3MTWQDX.mjs} +9 -3
- package/dist/zh-F3MTWQDX.mjs.map +1 -0
- package/package.json +5 -3
- package/src/components/ProtectedErrorBoundary.tsx +95 -0
- package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +197 -0
- package/src/components/modals/PermissionDeniedModal.tsx +140 -0
- package/src/components/modals/ReferenceWizardModal.tsx +3 -2
- package/src/components/modals/SessionExpiredModal.tsx +101 -0
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +150 -0
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +115 -0
- package/src/components/resource/AnnotationHistory.tsx +5 -6
- package/src/components/resource/HistoryEvent.tsx +9 -8
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +33 -34
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +18 -19
- package/src/components/resource/__tests__/event-formatting.test.ts +70 -94
- package/src/components/resource/event-formatting.ts +92 -56
- package/src/components/resource/panels/ReferenceEntry.tsx +7 -5
- package/src/components/resource/panels/ResourceInfoPanel.tsx +18 -6
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +12 -12
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +24 -0
- package/src/features/resource-compose/components/ResourceComposePage.tsx +10 -1
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +4 -4
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +5 -10
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +23 -54
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +6 -6
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +7 -19
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +18 -44
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +6 -6
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +26 -26
- package/src/styles/features/compose.css +63 -0
- package/translations/ar.json +6 -2
- package/translations/bn.json +6 -2
- package/translations/cs.json +6 -2
- package/translations/da.json +6 -2
- package/translations/de.json +6 -2
- package/translations/el.json +6 -2
- package/translations/en.json +2 -0
- package/translations/es.json +6 -2
- package/translations/fa.json +6 -2
- package/translations/fi.json +6 -2
- package/translations/fr.json +6 -2
- package/translations/he.json +6 -2
- package/translations/hi.json +6 -2
- package/translations/id.json +6 -2
- package/translations/it.json +6 -2
- package/translations/ja.json +6 -2
- package/translations/ko.json +6 -2
- package/translations/ms.json +6 -2
- package/translations/nl.json +6 -2
- package/translations/no.json +6 -2
- package/translations/pl.json +6 -2
- package/translations/pt.json +6 -2
- package/translations/ro.json +6 -2
- package/translations/sv.json +6 -2
- package/translations/th.json +6 -2
- package/translations/tr.json +6 -2
- package/translations/uk.json +6 -2
- package/translations/vi.json +6 -2
- package/translations/zh.json +6 -2
- package/dist/TranslationManager-CudgH3gw.d.mts +0 -107
- package/dist/ar-R4CRNXEF.mjs.map +0 -1
- package/dist/bn-CZKGRHTA.mjs.map +0 -1
- package/dist/chunk-BQJWOK4C.mjs.map +0 -1
- package/dist/chunk-HNZOXH4L.mjs.map +0 -1
- package/dist/chunk-HVMAGUFA.mjs.map +0 -1
- package/dist/chunk-OL5UST25.mjs +0 -413
- package/dist/chunk-OL5UST25.mjs.map +0 -1
- package/dist/cs-4WIB2IHH.mjs.map +0 -1
- package/dist/da-JWYEUYPX.mjs.map +0 -1
- package/dist/de-GWUQZGER.mjs.map +0 -1
- package/dist/el-DM2GT7P5.mjs.map +0 -1
- package/dist/es-6LVQIM3D.mjs.map +0 -1
- package/dist/fa-IRUJY3QI.mjs.map +0 -1
- package/dist/fi-53FBOEVT.mjs.map +0 -1
- package/dist/fr-Q5KY7QL6.mjs.map +0 -1
- package/dist/he-HJNKULBY.mjs.map +0 -1
- package/dist/hi-UYZ4X6CR.mjs.map +0 -1
- package/dist/id-UAQMH6U2.mjs.map +0 -1
- package/dist/it-C7QEBNFA.mjs.map +0 -1
- package/dist/ja-THS6AOSJ.mjs.map +0 -1
- package/dist/ko-XKK3TWQG.mjs.map +0 -1
- package/dist/ms-GSK7LIF7.mjs.map +0 -1
- package/dist/nl-KUBWITGY.mjs.map +0 -1
- package/dist/no-ECWZUHT6.mjs.map +0 -1
- package/dist/pl-PLVWSZWS.mjs.map +0 -1
- package/dist/pt-AL74ZTKB.mjs.map +0 -1
- package/dist/ro-WTPHLHGS.mjs.map +0 -1
- package/dist/sv-QCLI7SG4.mjs.map +0 -1
- package/dist/th-WCKVZU6U.mjs.map +0 -1
- package/dist/tr-2CAFS2XS.mjs.map +0 -1
- package/dist/uk-TDE4JLCY.mjs.map +0 -1
- package/dist/vi-KKXZ4PCX.mjs.map +0 -1
- package/dist/zh-VH4XN5PV.mjs.map +0 -1
- /package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs.map → PdfAnnotationCanvas.client-CHDCGQBR.mjs.map} +0 -0
- /package/dist/{en-IUV4ZXKH.mjs.map → en-YPQQBI4T.mjs.map} +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionDeniedModal Tests
|
|
3
|
+
*
|
|
4
|
+
* The modal renders content when `permissionDeniedAt` is non-null on the
|
|
5
|
+
* KnowledgeBaseSession context, and is hidden otherwise. Button clicks call
|
|
6
|
+
* `acknowledgePermissionDenied()` and navigate the window or history.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { screen, fireEvent } from '@testing-library/react';
|
|
12
|
+
import '@testing-library/jest-dom';
|
|
13
|
+
import {
|
|
14
|
+
renderWithProviders,
|
|
15
|
+
createMockKnowledgeBaseSession,
|
|
16
|
+
} from '../../../test-utils';
|
|
17
|
+
import { PermissionDeniedModal } from '../PermissionDeniedModal';
|
|
18
|
+
|
|
19
|
+
vi.mock('@headlessui/react', () => ({
|
|
20
|
+
Dialog: ({ children, ...props }: any) => <div role="dialog" {...props}>{typeof children === 'function' ? children({ open: true }) : children}</div>,
|
|
21
|
+
DialogPanel: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
22
|
+
DialogTitle: ({ children, ...props }: any) => <h2 {...props}>{children}</h2>,
|
|
23
|
+
Transition: ({ show, children }: any) => show ? <>{children}</> : null,
|
|
24
|
+
TransitionChild: ({ children }: any) => <>{children}</>,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const originalLocation = window.location;
|
|
28
|
+
const originalHistoryBack = window.history.back;
|
|
29
|
+
let mockLocation: { href: string; pathname: string };
|
|
30
|
+
let mockHistoryBack: ReturnType<typeof vi.fn>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockLocation = { href: '', pathname: '/admin/users' };
|
|
34
|
+
Object.defineProperty(window, 'location', {
|
|
35
|
+
value: mockLocation,
|
|
36
|
+
writable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
});
|
|
39
|
+
mockHistoryBack = vi.fn();
|
|
40
|
+
window.history.back = mockHistoryBack;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
Object.defineProperty(window, 'location', {
|
|
45
|
+
value: originalLocation,
|
|
46
|
+
writable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
});
|
|
49
|
+
window.history.back = originalHistoryBack;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('PermissionDeniedModal', () => {
|
|
53
|
+
describe('initial render', () => {
|
|
54
|
+
it('does not render modal content when permissionDeniedAt is null', () => {
|
|
55
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
56
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
57
|
+
permissionDeniedAt: null,
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
expect(screen.queryByText('Access Denied')).not.toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('when permissionDeniedAt is set', () => {
|
|
65
|
+
it('shows modal with default message when no message provided', () => {
|
|
66
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
67
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
68
|
+
permissionDeniedAt: Date.now(),
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(screen.getByText('Access Denied')).toBeInTheDocument();
|
|
73
|
+
expect(screen.getByText('You do not have permission to perform this action.')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('shows custom message from permissionDeniedMessage', () => {
|
|
77
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
78
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
79
|
+
permissionDeniedAt: Date.now(),
|
|
80
|
+
permissionDeniedMessage: 'Admin access required for this resource',
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(screen.getByText('Admin access required for this resource')).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('renders all three action buttons', () => {
|
|
88
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
89
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
90
|
+
permissionDeniedAt: Date.now(),
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(screen.getByRole('button', { name: /go back/i })).toBeInTheDocument();
|
|
95
|
+
expect(screen.getByRole('button', { name: /go to home/i })).toBeInTheDocument();
|
|
96
|
+
expect(screen.getByRole('button', { name: /switch account/i })).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('button actions', () => {
|
|
101
|
+
it('acknowledges and calls window.history.back on Go Back', () => {
|
|
102
|
+
const ack = vi.fn();
|
|
103
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
104
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
105
|
+
permissionDeniedAt: Date.now(),
|
|
106
|
+
permissionDeniedMessage: 'denied',
|
|
107
|
+
acknowledgePermissionDenied: ack,
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
fireEvent.click(screen.getByRole('button', { name: /go back/i }));
|
|
112
|
+
|
|
113
|
+
expect(ack).toHaveBeenCalled();
|
|
114
|
+
expect(mockHistoryBack).toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('acknowledges and navigates to / on Go to Home', () => {
|
|
118
|
+
const ack = vi.fn();
|
|
119
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
120
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
121
|
+
permissionDeniedAt: Date.now(),
|
|
122
|
+
permissionDeniedMessage: 'denied',
|
|
123
|
+
acknowledgePermissionDenied: ack,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
fireEvent.click(screen.getByRole('button', { name: /go to home/i }));
|
|
128
|
+
|
|
129
|
+
expect(ack).toHaveBeenCalled();
|
|
130
|
+
expect(mockLocation.href).toBe('/');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('acknowledges and navigates to /auth/connect with current path on Switch Account', () => {
|
|
134
|
+
const ack = vi.fn();
|
|
135
|
+
mockLocation.pathname = '/admin/users';
|
|
136
|
+
renderWithProviders(<PermissionDeniedModal />, {
|
|
137
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
138
|
+
permissionDeniedAt: Date.now(),
|
|
139
|
+
permissionDeniedMessage: 'denied',
|
|
140
|
+
acknowledgePermissionDenied: ack,
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
fireEvent.click(screen.getByRole('button', { name: /switch account/i }));
|
|
145
|
+
|
|
146
|
+
expect(ack).toHaveBeenCalled();
|
|
147
|
+
expect(mockLocation.href).toBe('/auth/connect?callbackUrl=%2Fadmin%2Fusers');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionExpiredModal Tests
|
|
3
|
+
*
|
|
4
|
+
* The modal renders content when `sessionExpiredAt` is non-null on the
|
|
5
|
+
* KnowledgeBaseSession context, and is hidden otherwise. Button clicks
|
|
6
|
+
* call `acknowledgeSessionExpired()` and navigate the window.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { screen, fireEvent } from '@testing-library/react';
|
|
12
|
+
import '@testing-library/jest-dom';
|
|
13
|
+
import {
|
|
14
|
+
renderWithProviders,
|
|
15
|
+
createMockKnowledgeBaseSession,
|
|
16
|
+
} from '../../../test-utils';
|
|
17
|
+
import { SessionExpiredModal } from '../SessionExpiredModal';
|
|
18
|
+
|
|
19
|
+
vi.mock('@headlessui/react', () => ({
|
|
20
|
+
Dialog: ({ children, ...props }: any) => <div role="dialog" {...props}>{typeof children === 'function' ? children({ open: true }) : children}</div>,
|
|
21
|
+
DialogPanel: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
22
|
+
DialogTitle: ({ children, ...props }: any) => <h2 {...props}>{children}</h2>,
|
|
23
|
+
Transition: ({ show, children }: any) => show ? <>{children}</> : null,
|
|
24
|
+
TransitionChild: ({ children }: any) => <>{children}</>,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const originalLocation = window.location;
|
|
28
|
+
let mockLocation: { href: string; pathname: string };
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
mockLocation = { href: '', pathname: '/know/discover' };
|
|
32
|
+
Object.defineProperty(window, 'location', {
|
|
33
|
+
value: mockLocation,
|
|
34
|
+
writable: true,
|
|
35
|
+
configurable: true,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
Object.defineProperty(window, 'location', {
|
|
41
|
+
value: originalLocation,
|
|
42
|
+
writable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('SessionExpiredModal', () => {
|
|
48
|
+
describe('initial render', () => {
|
|
49
|
+
it('does not render modal content when sessionExpiredAt is null', () => {
|
|
50
|
+
renderWithProviders(<SessionExpiredModal />, {
|
|
51
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
52
|
+
sessionExpiredAt: null,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
expect(screen.queryByText('Session Expired')).not.toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('when sessionExpiredAt is set', () => {
|
|
60
|
+
it('renders the modal with default message', () => {
|
|
61
|
+
renderWithProviders(<SessionExpiredModal />, {
|
|
62
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
63
|
+
sessionExpiredAt: Date.now(),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText('Session Expired')).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByRole('button', { name: /sign in again/i })).toBeInTheDocument();
|
|
69
|
+
expect(screen.getByRole('button', { name: /go to home/i })).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders the custom message from sessionExpiredMessage', () => {
|
|
73
|
+
renderWithProviders(<SessionExpiredModal />, {
|
|
74
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
75
|
+
sessionExpiredAt: Date.now(),
|
|
76
|
+
sessionExpiredMessage: 'Your token expired at 5pm',
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
expect(screen.getByText(/your token expired at 5pm/i)).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('button actions', () => {
|
|
84
|
+
it('calls acknowledgeSessionExpired and navigates to /auth/connect on Sign In Again', () => {
|
|
85
|
+
const ack = vi.fn();
|
|
86
|
+
mockLocation.pathname = '/know/discover';
|
|
87
|
+
renderWithProviders(<SessionExpiredModal />, {
|
|
88
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
89
|
+
sessionExpiredAt: Date.now(),
|
|
90
|
+
acknowledgeSessionExpired: ack,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
fireEvent.click(screen.getByRole('button', { name: /sign in again/i }));
|
|
95
|
+
|
|
96
|
+
expect(ack).toHaveBeenCalled();
|
|
97
|
+
expect(mockLocation.href).toBe('/auth/connect?callbackUrl=%2Fknow%2Fdiscover');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('calls acknowledgeSessionExpired and navigates to / on Go to Home', () => {
|
|
101
|
+
const ack = vi.fn();
|
|
102
|
+
renderWithProviders(<SessionExpiredModal />, {
|
|
103
|
+
knowledgeBaseSession: createMockKnowledgeBaseSession({
|
|
104
|
+
sessionExpiredAt: Date.now(),
|
|
105
|
+
acknowledgeSessionExpired: ack,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
fireEvent.click(screen.getByRole('button', { name: /go to home/i }));
|
|
110
|
+
|
|
111
|
+
expect(ack).toHaveBeenCalled();
|
|
112
|
+
expect(mockLocation.href).toBe('/');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -5,7 +5,7 @@ import { useTranslations } from '../../contexts/TranslationContext';
|
|
|
5
5
|
import type { RouteBuilder, LinkComponentProps } from '../../contexts/RoutingContext';
|
|
6
6
|
import { useResources } from '../../lib/api-hooks';
|
|
7
7
|
import type { ResourceId } from '@semiont/core';
|
|
8
|
-
import { getAnnotationUriFromEvent } from '@semiont/core';
|
|
8
|
+
import { getAnnotationUriFromEvent, type StoredEventLike } from '@semiont/core';
|
|
9
9
|
import { HistoryEvent } from './HistoryEvent';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
@@ -36,11 +36,10 @@ export function AnnotationHistory({ rUri, hoveredAnnotationId, onEventHover, onE
|
|
|
36
36
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
37
37
|
|
|
38
38
|
// Sort events by oldest first (most recent at bottom)
|
|
39
|
-
// Filter out
|
|
40
|
-
const events = !eventsData?.events ? [] :
|
|
39
|
+
// Filter out job events - they're represented by mark:body-updated events instead
|
|
40
|
+
const events: StoredEventLike[] = !eventsData?.events ? [] : (eventsData.events as StoredEventLike[])
|
|
41
41
|
.filter((e) => {
|
|
42
|
-
|
|
43
|
-
return eventType !== 'job.started' && eventType !== 'job.progress' && eventType !== 'job.completed';
|
|
42
|
+
return e.type !== 'job:started' && e.type !== 'job:progress' && e.type !== 'job:completed';
|
|
44
43
|
})
|
|
45
44
|
.sort((a, b) => a.metadata.sequenceNumber - b.metadata.sequenceNumber);
|
|
46
45
|
|
|
@@ -110,7 +109,7 @@ export function AnnotationHistory({ rUri, hoveredAnnotationId, onEventHover, onE
|
|
|
110
109
|
|
|
111
110
|
return (
|
|
112
111
|
<HistoryEvent
|
|
113
|
-
key={stored.
|
|
112
|
+
key={stored.id}
|
|
114
113
|
event={stored}
|
|
115
114
|
annotations={annotations}
|
|
116
115
|
allEvents={events}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useRef, useCallback, useEffect } from 'react';
|
|
4
4
|
import type { RouteBuilder, LinkComponentProps } from '../../contexts/RoutingContext';
|
|
5
|
-
import type { StoredEventLike,
|
|
5
|
+
import type { StoredEventLike, PersistedEventType } from '@semiont/core';
|
|
6
6
|
import { getAnnotationUriFromEvent } from '@semiont/core';
|
|
7
7
|
import {
|
|
8
8
|
formatEventType,
|
|
9
9
|
getEventEmoji,
|
|
10
10
|
formatRelativeTime,
|
|
11
|
+
formatUserId,
|
|
11
12
|
getEventDisplayContent,
|
|
12
13
|
getEventEntityTypes,
|
|
13
14
|
getResourceCreationDetails,
|
|
@@ -85,7 +86,7 @@ export function HistoryEvent({
|
|
|
85
86
|
const eventWrapperProps = annotationUri ? {
|
|
86
87
|
type: 'button' as const,
|
|
87
88
|
onClick: () => onEventClick?.(annotationUri),
|
|
88
|
-
'aria-label': t('viewAnnotation', { content: displayContent?.exact || formatEventType(event.
|
|
89
|
+
'aria-label': t('viewAnnotation', { content: displayContent?.exact || formatEventType(event.type as PersistedEventType, t) }),
|
|
89
90
|
className: 'semiont-history-event',
|
|
90
91
|
'data-related': isRelated ? 'true' : 'false',
|
|
91
92
|
'data-interactive': 'true'
|
|
@@ -109,7 +110,7 @@ export function HistoryEvent({
|
|
|
109
110
|
onMouseEnter={handleEmojiMouseEnter}
|
|
110
111
|
onMouseLeave={handleEmojiMouseLeave}
|
|
111
112
|
>
|
|
112
|
-
{getEventEmoji(event.
|
|
113
|
+
{getEventEmoji(event.type as PersistedEventType, event.payload)}
|
|
113
114
|
</span>
|
|
114
115
|
{displayContent ? (
|
|
115
116
|
displayContent.isTag ? (
|
|
@@ -127,16 +128,16 @@ export function HistoryEvent({
|
|
|
127
128
|
)
|
|
128
129
|
) : (
|
|
129
130
|
<span className="semiont-history-event__text">
|
|
130
|
-
{formatEventType(event.
|
|
131
|
+
{formatEventType(event.type as PersistedEventType, t, event.payload)}
|
|
131
132
|
</span>
|
|
132
133
|
)}
|
|
133
|
-
{event.
|
|
134
|
+
{event.userId && (
|
|
134
135
|
<span className="semiont-history-event__user">
|
|
135
|
-
{event.
|
|
136
|
+
{formatUserId(event.userId)}
|
|
136
137
|
</span>
|
|
137
138
|
)}
|
|
138
139
|
<span className="semiont-history-event__timestamp">
|
|
139
|
-
{formatRelativeTime(event.
|
|
140
|
+
{formatRelativeTime(event.timestamp, t)}
|
|
140
141
|
</span>
|
|
141
142
|
</div>
|
|
142
143
|
{entityTypes.length > 0 && (
|
|
@@ -155,7 +156,7 @@ export function HistoryEvent({
|
|
|
155
156
|
{creationDetails && (
|
|
156
157
|
<div className="semiont-history-event__details">
|
|
157
158
|
<span className="semiont-history-event__detail">
|
|
158
|
-
{t('user')}: <span className="semiont-history-event__detail-value">{creationDetails.userId}</span>
|
|
159
|
+
{t('user')}: <span className="semiont-history-event__detail-value">{creationDetails.userId ? formatUserId(creationDetails.userId) : ''}</span>
|
|
159
160
|
</span>
|
|
160
161
|
<span className="semiont-history-event__detail">
|
|
161
162
|
{t('method')}: <span className="semiont-history-event__detail-value semiont-history-event__detail-value--uppercase">{creationDetails.method}</span>
|
|
@@ -40,8 +40,8 @@ vi.mock('../../../lib/api-hooks', () => ({
|
|
|
40
40
|
|
|
41
41
|
// Mock HistoryEvent to avoid deep rendering and mocking all its dependencies
|
|
42
42
|
const MockHistoryEvent = vi.fn(({ event }: any) => (
|
|
43
|
-
<div data-testid={`history-event-${event.
|
|
44
|
-
{event.
|
|
43
|
+
<div data-testid={`history-event-${event.id}`}>
|
|
44
|
+
{event.type}
|
|
45
45
|
</div>
|
|
46
46
|
));
|
|
47
47
|
|
|
@@ -54,23 +54,22 @@ const mockGetAnnotationUri = getAnnotationUriFromEvent as ReturnType<typeof vi.f
|
|
|
54
54
|
|
|
55
55
|
const testRId = 'res-1' as ResourceId;
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
/** Returns flat StoredEventResponse shape (matches API response) */
|
|
58
|
+
function makeStoredEvent(id: string, type: string, seq: number, overrides: Record<string, any> = {}): any {
|
|
58
59
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
...overrides,
|
|
68
|
-
},
|
|
60
|
+
id,
|
|
61
|
+
type,
|
|
62
|
+
timestamp: '2026-03-06T12:00:00Z',
|
|
63
|
+
resourceId: 'res-1',
|
|
64
|
+
userId: 'user-1',
|
|
65
|
+
version: 1,
|
|
66
|
+
payload: {},
|
|
67
|
+
...overrides,
|
|
69
68
|
metadata: {
|
|
70
69
|
sequenceNumber: seq,
|
|
71
|
-
|
|
70
|
+
streamPosition: 0,
|
|
72
71
|
},
|
|
73
|
-
}
|
|
72
|
+
};
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
const MockLink = ({ href, children, ...props }: any) => <a href={href} {...props}>{children}</a>;
|
|
@@ -130,9 +129,9 @@ describe('AnnotationHistory', () => {
|
|
|
130
129
|
|
|
131
130
|
it('renders events sorted by sequence number', () => {
|
|
132
131
|
const events = [
|
|
133
|
-
makeStoredEvent('evt-3', '
|
|
134
|
-
makeStoredEvent('evt-1', '
|
|
135
|
-
makeStoredEvent('evt-2', '
|
|
132
|
+
makeStoredEvent('evt-3', 'mark:added', 3),
|
|
133
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
134
|
+
makeStoredEvent('evt-2', 'mark:added', 2),
|
|
136
135
|
];
|
|
137
136
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
138
137
|
|
|
@@ -152,18 +151,18 @@ describe('AnnotationHistory', () => {
|
|
|
152
151
|
|
|
153
152
|
// Verify HistoryEvent was called with events in sequence order
|
|
154
153
|
const calls = MockHistoryEvent.mock.calls;
|
|
155
|
-
expect(calls[0][0].event.
|
|
156
|
-
expect(calls[1][0].event.
|
|
157
|
-
expect(calls[2][0].event.
|
|
154
|
+
expect(calls[0][0].event.id).toBe('evt-1'); // .event is the React prop name
|
|
155
|
+
expect(calls[1][0].event.id).toBe('evt-2');
|
|
156
|
+
expect(calls[2][0].event.id).toBe('evt-3');
|
|
158
157
|
});
|
|
159
158
|
|
|
160
159
|
it('filters out job events', () => {
|
|
161
160
|
const events = [
|
|
162
|
-
makeStoredEvent('evt-1', '
|
|
163
|
-
makeStoredEvent('evt-2', 'job
|
|
164
|
-
makeStoredEvent('evt-3', 'job
|
|
165
|
-
makeStoredEvent('evt-4', 'job
|
|
166
|
-
makeStoredEvent('evt-5', '
|
|
161
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
162
|
+
makeStoredEvent('evt-2', 'job:started', 2),
|
|
163
|
+
makeStoredEvent('evt-3', 'job:progress', 3),
|
|
164
|
+
makeStoredEvent('evt-4', 'job:completed', 4),
|
|
165
|
+
makeStoredEvent('evt-5', 'mark:added', 5),
|
|
167
166
|
];
|
|
168
167
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
169
168
|
|
|
@@ -188,7 +187,7 @@ describe('AnnotationHistory', () => {
|
|
|
188
187
|
mockGetAnnotationUri.mockReturnValue(annotationUri);
|
|
189
188
|
|
|
190
189
|
const events = [
|
|
191
|
-
makeStoredEvent('evt-1', '
|
|
190
|
+
makeStoredEvent('evt-1', 'mark:added', 1),
|
|
192
191
|
];
|
|
193
192
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
194
193
|
|
|
@@ -209,7 +208,7 @@ describe('AnnotationHistory', () => {
|
|
|
209
208
|
mockGetAnnotationUri.mockReturnValue('http://localhost/annotations/ann-other');
|
|
210
209
|
|
|
211
210
|
const events = [
|
|
212
|
-
makeStoredEvent('evt-1', '
|
|
211
|
+
makeStoredEvent('evt-1', 'mark:added', 1),
|
|
213
212
|
];
|
|
214
213
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
215
214
|
|
|
@@ -228,7 +227,7 @@ describe('AnnotationHistory', () => {
|
|
|
228
227
|
|
|
229
228
|
it('passes isRelated=false when no hoveredAnnotationId', () => {
|
|
230
229
|
const events = [
|
|
231
|
-
makeStoredEvent('evt-1', '
|
|
230
|
+
makeStoredEvent('evt-1', 'mark:added', 1),
|
|
232
231
|
];
|
|
233
232
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
234
233
|
|
|
@@ -249,7 +248,7 @@ describe('AnnotationHistory', () => {
|
|
|
249
248
|
const onEventHover = vi.fn();
|
|
250
249
|
|
|
251
250
|
const events = [
|
|
252
|
-
makeStoredEvent('evt-1', '
|
|
251
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
253
252
|
];
|
|
254
253
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
255
254
|
|
|
@@ -270,7 +269,7 @@ describe('AnnotationHistory', () => {
|
|
|
270
269
|
|
|
271
270
|
it('does not pass onEventClick/onEventHover when not provided', () => {
|
|
272
271
|
const events = [
|
|
273
|
-
makeStoredEvent('evt-1', '
|
|
272
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
274
273
|
];
|
|
275
274
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
276
275
|
|
|
@@ -292,7 +291,7 @@ describe('AnnotationHistory', () => {
|
|
|
292
291
|
mockAnnotationsUseQuery.mockReturnValue({ data: { annotations: mockAnnotations } });
|
|
293
292
|
|
|
294
293
|
const events = [
|
|
295
|
-
makeStoredEvent('evt-1', '
|
|
294
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
296
295
|
];
|
|
297
296
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
298
297
|
|
|
@@ -312,7 +311,7 @@ describe('AnnotationHistory', () => {
|
|
|
312
311
|
mockAnnotationsUseQuery.mockReturnValue({ data: undefined });
|
|
313
312
|
|
|
314
313
|
const events = [
|
|
315
|
-
makeStoredEvent('evt-1', '
|
|
314
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
316
315
|
];
|
|
317
316
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
318
317
|
|
|
@@ -330,7 +329,7 @@ describe('AnnotationHistory', () => {
|
|
|
330
329
|
|
|
331
330
|
it('renders history panel structure with title and list', () => {
|
|
332
331
|
const events = [
|
|
333
|
-
makeStoredEvent('evt-1', '
|
|
332
|
+
makeStoredEvent('evt-1', 'yield:created', 1),
|
|
334
333
|
];
|
|
335
334
|
mockEventsUseQuery.mockReturnValue({ data: { events }, isLoading: false, isError: false });
|
|
336
335
|
|
|
@@ -28,6 +28,7 @@ vi.mock('../event-formatting', () => ({
|
|
|
28
28
|
formatEventType: vi.fn((_type: string, t: (key: string) => string) => t('resourceCreated')),
|
|
29
29
|
getEventEmoji: vi.fn(() => '\u{1F4C4}'),
|
|
30
30
|
formatRelativeTime: vi.fn(() => '2 minutes ago'),
|
|
31
|
+
formatUserId: vi.fn((id: string) => id),
|
|
31
32
|
getEventDisplayContent: vi.fn(() => mockDisplayContent),
|
|
32
33
|
getEventEntityTypes: vi.fn(() => mockEntityTypes),
|
|
33
34
|
getResourceCreationDetails: vi.fn(() => mockCreationDetails),
|
|
@@ -49,23 +50,21 @@ const mockGetEventEntityTypes = getEventEntityTypes as ReturnType<typeof vi.fn>;
|
|
|
49
50
|
const mockGetResourceCreationDetails = getResourceCreationDetails as ReturnType<typeof vi.fn>;
|
|
50
51
|
const mockFormatEventType = formatEventType as ReturnType<typeof vi.fn>;
|
|
51
52
|
|
|
52
|
-
function makeStoredEvent(overrides:
|
|
53
|
+
function makeStoredEvent(overrides: Record<string, any> = {}): any {
|
|
53
54
|
return {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
...overrides,
|
|
63
|
-
},
|
|
55
|
+
id: 'evt-1',
|
|
56
|
+
type: 'yield:created',
|
|
57
|
+
timestamp: '2026-03-06T12:00:00Z',
|
|
58
|
+
resourceId: 'res-1',
|
|
59
|
+
userId: 'user-1',
|
|
60
|
+
version: 1,
|
|
61
|
+
payload: { name: 'Test', format: 'text/plain', contentChecksum: 'abc', creationMethod: 'upload' },
|
|
62
|
+
...overrides,
|
|
64
63
|
metadata: {
|
|
65
64
|
sequenceNumber: 1,
|
|
66
|
-
|
|
65
|
+
streamPosition: 0,
|
|
67
66
|
},
|
|
68
|
-
}
|
|
67
|
+
};
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
const mockT = (key: string) => key;
|
|
@@ -139,7 +138,7 @@ describe('HistoryEvent', () => {
|
|
|
139
138
|
|
|
140
139
|
it('renders as button when annotationUri exists', () => {
|
|
141
140
|
mockGetAnnotationUri.mockReturnValue('http://localhost/annotations/ann-1');
|
|
142
|
-
const event = makeStoredEvent({ type: '
|
|
141
|
+
const event = makeStoredEvent({ type: 'mark:added' } as any);
|
|
143
142
|
const { container } = renderWithProviders(
|
|
144
143
|
<HistoryEvent
|
|
145
144
|
event={event}
|
|
@@ -160,7 +159,7 @@ describe('HistoryEvent', () => {
|
|
|
160
159
|
const annotationUri = 'http://localhost/annotations/ann-1';
|
|
161
160
|
mockGetAnnotationUri.mockReturnValue(annotationUri);
|
|
162
161
|
const onEventClick = vi.fn();
|
|
163
|
-
const event = makeStoredEvent({ type: '
|
|
162
|
+
const event = makeStoredEvent({ type: 'mark:added' } as any);
|
|
164
163
|
|
|
165
164
|
renderWithProviders(
|
|
166
165
|
<HistoryEvent
|
|
@@ -364,7 +363,7 @@ describe('HistoryEvent', () => {
|
|
|
364
363
|
const annotationUri = 'http://localhost/annotations/ann-1';
|
|
365
364
|
mockGetAnnotationUri.mockReturnValue(annotationUri);
|
|
366
365
|
const onEventRef = vi.fn();
|
|
367
|
-
const event = makeStoredEvent({ type: '
|
|
366
|
+
const event = makeStoredEvent({ type: 'mark:added' } as any);
|
|
368
367
|
|
|
369
368
|
renderWithProviders(
|
|
370
369
|
<HistoryEvent
|
|
@@ -387,7 +386,7 @@ describe('HistoryEvent', () => {
|
|
|
387
386
|
const annotationUri = 'http://localhost/annotations/ann-1';
|
|
388
387
|
mockGetAnnotationUri.mockReturnValue(annotationUri);
|
|
389
388
|
const onEventHover = vi.fn();
|
|
390
|
-
const event = makeStoredEvent({ type: '
|
|
389
|
+
const event = makeStoredEvent({ type: 'mark:added' } as any);
|
|
391
390
|
|
|
392
391
|
const { container } = renderWithProviders(
|
|
393
392
|
<HistoryEvent
|
|
@@ -420,7 +419,7 @@ describe('HistoryEvent', () => {
|
|
|
420
419
|
const annotationUri = 'http://localhost/annotations/ann-1';
|
|
421
420
|
mockGetAnnotationUri.mockReturnValue(annotationUri);
|
|
422
421
|
const onEventHover = vi.fn();
|
|
423
|
-
const event = makeStoredEvent({ type: '
|
|
422
|
+
const event = makeStoredEvent({ type: 'mark:added' } as any);
|
|
424
423
|
|
|
425
424
|
const { container } = renderWithProviders(
|
|
426
425
|
<HistoryEvent
|
|
@@ -454,7 +453,7 @@ describe('HistoryEvent', () => {
|
|
|
454
453
|
|
|
455
454
|
it('sets data-interactive on button wrapper', () => {
|
|
456
455
|
mockGetAnnotationUri.mockReturnValue('http://localhost/annotations/ann-1');
|
|
457
|
-
const event = makeStoredEvent({ type: '
|
|
456
|
+
const event = makeStoredEvent({ type: 'mark:added' } as any);
|
|
458
457
|
const { container } = renderWithProviders(
|
|
459
458
|
<HistoryEvent
|
|
460
459
|
event={event}
|