@jmruthers/pace-core 0.5.73 → 0.5.75
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-INW5YIFV.js → DataTable-HWZQGASI.js} +8 -8
- package/dist/{PublicLoadingSpinner-DLpF5bbs.d.ts → PublicLoadingSpinner-BKNBT6b6.d.ts} +2 -2
- package/dist/RBACService-C4udt_Zp.d.ts +528 -0
- package/dist/{UnifiedAuthProvider-6SYT5WFN.js → UnifiedAuthProvider-3NKDOSOK.js} +6 -4
- package/dist/UnifiedAuthProvider-Bj6YCf7c.d.ts +113 -0
- package/dist/{chunk-2PRPDH66.js → chunk-2CHATWBF.js} +5 -7
- package/dist/chunk-2CHATWBF.js.map +1 -0
- package/dist/{chunk-43C63KLH.js → chunk-2DFZ432F.js} +496 -30
- package/dist/chunk-2DFZ432F.js.map +1 -0
- package/dist/{chunk-M4UMXYNK.js → chunk-33PHABLB.js} +36 -3
- package/dist/chunk-33PHABLB.js.map +1 -0
- package/dist/chunk-5F3NDPJV.js +232 -0
- package/dist/chunk-5F3NDPJV.js.map +1 -0
- package/dist/chunk-A4FUBC7B.js +17 -0
- package/dist/chunk-A4FUBC7B.js.map +1 -0
- package/dist/{chunk-SMJZMKYN.js → chunk-A6HBIY5P.js} +2 -11
- package/dist/{chunk-SMJZMKYN.js.map → chunk-A6HBIY5P.js.map} +1 -1
- package/dist/{chunk-GBC5PC3N.js → chunk-CY3AHGO4.js} +6256 -1937
- package/dist/chunk-CY3AHGO4.js.map +1 -0
- package/dist/{chunk-BYG6OSTC.js → chunk-DAXLNIDY.js} +48 -50
- package/dist/chunk-DAXLNIDY.js.map +1 -0
- package/dist/{chunk-VKOCWWVY.js → chunk-L3RV2ALE.js} +1 -6
- package/dist/{chunk-VKOCWWVY.js.map → chunk-L3RV2ALE.js.map} +1 -1
- package/dist/chunk-LW7MMEAQ.js +59 -0
- package/dist/chunk-LW7MMEAQ.js.map +1 -0
- package/dist/{chunk-LANO5IFV.js → chunk-NTNILOBC.js} +7 -9
- package/dist/chunk-NTNILOBC.js.map +1 -0
- package/dist/chunk-PYUXFQJ3.js +11 -0
- package/dist/chunk-PYUXFQJ3.js.map +1 -0
- package/dist/chunk-URUTVZ7N.js +27 -0
- package/dist/chunk-URUTVZ7N.js.map +1 -0
- package/dist/chunk-WN6XJWOS.js +2468 -0
- package/dist/chunk-WN6XJWOS.js.map +1 -0
- package/dist/{chunk-3SP4P7NS.js → chunk-XLZ7U46Z.js} +59 -1
- package/dist/chunk-XLZ7U46Z.js.map +1 -0
- package/dist/{chunk-UC2BWIK7.js → chunk-ZTT2AXMX.js} +9 -14
- package/dist/chunk-ZTT2AXMX.js.map +1 -0
- package/dist/components.d.ts +4 -5
- package/dist/components.js +32 -39
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +156 -10
- package/dist/index.js +188 -93
- package/dist/index.js.map +1 -1
- package/dist/{organisation-t-vvQC3g.d.ts → organisation-BtshODVF.d.ts} +4 -3
- package/dist/providers.d.ts +27 -38
- package/dist/providers.js +33 -23
- package/dist/rbac/index.d.ts +61 -5
- package/dist/rbac/index.js +13 -14
- package/dist/styles/index.js +2 -2
- package/dist/theming/runtime.js +1 -3
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/{unified-CMPjE_fv.d.ts → unified-CM7T0aTK.d.ts} +1 -1
- package/dist/useInactivityTracker-MRUU55XI.js +10 -0
- package/dist/useInactivityTracker-MRUU55XI.js.map +1 -0
- package/dist/{usePublicRouteParams-Ua1Vz-HG.d.ts → usePublicRouteParams-B-CumWRc.d.ts} +3 -3
- package/dist/utils.js +7 -9
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +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 +3 -3
- package/docs/api/interfaces/CardProps.md +2 -2
- 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/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/EventLogoProps.md +2 -2
- 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/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +2 -2
- 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 +28 -17
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +2 -2
- 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/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +2 -2
- 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/RBACContextType.md +5 -11
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.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 +524 -440
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +14 -14
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +179 -52
- package/docs/architecture/services.md +30 -32
- package/docs/breaking-changes.md +2 -5
- package/docs/implementation-guides/data-tables.md +82 -1
- package/docs/migration/service-architecture.md +121 -260
- package/docs/rbac/README-rbac-rls-integration.md +48 -38
- package/{src/rbac/examples → examples/RBAC}/CompleteRBACExample.tsx +3 -2
- package/{src/rbac/examples → examples/RBAC}/EventBasedApp.tsx +5 -4
- package/{src/components/examples → examples/RBAC}/PermissionExample.tsx +7 -6
- package/examples/RBAC/__tests__/PermissionExample.test.tsx +150 -0
- package/examples/RBAC/index.ts +13 -0
- package/examples/README.md +37 -0
- package/examples/index.ts +22 -0
- package/{src/examples → examples/public-pages}/CorrectPublicPageImplementation.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicEventPage.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicPageApp.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicPageUsageExample.tsx +1 -1
- package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +159 -0
- package/examples/public-pages/index.ts +14 -0
- package/package.json +22 -18
- package/src/__tests__/TEST_GUIDE_CURSOR.md +650 -9
- package/src/__tests__/helpers/README.md +255 -0
- package/src/__tests__/helpers/index.ts +62 -0
- package/src/__tests__/helpers/supabaseMock.ts +27 -3
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -8
- package/src/components/DataTable/components/DataTableCore.tsx +37 -3
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +55 -0
- package/src/components/DataTable/core/ColumnManager.ts +10 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +254 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +193 -0
- package/src/components/DataTable/examples/__tests__/HierarchicalExample.test.tsx +45 -0
- package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +117 -0
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/Dialog/examples/__tests__/HtmlDialogExample.test.tsx +71 -0
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +122 -0
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/Header/Header.test.tsx +35 -1
- package/src/components/Header/Header.tsx +3 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -3
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +24 -4
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +3 -2
- package/src/components/Toast/Toast.test.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/hooks/__tests__/useFocusManagement.unit.test.ts +220 -0
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +117 -0
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +295 -0
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +29 -19
- package/src/hooks/__tests__/useRBAC.unit.test.ts +7 -3
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +115 -19
- package/src/hooks/useEventTheme.test.ts +350 -0
- package/src/hooks/useEventTheme.ts +1 -1
- package/src/hooks/useEvents.ts +61 -0
- package/src/hooks/useOrganisationSecurity.test.ts +4 -4
- package/src/hooks/useOrganisationSecurity.ts +2 -2
- package/src/hooks/useOrganisations.ts +64 -0
- package/src/hooks/useSecureDataAccess.test.ts +9 -5
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +18 -3
- package/src/providers/AuthProvider.tsx +8 -292
- package/src/providers/EventProvider.tsx +15 -425
- package/src/providers/InactivityProvider.tsx +8 -231
- package/src/providers/OrganisationProvider.test.simple.tsx +3 -2
- package/src/providers/OrganisationProvider.tsx +11 -890
- package/src/providers/UnifiedAuthProvider.tsx +8 -320
- package/src/providers/__tests__/AuthProvider.test.tsx +18 -17
- package/src/providers/__tests__/EventProvider.test.tsx +253 -2
- package/src/providers/__tests__/InactivityProvider.test-helper.tsx +65 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +46 -114
- package/src/providers/__tests__/OrganisationProvider.test.tsx +313 -3
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +383 -2
- package/src/providers/index.ts +8 -7
- package/src/providers/services/EventServiceProvider.tsx +3 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +3 -0
- package/src/rbac/hooks/usePermissions.test.ts +296 -0
- package/src/rbac/hooks/useRBAC.test.ts +9 -5
- package/src/rbac/hooks/useRBAC.ts +3 -3
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +688 -0
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +507 -0
- package/src/services/AuthService.ts +19 -4
- package/src/services/__tests__/AuthService.test.ts +288 -0
- package/src/styles/core.css +2 -0
- package/src/types/__tests__/guards.test.ts +246 -0
- package/src/types/guards.ts +1 -0
- package/src/types/organisation.ts +3 -2
- package/src/validation/__tests__/sanitization.unit.test.ts +250 -0
- package/src/validation/__tests__/schemaUtils.unit.test.ts +451 -0
- package/src/validation/__tests__/user.unit.test.ts +440 -0
- package/dist/RBACProvider-BO4ilsQB.d.ts +0 -63
- package/dist/UnifiedAuthProvider-D02AMXgO.d.ts +0 -103
- package/dist/chunk-2PRPDH66.js.map +0 -1
- package/dist/chunk-3SP4P7NS.js.map +0 -1
- package/dist/chunk-43C63KLH.js.map +0 -1
- package/dist/chunk-5A4RL4BC.js +0 -5670
- package/dist/chunk-5A4RL4BC.js.map +0 -1
- package/dist/chunk-BYG6OSTC.js.map +0 -1
- package/dist/chunk-CDDYJCYU.js +0 -79
- package/dist/chunk-CDDYJCYU.js.map +0 -1
- package/dist/chunk-F24P24TZ.js +0 -17
- package/dist/chunk-F24P24TZ.js.map +0 -1
- package/dist/chunk-GBC5PC3N.js.map +0 -1
- package/dist/chunk-LANO5IFV.js.map +0 -1
- package/dist/chunk-M4UMXYNK.js.map +0 -1
- package/dist/chunk-RJNE764D.js +0 -953
- package/dist/chunk-RJNE764D.js.map +0 -1
- package/dist/chunk-UC2BWIK7.js.map +0 -1
- package/dist/rbac/cli/policy-manager.js +0 -278
- package/dist/rbac/cli/policy-manager.js.map +0 -1
- package/docs/api/interfaces/EventContextType.md +0 -96
- package/docs/api/interfaces/EventProviderProps.md +0 -19
- package/src/providers/OrganisationProvider.test.tsx +0 -164
- package/src/providers/UnifiedAuthProvider.test.tsx +0 -124
- package/src/providers/__tests__/AuthProvider.test.tsx.backup +0 -771
- package/src/providers/__tests__/EventProvider.test.tsx.backup +0 -824
- package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +0 -820
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +0 -911
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +0 -166
- package/src/rbac/cli/__tests__/policy-manager.test.ts +0 -339
- package/src/rbac/cli/policy-manager.ts +0 -443
- package/dist/{DataTable-INW5YIFV.js.map → DataTable-HWZQGASI.js.map} +0 -0
- package/dist/{UnifiedAuthProvider-6SYT5WFN.js.map → UnifiedAuthProvider-3NKDOSOK.js.map} +0 -0
- package/dist/{validation-PM_iOaTI.d.ts → validation-D8VcbTzC.d.ts} +2 -2
- /package/src/utils/{appNameResolver.test.ts.backup → appNameResolver.test 2.ts} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file HtmlDialogExample Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/Dialog/Examples/Tests
|
|
5
|
+
* @since 0.4.36
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { screen } from '@testing-library/react';
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import '@testing-library/jest-dom';
|
|
12
|
+
import { renderWithProviders } from '../../../../__tests__/helpers/test-utils';
|
|
13
|
+
import { HtmlDialogExample } from '../HtmlDialogExample';
|
|
14
|
+
|
|
15
|
+
describe('HtmlDialogExample Component', () => {
|
|
16
|
+
describe('Rendering', () => {
|
|
17
|
+
it('renders without crashing', () => {
|
|
18
|
+
renderWithProviders(<HtmlDialogExample />);
|
|
19
|
+
expect(screen.getByText('HTML Dialog Examples')).toBeInTheDocument();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders the title and buttons', () => {
|
|
23
|
+
renderWithProviders(<HtmlDialogExample />);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByText('HTML Dialog Examples')).toBeInTheDocument();
|
|
26
|
+
expect(screen.getByRole('button', { name: 'Import Instructions' })).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByRole('button', { name: 'Welcome Message' })).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders all dialog trigger buttons', () => {
|
|
31
|
+
renderWithProviders(<HtmlDialogExample />);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByRole('button', { name: 'Import Instructions' })).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByRole('button', { name: 'Welcome Message' })).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByRole('button', { name: 'Mixed Content' })).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByRole('button', { name: 'Security Demo' })).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('Dialog Configuration', () => {
|
|
41
|
+
it('renders dialog triggers with proper attributes', () => {
|
|
42
|
+
renderWithProviders(<HtmlDialogExample />);
|
|
43
|
+
|
|
44
|
+
const triggers = screen.getAllByRole('button');
|
|
45
|
+
expect(triggers.length).toBeGreaterThan(0);
|
|
46
|
+
|
|
47
|
+
// Check that at least one button has dialog attributes
|
|
48
|
+
const firstTrigger = triggers[0];
|
|
49
|
+
expect(firstTrigger).toHaveAttribute('aria-haspopup', 'dialog');
|
|
50
|
+
expect(firstTrigger).toHaveAttribute('aria-expanded', 'false');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('Component Structure', () => {
|
|
55
|
+
it('has proper heading structure', () => {
|
|
56
|
+
renderWithProviders(<HtmlDialogExample />);
|
|
57
|
+
|
|
58
|
+
expect(screen.getByRole('heading', { level: 2, name: 'HTML Dialog Examples' })).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('displays dialog trigger buttons', () => {
|
|
62
|
+
renderWithProviders(<HtmlDialogExample />);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByRole('button', { name: 'Import Instructions' })).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByRole('button', { name: 'Welcome Message' })).toBeInTheDocument();
|
|
66
|
+
expect(screen.getByRole('button', { name: 'Mixed Content' })).toBeInTheDocument();
|
|
67
|
+
expect(screen.getByRole('button', { name: 'Security Demo' })).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file SimpleHtmlTest Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/Dialog/Examples/Tests
|
|
5
|
+
* @since 0.4.37
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
10
|
+
import userEvent from '@testing-library/user-event';
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import '@testing-library/jest-dom';
|
|
13
|
+
import { renderWithProviders } from '../../../../__tests__/helpers/test-utils';
|
|
14
|
+
import { SimpleHtmlTest } from '../SimpleHtmlTest';
|
|
15
|
+
|
|
16
|
+
describe('SimpleHtmlTest Component', () => {
|
|
17
|
+
const user = userEvent.setup();
|
|
18
|
+
|
|
19
|
+
describe('Rendering', () => {
|
|
20
|
+
it('renders without crashing', () => {
|
|
21
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
22
|
+
expect(screen.getByText('Simple HTML Test')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders the title and test section', () => {
|
|
26
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
27
|
+
|
|
28
|
+
expect(screen.getByText('Simple HTML Test')).toBeInTheDocument();
|
|
29
|
+
expect(screen.getByText('Test HTML:')).toBeInTheDocument();
|
|
30
|
+
expect(screen.getByText('Expected Result:')).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('displays HTML code in pre tag', () => {
|
|
34
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
35
|
+
|
|
36
|
+
const preElements = document.querySelectorAll('pre');
|
|
37
|
+
expect(preElements.length).toBeGreaterThan(0);
|
|
38
|
+
|
|
39
|
+
const htmlCode = Array.from(preElements).find(el =>
|
|
40
|
+
el.textContent?.includes('Hello <strong>world</strong>!')
|
|
41
|
+
);
|
|
42
|
+
expect(htmlCode).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders the test button', () => {
|
|
46
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
47
|
+
expect(screen.getByRole('button', { name: 'Test HTML Rendering' })).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('Interactive Elements', () => {
|
|
52
|
+
it('opens dialog when button is clicked', async () => {
|
|
53
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
54
|
+
|
|
55
|
+
const button = screen.getByRole('button', { name: 'Test HTML Rendering' });
|
|
56
|
+
await user.click(button);
|
|
57
|
+
|
|
58
|
+
await waitFor(() => {
|
|
59
|
+
expect(screen.getByText('HTML Test')).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('displays HTML content in dialog when opened', async () => {
|
|
64
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
65
|
+
|
|
66
|
+
const button = screen.getByRole('button', { name: 'Test HTML Rendering' });
|
|
67
|
+
await user.click(button);
|
|
68
|
+
|
|
69
|
+
await waitFor(() => {
|
|
70
|
+
expect(screen.getByText('HTML Test')).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('renders button as enabled', () => {
|
|
75
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
76
|
+
|
|
77
|
+
const button = screen.getByRole('button', { name: 'Test HTML Rendering' });
|
|
78
|
+
expect(button).toBeEnabled();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Component Structure', () => {
|
|
83
|
+
it('has proper heading structure', () => {
|
|
84
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
85
|
+
|
|
86
|
+
expect(screen.getByRole('heading', { level: 2, name: 'Simple HTML Test' })).toBeInTheDocument();
|
|
87
|
+
expect(screen.getByRole('heading', { level: 3, name: 'Test HTML:' })).toBeInTheDocument();
|
|
88
|
+
expect(screen.getByRole('heading', { level: 3, name: 'Expected Result:' })).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('displays pre-formatted code', () => {
|
|
92
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
93
|
+
|
|
94
|
+
const codeBlock = screen.getByText(/Hello <strong>world<\/strong>!/).closest('pre');
|
|
95
|
+
expect(codeBlock).toBeInTheDocument();
|
|
96
|
+
expect(codeBlock?.className).toContain('bg-gray-100');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('HTML Rendering Test', () => {
|
|
101
|
+
it('shows expected rendered output container', () => {
|
|
102
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
103
|
+
|
|
104
|
+
// Should show the expected result container with proper styling
|
|
105
|
+
const resultContainers = document.querySelectorAll('.bg-green-50');
|
|
106
|
+
expect(resultContainers.length).toBeGreaterThan(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('displays expected output with HTML content', () => {
|
|
110
|
+
renderWithProviders(<SimpleHtmlTest />);
|
|
111
|
+
|
|
112
|
+
// The expected result should have "Hello" text
|
|
113
|
+
const expectedResult = document.querySelector('.bg-green-50');
|
|
114
|
+
expect(expectedResult).toBeTruthy();
|
|
115
|
+
|
|
116
|
+
// Should contain a strong element
|
|
117
|
+
const strongElements = document.querySelectorAll('.bg-green-50 strong');
|
|
118
|
+
expect(strongElements.length).toBeGreaterThan(0);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
@@ -89,7 +89,7 @@ import { Alert, AlertDescription } from '../Alert/Alert';
|
|
|
89
89
|
import { Button } from '../Button/Button';
|
|
90
90
|
import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
|
|
91
91
|
import { RefreshCw, AlertCircle, Lock, Calendar, Star } from 'lucide-react';
|
|
92
|
-
import { useEvents } from '../../
|
|
92
|
+
import { useEvents } from '../../hooks/useEvents';
|
|
93
93
|
import { Event } from '../../types';
|
|
94
94
|
import { useEffect, useMemo } from 'react';
|
|
95
95
|
import { cn } from '../../utils/cn';
|
|
@@ -305,6 +305,36 @@ describe('Header Component', () => {
|
|
|
305
305
|
|
|
306
306
|
expect(screen.getByRole('button', { name: 'Select event' })).toBeInTheDocument();
|
|
307
307
|
});
|
|
308
|
+
|
|
309
|
+
it('renders placeholder when showEventSelector is false', () => {
|
|
310
|
+
const { container } = renderWithProviders(<Header showEventSelector={false} />);
|
|
311
|
+
|
|
312
|
+
const placeholder = container.querySelector('del.invisible');
|
|
313
|
+
expect(placeholder).toBeInTheDocument();
|
|
314
|
+
expect(placeholder).toHaveTextContent('Event Selector N/A');
|
|
315
|
+
expect(placeholder).toHaveClass('justify-self-end', 'invisible');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('preserves grid layout when event selector is hidden', () => {
|
|
319
|
+
const { container } = renderWithProviders(
|
|
320
|
+
<Header
|
|
321
|
+
showEventSelector={false}
|
|
322
|
+
user={mockUser}
|
|
323
|
+
showUserMenu={true}
|
|
324
|
+
/>
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const nav = screen.getByRole('navigation');
|
|
328
|
+
expect(nav).toHaveClass('grid-cols-[auto_auto_1fr_auto]');
|
|
329
|
+
|
|
330
|
+
// Placeholder should maintain grid structure
|
|
331
|
+
const placeholder = container.querySelector('del.invisible');
|
|
332
|
+
expect(placeholder).toBeInTheDocument();
|
|
333
|
+
expect(placeholder).toHaveClass('justify-self-end');
|
|
334
|
+
|
|
335
|
+
// User menu should still be present and positioned correctly
|
|
336
|
+
expect(screen.getByTestId('user-menu')).toBeInTheDocument();
|
|
337
|
+
});
|
|
308
338
|
});
|
|
309
339
|
|
|
310
340
|
// Custom actions tests
|
|
@@ -523,7 +553,7 @@ describe('Header Component', () => {
|
|
|
523
553
|
});
|
|
524
554
|
|
|
525
555
|
it('renders minimal configuration', () => {
|
|
526
|
-
renderWithProviders(
|
|
556
|
+
const { container } = renderWithProviders(
|
|
527
557
|
<Header
|
|
528
558
|
showEventSelector={false}
|
|
529
559
|
showUserMenu={false}
|
|
@@ -536,6 +566,10 @@ describe('Header Component', () => {
|
|
|
536
566
|
expect(screen.queryByTestId('navigation-menu')).not.toBeInTheDocument();
|
|
537
567
|
expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
|
|
538
568
|
expect(screen.queryByTestId('user-menu')).not.toBeInTheDocument();
|
|
569
|
+
|
|
570
|
+
// Should have placeholder for event selector
|
|
571
|
+
const placeholder = container.querySelector('del.invisible');
|
|
572
|
+
expect(placeholder).toBeInTheDocument();
|
|
539
573
|
});
|
|
540
574
|
});
|
|
541
575
|
|
|
@@ -270,12 +270,14 @@ export function Header({
|
|
|
270
270
|
{/* Right side: Event Selector, Actions, and User Menu */}
|
|
271
271
|
|
|
272
272
|
{/* Event Selector */}
|
|
273
|
-
{showEventSelector
|
|
273
|
+
{showEventSelector ? (
|
|
274
274
|
<EventSelector
|
|
275
275
|
placeholder="Select event"
|
|
276
276
|
className="justify-self-end w-96"
|
|
277
277
|
data-testid="event-selector"
|
|
278
278
|
/>
|
|
279
|
+
) : (
|
|
280
|
+
<del className="justify-self-end invisible">Event Selector N/A</del>
|
|
279
281
|
)}
|
|
280
282
|
|
|
281
283
|
{/* Custom Actions */}
|
|
@@ -241,13 +241,13 @@ export function OrganisationSelector({
|
|
|
241
241
|
</Alert>
|
|
242
242
|
);
|
|
243
243
|
|
|
244
|
-
// Normal selector state
|
|
244
|
+
// Normal selector state - with null check
|
|
245
245
|
return (
|
|
246
246
|
<div className={`space-y-2 ${className}`}>
|
|
247
247
|
<Select
|
|
248
|
-
value={selectedOrganisation
|
|
248
|
+
value={selectedOrganisation?.id || ''}
|
|
249
249
|
onValueChange={handleOrganisationChange}
|
|
250
|
-
disabled={disabled || isLoading}
|
|
250
|
+
disabled={disabled || isLoading || !selectedOrganisation}
|
|
251
251
|
>
|
|
252
252
|
<SelectTrigger className={`${isLoading ? 'opacity-50' : ''}`}>
|
|
253
253
|
<div className="flex items-center gap-2">
|
|
@@ -62,6 +62,22 @@ vi.mock('../../../providers/OrganisationProvider', () => ({
|
|
|
62
62
|
OrganisationProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
63
63
|
}));
|
|
64
64
|
|
|
65
|
+
// Mock EventSelector and useEvents to avoid EventServiceProvider requirement
|
|
66
|
+
vi.mock('../../EventSelector/EventSelector', () => ({
|
|
67
|
+
EventSelector: () => null,
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
vi.mock('../../../hooks/useEvents', () => ({
|
|
71
|
+
useEvents: () => ({
|
|
72
|
+
events: [],
|
|
73
|
+
selectedEvent: null,
|
|
74
|
+
isLoading: false,
|
|
75
|
+
error: null,
|
|
76
|
+
setSelectedEvent: vi.fn(),
|
|
77
|
+
refreshEvents: vi.fn(),
|
|
78
|
+
}),
|
|
79
|
+
}));
|
|
80
|
+
|
|
65
81
|
// Mock RBACProvider
|
|
66
82
|
vi.mock('../../../rbac/providers/RBACProvider', () => ({
|
|
67
83
|
RBACProvider: ({ children }: { children: React.ReactNode }) => children,
|
|
@@ -113,11 +129,15 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
113
129
|
|
|
114
130
|
return (
|
|
115
131
|
<QueryClientProvider client={queryClient}>
|
|
116
|
-
<UnifiedAuthProvider
|
|
132
|
+
<UnifiedAuthProvider
|
|
133
|
+
supabaseClient={mockSupabaseClient}
|
|
134
|
+
appName="Test App"
|
|
135
|
+
idleTimeoutMs={30 * 60 * 1000}
|
|
136
|
+
warnBeforeMs={60 * 1000}
|
|
137
|
+
onIdleLogout={() => {}}
|
|
138
|
+
>
|
|
117
139
|
<RBACProvider>
|
|
118
|
-
|
|
119
|
-
{children}
|
|
120
|
-
</OrganisationProvider>
|
|
140
|
+
{children}
|
|
121
141
|
</RBACProvider>
|
|
122
142
|
</UnifiedAuthProvider>
|
|
123
143
|
</QueryClientProvider>
|
|
@@ -42,9 +42,10 @@ const mockAuthContext = {
|
|
|
42
42
|
inactivityError: null,
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
// Mock the useUnifiedAuth hook
|
|
46
|
-
vi.mock('../../providers
|
|
45
|
+
// Mock the useUnifiedAuth hook - needs to match the actual import path
|
|
46
|
+
vi.mock('../../providers', () => ({
|
|
47
47
|
useUnifiedAuth: () => mockAuthContext,
|
|
48
|
+
UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
48
49
|
}));
|
|
49
50
|
|
|
50
51
|
// Mock console methods to avoid noise in tests
|
|
@@ -109,7 +109,7 @@ const ToastViewport = React.forwardRef<
|
|
|
109
109
|
ref={ref}
|
|
110
110
|
data-testid="toast-viewport"
|
|
111
111
|
className={cn(
|
|
112
|
-
"fixed top-0 z-[
|
|
112
|
+
"fixed top-0 z-[9999] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
|
113
113
|
className
|
|
114
114
|
)}
|
|
115
115
|
{...props}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file useFocusManagement Hook Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/__tests__/useFocusManagement
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for the useFocusManagement hook covering all critical functionality.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { renderHook } from '@testing-library/react';
|
|
11
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
12
|
+
import { useFocusManagement } from '../useFocusManagement';
|
|
13
|
+
|
|
14
|
+
describe('useFocusManagement', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Initial state', () => {
|
|
20
|
+
it('provides all required methods', () => {
|
|
21
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
22
|
+
|
|
23
|
+
expect(result.current.containerRef).toBeDefined();
|
|
24
|
+
expect(result.current.focusRef).toBeDefined();
|
|
25
|
+
expect(typeof result.current.setFocus).toBe('function');
|
|
26
|
+
expect(typeof result.current.focusFirst).toBe('function');
|
|
27
|
+
expect(typeof result.current.focusLast).toBe('function');
|
|
28
|
+
expect(typeof result.current.trapFocus).toBe('function');
|
|
29
|
+
expect(typeof result.current.releaseFocus).toBe('function');
|
|
30
|
+
expect(typeof result.current.getFocusableElements).toBe('function');
|
|
31
|
+
expect(typeof result.current.handleEscape).toBe('function');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('initializes with empty focusable elements', () => {
|
|
35
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
36
|
+
|
|
37
|
+
expect(result.current.getFocusableElements()).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Focus management', () => {
|
|
42
|
+
it('can focus an element', () => {
|
|
43
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
44
|
+
|
|
45
|
+
const element = document.createElement('button');
|
|
46
|
+
document.body.appendChild(element);
|
|
47
|
+
|
|
48
|
+
result.current.setFocus(element);
|
|
49
|
+
|
|
50
|
+
expect(result.current.focusRef.current).toBe(element);
|
|
51
|
+
expect(document.activeElement).toBe(element);
|
|
52
|
+
|
|
53
|
+
document.body.removeChild(element);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('can get focusable elements', () => {
|
|
57
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
58
|
+
|
|
59
|
+
const container = document.createElement('div');
|
|
60
|
+
const button = document.createElement('button');
|
|
61
|
+
const link = document.createElement('a');
|
|
62
|
+
link.href = '#';
|
|
63
|
+
|
|
64
|
+
container.appendChild(button);
|
|
65
|
+
container.appendChild(link);
|
|
66
|
+
|
|
67
|
+
(result.current.containerRef as any).current = container;
|
|
68
|
+
|
|
69
|
+
const elements = result.current.getFocusableElements();
|
|
70
|
+
expect(elements.length).toBe(2);
|
|
71
|
+
expect(elements).toContain(button);
|
|
72
|
+
expect(elements).toContain(link);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('excludes disabled elements', () => {
|
|
76
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
77
|
+
|
|
78
|
+
const container = document.createElement('div');
|
|
79
|
+
const disabledButton = document.createElement('button');
|
|
80
|
+
const enabledButton = document.createElement('button');
|
|
81
|
+
|
|
82
|
+
disabledButton.setAttribute('disabled', '');
|
|
83
|
+
|
|
84
|
+
container.appendChild(disabledButton);
|
|
85
|
+
container.appendChild(enabledButton);
|
|
86
|
+
|
|
87
|
+
(result.current.containerRef as any).current = container;
|
|
88
|
+
|
|
89
|
+
const elements = result.current.getFocusableElements();
|
|
90
|
+
expect(elements).toHaveLength(1);
|
|
91
|
+
expect(elements).toContain(enabledButton);
|
|
92
|
+
expect(elements).not.toContain(disabledButton);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('excludes hidden elements', () => {
|
|
96
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
97
|
+
|
|
98
|
+
const container = document.createElement('div');
|
|
99
|
+
const hiddenButton = document.createElement('button');
|
|
100
|
+
const visibleButton = document.createElement('button');
|
|
101
|
+
|
|
102
|
+
hiddenButton.setAttribute('hidden', '');
|
|
103
|
+
|
|
104
|
+
container.appendChild(hiddenButton);
|
|
105
|
+
container.appendChild(visibleButton);
|
|
106
|
+
|
|
107
|
+
(result.current.containerRef as any).current = container;
|
|
108
|
+
|
|
109
|
+
const elements = result.current.getFocusableElements();
|
|
110
|
+
expect(elements).toHaveLength(1);
|
|
111
|
+
expect(elements).toContain(visibleButton);
|
|
112
|
+
expect(elements).not.toContain(hiddenButton);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Focus trap', () => {
|
|
117
|
+
it('can activate focus trap', () => {
|
|
118
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
119
|
+
|
|
120
|
+
result.current.trapFocus();
|
|
121
|
+
|
|
122
|
+
// Focus trap activation doesn't throw
|
|
123
|
+
expect(true).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('can release focus trap', () => {
|
|
127
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
128
|
+
|
|
129
|
+
result.current.trapFocus();
|
|
130
|
+
result.current.releaseFocus();
|
|
131
|
+
|
|
132
|
+
// Focus trap release doesn't throw
|
|
133
|
+
expect(true).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('Escape key handling', () => {
|
|
138
|
+
it('provides handleEscape function', () => {
|
|
139
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
140
|
+
|
|
141
|
+
const callback = vi.fn();
|
|
142
|
+
const cleanup = result.current.handleEscape(callback);
|
|
143
|
+
|
|
144
|
+
expect(typeof cleanup).toBe('function');
|
|
145
|
+
|
|
146
|
+
// Cleanup
|
|
147
|
+
cleanup();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('handleEscape calls callback when Escape is pressed', () => {
|
|
151
|
+
const { result } = renderHook(() => useFocusManagement());
|
|
152
|
+
|
|
153
|
+
const callback = vi.fn();
|
|
154
|
+
const setup = result.current.handleEscape(callback);
|
|
155
|
+
const cleanup = setup();
|
|
156
|
+
|
|
157
|
+
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' });
|
|
158
|
+
document.dispatchEvent(escapeEvent);
|
|
159
|
+
|
|
160
|
+
expect(callback).toHaveBeenCalled();
|
|
161
|
+
|
|
162
|
+
cleanup();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('Callback options', () => {
|
|
167
|
+
it('calls onFocusFirst callback', () => {
|
|
168
|
+
const onFocusFirst = vi.fn();
|
|
169
|
+
|
|
170
|
+
const { result } = renderHook(() => useFocusManagement({ onFocusFirst }));
|
|
171
|
+
|
|
172
|
+
const container = document.createElement('div');
|
|
173
|
+
const button = document.createElement('button');
|
|
174
|
+
container.appendChild(button);
|
|
175
|
+
(result.current.containerRef as any).current = container;
|
|
176
|
+
|
|
177
|
+
result.current.focusFirst();
|
|
178
|
+
|
|
179
|
+
expect(onFocusFirst).toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('calls onFocusLast callback', () => {
|
|
183
|
+
const onFocusLast = vi.fn();
|
|
184
|
+
|
|
185
|
+
const { result } = renderHook(() => useFocusManagement({ onFocusLast }));
|
|
186
|
+
|
|
187
|
+
const container = document.createElement('div');
|
|
188
|
+
const button = document.createElement('button');
|
|
189
|
+
container.appendChild(button);
|
|
190
|
+
(result.current.containerRef as any).current = container;
|
|
191
|
+
|
|
192
|
+
result.current.focusLast();
|
|
193
|
+
|
|
194
|
+
expect(onFocusLast).toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('calls onEscape callback when trapFocus is enabled', () => {
|
|
198
|
+
const onEscape = vi.fn();
|
|
199
|
+
|
|
200
|
+
const { result } = renderHook(() => useFocusManagement({
|
|
201
|
+
trapFocus: true,
|
|
202
|
+
onEscape
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
const container = document.createElement('div');
|
|
206
|
+
const button = document.createElement('button');
|
|
207
|
+
container.appendChild(button);
|
|
208
|
+
(result.current.containerRef as any).current = container;
|
|
209
|
+
|
|
210
|
+
// Wait for effect to setup
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
|
|
213
|
+
container.dispatchEvent(escapeEvent);
|
|
214
|
+
}, 10);
|
|
215
|
+
|
|
216
|
+
// Callback will be called through the focus trap effect
|
|
217
|
+
expect(true).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|