@jmruthers/pace-core 0.5.118 → 0.5.120

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 (181) hide show
  1. package/dist/{DataTable-ZOAKQ3SU.js → DataTable-DGZDJUYM.js} +7 -7
  2. package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
  3. package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
  4. package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
  5. package/dist/chunk-BHWIUEYH.js.map +1 -0
  6. package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
  7. package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
  8. package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
  9. package/dist/{chunk-2GJ5GL77.js → chunk-GKHF54DI.js} +2 -2
  10. package/dist/chunk-GKHF54DI.js.map +1 -0
  11. package/dist/{chunk-UKZWNQMB.js → chunk-HFBOFZ3Z.js} +5 -18
  12. package/dist/chunk-HFBOFZ3Z.js.map +1 -0
  13. package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
  14. package/dist/{chunk-2LM4QQGH.js → chunk-QPI2CCBA.js} +9 -9
  15. package/dist/chunk-QPI2CCBA.js.map +1 -0
  16. package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
  17. package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
  18. package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
  19. package/dist/components.d.ts +1 -1
  20. package/dist/components.js +9 -9
  21. package/dist/hooks.d.ts +1 -1
  22. package/dist/hooks.js +8 -8
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +12 -12
  25. package/dist/providers.js +2 -2
  26. package/dist/rbac/index.js +7 -7
  27. package/dist/{useToast-Cs_g32bg.d.ts → useToast-C8gR5ir4.d.ts} +2 -2
  28. package/dist/utils.js +1 -1
  29. package/docs/api/classes/ColumnFactory.md +1 -1
  30. package/docs/api/classes/ErrorBoundary.md +1 -1
  31. package/docs/api/classes/InvalidScopeError.md +1 -1
  32. package/docs/api/classes/MissingUserContextError.md +1 -1
  33. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  34. package/docs/api/classes/PermissionDeniedError.md +1 -1
  35. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  36. package/docs/api/classes/RBACAuditManager.md +1 -1
  37. package/docs/api/classes/RBACCache.md +1 -1
  38. package/docs/api/classes/RBACEngine.md +1 -1
  39. package/docs/api/classes/RBACError.md +1 -1
  40. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  41. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  42. package/docs/api/classes/StorageUtils.md +1 -1
  43. package/docs/api/enums/FileCategory.md +1 -1
  44. package/docs/api/interfaces/AggregateConfig.md +1 -1
  45. package/docs/api/interfaces/ButtonProps.md +1 -1
  46. package/docs/api/interfaces/CardProps.md +1 -1
  47. package/docs/api/interfaces/ColorPalette.md +1 -1
  48. package/docs/api/interfaces/ColorShade.md +1 -1
  49. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  50. package/docs/api/interfaces/DataRecord.md +1 -1
  51. package/docs/api/interfaces/DataTableAction.md +1 -1
  52. package/docs/api/interfaces/DataTableColumn.md +1 -1
  53. package/docs/api/interfaces/DataTableProps.md +1 -1
  54. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  55. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  56. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  57. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  58. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  59. package/docs/api/interfaces/FileMetadata.md +1 -1
  60. package/docs/api/interfaces/FileReference.md +1 -1
  61. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  62. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  63. package/docs/api/interfaces/FileUploadProps.md +1 -1
  64. package/docs/api/interfaces/FooterProps.md +1 -1
  65. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  66. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  67. package/docs/api/interfaces/InputProps.md +1 -1
  68. package/docs/api/interfaces/LabelProps.md +1 -1
  69. package/docs/api/interfaces/LoginFormProps.md +1 -1
  70. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  71. package/docs/api/interfaces/NavigationContextType.md +1 -1
  72. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  73. package/docs/api/interfaces/NavigationItem.md +1 -1
  74. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  76. package/docs/api/interfaces/Organisation.md +1 -1
  77. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  78. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  79. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  80. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  81. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  82. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  83. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  84. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  85. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  86. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  87. package/docs/api/interfaces/PaletteData.md +1 -1
  88. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  89. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  90. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  91. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  92. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  93. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  94. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  95. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  96. package/docs/api/interfaces/RBACConfig.md +1 -1
  97. package/docs/api/interfaces/RBACLogger.md +1 -1
  98. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  99. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  100. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  101. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  102. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  103. package/docs/api/interfaces/RouteConfig.md +1 -1
  104. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  105. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  106. package/docs/api/interfaces/StorageConfig.md +1 -1
  107. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  108. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  109. package/docs/api/interfaces/StorageListOptions.md +1 -1
  110. package/docs/api/interfaces/StorageListResult.md +1 -1
  111. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  112. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  113. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  114. package/docs/api/interfaces/StyleImport.md +1 -1
  115. package/docs/api/interfaces/SwitchProps.md +1 -1
  116. package/docs/api/interfaces/ToastActionElement.md +1 -1
  117. package/docs/api/interfaces/ToastProps.md +1 -1
  118. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  119. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  120. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  121. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  123. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  124. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  125. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  127. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  128. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  129. package/docs/api/interfaces/UserEventAccess.md +1 -1
  130. package/docs/api/interfaces/UserMenuProps.md +1 -1
  131. package/docs/api/interfaces/UserProfile.md +1 -1
  132. package/docs/api/modules.md +2 -2
  133. package/package.json +1 -1
  134. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
  135. package/src/components/DataTable/components/DataTableCore.tsx +5 -0
  136. package/src/components/DataTable/components/EditableRow.tsx +9 -18
  137. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +616 -9
  138. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
  139. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
  140. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
  141. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
  142. package/src/components/Toast/Toast.tsx +1 -1
  143. package/src/hooks/__tests__/index.unit.test.ts +223 -0
  144. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
  145. package/src/hooks/__tests__/useEvents.unit.test.ts +251 -0
  146. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
  147. package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
  148. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +19 -9
  149. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
  150. package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
  151. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
  152. package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
  153. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +661 -0
  154. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
  155. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +371 -0
  156. package/src/hooks/__tests__/useToast.unit.test.tsx +449 -30
  157. package/src/hooks/useSecureDataAccess.test.ts +1 -0
  158. package/src/hooks/useToast.ts +4 -4
  159. package/src/rbac/audit-enhanced.ts +339 -0
  160. package/src/services/EventService.ts +1 -0
  161. package/src/services/__tests__/AuthService.test.ts +473 -0
  162. package/src/services/__tests__/EventService.test.ts +390 -0
  163. package/src/services/__tests__/InactivityService.test.ts +217 -0
  164. package/src/services/__tests__/OrganisationService.test.ts +371 -0
  165. package/src/styles/core.css +1 -0
  166. package/dist/chunk-2GJ5GL77.js.map +0 -1
  167. package/dist/chunk-2LM4QQGH.js.map +0 -1
  168. package/dist/chunk-KA3PSVNV.js.map +0 -1
  169. package/dist/chunk-UKZWNQMB.js.map +0 -1
  170. package/src/components/DataTable/utils/debugTools.ts +0 -609
  171. package/src/rbac/testing/index.tsx +0 -340
  172. /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-DGZDJUYM.js.map} +0 -0
  173. /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
  174. /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
  175. /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
  176. /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
  177. /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
  178. /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
  179. /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
  180. /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
  181. /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
@@ -6,7 +6,8 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
- import { generateCSVContent, exportToCSV } from '../exportUtils';
9
+ import { generateCSVContent, exportToCSV, exportToCSVWithTableRows } from '../exportUtils';
10
+ import type { ExportColumn } from '../exportUtils';
10
11
 
11
12
  // Mock DOM methods
12
13
  const mockCreateElement = vi.fn();
@@ -486,3 +487,456 @@ describe('[unit] CSV Escaping and Security', () => {
486
487
  expect(sanitized).toContain("'=FORMULA");
487
488
  });
488
489
  });
490
+
491
+ describe('[unit] exportToCSVWithTableRows', () => {
492
+ let mockLink: any;
493
+ let mockColumnIdToTableColumn: Map<string, any>;
494
+
495
+ beforeEach(() => {
496
+ vi.clearAllMocks();
497
+ vi.useFakeTimers();
498
+
499
+ mockLink = {
500
+ setAttribute: vi.fn(),
501
+ style: { display: 'none' },
502
+ click: vi.fn(() => {
503
+ // Simulate onclick being called after click
504
+ if (mockLink.onclick) {
505
+ mockLink.onclick();
506
+ }
507
+ }),
508
+ onclick: null as any,
509
+ };
510
+
511
+ mockCreateElement.mockReturnValue(mockLink);
512
+ mockCreateObjectURL.mockReturnValue('blob:mock-url');
513
+ mockColumnIdToTableColumn = new Map();
514
+ });
515
+
516
+ afterEach(() => {
517
+ vi.useRealTimers();
518
+ vi.clearAllMocks();
519
+ });
520
+
521
+ const createMockTableRow = (original: any, getValue: (columnId: string) => any = () => undefined) => ({
522
+ original,
523
+ getValue,
524
+ id: `row-${original.id}`,
525
+ });
526
+
527
+ it('exports CSV with table rows using getValue()', async () => {
528
+ const tableRows = [
529
+ createMockTableRow(
530
+ { id: 1, name: 'John Doe', email: 'john@example.com' },
531
+ (columnId) => {
532
+ if (columnId === 'name') return 'John Doe';
533
+ if (columnId === 'email') return 'john@example.com';
534
+ return undefined;
535
+ }
536
+ ),
537
+ ];
538
+
539
+ const columns: ExportColumn[] = [
540
+ { id: 'name', header: 'Name', accessorKey: 'name' },
541
+ { id: 'email', header: 'Email', accessorKey: 'email' },
542
+ ];
543
+
544
+ mockColumnIdToTableColumn.set('name', {});
545
+ mockColumnIdToTableColumn.set('email', {});
546
+
547
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
548
+
549
+ // Advance timers to trigger onclick setTimeout
550
+ vi.advanceTimersByTime(100);
551
+
552
+ await promise;
553
+
554
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
555
+ const csvContent = blobCall.content[0];
556
+
557
+ expect(csvContent).toContain('"Name","Email"');
558
+ expect(csvContent).toContain('"John Doe","john@example.com"');
559
+ });
560
+
561
+ it('handles ID columns by getting directly from original data', async () => {
562
+ const tableRows = [
563
+ createMockTableRow(
564
+ { id: 1, name: 'John Doe', referenceId: 'ref-123' },
565
+ (columnId) => {
566
+ if (columnId === 'name') return 'John Doe';
567
+ return undefined;
568
+ }
569
+ ),
570
+ ];
571
+
572
+ const columns: ExportColumn[] = [
573
+ { id: 'name', header: 'Name', accessorKey: 'name' },
574
+ { id: 'referenceId', header: 'Reference ID', accessorKey: 'referenceId', isIdColumn: true },
575
+ ];
576
+
577
+ mockColumnIdToTableColumn.set('name', {});
578
+
579
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
580
+
581
+ // Advance timers to trigger onclick setTimeout
582
+ vi.advanceTimersByTime(100);
583
+
584
+ await promise;
585
+
586
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
587
+ const csvContent = blobCall.content[0];
588
+
589
+ expect(csvContent).toContain('"Reference ID"');
590
+ expect(csvContent).toContain('"ref-123"');
591
+ });
592
+
593
+ it('falls back to accessorFn when getValue() fails', async () => {
594
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
595
+
596
+ const tableRows = [
597
+ createMockTableRow(
598
+ { id: 1, name: 'John Doe' },
599
+ () => {
600
+ throw new Error('getValue failed');
601
+ }
602
+ ),
603
+ ];
604
+
605
+ const columns: ExportColumn[] = [
606
+ {
607
+ id: 'name',
608
+ header: 'Name',
609
+ accessorKey: 'name',
610
+ accessorFn: (row: any) => row.name.toUpperCase(),
611
+ },
612
+ ];
613
+
614
+ mockColumnIdToTableColumn.set('name', {});
615
+
616
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
617
+
618
+ // Advance timers to trigger onclick setTimeout
619
+ vi.advanceTimersByTime(100);
620
+
621
+ await promise;
622
+
623
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
624
+ const csvContent = blobCall.content[0];
625
+
626
+ expect(csvContent).toContain('"JOHN DOE"');
627
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
628
+
629
+ consoleWarnSpy.mockRestore();
630
+ });
631
+
632
+ it('falls back to direct property access when getValue() fails and no accessorFn', async () => {
633
+ const tableRows = [
634
+ createMockTableRow(
635
+ { id: 1, name: 'John Doe' },
636
+ () => {
637
+ throw new Error('getValue failed');
638
+ }
639
+ ),
640
+ ];
641
+
642
+ const columns: ExportColumn[] = [
643
+ { id: 'name', header: 'Name', accessorKey: 'name' },
644
+ ];
645
+
646
+ mockColumnIdToTableColumn.set('name', {});
647
+
648
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
649
+
650
+ // Advance timers to trigger onclick setTimeout
651
+ vi.advanceTimersByTime(100);
652
+
653
+ await promise;
654
+
655
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
656
+ const csvContent = blobCall.content[0];
657
+
658
+ expect(csvContent).toContain('"John Doe"');
659
+ });
660
+
661
+ it('handles accessorFn error gracefully', async () => {
662
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
663
+
664
+ const tableRows = [
665
+ createMockTableRow(
666
+ { id: 1, name: 'John Doe' },
667
+ () => {
668
+ throw new Error('getValue failed');
669
+ }
670
+ ),
671
+ ];
672
+
673
+ const columns: ExportColumn[] = [
674
+ {
675
+ id: 'name',
676
+ header: 'Name',
677
+ accessorKey: 'name',
678
+ accessorFn: () => {
679
+ throw new Error('accessorFn failed');
680
+ },
681
+ },
682
+ ];
683
+
684
+ mockColumnIdToTableColumn.set('name', {});
685
+
686
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
687
+
688
+ // Advance timers to trigger onclick setTimeout
689
+ vi.advanceTimersByTime(100);
690
+
691
+ await promise;
692
+
693
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
694
+ const csvContent = blobCall.content[0];
695
+
696
+ // Should export empty value for failed accessorFn
697
+ expect(csvContent).toContain('"Name"');
698
+ expect(consoleWarnSpy).toHaveBeenCalled();
699
+
700
+ consoleWarnSpy.mockRestore();
701
+ });
702
+
703
+ it('falls back to direct property access when column not in columnIdToTableColumn', async () => {
704
+ const tableRows = [
705
+ createMockTableRow({ id: 1, name: 'John Doe' }),
706
+ ];
707
+
708
+ const columns: ExportColumn[] = [
709
+ { id: 'name', header: 'Name', accessorKey: 'name' },
710
+ ];
711
+
712
+ // Don't add to columnIdToTableColumn
713
+
714
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
715
+
716
+ // Advance timers to trigger onclick setTimeout
717
+ vi.advanceTimersByTime(100);
718
+
719
+ await promise;
720
+
721
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
722
+ const csvContent = blobCall.content[0];
723
+
724
+ expect(csvContent).toContain('"John Doe"');
725
+ });
726
+
727
+ it('formats values according to locale', async () => {
728
+ const tableRows = [
729
+ createMockTableRow(
730
+ { id: 1, value: 1234.56, date: new Date('2025-01-15'), active: true },
731
+ (columnId) => {
732
+ if (columnId === 'value') return 1234.56;
733
+ if (columnId === 'date') return new Date('2025-01-15');
734
+ if (columnId === 'active') return true;
735
+ return undefined;
736
+ }
737
+ ),
738
+ ];
739
+
740
+ const columns: ExportColumn[] = [
741
+ { id: 'value', header: 'Value', accessorKey: 'value' },
742
+ { id: 'date', header: 'Date', accessorKey: 'date' },
743
+ { id: 'active', header: 'Active', accessorKey: 'active' },
744
+ ];
745
+
746
+ mockColumnIdToTableColumn.set('value', {});
747
+ mockColumnIdToTableColumn.set('date', {});
748
+ mockColumnIdToTableColumn.set('active', {});
749
+
750
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn, 'test.csv', {
751
+ locale: 'en-US',
752
+ });
753
+
754
+ // Advance timers to trigger onclick setTimeout
755
+ vi.advanceTimersByTime(100);
756
+
757
+ await promise;
758
+
759
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
760
+ const csvContent = blobCall.content[0];
761
+
762
+ // Locale formatting should be applied
763
+ expect(csvContent).toBeTruthy();
764
+ });
765
+
766
+ it('sanitizes values for security by default', async () => {
767
+ const tableRows = [
768
+ createMockTableRow(
769
+ { id: 1, name: '=SUM(A1:A10)' },
770
+ (columnId) => {
771
+ if (columnId === 'name') return '=SUM(A1:A10)';
772
+ return undefined;
773
+ }
774
+ ),
775
+ ];
776
+
777
+ const columns: ExportColumn[] = [
778
+ { id: 'name', header: 'Name', accessorKey: 'name' },
779
+ ];
780
+
781
+ mockColumnIdToTableColumn.set('name', {});
782
+
783
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
784
+
785
+ // Advance timers to trigger onclick setTimeout
786
+ vi.advanceTimersByTime(100);
787
+
788
+ await promise;
789
+
790
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
791
+ const csvContent = blobCall.content[0];
792
+
793
+ // Should sanitize CSV injection attempts
794
+ expect(csvContent).toContain("'=SUM");
795
+ });
796
+
797
+ it('allows disabling security sanitization', async () => {
798
+ const tableRows = [
799
+ createMockTableRow(
800
+ { id: 1, name: '=SUM(A1:A10)' },
801
+ (columnId) => {
802
+ if (columnId === 'name') return '=SUM(A1:A10)';
803
+ return undefined;
804
+ }
805
+ ),
806
+ ];
807
+
808
+ const columns: ExportColumn[] = [
809
+ { id: 'name', header: 'Name', accessorKey: 'name' },
810
+ ];
811
+
812
+ mockColumnIdToTableColumn.set('name', {});
813
+
814
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn, 'test.csv', {
815
+ sanitizeForSecurity: false,
816
+ });
817
+
818
+ // Advance timers to trigger onclick setTimeout
819
+ vi.advanceTimersByTime(100);
820
+
821
+ await promise;
822
+
823
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
824
+ const csvContent = blobCall.content[0];
825
+
826
+ // Should still quote but not prefix with single quote
827
+ expect(csvContent).toContain('"=SUM');
828
+ expect(csvContent).not.toContain("'=SUM");
829
+ });
830
+
831
+ it('throws error when window is undefined (SSR)', async () => {
832
+ const originalWindow = global.window;
833
+ // @ts-expect-error - Testing SSR scenario
834
+ delete global.window;
835
+
836
+ const tableRows = [createMockTableRow({ id: 1 })];
837
+ const columns: ExportColumn[] = [{ id: 'id', header: 'ID', accessorKey: 'id' }];
838
+
839
+ await expect(
840
+ exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn)
841
+ ).rejects.toThrow('CSV export is only available in browser environments');
842
+
843
+ global.window = originalWindow;
844
+ });
845
+
846
+ it('throws error when tableRows is empty', async () => {
847
+ const columns: ExportColumn[] = [{ id: 'id', header: 'ID', accessorKey: 'id' }];
848
+
849
+ await expect(
850
+ exportToCSVWithTableRows([], columns, mockColumnIdToTableColumn)
851
+ ).rejects.toThrow('No data to export');
852
+ });
853
+
854
+ it('throws error when columns is empty', async () => {
855
+ const tableRows = [createMockTableRow({ id: 1 })];
856
+
857
+ await expect(
858
+ exportToCSVWithTableRows(tableRows, [], mockColumnIdToTableColumn)
859
+ ).rejects.toThrow('No columns defined for export');
860
+ });
861
+
862
+ it('handles multiple rows correctly', async () => {
863
+ const tableRows = [
864
+ createMockTableRow(
865
+ { id: 1, name: 'John Doe' },
866
+ (columnId) => columnId === 'name' ? 'John Doe' : undefined
867
+ ),
868
+ createMockTableRow(
869
+ { id: 2, name: 'Jane Smith' },
870
+ (columnId) => columnId === 'name' ? 'Jane Smith' : undefined
871
+ ),
872
+ ];
873
+
874
+ const columns: ExportColumn[] = [
875
+ { id: 'name', header: 'Name', accessorKey: 'name' },
876
+ ];
877
+
878
+ mockColumnIdToTableColumn.set('name', {});
879
+
880
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
881
+
882
+ // Advance timers to trigger onclick setTimeout
883
+ vi.advanceTimersByTime(100);
884
+
885
+ await promise;
886
+
887
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
888
+ const csvContent = blobCall.content[0];
889
+
890
+ expect(csvContent).toContain('"John Doe"');
891
+ expect(csvContent).toContain('"Jane Smith"');
892
+ });
893
+
894
+ it('uses custom filename when provided', async () => {
895
+ const tableRows = [createMockTableRow({ id: 1 })];
896
+ const columns: ExportColumn[] = [{ id: 'id', header: 'ID', accessorKey: 'id' }];
897
+
898
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn, 'custom-export.csv');
899
+
900
+ // Advance timers to trigger onclick setTimeout
901
+ vi.advanceTimersByTime(100);
902
+
903
+ await promise;
904
+
905
+ expect(mockLink.setAttribute).toHaveBeenCalledWith('download', 'custom-export.csv');
906
+ });
907
+
908
+ it('handles columns with computed values via accessorFn in getValue()', async () => {
909
+ const tableRows = [
910
+ createMockTableRow(
911
+ { id: 1, firstName: 'John', lastName: 'Doe' },
912
+ (columnId) => {
913
+ if (columnId === 'fullName') return 'John Doe';
914
+ return undefined;
915
+ }
916
+ ),
917
+ ];
918
+
919
+ const columns: ExportColumn[] = [
920
+ {
921
+ id: 'fullName',
922
+ header: 'Full Name',
923
+ accessorFn: (row: any) => `${row.firstName} ${row.lastName}`,
924
+ },
925
+ ];
926
+
927
+ mockColumnIdToTableColumn.set('fullName', {});
928
+
929
+ const promise = exportToCSVWithTableRows(tableRows, columns, mockColumnIdToTableColumn);
930
+
931
+ // Advance timers to trigger onclick setTimeout
932
+ vi.advanceTimersByTime(100);
933
+
934
+ await promise;
935
+
936
+ const blobCall = mockCreateObjectURL.mock.calls[0][0];
937
+ const csvContent = blobCall.content[0];
938
+
939
+ expect(csvContent).toContain('"Full Name"');
940
+ expect(csvContent).toContain('"John Doe"');
941
+ });
942
+ });
@@ -12,7 +12,7 @@
12
12
  * - Customizable positioning and styling
13
13
  * - Swipe gestures for dismissal
14
14
  * - Keyboard navigation support
15
- * - Auto-dismiss with default 10 second duration managed internally
15
+ * - Auto-dismiss with default 5 second duration managed internally
16
16
  * - Action buttons and close functionality
17
17
  * - Responsive design
18
18
  * - Accessibility compliant
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @file Hooks Index Exports Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__
5
+ * @since 0.1.0
6
+ *
7
+ * Tests to verify that all exports from the hooks index file are properly accessible.
8
+ * This ensures the module structure is correct and all re-exports work as expected.
9
+ */
10
+
11
+ import { describe, it, expect } from 'vitest';
12
+ import * as Hooks from '../index';
13
+
14
+ describe('hooks index exports', () => {
15
+ describe('UI Interaction Hooks', () => {
16
+ it('should export useToast', () => {
17
+ expect(Hooks).toHaveProperty('useToast');
18
+ expect(typeof Hooks.useToast).toBe('function');
19
+ });
20
+
21
+ it('should export useFocusManagement', () => {
22
+ expect(Hooks).toHaveProperty('useFocusManagement');
23
+ expect(typeof Hooks.useFocusManagement).toBe('function');
24
+ });
25
+
26
+ it('should export useFocusTrap', () => {
27
+ expect(Hooks).toHaveProperty('useFocusTrap');
28
+ expect(typeof Hooks.useFocusTrap).toBe('function');
29
+ });
30
+
31
+ it('should export useKeyboardShortcuts', () => {
32
+ expect(Hooks).toHaveProperty('useKeyboardShortcuts');
33
+ expect(typeof Hooks.useKeyboardShortcuts).toBe('function');
34
+ });
35
+
36
+ it('should export useIsMobile', () => {
37
+ expect(Hooks).toHaveProperty('useIsMobile');
38
+ expect(typeof Hooks.useIsMobile).toBe('function');
39
+ });
40
+ });
41
+
42
+ describe('Event Theming Hook', () => {
43
+ it('should export useEventTheme', () => {
44
+ expect(Hooks).toHaveProperty('useEventTheme');
45
+ expect(typeof Hooks.useEventTheme).toBe('function');
46
+ });
47
+ });
48
+
49
+ describe('Data & State Hooks', () => {
50
+ it('should export useDebounce', () => {
51
+ expect(Hooks).toHaveProperty('useDebounce');
52
+ expect(typeof Hooks.useDebounce).toBe('function');
53
+ });
54
+
55
+ it('should export useDataTableState', () => {
56
+ expect(Hooks).toHaveProperty('useDataTableState');
57
+ expect(typeof Hooks.useDataTableState).toBe('function');
58
+ });
59
+ });
60
+
61
+ describe('Organisation Hooks', () => {
62
+ it('should export useOrganisationPermissions', () => {
63
+ expect(Hooks).toHaveProperty('useOrganisationPermissions');
64
+ expect(typeof Hooks.useOrganisationPermissions).toBe('function');
65
+ });
66
+
67
+ it('should export useSecureDataAccess', () => {
68
+ expect(Hooks).toHaveProperty('useSecureDataAccess');
69
+ expect(typeof Hooks.useSecureDataAccess).toBe('function');
70
+ });
71
+
72
+ it('should export useOrganisationSecurity', () => {
73
+ expect(Hooks).toHaveProperty('useOrganisationSecurity');
74
+ expect(typeof Hooks.useOrganisationSecurity).toBe('function');
75
+ });
76
+ });
77
+
78
+ describe('Form Hooks', () => {
79
+ it('should export useZodForm', () => {
80
+ expect(Hooks).toHaveProperty('useZodForm');
81
+ expect(typeof Hooks.useZodForm).toBe('function');
82
+ });
83
+ });
84
+
85
+ describe('Performance Hooks', () => {
86
+ it('should export useComponentPerformance', () => {
87
+ expect(Hooks).toHaveProperty('useComponentPerformance');
88
+ expect(typeof Hooks.useComponentPerformance).toBe('function');
89
+ });
90
+
91
+ it('should export useAppConfig', () => {
92
+ expect(Hooks).toHaveProperty('useAppConfig');
93
+ expect(typeof Hooks.useAppConfig).toBe('function');
94
+ });
95
+
96
+ it('should export usePerformanceMonitor', () => {
97
+ expect(Hooks).toHaveProperty('usePerformanceMonitor');
98
+ expect(typeof Hooks.usePerformanceMonitor).toBe('function');
99
+ });
100
+
101
+ it('should export useDataTablePerformance', () => {
102
+ expect(Hooks).toHaveProperty('useDataTablePerformance');
103
+ expect(typeof Hooks.useDataTablePerformance).toBe('function');
104
+ });
105
+ });
106
+
107
+ describe('File Display Hooks', () => {
108
+ it('should export useFileDisplay', () => {
109
+ expect(Hooks).toHaveProperty('useFileDisplay');
110
+ expect(typeof Hooks.useFileDisplay).toBe('function');
111
+ });
112
+
113
+ it('should export clearFileDisplayCache', () => {
114
+ expect(Hooks).toHaveProperty('clearFileDisplayCache');
115
+ expect(typeof Hooks.clearFileDisplayCache).toBe('function');
116
+ });
117
+
118
+ it('should export getFileDisplayCacheStats', () => {
119
+ expect(Hooks).toHaveProperty('getFileDisplayCacheStats');
120
+ expect(typeof Hooks.getFileDisplayCacheStats).toBe('function');
121
+ });
122
+
123
+ it('should export invalidateFileDisplayCache', () => {
124
+ expect(Hooks).toHaveProperty('invalidateFileDisplayCache');
125
+ expect(typeof Hooks.invalidateFileDisplayCache).toBe('function');
126
+ });
127
+ });
128
+
129
+ describe('Public Data Access Hooks', () => {
130
+ it('should export public hooks via wildcard export', () => {
131
+ // Wildcard export from './public' should include public hooks
132
+ // These are tested through actual usage, but we verify the export path exists
133
+ expect(Hooks).toBeDefined();
134
+ });
135
+ });
136
+
137
+ describe('Type Exports', () => {
138
+ it('should export UseOrganisationPermissionsReturn type', () => {
139
+ // Type exports are compile-time only, but we verify they're referenced
140
+ // This is mainly for documentation purposes
141
+ expect(Hooks).toBeDefined();
142
+ });
143
+
144
+ it('should export SecureDataAccessReturn type', () => {
145
+ // Type exports are compile-time only
146
+ expect(Hooks).toBeDefined();
147
+ });
148
+
149
+ it('should export OrganisationSecurityHook type', () => {
150
+ // Type exports are compile-time only
151
+ expect(Hooks).toBeDefined();
152
+ });
153
+
154
+ it('should export UseAppConfigReturn type', () => {
155
+ // Type exports are compile-time only
156
+ expect(Hooks).toBeDefined();
157
+ });
158
+
159
+ it('should export UseDataTablePerformanceOptions type', () => {
160
+ // Type exports are compile-time only
161
+ expect(Hooks).toBeDefined();
162
+ });
163
+
164
+ it('should export UseDataTablePerformanceReturn type', () => {
165
+ // Type exports are compile-time only
166
+ expect(Hooks).toBeDefined();
167
+ });
168
+
169
+ it('should export UseFileDisplayReturn type', () => {
170
+ // Type exports are compile-time only
171
+ expect(Hooks).toBeDefined();
172
+ });
173
+
174
+ it('should export UseFileDisplayOptions type', () => {
175
+ // Type exports are compile-time only
176
+ expect(Hooks).toBeDefined();
177
+ });
178
+ });
179
+
180
+ describe('Module Structure', () => {
181
+ it('should maintain correct module structure for re-exports', () => {
182
+ // Verify that all hooks are functions
183
+ const hookNames = [
184
+ 'useToast',
185
+ 'useFocusManagement',
186
+ 'useFocusTrap',
187
+ 'useKeyboardShortcuts',
188
+ 'useIsMobile',
189
+ 'useEventTheme',
190
+ 'useDebounce',
191
+ 'useDataTableState',
192
+ 'useOrganisationPermissions',
193
+ 'useSecureDataAccess',
194
+ 'useOrganisationSecurity',
195
+ 'useZodForm',
196
+ 'useComponentPerformance',
197
+ 'useAppConfig',
198
+ 'usePerformanceMonitor',
199
+ 'useDataTablePerformance',
200
+ 'useFileDisplay'
201
+ ];
202
+
203
+ hookNames.forEach((hookName) => {
204
+ expect(Hooks).toHaveProperty(hookName);
205
+ expect(typeof Hooks[hookName as keyof typeof Hooks]).toBe('function');
206
+ });
207
+ });
208
+
209
+ it('should export utility functions', () => {
210
+ const utilityFunctions = [
211
+ 'clearFileDisplayCache',
212
+ 'getFileDisplayCacheStats',
213
+ 'invalidateFileDisplayCache'
214
+ ];
215
+
216
+ utilityFunctions.forEach((funcName) => {
217
+ expect(Hooks).toHaveProperty(funcName);
218
+ expect(typeof Hooks[funcName as keyof typeof Hooks]).toBe('function');
219
+ });
220
+ });
221
+ });
222
+ });
223
+