@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.
Files changed (234) hide show
  1. package/dist/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
  2. package/dist/{DataTable-H5KJCAIS.js → DataTable-ZOAKQ3SU.js} +10 -9
  3. package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
  4. package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
  5. package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
  6. package/dist/{chunk-SYXOZQ4P.js → chunk-2GJ5GL77.js} +1 -1
  7. package/dist/chunk-2GJ5GL77.js.map +1 -0
  8. package/dist/{chunk-XYRZV7R5.js → chunk-2LM4QQGH.js} +30 -34
  9. package/dist/chunk-2LM4QQGH.js.map +1 -0
  10. package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
  11. package/dist/chunk-3DBFLLLU.js.map +1 -0
  12. package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
  13. package/dist/chunk-ECOVPXYS.js.map +1 -0
  14. package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
  15. package/dist/chunk-KA3PSVNV.js.map +1 -0
  16. package/dist/{chunk-HKWQN44G.js → chunk-KMPWND3F.js} +15 -15
  17. package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
  18. package/dist/{chunk-NEONKMTU.js → chunk-LZYHAL7Y.js} +9 -4
  19. package/dist/{chunk-NEONKMTU.js.map → chunk-LZYHAL7Y.js.map} +1 -1
  20. package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
  21. package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
  22. package/dist/chunk-P3PUOL6B.js.map +1 -0
  23. package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
  24. package/dist/chunk-PHDAXDHB.js.map +1 -0
  25. package/dist/chunk-UJI6WSMD.js +201 -0
  26. package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
  27. package/dist/{chunk-OUU3SP6I.js → chunk-UKZWNQMB.js} +50 -7
  28. package/dist/{chunk-OUU3SP6I.js.map → chunk-UKZWNQMB.js.map} +1 -1
  29. package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
  30. package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
  31. package/dist/components.d.ts +1 -1
  32. package/dist/components.js +12 -11
  33. package/dist/components.js.map +1 -1
  34. package/dist/hooks.d.ts +1 -1
  35. package/dist/hooks.js +10 -9
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.js +19 -16
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers.d.ts +2 -2
  41. package/dist/providers.js +3 -2
  42. package/dist/rbac/index.d.ts +82 -1
  43. package/dist/rbac/index.js +13 -10
  44. package/dist/{useToast-DVT4dMtf.d.ts → useToast-Cs_g32bg.d.ts} +1 -1
  45. package/dist/utils.js +6 -4
  46. package/dist/utils.js.map +1 -1
  47. package/dist/validation.js +3 -1
  48. package/dist/validation.js.map +1 -1
  49. package/docs/README.md +4 -0
  50. package/docs/api/classes/ColumnFactory.md +1 -1
  51. package/docs/api/classes/ErrorBoundary.md +1 -1
  52. package/docs/api/classes/InvalidScopeError.md +1 -1
  53. package/docs/api/classes/MissingUserContextError.md +1 -1
  54. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  55. package/docs/api/classes/PermissionDeniedError.md +1 -1
  56. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  57. package/docs/api/classes/RBACAuditManager.md +35 -12
  58. package/docs/api/classes/RBACCache.md +1 -1
  59. package/docs/api/classes/RBACEngine.md +1 -1
  60. package/docs/api/classes/RBACError.md +1 -1
  61. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  62. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  63. package/docs/api/classes/StorageUtils.md +1 -1
  64. package/docs/api/enums/FileCategory.md +1 -1
  65. package/docs/api/interfaces/AggregateConfig.md +1 -1
  66. package/docs/api/interfaces/ButtonProps.md +1 -1
  67. package/docs/api/interfaces/CardProps.md +1 -1
  68. package/docs/api/interfaces/ColorPalette.md +1 -1
  69. package/docs/api/interfaces/ColorShade.md +1 -1
  70. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  71. package/docs/api/interfaces/DataRecord.md +1 -1
  72. package/docs/api/interfaces/DataTableAction.md +1 -1
  73. package/docs/api/interfaces/DataTableColumn.md +1 -1
  74. package/docs/api/interfaces/DataTableProps.md +1 -1
  75. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  76. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  77. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  78. package/docs/api/interfaces/EventAppRoleData.md +71 -0
  79. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  80. package/docs/api/interfaces/FileMetadata.md +1 -1
  81. package/docs/api/interfaces/FileReference.md +1 -1
  82. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  83. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  84. package/docs/api/interfaces/FileUploadProps.md +1 -1
  85. package/docs/api/interfaces/FooterProps.md +1 -1
  86. package/docs/api/interfaces/GrantEventAppRoleParams.md +122 -0
  87. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  88. package/docs/api/interfaces/InputProps.md +1 -1
  89. package/docs/api/interfaces/LabelProps.md +1 -1
  90. package/docs/api/interfaces/LoginFormProps.md +1 -1
  91. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  92. package/docs/api/interfaces/NavigationContextType.md +1 -1
  93. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  94. package/docs/api/interfaces/NavigationItem.md +1 -1
  95. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  97. package/docs/api/interfaces/Organisation.md +1 -1
  98. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  99. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  100. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  101. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  102. package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
  103. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  104. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  105. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  106. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  107. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  108. package/docs/api/interfaces/PaletteData.md +1 -1
  109. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  110. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  111. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  112. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  113. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  114. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  115. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  116. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  117. package/docs/api/interfaces/RBACConfig.md +1 -1
  118. package/docs/api/interfaces/RBACLogger.md +1 -1
  119. package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
  120. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  121. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  122. package/docs/api/interfaces/RoleManagementResult.md +52 -0
  123. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  124. package/docs/api/interfaces/RouteConfig.md +1 -1
  125. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  126. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  127. package/docs/api/interfaces/StorageConfig.md +1 -1
  128. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  129. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  130. package/docs/api/interfaces/StorageListOptions.md +1 -1
  131. package/docs/api/interfaces/StorageListResult.md +1 -1
  132. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  133. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  134. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  135. package/docs/api/interfaces/StyleImport.md +1 -1
  136. package/docs/api/interfaces/SwitchProps.md +1 -1
  137. package/docs/api/interfaces/ToastActionElement.md +1 -1
  138. package/docs/api/interfaces/ToastProps.md +1 -1
  139. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  140. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  141. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  142. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  143. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  144. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  145. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  146. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  147. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  148. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  149. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  150. package/docs/api/interfaces/UserEventAccess.md +1 -1
  151. package/docs/api/interfaces/UserMenuProps.md +1 -1
  152. package/docs/api/interfaces/UserProfile.md +1 -1
  153. package/docs/api/modules.md +41 -14
  154. package/docs/architecture/rpc-function-standards.md +193 -0
  155. package/package.json +1 -1
  156. package/src/__tests__/TEST_STANDARD.md +244 -2
  157. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
  158. package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
  159. package/src/components/DataTable/components/DataTableCore.tsx +29 -2
  160. package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
  161. package/src/components/DataTable/components/EditableRow.tsx +18 -1
  162. package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
  163. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
  164. package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
  165. package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
  166. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
  167. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
  168. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
  169. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
  170. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
  171. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
  172. package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
  173. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
  174. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
  175. package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
  176. package/src/components/EventSelector/EventSelector.tsx +5 -25
  177. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
  178. package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
  179. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
  180. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
  181. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
  182. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
  183. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
  184. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
  185. package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
  186. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
  187. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
  188. package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
  189. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
  190. package/src/components/Select/Select.tsx +8 -0
  191. package/src/components/Toast/Toast.tsx +1 -1
  192. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
  193. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
  194. package/src/hooks/useEventTheme.ts +49 -18
  195. package/src/hooks/usePermissionCache.ts +5 -3
  196. package/src/hooks/useSecureDataAccess.ts +11 -1
  197. package/src/hooks/useToast.ts +1 -1
  198. package/src/providers/services/EventServiceProvider.tsx +15 -8
  199. package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
  200. package/src/rbac/audit.test.ts +206 -0
  201. package/src/rbac/audit.ts +37 -2
  202. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
  203. package/src/rbac/errors.test.ts +340 -0
  204. package/src/rbac/hooks/index.ts +9 -0
  205. package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
  206. package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
  207. package/src/rbac/hooks/useRoleManagement.ts +255 -0
  208. package/src/services/AuthService.ts +10 -0
  209. package/src/services/EventService.ts +111 -50
  210. package/src/services/__tests__/AuthService.test.ts +1 -1
  211. package/src/services/__tests__/EventService.test.ts +60 -45
  212. package/src/services/interfaces/IEventService.ts +1 -1
  213. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
  214. package/src/utils/__tests__/logger.unit.test.ts +398 -0
  215. package/src/utils/__tests__/validation.unit.test.ts +225 -1
  216. package/src/utils/file-reference.test.ts +214 -0
  217. package/dist/chunk-3OGQLOJM.js.map +0 -1
  218. package/dist/chunk-5CDJCTOO.js +0 -190
  219. package/dist/chunk-F6QB26OS.js.map +0 -1
  220. package/dist/chunk-KTHLNIMA.js.map +0 -1
  221. package/dist/chunk-OO3V7W4H.js.map +0 -1
  222. package/dist/chunk-SYXOZQ4P.js.map +0 -1
  223. package/dist/chunk-XYRZV7R5.js.map +0 -1
  224. package/dist/chunk-ZPXWJA4H.js.map +0 -1
  225. package/src/rbac/audit-enhanced.ts +0 -351
  226. /package/dist/{DataTable-H5KJCAIS.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
  227. /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
  228. /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
  229. /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
  230. /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
  231. /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
  232. /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
  233. /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
  234. /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
+