@jmruthers/pace-core 0.2.4 → 0.2.6
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-GZHIDA4X.js → api-ETQ6YJ3C.js} +2 -2
- package/dist/{chunk-DY5E3AT7.js → chunk-BEZRLNK3.js} +13 -3
- package/dist/chunk-BEZRLNK3.js.map +1 -0
- package/dist/{chunk-6ZQVSHKL.js → chunk-C5G2A4PO.js} +7 -3
- 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-OKXMUYIB.js → chunk-HNDFPXUU.js} +5 -5
- package/dist/{chunk-7JL3T7BO.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 -15
- 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 -16
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -0
- package/dist/rbac/index.js +22 -10
- package/dist/rbac/index.js.map +1 -1
- 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/api.ts +2 -0
- package/src/rbac/cache.ts +2 -0
- package/src/rbac/hooks.ts +15 -0
- package/src/rbac/types.ts +2 -0
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +13 -18
- package/src/utils/storage/__tests__/helpers.unit.test.ts +9 -7
- package/dist/chunk-6ZQVSHKL.js.map +0 -1
- package/dist/chunk-7JL3T7BO.js.map +0 -1
- package/dist/chunk-DY5E3AT7.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-GZHIDA4X.js.map → api-ETQ6YJ3C.js.map} +0 -0
- /package/dist/{chunk-TMRLB2LA.js.map → chunk-HEMJ4SUJ.js.map} +0 -0
- /package/dist/{chunk-OKXMUYIB.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
|
@@ -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
|
});
|
|
@@ -39,19 +39,37 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
39
39
|
{
|
|
40
40
|
id: 'name',
|
|
41
41
|
isPlaceholder: false,
|
|
42
|
-
column: {
|
|
42
|
+
column: {
|
|
43
|
+
columnDef: { header: 'Name' },
|
|
44
|
+
getCanSort: () => true,
|
|
45
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
46
|
+
getIsSorted: () => false,
|
|
47
|
+
getIsVisible: () => true,
|
|
48
|
+
},
|
|
43
49
|
getContext: () => ({}),
|
|
44
50
|
},
|
|
45
51
|
{
|
|
46
52
|
id: 'email',
|
|
47
53
|
isPlaceholder: false,
|
|
48
|
-
column: {
|
|
54
|
+
column: {
|
|
55
|
+
columnDef: { header: 'Email' },
|
|
56
|
+
getCanSort: () => true,
|
|
57
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
58
|
+
getIsSorted: () => false,
|
|
59
|
+
getIsVisible: () => true,
|
|
60
|
+
},
|
|
49
61
|
getContext: () => ({}),
|
|
50
62
|
},
|
|
51
63
|
{
|
|
52
64
|
id: 'role',
|
|
53
65
|
isPlaceholder: false,
|
|
54
|
-
column: {
|
|
66
|
+
column: {
|
|
67
|
+
columnDef: { header: 'Role' },
|
|
68
|
+
getCanSort: () => true,
|
|
69
|
+
getToggleSortingHandler: () => vi.fn(),
|
|
70
|
+
getIsSorted: () => false,
|
|
71
|
+
getIsVisible: () => true,
|
|
72
|
+
},
|
|
55
73
|
getContext: () => ({}),
|
|
56
74
|
},
|
|
57
75
|
],
|
|
@@ -167,14 +185,6 @@ vi.mock('../core/DataTableContext', () => ({
|
|
|
167
185
|
}));
|
|
168
186
|
|
|
169
187
|
// Mock individual components
|
|
170
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
171
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
172
|
-
<div data-testid="data-table-header">
|
|
173
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
174
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
175
|
-
</div>
|
|
176
|
-
),
|
|
177
|
-
}));
|
|
178
188
|
|
|
179
189
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
180
190
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -255,8 +265,8 @@ describe('DataTable Workflows Tests', () => {
|
|
|
255
265
|
</MockRBACProvider>
|
|
256
266
|
);
|
|
257
267
|
|
|
258
|
-
expect(screen.
|
|
259
|
-
expect(screen.
|
|
268
|
+
expect(screen.getByText('Users Table')).toBeInTheDocument();
|
|
269
|
+
expect(screen.getByText('Manage user accounts')).toBeInTheDocument();
|
|
260
270
|
});
|
|
261
271
|
|
|
262
272
|
it('renders with actions when provided', () => {
|
|
@@ -650,8 +660,8 @@ describe('DataTable Workflows Tests', () => {
|
|
|
650
660
|
);
|
|
651
661
|
|
|
652
662
|
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
653
|
-
expect(screen.
|
|
654
|
-
expect(screen.
|
|
663
|
+
expect(screen.getByText('Comprehensive Workflow Test')).toBeInTheDocument();
|
|
664
|
+
expect(screen.getByText('Testing complete user workflows')).toBeInTheDocument();
|
|
655
665
|
});
|
|
656
666
|
|
|
657
667
|
it('handles workflow with different data types', () => {
|
|
@@ -8,7 +8,7 @@ This directory contains comprehensive tests for the DataTable component, designe
|
|
|
8
8
|
|
|
9
9
|
- **`DataTable.workflowValidation.test.tsx`** - Comprehensive workflow validation covering all core functionality
|
|
10
10
|
- **`DataTable.regressionFixes.test.tsx`** - Documents specific fixes and prevents known regressions
|
|
11
|
-
- **`DataTable.
|
|
11
|
+
- **`DataTable.sorting.test.tsx`** - Comprehensive sorting functionality tests including visual indicators and accessibility
|
|
12
12
|
|
|
13
13
|
### Existing Tests
|
|
14
14
|
|
|
@@ -49,6 +49,12 @@ The `DataTable.workflowValidation.test.tsx` file provides comprehensive coverage
|
|
|
49
49
|
- Filtering behavior
|
|
50
50
|
- Actions preservation during search
|
|
51
51
|
|
|
52
|
+
- **Sorting Workflow Validation (4 tests)**
|
|
53
|
+
- Sortable column indicators (↕️ unsorted, ↑ ascending, ↓ descending)
|
|
54
|
+
- Click behavior for sorting
|
|
55
|
+
- Non-sortable column behavior
|
|
56
|
+
- Visual feedback and accessibility
|
|
57
|
+
|
|
52
58
|
- **Data Integrity Validation (3 tests)**
|
|
53
59
|
- Exact prop data display
|
|
54
60
|
- Empty data handling
|
|
@@ -118,13 +124,16 @@ npm test -- src/components/DataTable/__tests__/DataTable.workflowValidation.test
|
|
|
118
124
|
# Regression fix tests
|
|
119
125
|
npm test -- src/components/DataTable/__tests__/DataTable.regressionFixes.test.tsx
|
|
120
126
|
|
|
127
|
+
# Sorting functionality tests
|
|
128
|
+
npm test -- src/components/DataTable/__tests__/DataTable.sorting.test.tsx
|
|
129
|
+
|
|
121
130
|
# Simple integration tests
|
|
122
131
|
npm test -- src/components/DataTable/__tests__/DataTable.simple.test.tsx
|
|
123
132
|
```
|
|
124
133
|
|
|
125
134
|
## Test Coverage Summary
|
|
126
135
|
|
|
127
|
-
- **
|
|
136
|
+
- **91 total tests** in new regression prevention suites
|
|
128
137
|
- **100% pass rate** for workflow validation
|
|
129
138
|
- **100% pass rate** for regression fixes
|
|
130
139
|
- **100% pass rate** for auto-sizing functionality
|
|
@@ -91,7 +91,6 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
91
91
|
getIsSorted: () => false,
|
|
92
92
|
},
|
|
93
93
|
getContext: () => ({}),
|
|
94
|
-
getSize: () => 150,
|
|
95
94
|
},
|
|
96
95
|
{
|
|
97
96
|
id: 'email',
|
|
@@ -103,7 +102,6 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
103
102
|
getIsSorted: () => false,
|
|
104
103
|
},
|
|
105
104
|
getContext: () => ({}),
|
|
106
|
-
getSize: () => 200,
|
|
107
105
|
},
|
|
108
106
|
{
|
|
109
107
|
id: 'role',
|
|
@@ -115,7 +113,6 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
115
113
|
getIsSorted: () => false,
|
|
116
114
|
},
|
|
117
115
|
getContext: () => ({}),
|
|
118
|
-
getSize: () => 100,
|
|
119
116
|
},
|
|
120
117
|
],
|
|
121
118
|
}],
|
|
@@ -203,14 +200,6 @@ vi.mock('../components/EmptyState', () => ({
|
|
|
203
200
|
}));
|
|
204
201
|
|
|
205
202
|
// Mock individual components
|
|
206
|
-
vi.mock('../components/DataTableHeader', () => ({
|
|
207
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
208
|
-
<div data-testid="data-table-header">
|
|
209
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
210
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
211
|
-
</div>
|
|
212
|
-
),
|
|
213
|
-
}));
|
|
214
203
|
|
|
215
204
|
vi.mock('../components/DataTableToolbar', () => ({
|
|
216
205
|
DataTableToolbar: ({ searchable, globalFilter, onGlobalFilterChange }: any) => (
|
|
@@ -23,7 +23,6 @@ const MockDeleteIcon = ({ className }: { className?: string }) => <div className
|
|
|
23
23
|
*/
|
|
24
24
|
export const mockComponents = {
|
|
25
25
|
DataTableContext: vi.fn(({ children }: any) => <div data-testid="data-table-context">{children}</div>),
|
|
26
|
-
DataTableHeader: vi.fn(() => <div data-testid="data-table-header">Header</div>),
|
|
27
26
|
DataTableToolbar: vi.fn(() => <div data-testid="data-table-toolbar">Toolbar</div>),
|
|
28
27
|
DataTableBody: vi.fn(() => <div data-testid="data-table-body">Body</div>),
|
|
29
28
|
DataTableModals: vi.fn(() => <div data-testid="data-table-modals">Modals</div>),
|
|
@@ -25,7 +25,7 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
25
25
|
);
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
<Select>
|
|
28
|
+
<Select className="w-52">
|
|
29
29
|
<SelectTrigger asChild>
|
|
30
30
|
<Button
|
|
31
31
|
variant="outline"
|
|
@@ -34,7 +34,7 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
34
34
|
<span className="truncate">Columns</span>
|
|
35
35
|
</Button>
|
|
36
36
|
</SelectTrigger>
|
|
37
|
-
<SelectContent
|
|
37
|
+
<SelectContent>
|
|
38
38
|
<div className="p-2 space-y-1">
|
|
39
39
|
<div className="flex gap-1 mb-2">
|
|
40
40
|
<Button
|