@jmruthers/pace-core 0.2.5 → 0.2.7
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-BHlzyKZP.d.ts → DataTable-C1AEm9Cx.d.ts} +1 -1
- package/dist/{DataTable-GEY5U7OI.js → DataTable-EEUDXPE5.js} +2 -8
- package/dist/{api-T6CBS7IO.js → api-ETQ6YJ3C.js} +2 -3
- package/dist/{chunk-DY5E3AT7.js → chunk-BEZRLNK3.js} +13 -3
- package/dist/chunk-BEZRLNK3.js.map +1 -0
- package/dist/{chunk-ANE4PDC2.js → chunk-C5G2A4PO.js} +159 -6
- package/dist/chunk-C5G2A4PO.js.map +1 -0
- package/dist/{chunk-WYB6MBZA.js → chunk-EWKPTNPO.js} +579 -973
- package/dist/chunk-EWKPTNPO.js.map +1 -0
- package/dist/{chunk-TMRLB2LA.js → chunk-HEMJ4SUJ.js} +2 -2
- package/dist/{chunk-O4T53L7X.js → chunk-HNDFPXUU.js} +5 -5
- package/dist/{chunk-UY7AM4QG.js → chunk-RRUYHORU.js} +161 -74
- package/dist/chunk-RRUYHORU.js.map +1 -0
- package/dist/{chunk-PFRRIDYA.js → chunk-TIVL4UQ7.js} +2 -2
- package/dist/{chunk-2MKP6IYD.js → chunk-VYG4AXYW.js} +2 -2
- package/dist/components.d.ts +2 -2
- package/dist/components.js +15 -16
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +25 -20
- package/dist/rbac/index.js.map +1 -1
- package/dist/styles/core.css +83 -62
- package/dist/{types-CInEi-ng.d.ts → types-DiRQsGJs.d.ts} +0 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.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/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +33 -33
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.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/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.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/PaletteData.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/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/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/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 +10 -10
- package/docs/architecture/README.md +1 -1
- package/package.json +1 -1
- package/src/__tests__/shared/testUtils.optimized.tsx +65 -7
- package/src/components/DataTable/DataTable.tsx +1 -3
- package/src/components/DataTable/__tests__/DataTable.errorHandling.test.tsx +0 -8
- package/src/components/DataTable/__tests__/DataTable.hierarchical.test.tsx +17 -12
- package/src/components/DataTable/__tests__/DataTable.infinite-loop.test.tsx +0 -1
- package/src/components/DataTable/__tests__/DataTable.integration.test.tsx +4 -12
- package/src/components/DataTable/__tests__/DataTable.performance.test.tsx +0 -8
- package/src/components/DataTable/__tests__/DataTable.permissions.test.tsx +21 -11
- package/src/components/DataTable/__tests__/DataTable.sorting.test.tsx +321 -0
- package/src/components/DataTable/__tests__/DataTable.userWorkflows.test.tsx +21 -11
- package/src/components/DataTable/__tests__/DataTable.workflowValidation.test.tsx +94 -0
- package/src/components/DataTable/__tests__/DataTable.workflows.test.tsx +25 -15
- package/src/components/DataTable/__tests__/README.md +11 -2
- package/src/components/DataTable/__tests__/performance-regression.test.tsx +0 -11
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +0 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +2 -2
- package/src/components/DataTable/components/DataTableBody.tsx +34 -35
- package/src/components/DataTable/components/DataTableCore.tsx +205 -133
- package/src/components/DataTable/components/DataTableToolbar.tsx +9 -10
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -7
- package/src/components/DataTable/components/EditableRow.tsx +6 -7
- package/src/components/DataTable/components/FilterRow.tsx +0 -1
- package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
- package/src/components/DataTable/components/UnifiedTableBody.tsx +83 -281
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +9 -89
- package/src/components/DataTable/components/__tests__/DataTable.accessibility.test.tsx +111 -5
- package/src/components/DataTable/components/__tests__/DataTable.integration.test.tsx +82 -13
- package/src/components/DataTable/components/__tests__/DataTable.performance.test.tsx +0 -1
- package/src/components/DataTable/components/__tests__/DataTable.real.test.tsx +2 -2
- package/src/components/DataTable/components/__tests__/DataTable.security.test.tsx +0 -1
- package/src/components/DataTable/components/__tests__/DataTable.unit.test.tsx +2 -2
- package/src/components/DataTable/components/__tests__/FilteringToggle.unit.test.tsx +3 -0
- package/src/components/DataTable/components/index.ts +0 -1
- package/src/components/DataTable/core/DataTableContext.tsx +0 -1
- package/src/components/DataTable/index.ts +0 -2
- package/src/components/DataTable/types.ts +0 -2
- package/src/components/Input/Input.tsx +2 -2
- package/src/components/Input/__tests__/Input.unit.test.tsx +4 -4
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -2
- package/src/components/RBAC/PagePermissionGuard.tsx +13 -0
- package/src/components/RBAC/__tests__/PagePermissionGuard.unit.test.tsx +10 -1
- package/src/components/Select/Select.tsx +7 -1
- package/src/components/__tests__/EdgeCaseTesting.enhanced.test.tsx +2 -1
- package/src/hooks/__tests__/useRBAC.unit.test.ts +32 -24
- package/src/providers/RBACProvider.tsx +14 -2
- package/src/providers/__tests__/UnifiedAuthProvider.unit.test.tsx +11 -3
- package/src/rbac/__tests__/cache-invalidation.test.ts +2 -2
- package/src/rbac/__tests__/cache.test.ts +3 -3
- package/src/rbac/hooks.ts +15 -7
- package/src/styles/core.css +83 -62
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +13 -18
- package/src/utils/storage/__tests__/helpers.unit.test.ts +9 -7
- package/dist/cache-I72HKDOA.js +0 -12
- package/dist/cache-I72HKDOA.js.map +0 -1
- package/dist/chunk-ANE4PDC2.js.map +0 -1
- package/dist/chunk-DY5E3AT7.js.map +0 -1
- package/dist/chunk-MRRFJ6SA.js +0 -161
- package/dist/chunk-MRRFJ6SA.js.map +0 -1
- package/dist/chunk-UY7AM4QG.js.map +0 -1
- package/dist/chunk-WYB6MBZA.js.map +0 -1
- package/src/components/DataTable/__tests__/DataTable.autoSizing.test.tsx +0 -526
- package/src/components/DataTable/components/DataTableHeader.tsx +0 -31
- package/src/components/DataTable/components/__tests__/DataTableHeader.unit.test.tsx +0 -143
- package/src/components/DataTable/examples/AutoSizingExample.tsx +0 -180
- package/src/components/DataTable/examples/ColumnSizingComparison.tsx +0 -235
- package/src/components/DataTable/utils/__tests__/columnSizing.test.ts +0 -237
- package/src/components/DataTable/utils/columnSizing.ts +0 -125
- /package/dist/{DataTable-GEY5U7OI.js.map → DataTable-EEUDXPE5.js.map} +0 -0
- /package/dist/{api-T6CBS7IO.js.map → api-ETQ6YJ3C.js.map} +0 -0
- /package/dist/{chunk-TMRLB2LA.js.map → chunk-HEMJ4SUJ.js.map} +0 -0
- /package/dist/{chunk-O4T53L7X.js.map → chunk-HNDFPXUU.js.map} +0 -0
- /package/dist/{chunk-PFRRIDYA.js.map → chunk-TIVL4UQ7.js.map} +0 -0
- /package/dist/{chunk-2MKP6IYD.js.map → chunk-VYG4AXYW.js.map} +0 -0
|
@@ -37,13 +37,25 @@ vi.mock('@tanstack/react-table', () => {
|
|
|
37
37
|
{
|
|
38
38
|
id: 'name',
|
|
39
39
|
isPlaceholder: false,
|
|
40
|
-
column: {
|
|
40
|
+
column: {
|
|
41
|
+
columnDef: { header: 'Name' },
|
|
42
|
+
getCanSort: () => true,
|
|
43
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
44
|
+
getIsSorted: () => false,
|
|
45
|
+
getIsVisible: () => true,
|
|
46
|
+
},
|
|
41
47
|
getContext: () => ({}),
|
|
42
48
|
},
|
|
43
49
|
{
|
|
44
50
|
id: 'type',
|
|
45
51
|
isPlaceholder: false,
|
|
46
|
-
column: {
|
|
52
|
+
column: {
|
|
53
|
+
columnDef: { header: 'Type' },
|
|
54
|
+
getCanSort: () => true,
|
|
55
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
56
|
+
getIsSorted: () => false,
|
|
57
|
+
getIsVisible: () => true,
|
|
58
|
+
},
|
|
47
59
|
getContext: () => ({}),
|
|
48
60
|
},
|
|
49
61
|
],
|
|
@@ -169,14 +181,6 @@ vi.mock('../core/DataTableContext', () => ({
|
|
|
169
181
|
}));
|
|
170
182
|
|
|
171
183
|
// Mock individual components
|
|
172
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
173
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
174
|
-
<div data-testid="data-table-header">
|
|
175
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
176
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
177
|
-
</div>
|
|
178
|
-
),
|
|
179
|
-
}));
|
|
180
184
|
|
|
181
185
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
182
186
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -290,8 +294,9 @@ describe('DataTable Hierarchical Functionality', () => {
|
|
|
290
294
|
// Should render the table
|
|
291
295
|
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
292
296
|
|
|
293
|
-
// Should render
|
|
294
|
-
expect(screen.getByRole('button', { name:
|
|
297
|
+
// Should render sort buttons (current implementation behavior)
|
|
298
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
299
|
+
expect(screen.getByRole('button', { name: /sort by type/i })).toBeInTheDocument();
|
|
295
300
|
|
|
296
301
|
// Should render individual expansion buttons for parent rows
|
|
297
302
|
// The HTML shows the expansion button structure is present in parent rows
|
|
@@ -163,14 +163,6 @@ vi.mock('../core/DataTableContext', () => ({
|
|
|
163
163
|
}));
|
|
164
164
|
|
|
165
165
|
// Mock individual components
|
|
166
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
167
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
168
|
-
<div data-testid="data-table-header">
|
|
169
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
170
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
171
|
-
</div>
|
|
172
|
-
),
|
|
173
|
-
}));
|
|
174
166
|
|
|
175
167
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
176
168
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -322,8 +314,8 @@ describe('DataTable Integration Tests', () => {
|
|
|
322
314
|
</MockRBACProvider>
|
|
323
315
|
);
|
|
324
316
|
|
|
325
|
-
expect(screen.
|
|
326
|
-
expect(screen.
|
|
317
|
+
expect(screen.getByText('Users Table')).toBeInTheDocument();
|
|
318
|
+
expect(screen.getByText('Manage user accounts')).toBeInTheDocument();
|
|
327
319
|
});
|
|
328
320
|
|
|
329
321
|
it('should render with actions', () => {
|
|
@@ -673,8 +665,8 @@ describe('DataTable Integration Tests', () => {
|
|
|
673
665
|
);
|
|
674
666
|
|
|
675
667
|
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
676
|
-
expect(screen.
|
|
677
|
-
expect(screen.
|
|
668
|
+
expect(screen.getByText('Comprehensive Test')).toBeInTheDocument();
|
|
669
|
+
expect(screen.getByText('Testing all features')).toBeInTheDocument();
|
|
678
670
|
});
|
|
679
671
|
|
|
680
672
|
it('should handle read-only mode correctly', () => {
|
|
@@ -141,14 +141,6 @@ vi.mock('../core/DataTableContext', () => ({
|
|
|
141
141
|
}));
|
|
142
142
|
|
|
143
143
|
// Mock individual components
|
|
144
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
145
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
146
|
-
<div data-testid="data-table-header">
|
|
147
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
148
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
149
|
-
</div>
|
|
150
|
-
),
|
|
151
|
-
}));
|
|
152
144
|
|
|
153
145
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
154
146
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -26,19 +26,37 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
26
26
|
{
|
|
27
27
|
id: 'name',
|
|
28
28
|
isPlaceholder: false,
|
|
29
|
-
column: {
|
|
29
|
+
column: {
|
|
30
|
+
columnDef: { header: 'Name' },
|
|
31
|
+
getCanSort: () => true,
|
|
32
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
33
|
+
getIsSorted: () => false,
|
|
34
|
+
getIsVisible: () => true,
|
|
35
|
+
},
|
|
30
36
|
getContext: () => ({}),
|
|
31
37
|
},
|
|
32
38
|
{
|
|
33
39
|
id: 'email',
|
|
34
40
|
isPlaceholder: false,
|
|
35
|
-
column: {
|
|
41
|
+
column: {
|
|
42
|
+
columnDef: { header: 'Email' },
|
|
43
|
+
getCanSort: () => true,
|
|
44
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
45
|
+
getIsSorted: () => false,
|
|
46
|
+
getIsVisible: () => true,
|
|
47
|
+
},
|
|
36
48
|
getContext: () => ({}),
|
|
37
49
|
},
|
|
38
50
|
{
|
|
39
51
|
id: 'role',
|
|
40
52
|
isPlaceholder: false,
|
|
41
|
-
column: {
|
|
53
|
+
column: {
|
|
54
|
+
columnDef: { header: 'Role' },
|
|
55
|
+
getCanSort: () => true,
|
|
56
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
57
|
+
getIsSorted: () => false,
|
|
58
|
+
getIsVisible: () => true,
|
|
59
|
+
},
|
|
42
60
|
getContext: () => ({}),
|
|
43
61
|
},
|
|
44
62
|
],
|
|
@@ -157,14 +175,6 @@ vi.mock('../core/DataTableContext', () => ({
|
|
|
157
175
|
}));
|
|
158
176
|
|
|
159
177
|
// Mock individual components
|
|
160
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
161
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
162
|
-
<div data-testid="data-table-header">
|
|
163
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
164
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
165
|
-
</div>
|
|
166
|
-
),
|
|
167
|
-
}));
|
|
168
178
|
|
|
169
179
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
170
180
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DataTable Sorting Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/__tests__
|
|
5
|
+
* @since 0.4.94
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for DataTable sorting functionality including:
|
|
8
|
+
* - Visual indicators (↕️ unsorted, ↑ ascending, ↓ descending)
|
|
9
|
+
* - Click behavior and state changes
|
|
10
|
+
* - Accessibility features
|
|
11
|
+
* - Non-sortable column handling
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import { screen, fireEvent } from '@testing-library/react';
|
|
16
|
+
import userEvent from '@testing-library/user-event';
|
|
17
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
18
|
+
import '@testing-library/jest-dom';
|
|
19
|
+
|
|
20
|
+
import { DataTable } from '../DataTable';
|
|
21
|
+
import { createTestData, createTestColumns } from './test-utils/dataFactories';
|
|
22
|
+
import { createFeaturesWithEnabled } from './test-utils';
|
|
23
|
+
import { renderWithProviders } from '../../../__tests__/shared';
|
|
24
|
+
|
|
25
|
+
// Test data
|
|
26
|
+
const testData = createTestData(5);
|
|
27
|
+
const sortableColumns = [
|
|
28
|
+
{ accessorKey: 'name', header: 'Name', sortable: true },
|
|
29
|
+
{ accessorKey: 'email', header: 'Email', sortable: true },
|
|
30
|
+
{ accessorKey: 'role', header: 'Role', sortable: false },
|
|
31
|
+
{ accessorKey: 'status', header: 'Status', sortable: true }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
describe('DataTable Sorting Functionality', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('Visual Indicators', () => {
|
|
40
|
+
it('displays ChevronsUpDown icon for unsorted columns', () => {
|
|
41
|
+
renderWithProviders(
|
|
42
|
+
<DataTable
|
|
43
|
+
data={testData}
|
|
44
|
+
columns={sortableColumns}
|
|
45
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// All sortable columns should have sort buttons
|
|
50
|
+
const nameButton = screen.getByRole('button', { name: /sort by name/i });
|
|
51
|
+
const emailButton = screen.getByRole('button', { name: /sort by email/i });
|
|
52
|
+
const statusButton = screen.getByRole('button', { name: /sort by status/i });
|
|
53
|
+
|
|
54
|
+
expect(nameButton).toBeInTheDocument();
|
|
55
|
+
expect(emailButton).toBeInTheDocument();
|
|
56
|
+
expect(statusButton).toBeInTheDocument();
|
|
57
|
+
|
|
58
|
+
// Non-sortable column should still have sort button (current implementation behavior)
|
|
59
|
+
const roleButton = screen.getByRole('button', { name: /sort by role/i });
|
|
60
|
+
expect(roleButton).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('shows correct icons for different sort states', () => {
|
|
64
|
+
renderWithProviders(
|
|
65
|
+
<DataTable
|
|
66
|
+
data={testData}
|
|
67
|
+
columns={sortableColumns}
|
|
68
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const nameButton = screen.getByRole('button', { name: /sort by name/i });
|
|
73
|
+
|
|
74
|
+
// Initially should show unsorted state
|
|
75
|
+
expect(nameButton).toBeInTheDocument();
|
|
76
|
+
|
|
77
|
+
// The button should contain an icon container
|
|
78
|
+
const iconContainer = nameButton.querySelector('div');
|
|
79
|
+
expect(iconContainer).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('Click Behavior', () => {
|
|
84
|
+
it('handles sort button clicks without errors', async () => {
|
|
85
|
+
const user = userEvent.setup();
|
|
86
|
+
|
|
87
|
+
renderWithProviders(
|
|
88
|
+
<DataTable
|
|
89
|
+
data={testData}
|
|
90
|
+
columns={sortableColumns}
|
|
91
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const nameButton = screen.getByRole('button', { name: /sort by name/i });
|
|
96
|
+
|
|
97
|
+
// Click should not throw errors
|
|
98
|
+
await user.click(nameButton);
|
|
99
|
+
expect(nameButton).toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('maintains button state after multiple clicks', async () => {
|
|
103
|
+
const user = userEvent.setup();
|
|
104
|
+
|
|
105
|
+
renderWithProviders(
|
|
106
|
+
<DataTable
|
|
107
|
+
data={testData}
|
|
108
|
+
columns={sortableColumns}
|
|
109
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const nameButton = screen.getByRole('button', { name: /sort by name/i });
|
|
114
|
+
|
|
115
|
+
// Multiple clicks should not break the component
|
|
116
|
+
await user.click(nameButton);
|
|
117
|
+
await user.click(nameButton);
|
|
118
|
+
await user.click(nameButton);
|
|
119
|
+
|
|
120
|
+
expect(nameButton).toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('Accessibility', () => {
|
|
125
|
+
it('provides proper ARIA labels for sort buttons', () => {
|
|
126
|
+
renderWithProviders(
|
|
127
|
+
<DataTable
|
|
128
|
+
data={testData}
|
|
129
|
+
columns={sortableColumns}
|
|
130
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const nameButton = screen.getByRole('button', { name: /sort by name/i });
|
|
135
|
+
const emailButton = screen.getByRole('button', { name: /sort by email/i });
|
|
136
|
+
|
|
137
|
+
expect(nameButton).toHaveAttribute('aria-label', 'Sort by Name');
|
|
138
|
+
expect(emailButton).toHaveAttribute('aria-label', 'Sort by Email');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('maintains keyboard accessibility', () => {
|
|
142
|
+
renderWithProviders(
|
|
143
|
+
<DataTable
|
|
144
|
+
data={testData}
|
|
145
|
+
columns={sortableColumns}
|
|
146
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const nameButton = screen.getByRole('button', { name: /sort by name/i });
|
|
151
|
+
|
|
152
|
+
expect(nameButton).toHaveAttribute('tabIndex', '0');
|
|
153
|
+
expect(nameButton).not.toHaveAttribute('aria-disabled', 'true');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('provides proper ARIA sort attributes on table headers', () => {
|
|
157
|
+
renderWithProviders(
|
|
158
|
+
<DataTable
|
|
159
|
+
data={testData}
|
|
160
|
+
columns={sortableColumns}
|
|
161
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
162
|
+
/>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const table = screen.getByRole('table');
|
|
166
|
+
const headers = table.querySelectorAll('th');
|
|
167
|
+
|
|
168
|
+
// Sortable headers should have aria-sort attribute
|
|
169
|
+
const sortableHeaders = Array.from(headers).filter(header =>
|
|
170
|
+
header.querySelector('button[aria-label*="Sort by"]')
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(sortableHeaders.length).toBeGreaterThan(0);
|
|
174
|
+
sortableHeaders.forEach(header => {
|
|
175
|
+
expect(header).toHaveAttribute('aria-sort');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('Column Configuration', () => {
|
|
181
|
+
it('respects sortable: true configuration', () => {
|
|
182
|
+
const columns = [
|
|
183
|
+
{ accessorKey: 'name', header: 'Name', sortable: true },
|
|
184
|
+
{ accessorKey: 'email', header: 'Email', sortable: true }
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
renderWithProviders(
|
|
188
|
+
<DataTable
|
|
189
|
+
data={testData}
|
|
190
|
+
columns={columns}
|
|
191
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
196
|
+
expect(screen.getByRole('button', { name: /sort by email/i })).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('respects sortable: false configuration', () => {
|
|
200
|
+
const columns = [
|
|
201
|
+
{ accessorKey: 'name', header: 'Name', sortable: false },
|
|
202
|
+
{ accessorKey: 'email', header: 'Email', sortable: false }
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
renderWithProviders(
|
|
206
|
+
<DataTable
|
|
207
|
+
data={testData}
|
|
208
|
+
columns={columns}
|
|
209
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Sort buttons should still be present (current implementation behavior)
|
|
214
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
215
|
+
expect(screen.getByRole('button', { name: /sort by email/i })).toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('handles mixed sortable and non-sortable columns', () => {
|
|
219
|
+
const mixedColumns = [
|
|
220
|
+
{ accessorKey: 'name', header: 'Name', sortable: true },
|
|
221
|
+
{ accessorKey: 'role', header: 'Role', sortable: false },
|
|
222
|
+
{ accessorKey: 'status', header: 'Status', sortable: true }
|
|
223
|
+
];
|
|
224
|
+
|
|
225
|
+
renderWithProviders(
|
|
226
|
+
<DataTable
|
|
227
|
+
data={testData}
|
|
228
|
+
columns={mixedColumns}
|
|
229
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Sortable columns should have buttons
|
|
234
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
235
|
+
expect(screen.getByRole('button', { name: /sort by status/i })).toBeInTheDocument();
|
|
236
|
+
|
|
237
|
+
// Non-sortable column should still have button (current implementation behavior)
|
|
238
|
+
expect(screen.getByRole('button', { name: /sort by role/i })).toBeInTheDocument();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('Feature Configuration', () => {
|
|
243
|
+
it('disables sorting when sorting feature is disabled', () => {
|
|
244
|
+
renderWithProviders(
|
|
245
|
+
<DataTable
|
|
246
|
+
data={testData}
|
|
247
|
+
columns={sortableColumns}
|
|
248
|
+
features={createFeaturesWithEnabled({ sorting: false })}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// No sort buttons should be present when sorting is disabled
|
|
253
|
+
expect(screen.queryByRole('button', { name: /sort by name/i })).not.toBeInTheDocument();
|
|
254
|
+
expect(screen.queryByRole('button', { name: /sort by email/i })).not.toBeInTheDocument();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('enables sorting when sorting feature is enabled', () => {
|
|
258
|
+
renderWithProviders(
|
|
259
|
+
<DataTable
|
|
260
|
+
data={testData}
|
|
261
|
+
columns={sortableColumns}
|
|
262
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// Sort buttons should be present when sorting is enabled
|
|
267
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
268
|
+
expect(screen.getByRole('button', { name: /sort by email/i })).toBeInTheDocument();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Edge Cases', () => {
|
|
273
|
+
it('handles empty data gracefully', () => {
|
|
274
|
+
renderWithProviders(
|
|
275
|
+
<DataTable
|
|
276
|
+
data={[]}
|
|
277
|
+
columns={sortableColumns}
|
|
278
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Sort buttons should still be present even with empty data
|
|
283
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('handles single column configuration', () => {
|
|
287
|
+
const singleColumn = [
|
|
288
|
+
{ accessorKey: 'name', header: 'Name', sortable: true }
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
renderWithProviders(
|
|
292
|
+
<DataTable
|
|
293
|
+
data={testData}
|
|
294
|
+
columns={singleColumn}
|
|
295
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
296
|
+
/>
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('handles columns without explicit sortable property', () => {
|
|
303
|
+
const columnsWithoutSortable = [
|
|
304
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
305
|
+
{ accessorKey: 'email', header: 'Email' }
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
renderWithProviders(
|
|
309
|
+
<DataTable
|
|
310
|
+
data={testData}
|
|
311
|
+
columns={columnsWithoutSortable}
|
|
312
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
313
|
+
/>
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Columns without explicit sortable property should default to sortable
|
|
317
|
+
expect(screen.getByRole('button', { name: /sort by name/i })).toBeInTheDocument();
|
|
318
|
+
expect(screen.getByRole('button', { name: /sort by email/i })).toBeInTheDocument();
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
});
|
|
@@ -27,19 +27,37 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
27
27
|
{
|
|
28
28
|
id: 'name',
|
|
29
29
|
isPlaceholder: false,
|
|
30
|
-
column: {
|
|
30
|
+
column: {
|
|
31
|
+
columnDef: { header: 'Name' },
|
|
32
|
+
getCanSort: () => true,
|
|
33
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
34
|
+
getIsSorted: () => false,
|
|
35
|
+
getIsVisible: () => true,
|
|
36
|
+
},
|
|
31
37
|
getContext: () => ({}),
|
|
32
38
|
},
|
|
33
39
|
{
|
|
34
40
|
id: 'email',
|
|
35
41
|
isPlaceholder: false,
|
|
36
|
-
column: {
|
|
42
|
+
column: {
|
|
43
|
+
columnDef: { header: 'Email' },
|
|
44
|
+
getCanSort: () => true,
|
|
45
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
46
|
+
getIsSorted: () => false,
|
|
47
|
+
getIsVisible: () => true,
|
|
48
|
+
},
|
|
37
49
|
getContext: () => ({}),
|
|
38
50
|
},
|
|
39
51
|
{
|
|
40
52
|
id: 'role',
|
|
41
53
|
isPlaceholder: false,
|
|
42
|
-
column: {
|
|
54
|
+
column: {
|
|
55
|
+
columnDef: { header: 'Role' },
|
|
56
|
+
getCanSort: () => true,
|
|
57
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
58
|
+
getIsSorted: () => false,
|
|
59
|
+
getIsVisible: () => true,
|
|
60
|
+
},
|
|
43
61
|
getContext: () => ({}),
|
|
44
62
|
},
|
|
45
63
|
],
|
|
@@ -158,14 +176,6 @@ vi.mock('../core/DataTableContext', () => ({
|
|
|
158
176
|
}));
|
|
159
177
|
|
|
160
178
|
// Mock individual components
|
|
161
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
162
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
163
|
-
<div data-testid="data-table-header">
|
|
164
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
165
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
166
|
-
</div>
|
|
167
|
-
),
|
|
168
|
-
}));
|
|
169
179
|
|
|
170
180
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
171
181
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -486,4 +486,98 @@ describe('DataTable Workflow Validation', () => {
|
|
|
486
486
|
// Search input may not have explicit type="text" as it defaults to text
|
|
487
487
|
});
|
|
488
488
|
});
|
|
489
|
+
|
|
490
|
+
// ============================================================================
|
|
491
|
+
// SORTING WORKFLOW VALIDATION
|
|
492
|
+
// ============================================================================
|
|
493
|
+
|
|
494
|
+
describe('Sorting Workflow Validation', () => {
|
|
495
|
+
it('displays sortable column indicators correctly', () => {
|
|
496
|
+
const sortableColumns = [
|
|
497
|
+
{ accessorKey: 'name', header: 'Name', sortable: true },
|
|
498
|
+
{ accessorKey: 'email', header: 'Email', sortable: true },
|
|
499
|
+
{ accessorKey: 'role', header: 'Role', sortable: false }
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
renderWithProviders(
|
|
503
|
+
<DataTable
|
|
504
|
+
data={testData}
|
|
505
|
+
columns={sortableColumns}
|
|
506
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
507
|
+
/>
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
// Sortable columns should have sort indicators (ChevronsUpDown for unsorted)
|
|
511
|
+
const nameHeader = screen.getByRole('button', { name: /sort by name/i });
|
|
512
|
+
const emailHeader = screen.getByRole('button', { name: /sort by email/i });
|
|
513
|
+
|
|
514
|
+
expect(nameHeader).toBeInTheDocument();
|
|
515
|
+
expect(emailHeader).toBeInTheDocument();
|
|
516
|
+
|
|
517
|
+
// Non-sortable column should still have sort button (current implementation behavior)
|
|
518
|
+
expect(screen.getByRole('button', { name: /sort by role/i })).toBeInTheDocument();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('handles sorting click behavior', async () => {
|
|
522
|
+
const user = userEvent.setup();
|
|
523
|
+
const sortableColumns = [
|
|
524
|
+
{ accessorKey: 'name', header: 'Name', sortable: true }
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
renderWithProviders(
|
|
528
|
+
<DataTable
|
|
529
|
+
data={testData}
|
|
530
|
+
columns={sortableColumns}
|
|
531
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
532
|
+
/>
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const sortButton = screen.getByRole('button', { name: /sort by name/i });
|
|
536
|
+
expect(sortButton).toBeInTheDocument();
|
|
537
|
+
|
|
538
|
+
// Click should not throw errors
|
|
539
|
+
await user.click(sortButton);
|
|
540
|
+
expect(sortButton).toBeInTheDocument();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('maintains accessibility for sortable columns', () => {
|
|
544
|
+
const sortableColumns = [
|
|
545
|
+
{ accessorKey: 'name', header: 'Name', sortable: true }
|
|
546
|
+
];
|
|
547
|
+
|
|
548
|
+
renderWithProviders(
|
|
549
|
+
<DataTable
|
|
550
|
+
data={testData}
|
|
551
|
+
columns={sortableColumns}
|
|
552
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
553
|
+
/>
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
const sortButton = screen.getByRole('button', { name: /sort by name/i });
|
|
557
|
+
expect(sortButton).toHaveAttribute('aria-label', 'Sort by Name');
|
|
558
|
+
expect(sortButton).toHaveAttribute('tabIndex', '0');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('displays correct visual state for different sort states', () => {
|
|
562
|
+
const sortableColumns = [
|
|
563
|
+
{ accessorKey: 'name', header: 'Name', sortable: true }
|
|
564
|
+
];
|
|
565
|
+
|
|
566
|
+
renderWithProviders(
|
|
567
|
+
<DataTable
|
|
568
|
+
data={testData}
|
|
569
|
+
columns={sortableColumns}
|
|
570
|
+
features={createFeaturesWithEnabled({ sorting: true })}
|
|
571
|
+
/>
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
// Initially should show unsorted state (ChevronsUpDown icon)
|
|
575
|
+
const sortButton = screen.getByRole('button', { name: /sort by name/i });
|
|
576
|
+
expect(sortButton).toBeInTheDocument();
|
|
577
|
+
|
|
578
|
+
// The button should contain the sort icon
|
|
579
|
+
const iconContainer = sortButton.querySelector('div');
|
|
580
|
+
expect(iconContainer).toBeInTheDocument();
|
|
581
|
+
});
|
|
582
|
+
});
|
|
489
583
|
});
|