@jmruthers/pace-core 0.5.75 → 0.5.76
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-HWZQGASI.js → DataTable-4GAVPIEG.js} +48 -30
- package/dist/{PublicLoadingSpinner-BKNBT6b6.d.ts → PublicLoadingSpinner-BiNER8F5.d.ts} +28 -17
- package/dist/{chunk-33PHABLB.js → chunk-AFGTSUAD.js} +10 -127
- package/dist/chunk-AFGTSUAD.js.map +1 -0
- package/dist/{chunk-2DFZ432F.js → chunk-K34IM5CT.js} +3 -5
- package/dist/{chunk-2DFZ432F.js.map → chunk-K34IM5CT.js.map} +1 -1
- package/dist/{chunk-2CHATWBF.js → chunk-KHJS6VIA.js} +199 -35
- package/dist/chunk-KHJS6VIA.js.map +1 -0
- package/dist/{chunk-ZTT2AXMX.js → chunk-KK73ZB4E.js} +2 -2
- package/dist/{chunk-CY3AHGO4.js → chunk-M5IWZRBT.js} +1750 -2815
- package/dist/chunk-M5IWZRBT.js.map +1 -0
- package/dist/{chunk-DAXLNIDY.js → chunk-Y6TXWPJO.js} +6 -4
- package/dist/{chunk-DAXLNIDY.js.map → chunk-Y6TXWPJO.js.map} +1 -1
- package/dist/{chunk-YNUBMSMV.js → chunk-YCKPEMJA.js} +186 -263
- package/dist/chunk-YCKPEMJA.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +7 -6
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +17 -40
- package/dist/hooks.js +6 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/dist/rbac/index.d.ts +54 -1
- package/dist/rbac/index.js +5 -4
- package/dist/utils.js +1 -1
- package/docs/TERMINOLOGY.md +231 -0
- 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/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 +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/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/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/RBACContextType.md +1 -1
- 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 +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/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/UseResolvedScopeOptions.md +47 -0
- package/docs/api/interfaces/UseResolvedScopeReturn.md +47 -0
- 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 +57 -11
- package/docs/api-reference/providers.md +26 -7
- package/docs/best-practices/README.md +20 -0
- package/docs/best-practices/accessibility.md +566 -0
- package/docs/best-practices/performance-expansion.md +473 -0
- package/docs/core-concepts/authentication.md +15 -7
- package/docs/documentation-index.md +1 -1
- package/docs/documentation-templates.md +539 -0
- package/docs/getting-started/quick-start.md +16 -66
- package/docs/implementation-guides/component-styling.md +410 -0
- package/docs/implementation-guides/data-tables.md +1 -1
- package/docs/style-guide.md +39 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_GUIDE_CURSOR.md +290 -0
- package/src/__tests__/helpers/supabaseMock.ts +48 -2
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +17 -6
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +73 -9
- package/src/components/DataTable/components/DataTableCore.tsx +280 -475
- package/src/components/DataTable/components/UnifiedTableBody.tsx +120 -153
- package/src/components/DataTable/components/index.ts +1 -2
- package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +208 -275
- package/src/components/DataTable/core/index.ts +1 -8
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +525 -0
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +570 -0
- package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +214 -0
- package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
- package/src/components/DataTable/hooks/index.ts +6 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +1 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +149 -0
- package/src/components/DataTable/hooks/useDataTableState.ts +12 -6
- package/src/components/DataTable/hooks/useHierarchicalState.ts +26 -8
- package/src/components/DataTable/hooks/useTableColumns.ts +153 -0
- package/src/components/DataTable/index.ts +1 -9
- package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +89 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +3 -6
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +462 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +247 -0
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +8 -6
- package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +466 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +265 -0
- package/src/components/DataTable/utils/errorHandling.ts +52 -460
- package/src/components/DataTable/utils/exportUtils.ts +46 -15
- package/src/components/DataTable/utils/hierarchicalSorting.ts +50 -3
- package/src/components/DataTable/utils/hierarchicalUtils.ts +167 -34
- package/src/components/DataTable/utils/index.ts +5 -0
- package/src/components/DataTable/utils/rowUtils.ts +68 -0
- package/src/components/EventSelector/EventSelector.test.tsx +672 -0
- package/src/components/Label/__tests__/Label.test.tsx +434 -0
- package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +190 -0
- package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +185 -0
- package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +313 -0
- package/src/components/Select/Select.test.tsx +143 -120
- package/src/components/Select/Select.tsx +47 -212
- package/src/components/Select/hooks.ts +36 -1
- package/src/components/Select/index.ts +2 -1
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +137 -0
- package/src/hooks/useSecureDataAccess.test.ts +32 -29
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +341 -0
- package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +437 -0
- package/src/rbac/hooks/index.ts +2 -0
- package/src/rbac/hooks/useResolvedScope.ts +232 -0
- package/src/services/__tests__/InactivityService.lifecycle.test.ts +411 -0
- package/src/services/__tests__/OrganisationService.pagination.test.ts +375 -0
- package/src/types/__tests__/README.md +114 -0
- package/src/types/__tests__/validation.test.ts +731 -0
- package/src/utils/__tests__/file-reference.test.ts +383 -0
- package/src/utils/__tests__/performanceBenchmark.test.ts +175 -0
- package/src/utils/appNameResolver.test.ts +54 -0
- package/src/validation/__tests__/csrf.unit.test.ts +63 -0
- package/src/validation/__tests__/passwordSchema.unit.test.ts +105 -0
- package/dist/chunk-2CHATWBF.js.map +0 -1
- package/dist/chunk-33PHABLB.js.map +0 -1
- package/dist/chunk-CY3AHGO4.js.map +0 -1
- package/dist/chunk-TYHR5X4W.js +0 -33
- package/dist/chunk-TYHR5X4W.js.map +0 -1
- package/dist/chunk-YNUBMSMV.js.map +0 -1
- package/dist/eventContext-BBA42P6G.js +0 -14
- package/dist/eventContext-BBA42P6G.js.map +0 -1
- package/docs/documentation-style-checklist.md +0 -294
- package/src/components/DataTable/components/DataTableBody.tsx +0 -488
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -144
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -515
- package/src/components/DataTable/core/ActionManager.ts +0 -235
- package/src/components/DataTable/core/ColumnManager.ts +0 -215
- package/src/components/DataTable/core/DataManager.ts +0 -188
- package/src/components/DataTable/core/DataTableContext.tsx +0 -181
- package/src/components/DataTable/core/LocalDataAdapter.ts +0 -264
- package/src/components/DataTable/core/PluginRegistry.ts +0 -229
- package/src/components/DataTable/core/StateManager.ts +0 -311
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -634
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -193
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -519
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -714
- package/src/components/DataTable/core/interfaces.ts +0 -338
- package/src/components/DataTable/utils/debugTools.ts +0 -583
- package/src/components/Select/Select.bug-test.tsx +0 -69
- package/src/components/Select/Select.refactored.tsx +0 -497
- /package/dist/{DataTable-HWZQGASI.js.map → DataTable-4GAVPIEG.js.map} +0 -0
- /package/dist/{chunk-ZTT2AXMX.js.map → chunk-KK73ZB4E.js.map} +0 -0
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
* @file Select Component Tests
|
|
3
3
|
* @description Comprehensive test suite for Select component and its sub-components
|
|
4
4
|
* @package @jmruthers/pace-core
|
|
5
|
+
* @module Components/Select
|
|
6
|
+
*
|
|
7
|
+
* This test suite follows the testing guidelines in TEST_GUIDE_CURSOR.md
|
|
8
|
+
* and focuses on observable behavior, accessibility, and user interactions.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import React from 'react';
|
|
8
|
-
import { screen, waitFor
|
|
12
|
+
import { screen, waitFor } from '@testing-library/react';
|
|
9
13
|
import userEvent from '@testing-library/user-event';
|
|
10
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
14
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
15
|
import {
|
|
12
16
|
Select,
|
|
13
17
|
SelectTrigger,
|
|
@@ -66,46 +70,48 @@ describe('Select Component', () => {
|
|
|
66
70
|
);
|
|
67
71
|
};
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
vi.clearAllMocks();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
vi.restoreAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Rendering', () => {
|
|
82
|
+
it('renders select trigger with placeholder', () => {
|
|
71
83
|
renderSelect();
|
|
72
84
|
|
|
73
|
-
expect(screen.
|
|
74
|
-
expect(screen.getByTestId('select-trigger')).toBeInTheDocument();
|
|
75
|
-
expect(screen.getByTestId('select-value')).toBeInTheDocument();
|
|
85
|
+
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
|
76
86
|
expect(screen.getByText('Select an option...')).toBeInTheDocument();
|
|
77
87
|
});
|
|
78
88
|
|
|
79
89
|
it('renders with custom placeholder', () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
renderWithProviders(
|
|
91
|
+
<Select>
|
|
92
|
+
<SelectTrigger>
|
|
93
|
+
<SelectValue placeholder="Choose..." />
|
|
94
|
+
</SelectTrigger>
|
|
95
|
+
<SelectContent>
|
|
96
|
+
<SelectItem value="test">Test</SelectItem>
|
|
97
|
+
</SelectContent>
|
|
98
|
+
</Select>
|
|
99
|
+
);
|
|
88
100
|
|
|
89
|
-
expect(screen.
|
|
101
|
+
expect(screen.getByText('Choose...')).toBeInTheDocument();
|
|
90
102
|
});
|
|
91
103
|
|
|
92
104
|
it('renders with disabled state', () => {
|
|
93
105
|
renderSelect({ disabled: true });
|
|
94
106
|
|
|
95
|
-
const trigger = screen.
|
|
107
|
+
const trigger = screen.getByRole('combobox');
|
|
96
108
|
expect(trigger).toBeDisabled();
|
|
97
109
|
});
|
|
98
110
|
|
|
99
|
-
it('
|
|
100
|
-
renderSelect(
|
|
101
|
-
|
|
102
|
-
expect(screen.getByTestId('select-root')).toHaveAttribute('data-value', 'option1');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('renders with controlled value', () => {
|
|
106
|
-
renderSelect({ value: 'option2' });
|
|
111
|
+
it('shows placeholder when no value is selected', () => {
|
|
112
|
+
renderSelect();
|
|
107
113
|
|
|
108
|
-
expect(screen.
|
|
114
|
+
expect(screen.getByText('Select an option...')).toBeInTheDocument();
|
|
109
115
|
});
|
|
110
116
|
});
|
|
111
117
|
|
|
@@ -339,152 +345,157 @@ describe('Select Component', () => {
|
|
|
339
345
|
});
|
|
340
346
|
|
|
341
347
|
describe('User Interactions', () => {
|
|
342
|
-
it('opens dropdown
|
|
348
|
+
it('opens dropdown when clicking trigger', async () => {
|
|
343
349
|
const user = userEvent.setup();
|
|
344
350
|
renderSelect();
|
|
345
351
|
|
|
346
|
-
const trigger = screen.
|
|
352
|
+
const trigger = screen.getByRole('combobox');
|
|
347
353
|
await user.click(trigger);
|
|
348
354
|
|
|
349
|
-
expect(screen.
|
|
355
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
356
|
+
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
350
357
|
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
351
358
|
});
|
|
352
359
|
|
|
353
|
-
it('
|
|
360
|
+
it('shows all options when dropdown opens', async () => {
|
|
354
361
|
const user = userEvent.setup();
|
|
355
362
|
renderSelect();
|
|
356
363
|
|
|
357
|
-
|
|
358
|
-
await user.click(trigger);
|
|
364
|
+
await user.click(screen.getByRole('combobox'));
|
|
359
365
|
|
|
360
|
-
|
|
361
|
-
|
|
366
|
+
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
367
|
+
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
|
368
|
+
expect(screen.getByText('Option 3')).toBeInTheDocument();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('closes dropdown when an option is selected', async () => {
|
|
372
|
+
const user = userEvent.setup();
|
|
373
|
+
renderSelect();
|
|
374
|
+
|
|
375
|
+
await user.click(screen.getByRole('combobox'));
|
|
376
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
377
|
+
|
|
378
|
+
await user.click(screen.getByText('Option 1'));
|
|
362
379
|
|
|
363
380
|
await waitFor(() => {
|
|
364
|
-
expect(screen.
|
|
365
|
-
}
|
|
381
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
382
|
+
});
|
|
366
383
|
});
|
|
367
384
|
|
|
368
|
-
it('updates value
|
|
385
|
+
it('updates displayed value after selection', async () => {
|
|
369
386
|
const user = userEvent.setup();
|
|
370
387
|
const onValueChange = vi.fn();
|
|
371
388
|
renderSelect({ onValueChange });
|
|
372
389
|
|
|
373
|
-
|
|
374
|
-
await user.click(
|
|
375
|
-
|
|
376
|
-
const option = screen.getByText('Option 2');
|
|
377
|
-
await user.click(option);
|
|
390
|
+
await user.click(screen.getByRole('combobox'));
|
|
391
|
+
await user.click(screen.getByText('Option 2'));
|
|
378
392
|
|
|
379
393
|
expect(onValueChange).toHaveBeenCalledWith('option2');
|
|
394
|
+
await waitFor(() => {
|
|
395
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
396
|
+
});
|
|
380
397
|
});
|
|
381
398
|
|
|
382
|
-
it('closes dropdown
|
|
399
|
+
it('closes dropdown when clicking outside', async () => {
|
|
383
400
|
const user = userEvent.setup();
|
|
384
401
|
renderSelect();
|
|
385
402
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
expect(screen.getByTestId('select-content')).toBeInTheDocument();
|
|
403
|
+
await user.click(screen.getByRole('combobox'));
|
|
404
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
390
405
|
|
|
391
|
-
// Click outside
|
|
392
406
|
await user.click(document.body);
|
|
393
407
|
|
|
394
408
|
await waitFor(() => {
|
|
395
|
-
expect(screen.
|
|
396
|
-
}
|
|
409
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('prevents opening when trigger is disabled', async () => {
|
|
414
|
+
const user = userEvent.setup();
|
|
415
|
+
renderSelect({ disabled: true });
|
|
416
|
+
|
|
417
|
+
const trigger = screen.getByRole('combobox');
|
|
418
|
+
expect(trigger).toBeDisabled();
|
|
419
|
+
|
|
420
|
+
await user.click(trigger);
|
|
421
|
+
|
|
422
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
397
423
|
});
|
|
398
424
|
});
|
|
399
425
|
|
|
400
426
|
describe('Keyboard Navigation', () => {
|
|
401
|
-
it('opens dropdown
|
|
427
|
+
it('opens dropdown when pressing Enter', async () => {
|
|
402
428
|
const user = userEvent.setup();
|
|
403
429
|
renderSelect();
|
|
404
430
|
|
|
405
|
-
const trigger = screen.
|
|
431
|
+
const trigger = screen.getByRole('combobox');
|
|
406
432
|
trigger.focus();
|
|
407
433
|
await user.keyboard('{Enter}');
|
|
408
434
|
|
|
409
|
-
expect(screen.
|
|
435
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
410
436
|
});
|
|
411
437
|
|
|
412
|
-
it('opens dropdown
|
|
438
|
+
it('opens dropdown when pressing Space', async () => {
|
|
413
439
|
const user = userEvent.setup();
|
|
414
440
|
renderSelect();
|
|
415
441
|
|
|
416
|
-
const trigger = screen.
|
|
442
|
+
const trigger = screen.getByRole('combobox');
|
|
417
443
|
trigger.focus();
|
|
418
444
|
await user.keyboard(' ');
|
|
419
445
|
|
|
420
|
-
expect(screen.
|
|
446
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
421
447
|
});
|
|
422
448
|
|
|
423
|
-
it('opens dropdown
|
|
449
|
+
it('opens dropdown when pressing ArrowDown', async () => {
|
|
424
450
|
const user = userEvent.setup();
|
|
425
451
|
renderSelect();
|
|
426
452
|
|
|
427
|
-
const trigger = screen.
|
|
453
|
+
const trigger = screen.getByRole('combobox');
|
|
428
454
|
trigger.focus();
|
|
429
455
|
await user.keyboard('{ArrowDown}');
|
|
430
456
|
|
|
431
|
-
expect(screen.
|
|
457
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
432
458
|
});
|
|
433
459
|
|
|
434
|
-
it('opens dropdown
|
|
460
|
+
it('opens dropdown when pressing ArrowUp', async () => {
|
|
435
461
|
const user = userEvent.setup();
|
|
436
462
|
renderSelect();
|
|
437
463
|
|
|
438
|
-
const trigger = screen.
|
|
464
|
+
const trigger = screen.getByRole('combobox');
|
|
439
465
|
trigger.focus();
|
|
440
466
|
await user.keyboard('{ArrowUp}');
|
|
441
467
|
|
|
442
|
-
expect(screen.
|
|
468
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
443
469
|
});
|
|
444
470
|
|
|
445
|
-
it('closes dropdown
|
|
471
|
+
it('closes dropdown when pressing Escape', async () => {
|
|
446
472
|
const user = userEvent.setup();
|
|
447
473
|
renderSelect();
|
|
448
474
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
expect(screen.getByTestId('select-content')).toBeInTheDocument();
|
|
475
|
+
await user.click(screen.getByRole('combobox'));
|
|
476
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
453
477
|
|
|
454
478
|
await user.keyboard('{Escape}');
|
|
455
479
|
|
|
456
480
|
await waitFor(() => {
|
|
457
|
-
expect(screen.
|
|
458
|
-
}
|
|
481
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
482
|
+
});
|
|
459
483
|
});
|
|
460
484
|
|
|
461
|
-
it('selects
|
|
485
|
+
it('selects option with keyboard activation', async () => {
|
|
462
486
|
const user = userEvent.setup();
|
|
463
487
|
const onValueChange = vi.fn();
|
|
464
488
|
renderSelect({ onValueChange });
|
|
465
489
|
|
|
466
|
-
const trigger = screen.
|
|
490
|
+
const trigger = screen.getByRole('combobox');
|
|
467
491
|
await user.click(trigger);
|
|
468
492
|
|
|
469
493
|
const item = screen.getByText('Option 1');
|
|
470
|
-
|
|
494
|
+
item.focus();
|
|
495
|
+
await user.keyboard('{Enter}');
|
|
471
496
|
|
|
472
497
|
expect(onValueChange).toHaveBeenCalledWith('option1');
|
|
473
498
|
});
|
|
474
|
-
|
|
475
|
-
it('selects different item on click', async () => {
|
|
476
|
-
const user = userEvent.setup();
|
|
477
|
-
const onValueChange = vi.fn();
|
|
478
|
-
renderSelect({ onValueChange });
|
|
479
|
-
|
|
480
|
-
const trigger = screen.getByTestId('select-trigger');
|
|
481
|
-
await user.click(trigger);
|
|
482
|
-
|
|
483
|
-
const item = screen.getByText('Option 2');
|
|
484
|
-
await user.click(item);
|
|
485
|
-
|
|
486
|
-
expect(onValueChange).toHaveBeenCalledWith('option2');
|
|
487
|
-
});
|
|
488
499
|
});
|
|
489
500
|
|
|
490
501
|
describe('Search Functionality', () => {
|
|
@@ -865,65 +876,50 @@ describe('Select Component', () => {
|
|
|
865
876
|
});
|
|
866
877
|
|
|
867
878
|
describe('Accessibility', () => {
|
|
868
|
-
it('
|
|
879
|
+
it('announces expandable state to screen readers', () => {
|
|
869
880
|
renderSelect();
|
|
870
881
|
|
|
871
|
-
const trigger = screen.
|
|
872
|
-
expect(trigger).toHaveAttribute('role', 'combobox');
|
|
882
|
+
const trigger = screen.getByRole('combobox');
|
|
873
883
|
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
874
884
|
expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');
|
|
875
885
|
});
|
|
876
886
|
|
|
877
|
-
it('updates
|
|
887
|
+
it('updates expanded state when opened', async () => {
|
|
878
888
|
const user = userEvent.setup();
|
|
879
889
|
renderSelect();
|
|
880
890
|
|
|
881
|
-
const trigger = screen.
|
|
891
|
+
const trigger = screen.getByRole('combobox');
|
|
882
892
|
await user.click(trigger);
|
|
883
893
|
|
|
884
894
|
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
885
895
|
});
|
|
886
896
|
|
|
887
|
-
it('
|
|
897
|
+
it('provides listbox structure for screen readers', async () => {
|
|
888
898
|
const user = userEvent.setup();
|
|
889
899
|
renderSelect();
|
|
890
900
|
|
|
891
|
-
const trigger = screen.
|
|
901
|
+
const trigger = screen.getByRole('combobox');
|
|
892
902
|
await user.click(trigger);
|
|
893
903
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
it('has proper option roles on items', async () => {
|
|
899
|
-
const user = userEvent.setup();
|
|
900
|
-
renderSelect();
|
|
901
|
-
|
|
902
|
-
const trigger = screen.getByTestId('select-trigger');
|
|
903
|
-
await user.click(trigger);
|
|
904
|
-
|
|
905
|
-
const items = screen.getAllByRole('option');
|
|
906
|
-
expect(items).toHaveLength(3);
|
|
907
|
-
|
|
908
|
-
items.forEach(item => {
|
|
909
|
-
expect(item).toHaveAttribute('role', 'option');
|
|
910
|
-
});
|
|
904
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
905
|
+
const options = screen.getAllByRole('option');
|
|
906
|
+
expect(options).toHaveLength(3);
|
|
911
907
|
});
|
|
912
908
|
|
|
913
|
-
it('
|
|
909
|
+
it('marks selected option for screen readers', async () => {
|
|
914
910
|
const user = userEvent.setup();
|
|
915
911
|
renderSelect({ defaultValue: 'option2' });
|
|
916
912
|
|
|
917
|
-
const trigger = screen.
|
|
913
|
+
const trigger = screen.getByRole('combobox');
|
|
918
914
|
await user.click(trigger);
|
|
919
915
|
|
|
920
|
-
const
|
|
921
|
-
expect(
|
|
922
|
-
expect(
|
|
923
|
-
expect(
|
|
916
|
+
const options = screen.getAllByRole('option');
|
|
917
|
+
expect(options[0]).toHaveAttribute('aria-selected', 'false');
|
|
918
|
+
expect(options[1]).toHaveAttribute('aria-selected', 'true');
|
|
919
|
+
expect(options[2]).toHaveAttribute('aria-selected', 'false');
|
|
924
920
|
});
|
|
925
921
|
|
|
926
|
-
it('
|
|
922
|
+
it('announces search functionality for screen readers', async () => {
|
|
927
923
|
const user = userEvent.setup();
|
|
928
924
|
renderWithProviders(
|
|
929
925
|
<Select>
|
|
@@ -936,11 +932,38 @@ describe('Select Component', () => {
|
|
|
936
932
|
</Select>
|
|
937
933
|
);
|
|
938
934
|
|
|
939
|
-
|
|
935
|
+
await user.click(screen.getByRole('combobox'));
|
|
936
|
+
|
|
937
|
+
const searchInput = screen.getByLabelText('Search options');
|
|
938
|
+
expect(searchInput).toBeInTheDocument();
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('supports keyboard-only navigation', async () => {
|
|
942
|
+
const user = userEvent.setup();
|
|
943
|
+
renderSelect();
|
|
944
|
+
|
|
945
|
+
const trigger = screen.getByRole('combobox');
|
|
946
|
+
trigger.focus();
|
|
947
|
+
|
|
948
|
+
await user.keyboard('{Enter}');
|
|
949
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
950
|
+
|
|
951
|
+
await user.keyboard('{Escape}');
|
|
952
|
+
await waitFor(() => {
|
|
953
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
954
|
+
});
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
it('maintains focus management when interacting', async () => {
|
|
958
|
+
const user = userEvent.setup();
|
|
959
|
+
renderSelect();
|
|
960
|
+
|
|
961
|
+
const trigger = screen.getByRole('combobox');
|
|
940
962
|
await user.click(trigger);
|
|
941
963
|
|
|
942
|
-
const
|
|
943
|
-
|
|
964
|
+
const options = screen.getAllByRole('option');
|
|
965
|
+
options[0].focus();
|
|
966
|
+
expect(document.activeElement).toBe(options[0]);
|
|
944
967
|
});
|
|
945
968
|
});
|
|
946
969
|
|