@jmruthers/pace-core 0.5.118 → 0.5.119
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/dist/{DataTable-ZOAKQ3SU.js → DataTable-BQYGKVHR.js} +6 -6
- package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
- package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
- package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
- package/dist/chunk-BHWIUEYH.js.map +1 -0
- package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
- package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
- package/dist/{chunk-2LM4QQGH.js → chunk-F7COHU5B.js} +8 -8
- package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
- package/dist/{chunk-UKZWNQMB.js → chunk-NP5VABFV.js} +4 -4
- package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
- package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
- package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
- package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
- package/dist/components.js +8 -8
- package/dist/hooks.js +7 -7
- package/dist/index.js +11 -11
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +544 -9
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
- package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
- package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
- package/src/hooks/__tests__/index.unit.test.ts +223 -0
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
- package/src/hooks/__tests__/useEvents.unit.test.ts +249 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
- package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +608 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +372 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +431 -30
- package/src/hooks/useSecureDataAccess.test.ts +1 -0
- package/src/rbac/audit-enhanced.ts +339 -0
- package/src/services/EventService.ts +1 -0
- package/src/services/__tests__/AuthService.test.ts +473 -0
- package/src/services/__tests__/EventService.test.ts +390 -0
- package/src/services/__tests__/InactivityService.test.ts +217 -0
- package/src/services/__tests__/OrganisationService.test.ts +371 -0
- package/dist/chunk-KA3PSVNV.js.map +0 -1
- package/src/components/DataTable/utils/debugTools.ts +0 -609
- package/src/rbac/testing/index.tsx +0 -340
- /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-BQYGKVHR.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
- /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
- /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
- /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
- /package/dist/{chunk-2LM4QQGH.js.map → chunk-F7COHU5B.js.map} +0 -0
- /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
- /package/dist/{chunk-UKZWNQMB.js.map → chunk-NP5VABFV.js.map} +0 -0
- /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
- /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
- /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
- /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
|
@@ -1,62 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file useToast Hook Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/__tests__/useToast
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for the useToast hook covering all critical functionality.
|
|
8
|
+
*/
|
|
1
9
|
|
|
2
|
-
import { renderHook, act } from '@testing-library/react';
|
|
3
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
-
import { useToast, reset } from '../useToast';
|
|
10
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
11
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
12
|
+
import { useToast, reset, toast } from '../useToast';
|
|
13
|
+
|
|
14
|
+
const TOAST_REMOVE_DELAY = 1000;
|
|
15
|
+
const DEFAULT_TOAST_DURATION = 10000;
|
|
5
16
|
|
|
6
17
|
describe('useToast', () => {
|
|
7
18
|
beforeEach(() => {
|
|
19
|
+
vi.useFakeTimers();
|
|
8
20
|
reset();
|
|
9
21
|
});
|
|
10
22
|
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.clearAllTimers();
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Toast Creation', () => {
|
|
29
|
+
it('adds a toast with title and description', () => {
|
|
30
|
+
const { result } = renderHook(() => useToast());
|
|
13
31
|
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
act(() => {
|
|
33
|
+
result.current.toast({
|
|
34
|
+
title: 'Test Toast',
|
|
35
|
+
description: 'This is a test toast',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(result.current.toasts).toHaveLength(1);
|
|
40
|
+
expect(result.current.toasts[0]).toMatchObject({
|
|
16
41
|
title: 'Test Toast',
|
|
17
42
|
description: 'This is a test toast',
|
|
43
|
+
open: true,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('adds a toast with default duration', () => {
|
|
48
|
+
const { result } = renderHook(() => useToast());
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
result.current.toast({
|
|
52
|
+
title: 'Test Toast',
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(result.current.toasts[0].duration).toBe(DEFAULT_TOAST_DURATION);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('ignores custom duration and uses default', () => {
|
|
60
|
+
const { result } = renderHook(() => useToast());
|
|
61
|
+
|
|
62
|
+
act(() => {
|
|
63
|
+
// Even if duration is provided, it should be ignored
|
|
64
|
+
result.current.toast({
|
|
65
|
+
title: 'Test Toast',
|
|
66
|
+
// @ts-expect-error - duration should not be in props
|
|
67
|
+
duration: 5000,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(result.current.toasts[0].duration).toBe(DEFAULT_TOAST_DURATION);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('adds toast with variant', () => {
|
|
75
|
+
const { result } = renderHook(() => useToast());
|
|
76
|
+
|
|
77
|
+
act(() => {
|
|
78
|
+
result.current.toast({
|
|
79
|
+
title: 'Success Toast',
|
|
80
|
+
variant: 'success',
|
|
81
|
+
});
|
|
18
82
|
});
|
|
83
|
+
|
|
84
|
+
expect(result.current.toasts[0].variant).toBe('success');
|
|
19
85
|
});
|
|
20
86
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
87
|
+
it('adds toast with action', () => {
|
|
88
|
+
const { result } = renderHook(() => useToast());
|
|
89
|
+
const actionButton = <button>Action</button>;
|
|
90
|
+
|
|
91
|
+
act(() => {
|
|
92
|
+
result.current.toast({
|
|
93
|
+
title: 'Test Toast',
|
|
94
|
+
action: actionButton,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result.current.toasts[0].action).toBe(actionButton);
|
|
25
99
|
});
|
|
26
100
|
});
|
|
27
101
|
|
|
28
|
-
|
|
29
|
-
|
|
102
|
+
describe('Toast Dismissal', () => {
|
|
103
|
+
it('dismisses a toast using dismiss function', () => {
|
|
104
|
+
const { result } = renderHook(() => useToast());
|
|
30
105
|
|
|
31
|
-
|
|
106
|
+
let dismissFn: (() => void) | undefined;
|
|
32
107
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
108
|
+
act(() => {
|
|
109
|
+
const response = result.current.toast({
|
|
110
|
+
title: 'Test Toast',
|
|
111
|
+
});
|
|
112
|
+
dismissFn = response.dismiss;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(result.current.toasts).toHaveLength(1);
|
|
116
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
dismissFn?.();
|
|
36
120
|
});
|
|
37
|
-
|
|
121
|
+
|
|
122
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
38
123
|
});
|
|
39
124
|
|
|
40
|
-
|
|
125
|
+
it('dismisses a toast using dismiss method with toast ID', () => {
|
|
126
|
+
const { result } = renderHook(() => useToast());
|
|
127
|
+
|
|
128
|
+
let toastId: string | undefined;
|
|
41
129
|
|
|
42
|
-
|
|
43
|
-
|
|
130
|
+
act(() => {
|
|
131
|
+
const response = result.current.toast({
|
|
132
|
+
title: 'Test Toast',
|
|
133
|
+
});
|
|
134
|
+
toastId = response.id;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(result.current.toasts[0].open).toBe(true);
|
|
138
|
+
|
|
139
|
+
act(() => {
|
|
140
|
+
result.current.dismiss(toastId);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
44
144
|
});
|
|
45
145
|
|
|
46
|
-
|
|
47
|
-
|
|
146
|
+
it('dismisses all toasts when no ID provided', () => {
|
|
147
|
+
const { result } = renderHook(() => useToast());
|
|
48
148
|
|
|
49
|
-
|
|
50
|
-
|
|
149
|
+
act(() => {
|
|
150
|
+
result.current.toast({ title: 'Toast 1' });
|
|
151
|
+
result.current.toast({ title: 'Toast 2' });
|
|
152
|
+
result.current.toast({ title: 'Toast 3' });
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(result.current.toasts).toHaveLength(3);
|
|
156
|
+
expect(result.current.toasts.every(t => t.open)).toBe(true);
|
|
157
|
+
|
|
158
|
+
act(() => {
|
|
159
|
+
result.current.dismiss();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(result.current.toasts.every(t => !t.open)).toBe(true);
|
|
163
|
+
});
|
|
51
164
|
|
|
52
|
-
|
|
53
|
-
|
|
165
|
+
it('calls onOpenChange handler when open changes', () => {
|
|
166
|
+
const { result } = renderHook(() => useToast());
|
|
167
|
+
const onOpenChange = vi.fn();
|
|
168
|
+
|
|
169
|
+
act(() => {
|
|
54
170
|
result.current.toast({
|
|
55
|
-
title:
|
|
171
|
+
title: 'Test Toast',
|
|
172
|
+
onOpenChange,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
act(() => {
|
|
177
|
+
result.current.toasts[0].onOpenChange?.(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
181
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('removes toast after delay when dismissed', async () => {
|
|
185
|
+
const { result } = renderHook(() => useToast());
|
|
186
|
+
|
|
187
|
+
let toastId: string | undefined;
|
|
188
|
+
|
|
189
|
+
act(() => {
|
|
190
|
+
const response = result.current.toast({
|
|
191
|
+
title: 'Test Toast',
|
|
192
|
+
});
|
|
193
|
+
toastId = response.id;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(result.current.toasts).toHaveLength(1);
|
|
197
|
+
|
|
198
|
+
act(() => {
|
|
199
|
+
result.current.dismiss(toastId);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
203
|
+
expect(result.current.toasts).toHaveLength(1); // Still in array
|
|
204
|
+
|
|
205
|
+
// Advance time past removal delay
|
|
206
|
+
act(() => {
|
|
207
|
+
vi.advanceTimersByTime(TOAST_REMOVE_DELAY);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await waitFor(() => {
|
|
211
|
+
expect(result.current.toasts).toHaveLength(0);
|
|
212
|
+
}, { timeout: 100 });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('does not remove toast if already in remove queue', () => {
|
|
216
|
+
const { result } = renderHook(() => useToast());
|
|
217
|
+
|
|
218
|
+
let toastId: string | undefined;
|
|
219
|
+
|
|
220
|
+
act(() => {
|
|
221
|
+
const response = result.current.toast({
|
|
222
|
+
title: 'Test Toast',
|
|
223
|
+
});
|
|
224
|
+
toastId = response.id;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
act(() => {
|
|
228
|
+
result.current.dismiss(toastId);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Try to dismiss again
|
|
232
|
+
act(() => {
|
|
233
|
+
result.current.dismiss(toastId);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Should still only have one toast
|
|
237
|
+
expect(result.current.toasts).toHaveLength(1);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('Toast Updates', () => {
|
|
242
|
+
it('updates a toast with new properties', () => {
|
|
243
|
+
const { result } = renderHook(() => useToast());
|
|
244
|
+
|
|
245
|
+
let toastId: string | undefined;
|
|
246
|
+
let updateFn: ((props: any) => void) | undefined;
|
|
247
|
+
|
|
248
|
+
act(() => {
|
|
249
|
+
const response = result.current.toast({
|
|
250
|
+
title: 'Original Title',
|
|
251
|
+
description: 'Original Description',
|
|
252
|
+
});
|
|
253
|
+
toastId = response.id;
|
|
254
|
+
updateFn = response.update;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(result.current.toasts[0].title).toBe('Original Title');
|
|
258
|
+
expect(result.current.toasts[0].description).toBe('Original Description');
|
|
259
|
+
|
|
260
|
+
act(() => {
|
|
261
|
+
updateFn?.({
|
|
262
|
+
title: 'Updated Title',
|
|
263
|
+
description: 'Updated Description',
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(result.current.toasts[0].title).toBe('Updated Title');
|
|
268
|
+
expect(result.current.toasts[0].description).toBe('Updated Description');
|
|
269
|
+
expect(result.current.toasts[0].id).toBe(toastId);
|
|
270
|
+
expect(result.current.toasts[0].duration).toBe(DEFAULT_TOAST_DURATION);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('updates toast variant', () => {
|
|
274
|
+
const { result } = renderHook(() => useToast());
|
|
275
|
+
|
|
276
|
+
let updateFn: ((props: any) => void) | undefined;
|
|
277
|
+
|
|
278
|
+
act(() => {
|
|
279
|
+
const response = result.current.toast({
|
|
280
|
+
title: 'Test Toast',
|
|
281
|
+
variant: 'default',
|
|
282
|
+
});
|
|
283
|
+
updateFn = response.update;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result.current.toasts[0].variant).toBe('default');
|
|
287
|
+
|
|
288
|
+
act(() => {
|
|
289
|
+
updateFn?.({
|
|
290
|
+
variant: 'destructive',
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(result.current.toasts[0].variant).toBe('destructive');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('Toast Limits', () => {
|
|
299
|
+
it('limits toasts to maximum of 5', () => {
|
|
300
|
+
const { result } = renderHook(() => useToast());
|
|
301
|
+
|
|
302
|
+
act(() => {
|
|
303
|
+
for (let i = 0; i < 10; i++) {
|
|
304
|
+
result.current.toast({
|
|
305
|
+
title: `Toast ${i}`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(result.current.toasts.length).toBeLessThanOrEqual(5);
|
|
311
|
+
expect(result.current.toasts.length).toBe(5);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('keeps newest toasts when limit exceeded', () => {
|
|
315
|
+
const { result } = renderHook(() => useToast());
|
|
316
|
+
|
|
317
|
+
act(() => {
|
|
318
|
+
for (let i = 0; i < 7; i++) {
|
|
319
|
+
result.current.toast({
|
|
320
|
+
title: `Toast ${i}`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
expect(result.current.toasts.length).toBe(5);
|
|
326
|
+
// Should keep the last 5 (toasts 2-6)
|
|
327
|
+
expect(result.current.toasts[0].title).toBe('Toast 6');
|
|
328
|
+
expect(result.current.toasts[4].title).toBe('Toast 2');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('Direct Toast Function', () => {
|
|
333
|
+
it('creates toast using direct toast function', () => {
|
|
334
|
+
const { result } = renderHook(() => useToast());
|
|
335
|
+
|
|
336
|
+
act(() => {
|
|
337
|
+
toast({
|
|
338
|
+
title: 'Direct Toast',
|
|
56
339
|
});
|
|
57
|
-
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(result.current.toasts).toHaveLength(1);
|
|
343
|
+
expect(result.current.toasts[0].title).toBe('Direct Toast');
|
|
58
344
|
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('Reset Function', () => {
|
|
348
|
+
it('resets all toasts and clears timeouts', () => {
|
|
349
|
+
const { result } = renderHook(() => useToast());
|
|
59
350
|
|
|
60
|
-
|
|
351
|
+
act(() => {
|
|
352
|
+
result.current.toast({ title: 'Toast 1' });
|
|
353
|
+
result.current.toast({ title: 'Toast 2' });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(result.current.toasts).toHaveLength(2);
|
|
357
|
+
|
|
358
|
+
act(() => {
|
|
359
|
+
reset();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Create new hook instance to verify reset cleared global state
|
|
363
|
+
const { result: result2 } = renderHook(() => useToast());
|
|
364
|
+
expect(result2.current.toasts).toHaveLength(0);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe('Multiple Hook Instances', () => {
|
|
369
|
+
it('shares state between multiple hook instances', () => {
|
|
370
|
+
const { result: result1 } = renderHook(() => useToast());
|
|
371
|
+
const { result: result2 } = renderHook(() => useToast());
|
|
372
|
+
|
|
373
|
+
act(() => {
|
|
374
|
+
result1.current.toast({
|
|
375
|
+
title: 'Toast from instance 1',
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(result1.current.toasts).toHaveLength(1);
|
|
380
|
+
expect(result2.current.toasts).toHaveLength(1);
|
|
381
|
+
expect(result2.current.toasts[0].title).toBe('Toast from instance 1');
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe('Remove Queue', () => {
|
|
386
|
+
it('removes toast by ID after delay', async () => {
|
|
387
|
+
const { result } = renderHook(() => useToast());
|
|
388
|
+
|
|
389
|
+
let toastId: string | undefined;
|
|
390
|
+
|
|
391
|
+
act(() => {
|
|
392
|
+
const response = result.current.toast({
|
|
393
|
+
title: 'Test Toast',
|
|
394
|
+
});
|
|
395
|
+
toastId = response.id;
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(result.current.toasts).toHaveLength(1);
|
|
399
|
+
|
|
400
|
+
act(() => {
|
|
401
|
+
result.current.dismiss(toastId);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(result.current.toasts[0].open).toBe(false);
|
|
405
|
+
expect(result.current.toasts).toHaveLength(1); // Still in array
|
|
406
|
+
|
|
407
|
+
// Advance time past removal delay - this should trigger the setTimeout callback
|
|
408
|
+
act(() => {
|
|
409
|
+
vi.advanceTimersByTime(TOAST_REMOVE_DELAY);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// The setTimeout callback should have fired and dispatched REMOVE_TOAST
|
|
413
|
+
// Wait for React to process the state update
|
|
414
|
+
await waitFor(() => {
|
|
415
|
+
expect(result.current.toasts).toHaveLength(0);
|
|
416
|
+
}, { timeout: 200 });
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('dismisses all toasts when toastId is undefined', async () => {
|
|
420
|
+
const { result } = renderHook(() => useToast());
|
|
421
|
+
|
|
422
|
+
let toastId1: string | undefined;
|
|
423
|
+
let toastId2: string | undefined;
|
|
424
|
+
|
|
425
|
+
act(() => {
|
|
426
|
+
const response1 = result.current.toast({ title: 'Toast 1' });
|
|
427
|
+
const response2 = result.current.toast({ title: 'Toast 2' });
|
|
428
|
+
toastId1 = response1.id;
|
|
429
|
+
toastId2 = response2.id;
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
expect(result.current.toasts).toHaveLength(2);
|
|
433
|
+
|
|
434
|
+
// Dismiss all - this sets all toasts to open: false but doesn't add them to remove queue
|
|
435
|
+
act(() => {
|
|
436
|
+
result.current.dismiss(); // Dismiss all
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
expect(result.current.toasts.every(t => !t.open)).toBe(true);
|
|
440
|
+
expect(result.current.toasts).toHaveLength(2); // Still in array (not removed yet)
|
|
441
|
+
|
|
442
|
+
// Note: When dismissing all (toastId undefined), individual toasts are NOT added to remove queue
|
|
443
|
+
// They remain in the array but are closed. To actually remove them, we need to dismiss individually
|
|
444
|
+
// or wait for their auto-dismiss timers (if they have them)
|
|
445
|
+
|
|
446
|
+
// Test that dismissing individually removes them
|
|
447
|
+
act(() => {
|
|
448
|
+
result.current.dismiss(toastId1);
|
|
449
|
+
result.current.dismiss(toastId2);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Advance time past removal delay
|
|
453
|
+
act(() => {
|
|
454
|
+
vi.advanceTimersByTime(TOAST_REMOVE_DELAY);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Wait for state update
|
|
458
|
+
await waitFor(() => {
|
|
459
|
+
expect(result.current.toasts).toHaveLength(0);
|
|
460
|
+
}, { timeout: 200 });
|
|
461
|
+
});
|
|
61
462
|
});
|
|
62
463
|
});
|