@jmruthers/pace-core 0.5.115 → 0.5.116
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/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
- package/dist/{DataTable-H5KJCAIS.js → DataTable-ZOAKQ3SU.js} +10 -9
- package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
- package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
- package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
- package/dist/{chunk-SYXOZQ4P.js → chunk-2GJ5GL77.js} +1 -1
- package/dist/chunk-2GJ5GL77.js.map +1 -0
- package/dist/{chunk-XYRZV7R5.js → chunk-2LM4QQGH.js} +30 -34
- package/dist/chunk-2LM4QQGH.js.map +1 -0
- package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
- package/dist/chunk-3DBFLLLU.js.map +1 -0
- package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
- package/dist/chunk-ECOVPXYS.js.map +1 -0
- package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
- package/dist/chunk-KA3PSVNV.js.map +1 -0
- package/dist/{chunk-HKWQN44G.js → chunk-KMPWND3F.js} +15 -15
- package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
- package/dist/{chunk-NEONKMTU.js → chunk-LZYHAL7Y.js} +9 -4
- package/dist/{chunk-NEONKMTU.js.map → chunk-LZYHAL7Y.js.map} +1 -1
- package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
- package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
- package/dist/chunk-P3PUOL6B.js.map +1 -0
- package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
- package/dist/chunk-PHDAXDHB.js.map +1 -0
- package/dist/chunk-UJI6WSMD.js +201 -0
- package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
- package/dist/{chunk-OUU3SP6I.js → chunk-UKZWNQMB.js} +50 -7
- package/dist/{chunk-OUU3SP6I.js.map → chunk-UKZWNQMB.js.map} +1 -1
- package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
- package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +10 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +3 -2
- package/dist/rbac/index.d.ts +82 -1
- package/dist/rbac/index.js +13 -10
- package/dist/{useToast-DVT4dMtf.d.ts → useToast-Cs_g32bg.d.ts} +1 -1
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/dist/validation.js +3 -1
- package/dist/validation.js.map +1 -1
- package/docs/README.md +4 -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 +35 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +71 -0
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +122 -0
- 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 +27 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +52 -0
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +41 -14
- package/docs/architecture/rpc-function-standards.md +193 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_STANDARD.md +244 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
- package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
- package/src/components/DataTable/components/DataTableCore.tsx +29 -2
- package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
- package/src/components/DataTable/components/EditableRow.tsx +18 -1
- package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
- package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
- package/src/components/EventSelector/EventSelector.tsx +5 -25
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
- package/src/components/Select/Select.tsx +8 -0
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
- package/src/hooks/useEventTheme.ts +49 -18
- package/src/hooks/usePermissionCache.ts +5 -3
- package/src/hooks/useSecureDataAccess.ts +11 -1
- package/src/hooks/useToast.ts +1 -1
- package/src/providers/services/EventServiceProvider.tsx +15 -8
- package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
- package/src/rbac/audit.test.ts +206 -0
- package/src/rbac/audit.ts +37 -2
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
- package/src/rbac/errors.test.ts +340 -0
- package/src/rbac/hooks/index.ts +9 -0
- package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
- package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
- package/src/rbac/hooks/useRoleManagement.ts +255 -0
- package/src/services/AuthService.ts +10 -0
- package/src/services/EventService.ts +111 -50
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +60 -45
- package/src/services/interfaces/IEventService.ts +1 -1
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
- package/src/utils/__tests__/logger.unit.test.ts +398 -0
- package/src/utils/__tests__/validation.unit.test.ts +225 -1
- package/src/utils/file-reference.test.ts +214 -0
- package/dist/chunk-3OGQLOJM.js.map +0 -1
- package/dist/chunk-5CDJCTOO.js +0 -190
- package/dist/chunk-F6QB26OS.js.map +0 -1
- package/dist/chunk-KTHLNIMA.js.map +0 -1
- package/dist/chunk-OO3V7W4H.js.map +0 -1
- package/dist/chunk-SYXOZQ4P.js.map +0 -1
- package/dist/chunk-XYRZV7R5.js.map +0 -1
- package/dist/chunk-ZPXWJA4H.js.map +0 -1
- package/src/rbac/audit-enhanced.ts +0 -351
- /package/dist/{DataTable-H5KJCAIS.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
- /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
- /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
- /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
- /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
- /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
- /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
- /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Filter Row Component Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/Components/__tests__
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive test suite for FilterRow component following testing guidelines.
|
|
8
|
+
* Tests cover all major functionality, edge cases, and user interactions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { render, screen, renderHook } from '@testing-library/react';
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
import { createColumnHelper, useReactTable, getCoreRowModel, getFilteredRowModel } from '@tanstack/react-table';
|
|
15
|
+
import { FilterRow } from '../FilterRow';
|
|
16
|
+
import type { DataRecord } from '../../types';
|
|
17
|
+
|
|
18
|
+
// Mock ColumnFilter component
|
|
19
|
+
vi.mock('../ColumnFilter', () => ({
|
|
20
|
+
ColumnFilter: ({ column, filterType, options, placeholder }: any) => (
|
|
21
|
+
<div
|
|
22
|
+
data-testid="column-filter"
|
|
23
|
+
data-column-id={column.id}
|
|
24
|
+
data-filter-type={filterType}
|
|
25
|
+
data-options-count={options?.length || 0}
|
|
26
|
+
data-placeholder={placeholder}
|
|
27
|
+
>
|
|
28
|
+
Filter: {column.id}
|
|
29
|
+
</div>
|
|
30
|
+
),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
interface TestData extends DataRecord {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
age: number;
|
|
38
|
+
status: string;
|
|
39
|
+
createdAt: Date;
|
|
40
|
+
createdDate?: Date; // For date filter test
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('[component] FilterRow', () => {
|
|
44
|
+
const columnHelper = createColumnHelper<TestData>();
|
|
45
|
+
|
|
46
|
+
const defaultColumns = [
|
|
47
|
+
columnHelper.accessor('name', {
|
|
48
|
+
header: 'Name',
|
|
49
|
+
}),
|
|
50
|
+
columnHelper.accessor('email', {
|
|
51
|
+
header: 'Email',
|
|
52
|
+
}),
|
|
53
|
+
columnHelper.accessor('age', {
|
|
54
|
+
header: 'Age',
|
|
55
|
+
}),
|
|
56
|
+
columnHelper.accessor('status', {
|
|
57
|
+
header: 'Status',
|
|
58
|
+
filterSelectOptions: [
|
|
59
|
+
{ value: 'active', label: 'Active' },
|
|
60
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
61
|
+
],
|
|
62
|
+
}),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const defaultData: TestData[] = [
|
|
66
|
+
{ id: '1', name: 'John', email: 'john@example.com', age: 30, status: 'active', createdAt: new Date() },
|
|
67
|
+
{ id: '2', name: 'Jane', email: 'jane@example.com', age: 25, status: 'inactive', createdAt: new Date() },
|
|
68
|
+
{ id: '3', name: 'Bob', email: 'bob@example.com', age: 35, status: 'active', createdAt: new Date() },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const createTable = (columns = defaultColumns, data = defaultData) => {
|
|
72
|
+
// Use renderHook to call useReactTable hook properly
|
|
73
|
+
// Import the row model functions from TanStack Table
|
|
74
|
+
const { result } = renderHook(() => {
|
|
75
|
+
return useReactTable({
|
|
76
|
+
data,
|
|
77
|
+
columns,
|
|
78
|
+
enableFiltering: true,
|
|
79
|
+
// Use the actual row model functions from TanStack Table
|
|
80
|
+
getCoreRowModel: getCoreRowModel(),
|
|
81
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
82
|
+
// Provide minimal state to avoid errors
|
|
83
|
+
initialState: {
|
|
84
|
+
columnFilters: [],
|
|
85
|
+
sorting: [],
|
|
86
|
+
columnVisibility: {},
|
|
87
|
+
globalFilter: '',
|
|
88
|
+
pagination: { pageIndex: 0, pageSize: 10 },
|
|
89
|
+
grouping: [],
|
|
90
|
+
expanded: {},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
return result.current;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
vi.clearAllMocks();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
vi.clearAllMocks();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Rendering', () => {
|
|
106
|
+
it('renders filter row with all visible columns', () => {
|
|
107
|
+
const table = createTable();
|
|
108
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
109
|
+
|
|
110
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
111
|
+
|
|
112
|
+
const filterRow = screen.getByRole('row');
|
|
113
|
+
expect(filterRow).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('renders ColumnFilter for each filterable column', () => {
|
|
117
|
+
const table = createTable();
|
|
118
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
119
|
+
|
|
120
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
121
|
+
|
|
122
|
+
const filters = screen.getAllByTestId('column-filter');
|
|
123
|
+
expect(filters.length).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('displays "No filter" for non-filterable columns', () => {
|
|
127
|
+
const columns = [
|
|
128
|
+
columnHelper.accessor('name', {
|
|
129
|
+
header: 'Name',
|
|
130
|
+
enableColumnFilter: false,
|
|
131
|
+
}),
|
|
132
|
+
];
|
|
133
|
+
const table = createTable(columns);
|
|
134
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
135
|
+
|
|
136
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
137
|
+
|
|
138
|
+
expect(screen.getByText('No filter')).toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('renders filter with correct placeholder', () => {
|
|
142
|
+
const table = createTable();
|
|
143
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
144
|
+
|
|
145
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
146
|
+
|
|
147
|
+
const filters = screen.getAllByTestId('column-filter');
|
|
148
|
+
expect(filters[0]).toHaveAttribute('data-placeholder', expect.stringContaining('Filter'));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('Filter Type Detection', () => {
|
|
153
|
+
it('uses explicit filterType when provided', () => {
|
|
154
|
+
const columns = [
|
|
155
|
+
columnHelper.accessor('status', {
|
|
156
|
+
header: 'Status',
|
|
157
|
+
filterType: 'text',
|
|
158
|
+
}),
|
|
159
|
+
];
|
|
160
|
+
const table = createTable(columns);
|
|
161
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
162
|
+
|
|
163
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
164
|
+
|
|
165
|
+
const filter = screen.getByTestId('column-filter');
|
|
166
|
+
expect(filter).toHaveAttribute('data-filter-type', 'text');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('auto-detects select filter when filterSelectOptions provided', () => {
|
|
170
|
+
const columns = [
|
|
171
|
+
columnHelper.accessor('status', {
|
|
172
|
+
header: 'Status',
|
|
173
|
+
filterSelectOptions: [
|
|
174
|
+
{ value: 'active', label: 'Active' },
|
|
175
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
176
|
+
],
|
|
177
|
+
}),
|
|
178
|
+
];
|
|
179
|
+
const table = createTable(columns);
|
|
180
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
181
|
+
|
|
182
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
183
|
+
|
|
184
|
+
const filter = screen.getByTestId('column-filter');
|
|
185
|
+
expect(filter).toHaveAttribute('data-filter-type', 'select');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('auto-detects date filter for date columns', () => {
|
|
189
|
+
// Column ID must include 'date' or 'time' for auto-detection
|
|
190
|
+
const columns = [
|
|
191
|
+
columnHelper.accessor('createdDate', {
|
|
192
|
+
header: 'Created Date',
|
|
193
|
+
}),
|
|
194
|
+
];
|
|
195
|
+
// Use data with Date values - need to match the column accessor
|
|
196
|
+
const dateData: TestData[] = [
|
|
197
|
+
{ id: '1', name: 'John', email: 'john@example.com', age: 30, status: 'active', createdAt: new Date('2024-01-01'), createdDate: new Date('2024-01-01') },
|
|
198
|
+
];
|
|
199
|
+
const table = createTable(columns, dateData);
|
|
200
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
201
|
+
|
|
202
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
203
|
+
|
|
204
|
+
const filter = screen.getByTestId('column-filter');
|
|
205
|
+
// Column ID includes 'date', so it should detect as 'date'
|
|
206
|
+
expect(filter).toHaveAttribute('data-filter-type', 'date');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('auto-detects number filter for numeric columns', () => {
|
|
210
|
+
const columns = [
|
|
211
|
+
columnHelper.accessor('age', {
|
|
212
|
+
header: 'Age',
|
|
213
|
+
}),
|
|
214
|
+
];
|
|
215
|
+
const table = createTable(columns);
|
|
216
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
217
|
+
|
|
218
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
219
|
+
|
|
220
|
+
const filter = screen.getByTestId('column-filter');
|
|
221
|
+
expect(filter).toHaveAttribute('data-filter-type', 'number');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('auto-detects select filter when unique values ≤ 10', () => {
|
|
225
|
+
const limitedData: TestData[] = Array.from({ length: 5 }, (_, i) => ({
|
|
226
|
+
id: String(i),
|
|
227
|
+
name: `User ${i}`,
|
|
228
|
+
email: `user${i}@example.com`,
|
|
229
|
+
age: 20 + i,
|
|
230
|
+
status: ['active', 'inactive', 'pending'][i % 3],
|
|
231
|
+
createdAt: new Date(),
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
const columns = [
|
|
235
|
+
columnHelper.accessor('status', {
|
|
236
|
+
header: 'Status',
|
|
237
|
+
}),
|
|
238
|
+
];
|
|
239
|
+
const table = createTable(columns, limitedData);
|
|
240
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
241
|
+
|
|
242
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
243
|
+
|
|
244
|
+
const filter = screen.getByTestId('column-filter');
|
|
245
|
+
expect(filter).toHaveAttribute('data-filter-type', 'select');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('defaults to text filter when no auto-detection matches', () => {
|
|
249
|
+
// Use data with many unique values (>10) to avoid auto-detection as select
|
|
250
|
+
const manyUniqueData: TestData[] = Array.from({ length: 15 }, (_, i) => ({
|
|
251
|
+
id: String(i),
|
|
252
|
+
name: `Unique Name ${i}`,
|
|
253
|
+
email: `user${i}@example.com`,
|
|
254
|
+
age: 20 + i,
|
|
255
|
+
status: 'active',
|
|
256
|
+
createdAt: new Date(),
|
|
257
|
+
}));
|
|
258
|
+
|
|
259
|
+
const columns = [
|
|
260
|
+
columnHelper.accessor('name', {
|
|
261
|
+
header: 'Name',
|
|
262
|
+
}),
|
|
263
|
+
];
|
|
264
|
+
const table = createTable(columns, manyUniqueData);
|
|
265
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
266
|
+
|
|
267
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
268
|
+
|
|
269
|
+
const filter = screen.getByTestId('column-filter');
|
|
270
|
+
// With >10 unique values, it should default to 'text' filter
|
|
271
|
+
expect(filter).toHaveAttribute('data-filter-type', 'text');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('Filter Options Generation', () => {
|
|
276
|
+
it('uses filterSelectOptions when provided', () => {
|
|
277
|
+
const columns = [
|
|
278
|
+
columnHelper.accessor('status', {
|
|
279
|
+
header: 'Status',
|
|
280
|
+
filterSelectOptions: [
|
|
281
|
+
{ value: 'active', label: 'Active' },
|
|
282
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
283
|
+
],
|
|
284
|
+
}),
|
|
285
|
+
];
|
|
286
|
+
const table = createTable(columns);
|
|
287
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
288
|
+
|
|
289
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
290
|
+
|
|
291
|
+
const filter = screen.getByTestId('column-filter');
|
|
292
|
+
expect(filter).toHaveAttribute('data-options-count', '2');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('generates options from unique values when filterSelectOptions not provided', () => {
|
|
296
|
+
const columns = [
|
|
297
|
+
columnHelper.accessor('status', {
|
|
298
|
+
header: 'Status',
|
|
299
|
+
}),
|
|
300
|
+
];
|
|
301
|
+
const table = createTable(columns);
|
|
302
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
303
|
+
|
|
304
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
305
|
+
|
|
306
|
+
const filter = screen.getByTestId('column-filter');
|
|
307
|
+
// Should have generated options from unique values
|
|
308
|
+
expect(filter).toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('handles columns with no data', () => {
|
|
312
|
+
const columns = [
|
|
313
|
+
columnHelper.accessor('name', {
|
|
314
|
+
header: 'Name',
|
|
315
|
+
}),
|
|
316
|
+
];
|
|
317
|
+
const table = createTable(columns, []);
|
|
318
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
319
|
+
|
|
320
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
321
|
+
|
|
322
|
+
const filter = screen.getByTestId('column-filter');
|
|
323
|
+
expect(filter).toHaveAttribute('data-options-count', '0');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('filters out null and undefined values when generating options', () => {
|
|
327
|
+
const dataWithNulls: TestData[] = [
|
|
328
|
+
{ id: '1', name: 'John', email: 'john@example.com', age: 30, status: 'active', createdAt: new Date() },
|
|
329
|
+
{ id: '2', name: 'Jane', email: 'jane@example.com', age: 25, status: null as any, createdAt: new Date() },
|
|
330
|
+
{ id: '3', name: 'Bob', email: 'bob@example.com', age: 35, status: undefined as any, createdAt: new Date() },
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const columns = [
|
|
334
|
+
columnHelper.accessor('status', {
|
|
335
|
+
header: 'Status',
|
|
336
|
+
}),
|
|
337
|
+
];
|
|
338
|
+
const table = createTable(columns, dataWithNulls);
|
|
339
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
340
|
+
|
|
341
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
342
|
+
|
|
343
|
+
const filter = screen.getByTestId('column-filter');
|
|
344
|
+
expect(filter).toBeInTheDocument();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('Edge Cases', () => {
|
|
349
|
+
it('handles empty visibleColumns array', () => {
|
|
350
|
+
const table = createTable();
|
|
351
|
+
|
|
352
|
+
render(<FilterRow table={table} visibleColumns={[]} />);
|
|
353
|
+
|
|
354
|
+
const filterRow = screen.getByRole('row');
|
|
355
|
+
expect(filterRow).toBeInTheDocument();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('handles columns with missing column definitions', () => {
|
|
359
|
+
const table = createTable();
|
|
360
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
361
|
+
|
|
362
|
+
// This should not throw
|
|
363
|
+
expect(() => {
|
|
364
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
365
|
+
}).not.toThrow();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('handles columns with very long names', () => {
|
|
369
|
+
const longName = 'a'.repeat(100);
|
|
370
|
+
const columns = [
|
|
371
|
+
columnHelper.accessor('name', {
|
|
372
|
+
header: longName,
|
|
373
|
+
}),
|
|
374
|
+
];
|
|
375
|
+
const table = createTable(columns);
|
|
376
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
377
|
+
|
|
378
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
379
|
+
|
|
380
|
+
const filter = screen.getByTestId('column-filter');
|
|
381
|
+
expect(filter).toBeInTheDocument();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('handles columns with special characters in IDs', () => {
|
|
385
|
+
const columns = [
|
|
386
|
+
columnHelper.accessor('name', {
|
|
387
|
+
id: 'column-with-special-chars!@#',
|
|
388
|
+
header: 'Name',
|
|
389
|
+
}),
|
|
390
|
+
];
|
|
391
|
+
const table = createTable(columns);
|
|
392
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
393
|
+
|
|
394
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
395
|
+
|
|
396
|
+
const filter = screen.getByTestId('column-filter');
|
|
397
|
+
expect(filter).toHaveAttribute('data-column-id', 'column-with-special-chars!@#');
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('Accessibility', () => {
|
|
402
|
+
it('renders as table row with proper structure', () => {
|
|
403
|
+
const table = createTable();
|
|
404
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
405
|
+
|
|
406
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
407
|
+
|
|
408
|
+
const filterRow = screen.getByRole('row');
|
|
409
|
+
expect(filterRow).toBeInTheDocument();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('renders filter cells with proper structure', () => {
|
|
413
|
+
const table = createTable();
|
|
414
|
+
const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
|
|
415
|
+
|
|
416
|
+
render(<FilterRow table={table} visibleColumns={visibleColumns} />);
|
|
417
|
+
|
|
418
|
+
const filters = screen.getAllByTestId('column-filter');
|
|
419
|
+
expect(filters.length).toBeGreaterThan(0);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|