@jmruthers/pace-core 0.5.114 → 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 (236) hide show
  1. package/dist/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
  2. package/dist/{DataTable-3JRLZXER.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-4OX5PXHX.js → chunk-2GJ5GL77.js} +4 -5
  7. package/dist/chunk-2GJ5GL77.js.map +1 -0
  8. package/dist/{chunk-5YIZFEUQ.js → chunk-2LM4QQGH.js} +31 -35
  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-JHWQNJP3.js → chunk-UKZWNQMB.js} +65 -19
  28. package/dist/{chunk-JHWQNJP3.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-DRah6K-g.d.ts → useToast-Cs_g32bg.d.ts} +8 -6
  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 +43 -16
  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 +32 -17
  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/ImportModal.tsx +25 -2
  163. package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
  164. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
  165. package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
  166. package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
  167. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
  168. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
  169. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
  170. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
  171. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
  172. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
  173. package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
  174. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
  175. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
  176. package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
  177. package/src/components/EventSelector/EventSelector.tsx +5 -25
  178. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
  179. package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
  180. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
  181. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
  182. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
  183. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
  184. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
  185. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
  186. package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
  187. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
  188. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
  189. package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
  190. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
  191. package/src/components/Select/Select.tsx +8 -0
  192. package/src/components/Toast/Toast.test.tsx +8 -7
  193. package/src/components/Toast/Toast.tsx +4 -4
  194. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
  195. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
  196. package/src/hooks/useEventTheme.ts +49 -18
  197. package/src/hooks/usePermissionCache.ts +5 -3
  198. package/src/hooks/useSecureDataAccess.ts +11 -1
  199. package/src/hooks/useToast.ts +11 -12
  200. package/src/providers/services/EventServiceProvider.tsx +15 -8
  201. package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
  202. package/src/rbac/audit.test.ts +206 -0
  203. package/src/rbac/audit.ts +37 -2
  204. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
  205. package/src/rbac/errors.test.ts +340 -0
  206. package/src/rbac/hooks/index.ts +9 -0
  207. package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
  208. package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
  209. package/src/rbac/hooks/useRoleManagement.ts +255 -0
  210. package/src/services/AuthService.ts +10 -0
  211. package/src/services/EventService.ts +111 -50
  212. package/src/services/__tests__/AuthService.test.ts +1 -1
  213. package/src/services/__tests__/EventService.test.ts +60 -45
  214. package/src/services/interfaces/IEventService.ts +1 -1
  215. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
  216. package/src/utils/__tests__/logger.unit.test.ts +398 -0
  217. package/src/utils/__tests__/validation.unit.test.ts +225 -1
  218. package/src/utils/file-reference.test.ts +214 -0
  219. package/dist/chunk-3OGQLOJM.js.map +0 -1
  220. package/dist/chunk-4OX5PXHX.js.map +0 -1
  221. package/dist/chunk-5CDJCTOO.js +0 -190
  222. package/dist/chunk-5YIZFEUQ.js.map +0 -1
  223. package/dist/chunk-F6QB26OS.js.map +0 -1
  224. package/dist/chunk-KTHLNIMA.js.map +0 -1
  225. package/dist/chunk-OO3V7W4H.js.map +0 -1
  226. package/dist/chunk-ZPXWJA4H.js.map +0 -1
  227. package/src/rbac/audit-enhanced.ts +0 -351
  228. /package/dist/{DataTable-3JRLZXER.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
  229. /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
  230. /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
  231. /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
  232. /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
  233. /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
  234. /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
  235. /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
  236. /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
@@ -0,0 +1,393 @@
1
+ /**
2
+ * @file GroupHeader Component Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Components/__tests__
5
+ * @since 0.4.0
6
+ *
7
+ * Comprehensive test suite for GroupHeader 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 } from '@testing-library/react';
13
+ import userEvent from '@testing-library/user-event';
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+ import { GroupHeader } from '../GroupHeader';
16
+ import type { Row } from '@tanstack/react-table';
17
+
18
+ // Mock lucide-react icons
19
+ vi.mock('lucide-react', () => ({
20
+ ChevronDown: ({ className }: { className?: string }) => (
21
+ <div data-testid="chevron-down-icon" className={className}>ChevronDown</div>
22
+ ),
23
+ ChevronRight: ({ className }: { className?: string }) => (
24
+ <div data-testid="chevron-right-icon" className={className}>ChevronRight</div>
25
+ ),
26
+ }));
27
+
28
+ // Mock Button component
29
+ vi.mock('../../Button/Button', () => ({
30
+ Button: ({ children, onClick, variant, size, className, ...props }: any) => (
31
+ <button
32
+ onClick={onClick}
33
+ data-variant={variant}
34
+ data-size={size}
35
+ className={className}
36
+ {...props}
37
+ >
38
+ {children}
39
+ </button>
40
+ ),
41
+ }));
42
+
43
+ interface TestData {
44
+ id: string;
45
+ name: string;
46
+ category: string;
47
+ }
48
+
49
+ const createMockRow = (overrides: Partial<Row<TestData>> = {}): Row<TestData> => ({
50
+ id: 'row-1',
51
+ index: 0,
52
+ depth: 0,
53
+ original: { id: '1', name: 'Test', category: 'Category A' } as TestData,
54
+ getValue: vi.fn((columnId: string) => {
55
+ if (columnId === 'category') return 'Category A';
56
+ return 'value';
57
+ }),
58
+ renderValue: vi.fn(),
59
+ getUniqueValues: vi.fn(),
60
+ toggleSelected: vi.fn(),
61
+ getIsSelected: vi.fn(() => false),
62
+ getIsSomeSelected: vi.fn(() => false),
63
+ getIsAllSubRowsSelected: vi.fn(() => false),
64
+ getCanSelect: vi.fn(() => true),
65
+ getCanSelectSubRows: vi.fn(() => false),
66
+ getCanMultiSelect: vi.fn(() => true),
67
+ getToggleSelectedHandler: vi.fn(),
68
+ getParentRow: vi.fn(),
69
+ getParentRows: vi.fn(),
70
+ subRows: [],
71
+ getLeafValues: vi.fn(),
72
+ getAllCells: vi.fn(),
73
+ getLeftVisibleCells: vi.fn(),
74
+ getRightVisibleCells: vi.fn(),
75
+ getVisibleCells: vi.fn(),
76
+ getCenterVisibleCells: vi.fn(),
77
+ ...overrides,
78
+ } as unknown as Row<TestData>);
79
+
80
+ describe('[component] GroupHeader', () => {
81
+ const defaultProps = {
82
+ row: createMockRow(),
83
+ groupByColumn: 'category',
84
+ isExpanded: false,
85
+ onToggle: vi.fn(),
86
+ subRowsCount: 5,
87
+ };
88
+
89
+ beforeEach(() => {
90
+ vi.clearAllMocks();
91
+ });
92
+
93
+ afterEach(() => {
94
+ vi.clearAllMocks();
95
+ });
96
+
97
+ describe('Rendering', () => {
98
+ it('renders group header container', () => {
99
+ render(<GroupHeader {...defaultProps} />);
100
+
101
+ const container = screen.getByText(/Category A/i).closest('div');
102
+ expect(container).toBeInTheDocument();
103
+ });
104
+
105
+ it('displays group value', () => {
106
+ render(<GroupHeader {...defaultProps} />);
107
+
108
+ expect(screen.getByText(/Category A/i)).toBeInTheDocument();
109
+ });
110
+
111
+ it('displays subRowsCount', () => {
112
+ render(<GroupHeader {...defaultProps} subRowsCount={10} />);
113
+
114
+ expect(screen.getByText(/\(10 items\)/i)).toBeInTheDocument();
115
+ });
116
+
117
+ it('renders ChevronRight icon when not expanded', () => {
118
+ render(<GroupHeader {...defaultProps} isExpanded={false} />);
119
+
120
+ expect(screen.getByTestId('chevron-right-icon')).toBeInTheDocument();
121
+ expect(screen.queryByTestId('chevron-down-icon')).not.toBeInTheDocument();
122
+ });
123
+
124
+ it('renders ChevronDown icon when expanded', () => {
125
+ render(<GroupHeader {...defaultProps} isExpanded={true} />);
126
+
127
+ expect(screen.getByTestId('chevron-down-icon')).toBeInTheDocument();
128
+ expect(screen.queryByTestId('chevron-right-icon')).not.toBeInTheDocument();
129
+ });
130
+
131
+ it('renders toggle button', () => {
132
+ render(<GroupHeader {...defaultProps} />);
133
+
134
+ const button = screen.getByRole('button');
135
+ expect(button).toBeInTheDocument();
136
+ });
137
+ });
138
+
139
+ describe('Group Value Display', () => {
140
+ it('displays correct group value from row', () => {
141
+ const row = createMockRow({
142
+ getValue: vi.fn((columnId: string) => {
143
+ if (columnId === 'category') return 'Electronics';
144
+ return 'value';
145
+ }),
146
+ });
147
+
148
+ render(
149
+ <GroupHeader
150
+ {...defaultProps}
151
+ row={row}
152
+ />
153
+ );
154
+
155
+ expect(screen.getByText(/Electronics/i)).toBeInTheDocument();
156
+ });
157
+
158
+ it('converts group value to string', () => {
159
+ const row = createMockRow({
160
+ getValue: vi.fn((columnId: string) => {
161
+ if (columnId === 'category') return 123;
162
+ return 'value';
163
+ }),
164
+ });
165
+
166
+ render(
167
+ <GroupHeader
168
+ {...defaultProps}
169
+ row={row}
170
+ />
171
+ );
172
+
173
+ expect(screen.getByText(/123/i)).toBeInTheDocument();
174
+ });
175
+
176
+ it('handles null group value', () => {
177
+ const row = createMockRow({
178
+ getValue: vi.fn((columnId: string) => {
179
+ if (columnId === 'category') return null;
180
+ return 'value';
181
+ }),
182
+ });
183
+
184
+ render(
185
+ <GroupHeader
186
+ {...defaultProps}
187
+ row={row}
188
+ />
189
+ );
190
+
191
+ expect(screen.getByText(/null/i)).toBeInTheDocument();
192
+ });
193
+
194
+ it('handles undefined group value', () => {
195
+ const row = createMockRow({
196
+ getValue: vi.fn((columnId: string) => {
197
+ if (columnId === 'category') return undefined;
198
+ return 'value';
199
+ }),
200
+ });
201
+
202
+ render(
203
+ <GroupHeader
204
+ {...defaultProps}
205
+ row={row}
206
+ />
207
+ );
208
+
209
+ expect(screen.getByText(/undefined/i)).toBeInTheDocument();
210
+ });
211
+ });
212
+
213
+ describe('SubRows Count Display', () => {
214
+ it('displays correct count for single item', () => {
215
+ render(<GroupHeader {...defaultProps} subRowsCount={1} />);
216
+
217
+ expect(screen.getByText(/\(1 items\)/i)).toBeInTheDocument();
218
+ });
219
+
220
+ it('displays correct count for multiple items', () => {
221
+ render(<GroupHeader {...defaultProps} subRowsCount={25} />);
222
+
223
+ expect(screen.getByText(/\(25 items\)/i)).toBeInTheDocument();
224
+ });
225
+
226
+ it('displays zero count', () => {
227
+ render(<GroupHeader {...defaultProps} subRowsCount={0} />);
228
+
229
+ expect(screen.getByText(/\(0 items\)/i)).toBeInTheDocument();
230
+ });
231
+ });
232
+
233
+ describe('User Interactions', () => {
234
+ it('calls onToggle when button is clicked', async () => {
235
+ const user = userEvent.setup();
236
+ const handleToggle = vi.fn();
237
+
238
+ render(
239
+ <GroupHeader
240
+ {...defaultProps}
241
+ onToggle={handleToggle}
242
+ />
243
+ );
244
+
245
+ const button = screen.getByRole('button');
246
+ await user.click(button);
247
+
248
+ expect(handleToggle).toHaveBeenCalledTimes(1);
249
+ });
250
+
251
+ it('calls onToggle multiple times on rapid clicks', async () => {
252
+ const user = userEvent.setup();
253
+ const handleToggle = vi.fn();
254
+
255
+ render(
256
+ <GroupHeader
257
+ {...defaultProps}
258
+ onToggle={handleToggle}
259
+ />
260
+ );
261
+
262
+ const button = screen.getByRole('button');
263
+ await user.click(button);
264
+ await user.click(button);
265
+ await user.click(button);
266
+
267
+ expect(handleToggle).toHaveBeenCalledTimes(3);
268
+ });
269
+
270
+ it('updates icon when expanded state changes', () => {
271
+ const { rerender } = render(
272
+ <GroupHeader
273
+ {...defaultProps}
274
+ isExpanded={false}
275
+ />
276
+ );
277
+
278
+ expect(screen.getByTestId('chevron-right-icon')).toBeInTheDocument();
279
+
280
+ rerender(
281
+ <GroupHeader
282
+ {...defaultProps}
283
+ isExpanded={true}
284
+ />
285
+ );
286
+
287
+ expect(screen.getByTestId('chevron-down-icon')).toBeInTheDocument();
288
+ });
289
+ });
290
+
291
+ describe('Edge Cases', () => {
292
+ it('handles empty group value', () => {
293
+ const row = createMockRow({
294
+ getValue: vi.fn((columnId: string) => {
295
+ if (columnId === 'category') return '';
296
+ return 'value';
297
+ }),
298
+ });
299
+
300
+ render(
301
+ <GroupHeader
302
+ {...defaultProps}
303
+ row={row}
304
+ />
305
+ );
306
+
307
+ expect(screen.getByText(/\(5 items\)/i)).toBeInTheDocument();
308
+ });
309
+
310
+ it('handles very large subRowsCount', () => {
311
+ render(<GroupHeader {...defaultProps} subRowsCount={999999} />);
312
+
313
+ expect(screen.getByText(/\(999999 items\)/i)).toBeInTheDocument();
314
+ });
315
+
316
+ it('handles different groupByColumn values', () => {
317
+ const row = createMockRow({
318
+ getValue: vi.fn((columnId: string) => {
319
+ if (columnId === 'status') return 'Active';
320
+ return 'value';
321
+ }),
322
+ });
323
+
324
+ render(
325
+ <GroupHeader
326
+ {...defaultProps}
327
+ row={row}
328
+ groupByColumn="status"
329
+ />
330
+ );
331
+
332
+ expect(screen.getByText(/Active/i)).toBeInTheDocument();
333
+ });
334
+
335
+ it('handles special characters in group value', () => {
336
+ const row = createMockRow({
337
+ getValue: vi.fn((columnId: string) => {
338
+ if (columnId === 'category') return 'Category & Type (Special)';
339
+ return 'value';
340
+ }),
341
+ });
342
+
343
+ render(
344
+ <GroupHeader
345
+ {...defaultProps}
346
+ row={row}
347
+ />
348
+ );
349
+
350
+ expect(screen.getByText(/Category & Type \(Special\)/i)).toBeInTheDocument();
351
+ });
352
+ });
353
+
354
+ describe('Styling', () => {
355
+ it('has proper container styling', () => {
356
+ render(<GroupHeader {...defaultProps} />);
357
+
358
+ const container = screen.getByText(/Category A/i).closest('div');
359
+ expect(container).toHaveClass('flex', 'items-center');
360
+ });
361
+
362
+ it('applies background and border styling', () => {
363
+ render(<GroupHeader {...defaultProps} />);
364
+
365
+ const container = screen.getByText(/Category A/i).closest('div');
366
+ expect(container).toHaveClass('bg-muted/50', 'border-b');
367
+ });
368
+
369
+ it('applies font styling', () => {
370
+ render(<GroupHeader {...defaultProps} />);
371
+
372
+ const container = screen.getByText(/Category A/i).closest('div');
373
+ expect(container).toHaveClass('font-medium');
374
+ });
375
+ });
376
+
377
+ describe('Accessibility', () => {
378
+ it('has accessible button for toggling', () => {
379
+ render(<GroupHeader {...defaultProps} />);
380
+
381
+ const button = screen.getByRole('button');
382
+ expect(button).toBeInTheDocument();
383
+ });
384
+
385
+ it('displays group information clearly', () => {
386
+ render(<GroupHeader {...defaultProps} />);
387
+
388
+ expect(screen.getByText(/Category A/i)).toBeInTheDocument();
389
+ expect(screen.getByText(/\(5 items\)/i)).toBeInTheDocument();
390
+ });
391
+ });
392
+ });
393
+