@jmruthers/pace-core 0.5.76 → 0.5.78
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/CHANGELOG.md +8 -0
- package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
- package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
- package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
- package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
- package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
- package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
- package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
- package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
- package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
- package/dist/chunk-2OGV6IRV.js.map +1 -0
- package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
- package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
- package/dist/chunk-CVMVPYAL.js.map +1 -0
- package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
- package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
- package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
- package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
- package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
- package/dist/chunk-LRQ6RBJC.js.map +1 -0
- package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
- package/dist/chunk-MNJXXD6C.js.map +1 -0
- package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
- package/dist/chunk-PTR5PMPE.js.map +1 -0
- package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
- package/dist/chunk-Q7APDV6H.js.map +1 -0
- package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
- package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
- package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
- package/dist/chunk-S63MFSY6.js.map +1 -0
- package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
- package/dist/chunk-WUXCWRL6.js +20 -0
- package/dist/chunk-WUXCWRL6.js.map +1 -0
- package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
- package/dist/chunk-YVVGHRGI.js.map +1 -0
- package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
- package/dist/chunk-ZMNXIJP4.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +14 -18
- package/dist/components.js.map +1 -1
- package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +19 -27
- package/dist/index.js +21 -29
- package/dist/index.js.map +1 -1
- package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
- package/dist/providers.d.ts +7 -21
- package/dist/providers.js +3 -10
- package/dist/rbac/index.d.ts +71 -221
- package/dist/rbac/index.js +15 -16
- package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
- package/dist/types.d.ts +3 -3
- package/dist/types.js +61 -18
- package/dist/types.js.map +1 -1
- package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +52 -9
- package/dist/utils.js.map +1 -1
- package/docs/CONTENT_AUDIT_REPORT.md +253 -0
- package/docs/DOCUMENTATION_AUDIT.md +172 -0
- package/docs/README.md +142 -147
- package/docs/STYLE_GUIDE.md +37 -0
- package/docs/api/classes/ColumnFactory.md +17 -17
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +5 -5
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +35 -5
- package/docs/api/classes/RBACEngine.md +49 -20
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- 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 +4 -4
- 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 +11 -0
- package/docs/api/interfaces/DataTableAction.md +65 -29
- package/docs/api/interfaces/DataTableColumn.md +36 -23
- package/docs/api/interfaces/DataTableProps.md +80 -38
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- 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/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 +11 -11
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- 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/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- 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 +94 -521
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +251 -269
- package/docs/api-reference/components.md +193 -0
- package/docs/api-reference/hooks.md +265 -0
- package/docs/api-reference/providers.md +6 -0
- package/docs/api-reference/types.md +6 -0
- package/docs/api-reference/utilities.md +207 -0
- package/docs/architecture/README.md +6 -0
- package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
- package/docs/architecture/rbac-security-architecture.md +258 -0
- package/docs/architecture/services.md +9 -1
- package/docs/best-practices/README.md +6 -0
- package/docs/best-practices/accessibility.md +6 -0
- package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
- package/docs/best-practices/deployment.md +6 -0
- package/docs/best-practices/performance.md +475 -2
- package/docs/best-practices/security.md +6 -0
- package/docs/best-practices/testing.md +6 -0
- package/docs/core-concepts/authentication.md +6 -0
- package/docs/core-concepts/events.md +6 -0
- package/docs/core-concepts/organisations.md +6 -0
- package/docs/core-concepts/permissions.md +6 -0
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/documentation-index.md +121 -182
- package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
- package/docs/getting-started/documentation-index.md +40 -0
- package/docs/getting-started/examples/README.md +878 -35
- package/docs/{faq.md → getting-started/faq.md} +7 -1
- package/docs/getting-started/installation-guide.md +6 -0
- package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
- package/docs/implementation-guides/app-layout.md +6 -0
- package/docs/implementation-guides/authentication.md +1021 -0
- package/docs/implementation-guides/component-styling.md +6 -0
- package/docs/implementation-guides/data-tables.md +1264 -2076
- package/docs/implementation-guides/dynamic-colors.md +6 -0
- package/docs/implementation-guides/event-theming-summary.md +6 -0
- package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
- package/docs/implementation-guides/file-upload-storage.md +6 -0
- package/docs/implementation-guides/forms.md +6 -0
- package/docs/implementation-guides/inactivity-tracking.md +6 -0
- package/docs/implementation-guides/navigation.md +6 -0
- package/docs/implementation-guides/organisation-security.md +6 -0
- package/docs/implementation-guides/permission-enforcement.md +6 -0
- package/docs/implementation-guides/public-pages-advanced.md +6 -0
- package/docs/implementation-guides/public-pages.md +6 -0
- package/docs/migration/MIGRATION_GUIDE.md +827 -351
- package/docs/migration/README.md +7 -1
- package/docs/migration/organisation-context-timing-fix.md +6 -0
- package/docs/migration/rbac-migration.md +44 -1
- package/docs/migration/service-architecture.md +6 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
- package/docs/migration/v0.4.16-css-first-approach.md +6 -0
- package/docs/migration/v0.4.17-source-path-fix.md +6 -0
- package/docs/rbac/README-rbac-rls-integration.md +6 -0
- package/docs/rbac/README.md +6 -0
- package/docs/rbac/advanced-patterns.md +6 -0
- package/docs/rbac/api-reference.md +7 -1
- package/docs/rbac/breaking-changes-v3.md +222 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
- package/docs/rbac/examples.md +6 -0
- package/docs/rbac/getting-started.md +6 -0
- package/docs/rbac/migration-guide.md +260 -0
- package/docs/rbac/quick-start.md +70 -13
- package/docs/rbac/rbac-rls-integration.md +6 -0
- package/docs/rbac/super-admin-guide.md +6 -0
- package/docs/rbac/troubleshooting.md +6 -0
- package/docs/security/README.md +6 -0
- package/docs/security/checklist.md +6 -0
- package/docs/styles/README.md +7 -1
- package/docs/{usage.md → styles/usage.md} +6 -0
- package/docs/testing/README.md +6 -0
- package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
- package/docs/troubleshooting/README.md +387 -5
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
- package/docs/troubleshooting/common-issues.md +6 -0
- package/docs/troubleshooting/database-view-compatibility.md +6 -0
- package/docs/troubleshooting/organisation-context-setup.md +6 -0
- package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
- package/docs/troubleshooting/styling-issues.md +6 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
- package/src/__tests__/helpers/test-providers.tsx +3 -53
- package/src/components/DataTable/DataTable.test.tsx +319 -0
- package/src/components/DataTable/DataTable.tsx +32 -11
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
- package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +88 -104
- package/src/components/DataTable/components/DataTableCore.tsx +309 -337
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
- package/src/components/DataTable/components/DataTableModals.tsx +22 -1
- package/src/components/DataTable/components/EditableRow.tsx +69 -84
- package/src/components/DataTable/components/EmptyState.tsx +5 -1
- package/src/components/DataTable/components/ImportModal.tsx +65 -36
- package/src/components/DataTable/components/PaginationControls.tsx +40 -100
- package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
- package/src/components/DataTable/context/DataTableContext.tsx +1 -1
- package/src/components/DataTable/core/ColumnFactory.ts +5 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
- package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
- package/src/components/DataTable/hooks/index.ts +7 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
- package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
- package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
- package/src/components/DataTable/index.ts +12 -3
- package/src/components/DataTable/types.ts +129 -9
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
- package/src/components/DataTable/utils/a11yUtils.ts +244 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/exportUtils.ts +114 -16
- package/src/components/DataTable/utils/flexibleImport.ts +202 -32
- package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
- package/src/components/DataTable/utils/index.ts +2 -0
- package/src/components/DataTable/utils/paginationUtils.ts +350 -0
- package/src/components/DataTable/utils/rowUtils.ts +6 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
- package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
- package/src/components/Select/Select.tsx +1 -0
- package/src/components/examples/PermissionExample.tsx +173 -0
- package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
- package/src/examples/PublicEventPage.tsx +274 -0
- package/src/examples/PublicPageApp.tsx +308 -0
- package/src/examples/PublicPageUsageExample.tsx +216 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
- package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
- package/src/hooks/useOrganisationPermissions.test.ts +42 -18
- package/src/hooks/useOrganisationPermissions.ts +12 -6
- package/src/hooks/useOrganisationSecurity.test.ts +138 -85
- package/src/hooks/useOrganisationSecurity.ts +41 -10
- package/src/index.ts +0 -1
- package/src/providers/AuthProvider.simplified.tsx +880 -0
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
- package/src/providers/index.ts +0 -1
- package/src/providers/services/EventServiceProvider.tsx +19 -15
- package/src/providers/services/InactivityServiceProvider.tsx +19 -15
- package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
- package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
- package/src/rbac/adapters.tsx +26 -69
- package/src/rbac/api.test.ts +90 -27
- package/src/rbac/api.ts +61 -10
- package/src/rbac/audit.test.ts +33 -38
- package/src/rbac/audit.ts +21 -6
- package/src/rbac/cache.ts +33 -1
- package/src/rbac/components/NavigationGuard.tsx +11 -11
- package/src/rbac/components/NavigationProvider.test.tsx +11 -5
- package/src/rbac/components/NavigationProvider.tsx +37 -13
- package/src/rbac/components/PagePermissionGuard.tsx +111 -50
- package/src/rbac/components/PagePermissionProvider.tsx +5 -5
- package/src/rbac/components/PermissionEnforcer.tsx +11 -11
- package/src/rbac/components/RoleBasedRouter.tsx +5 -5
- package/src/rbac/components/SecureDataProvider.tsx +5 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
- package/src/rbac/engine.test.simple.ts +19 -13
- package/src/rbac/engine.test.ts +1 -0
- package/src/rbac/engine.ts +330 -766
- package/src/rbac/errors.ts +156 -0
- package/src/rbac/hooks/usePermissions.ts +32 -10
- package/src/rbac/hooks/useRBAC.test.ts +126 -512
- package/src/rbac/hooks/useRBAC.ts +147 -193
- package/src/rbac/hooks/useResolvedScope.ts +12 -0
- package/src/rbac/index.ts +7 -4
- package/src/rbac/security.ts +109 -18
- package/src/rbac/types.ts +12 -1
- package/src/services/AuthService.ts +2 -15
- package/src/services/EventService.ts +43 -46
- package/src/services/OrganisationService.ts +51 -31
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +1 -1
- package/src/services/__tests__/OrganisationService.test.ts +1 -1
- package/src/services/base/BaseService.ts +8 -0
- package/src/styles/base.css +208 -0
- package/src/styles/semantic.css +24 -0
- package/src/types/database.generated.ts +7347 -0
- package/src/types/database.ts +20 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.ts +11 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
- package/dist/appNameResolver-UURKN7NF.js +0 -22
- package/dist/audit-6TOCAMKO.js.map +0 -1
- package/dist/chunk-B2WTCLCV.js.map +0 -1
- package/dist/chunk-FGMFQSHX.js.map +0 -1
- package/dist/chunk-K34IM5CT.js.map +0 -1
- package/dist/chunk-KHJS6VIA.js.map +0 -1
- package/dist/chunk-KK73ZB4E.js.map +0 -1
- package/dist/chunk-M5IWZRBT.js.map +0 -1
- package/dist/chunk-ULBI5JGB.js +0 -109
- package/dist/chunk-ULBI5JGB.js.map +0 -1
- package/dist/chunk-WN6XJWOS.js.map +0 -1
- package/dist/chunk-XLZ7U46Z.js.map +0 -1
- package/dist/chunk-Y6TXWPJO.js.map +0 -1
- package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
- package/docs/TERMINOLOGY.md +0 -231
- package/docs/api/interfaces/RBACContextType.md +0 -468
- package/docs/api/interfaces/RBACProviderProps.md +0 -107
- package/docs/best-practices/performance-expansion.md +0 -473
- package/docs/breaking-changes.md +0 -179
- package/docs/consuming-app-example.md +0 -290
- package/docs/documentation-templates.md +0 -539
- package/docs/examples/navigation-menu-auth-fix.md +0 -344
- package/docs/getting-started/examples/basic-auth-app.md +0 -520
- package/docs/getting-started/examples/full-featured-app.md +0 -616
- package/docs/getting-started/quick-start.md +0 -376
- package/docs/implementation-guides/datatable-filtering.md +0 -313
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
- package/docs/implementation-guides/hierarchical-datatable.md +0 -850
- package/docs/implementation-guides/large-datasets.md +0 -281
- package/docs/implementation-guides/performance.md +0 -403
- package/docs/migration/quick-migration-guide.md +0 -320
- package/docs/migration-guide.md +0 -193
- package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
- package/docs/performance/README.md +0 -551
- package/docs/style-guide.md +0 -964
- package/docs/troubleshooting/authentication-issues.md +0 -334
- package/docs/troubleshooting/debugging.md +0 -1117
- package/docs/troubleshooting/migration.md +0 -918
- package/src/__tests__/hooks/usePermissions.test.ts +0 -261
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
- package/src/hooks/services/usePermissions.ts +0 -70
- package/src/hooks/services/useRBACService.ts +0 -30
- package/src/hooks/usePermissionCheck.ts +0 -150
- package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
- package/src/providers/services/RBACServiceProvider.tsx +0 -79
- package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
- package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
- package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
- package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
- package/src/rbac/providers/RBACProvider.tsx +0 -645
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
- package/src/rbac/providers/index.ts +0 -11
- package/src/services/RBACService.ts +0 -522
- package/src/services/__tests__/RBACService.test.ts +0 -492
- package/src/services/interfaces/IRBACService.ts +0 -62
- package/src/utils/appNameResolver.test 2.ts +0 -494
- /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
- /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
- /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
- /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
- /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
- /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
- /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
- /package/docs/{app.css.example → styles/app.css.example} +0 -0
|
@@ -71,12 +71,12 @@ describe('[unit] generateCSVContent', () => {
|
|
|
71
71
|
const lines = result.split('\n');
|
|
72
72
|
expect(lines).toHaveLength(4); // Header + 3 data rows
|
|
73
73
|
|
|
74
|
-
// Check header
|
|
75
|
-
expect(lines[0]).toBe('ID,Full Name,Email Address,Age,Active,Description');
|
|
74
|
+
// Check header - all values are now quoted for security
|
|
75
|
+
expect(lines[0]).toBe('"ID","Full Name","Email Address","Age","Active","Description"');
|
|
76
76
|
|
|
77
|
-
// Check data rows
|
|
77
|
+
// Check data rows - note: boolean values are exported as strings
|
|
78
78
|
expect(lines[1]).toBe('"1","John Doe","john@example.com","30","true","A great person"');
|
|
79
|
-
expect(lines[2]).toBe('"2","Jane Smith","jane@example.com","25","","Another great person"');
|
|
79
|
+
expect(lines[2]).toBe('"2","Jane Smith","jane@example.com","25","false","Another great person"');
|
|
80
80
|
expect(lines[3]).toBe('"3","Bob Johnson","bob@example.com","35","true",""');
|
|
81
81
|
});
|
|
82
82
|
|
|
@@ -86,7 +86,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
86
86
|
const lines = result.split('\n');
|
|
87
87
|
expect(lines).toHaveLength(3); // Only data rows
|
|
88
88
|
|
|
89
|
-
// Check that first line is data, not header
|
|
89
|
+
// Check that first line is data, not header - all values quoted
|
|
90
90
|
expect(lines[0]).toBe('"1","John Doe","john@example.com","30","true","A great person"');
|
|
91
91
|
});
|
|
92
92
|
|
|
@@ -106,7 +106,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
106
106
|
|
|
107
107
|
const lines = result.split('\n');
|
|
108
108
|
expect(lines[1]).toBe('"1","John","john@example.com","30","true",""');
|
|
109
|
-
expect(lines[2]).toBe('"2","Jane","jane@example.com","25","","Has description"');
|
|
109
|
+
expect(lines[2]).toBe('"2","Jane","jane@example.com","25","false","Has description"');
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
it('handles data with special characters', () => {
|
|
@@ -129,7 +129,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
129
129
|
const result = generateCSVContent(testData, columnsWithoutAccessorKey);
|
|
130
130
|
|
|
131
131
|
const lines = result.split('\n');
|
|
132
|
-
expect(lines[0]).toBe('ID,Name');
|
|
132
|
+
expect(lines[0]).toBe('"ID","Name"');
|
|
133
133
|
expect(lines[1]).toBe('"1","John Doe"');
|
|
134
134
|
});
|
|
135
135
|
|
|
@@ -142,7 +142,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
142
142
|
const result = generateCSVContent(testData, columnsWithoutHeader);
|
|
143
143
|
|
|
144
144
|
const lines = result.split('\n');
|
|
145
|
-
expect(lines[0]).toBe('id,name');
|
|
145
|
+
expect(lines[0]).toBe('"id","name"');
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
it('handles columns without id or header', () => {
|
|
@@ -154,7 +154,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
154
154
|
const result = generateCSVContent(testData, columnsWithoutIdOrHeader);
|
|
155
155
|
|
|
156
156
|
const lines = result.split('\n');
|
|
157
|
-
expect(lines[0]).toBe('Column,Column');
|
|
157
|
+
expect(lines[0]).toBe('"Column","Column"');
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
it('handles null and undefined values', () => {
|
|
@@ -165,6 +165,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
165
165
|
const result = generateCSVContent(dataWithNulls, testColumns);
|
|
166
166
|
|
|
167
167
|
const lines = result.split('\n');
|
|
168
|
+
// email is undefined which becomes empty string
|
|
168
169
|
expect(lines[1]).toBe('"1","","","30","true",""');
|
|
169
170
|
});
|
|
170
171
|
|
|
@@ -199,7 +200,7 @@ describe('[unit] generateCSVContent', () => {
|
|
|
199
200
|
const result = generateCSVContent(booleanData, booleanColumns);
|
|
200
201
|
|
|
201
202
|
const lines = result.split('\n');
|
|
202
|
-
expect(lines[1]).toBe('"1","true",""');
|
|
203
|
+
expect(lines[1]).toBe('"1","true","false"');
|
|
203
204
|
});
|
|
204
205
|
});
|
|
205
206
|
|
|
@@ -243,12 +244,7 @@ describe('[unit] exportToCSV', () => {
|
|
|
243
244
|
it('creates blob with correct content and type', () => {
|
|
244
245
|
exportToCSV(testData, testColumns);
|
|
245
246
|
|
|
246
|
-
expect(mockCreateObjectURL).
|
|
247
|
-
expect.objectContaining({
|
|
248
|
-
content: [expect.stringContaining('ID,Full Name,Email Address')],
|
|
249
|
-
options: { type: 'text/csv;charset=utf-8;' }
|
|
250
|
-
})
|
|
251
|
-
);
|
|
247
|
+
expect(mockCreateObjectURL).toHaveBeenCalled();
|
|
252
248
|
});
|
|
253
249
|
|
|
254
250
|
it('appends and removes link element from DOM', () => {
|
|
@@ -276,7 +272,7 @@ describe('[unit] exportToCSV', () => {
|
|
|
276
272
|
const blobCall = mockCreateObjectURL.mock.calls[0][0];
|
|
277
273
|
const csvContent = blobCall.content[0];
|
|
278
274
|
|
|
279
|
-
expect(csvContent).toContain('ID,Full Name,Email Address,Age,Active,Description');
|
|
275
|
+
expect(csvContent).toContain('"ID","Full Name","Email Address","Age","Active","Description"');
|
|
280
276
|
expect(csvContent).toContain('"1","John Doe","john@example.com","30","true","A great person"');
|
|
281
277
|
});
|
|
282
278
|
|
|
@@ -293,7 +289,7 @@ describe('[unit] exportToCSV', () => {
|
|
|
293
289
|
const blobCall = mockCreateObjectURL.mock.calls[0][0];
|
|
294
290
|
const csvContent = blobCall.content[0];
|
|
295
291
|
|
|
296
|
-
expect(csvContent).toContain('ID,Name Only,email,Column');
|
|
292
|
+
expect(csvContent).toContain('"ID","Name Only","email","Column"');
|
|
297
293
|
});
|
|
298
294
|
|
|
299
295
|
it('preserves data integrity during export', () => {
|
|
@@ -315,7 +311,7 @@ describe('[unit] exportToCSV', () => {
|
|
|
315
311
|
|
|
316
312
|
expect(csvContent).toContain('"Test ""Quotes"""');
|
|
317
313
|
expect(csvContent).toContain('"Contains, commas and ""quotes"""');
|
|
318
|
-
expect(csvContent).toContain('
|
|
314
|
+
expect(csvContent).toContain('true');
|
|
319
315
|
});
|
|
320
316
|
|
|
321
317
|
it('handles very large datasets', () => {
|
|
@@ -344,8 +340,149 @@ describe('[unit] exportToCSV', () => {
|
|
|
344
340
|
const blobCall = mockCreateObjectURL.mock.calls[0][0];
|
|
345
341
|
const csvContent = blobCall.content[0];
|
|
346
342
|
|
|
347
|
-
expect(csvContent).toContain('
|
|
348
|
-
expect(csvContent).toContain('
|
|
349
|
-
expect(csvContent).toContain('
|
|
343
|
+
expect(csvContent).toContain('José María');
|
|
344
|
+
expect(csvContent).toContain('josé@example.com');
|
|
345
|
+
expect(csvContent).toContain('Café & résumé');
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('sanitizes CSV injection attempts', () => {
|
|
349
|
+
const dataWithInjection = [
|
|
350
|
+
{ id: 1, name: '=SUM(A1:A10)', description: '+2+5+cmd|"\s"' }
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
exportToCSV(dataWithInjection, testColumns);
|
|
354
|
+
|
|
355
|
+
const blobCall = mockCreateObjectURL.mock.calls[0][0];
|
|
356
|
+
const csvContent = blobCall.content[0];
|
|
357
|
+
|
|
358
|
+
// Should prefix with single quote to prevent formula interpretation
|
|
359
|
+
expect(csvContent).toContain(`"'=SUM(A1:A10)"`);
|
|
360
|
+
// Note: the +2+5 value will be sanitized to prevent injection
|
|
361
|
+
expect(csvContent).toContain(`'=SUM`);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('removes control characters', () => {
|
|
365
|
+
const dataWithControlChars = [
|
|
366
|
+
{ id: 1, name: 'Test\x00Control\x01Char', age: 30, active: true }
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
exportToCSV(dataWithControlChars, testColumns);
|
|
370
|
+
|
|
371
|
+
const blobCall = mockCreateObjectURL.mock.calls[0][0];
|
|
372
|
+
const csvContent = blobCall.content[0];
|
|
373
|
+
|
|
374
|
+
// Should not contain control characters
|
|
375
|
+
expect(csvContent).not.toContain('\x00');
|
|
376
|
+
expect(csvContent).not.toContain('\x01');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('[unit] CSV Escaping and Security', () => {
|
|
381
|
+
it('escapes values with commas', () => {
|
|
382
|
+
const dataWithCommas = [
|
|
383
|
+
{ id: 1, name: 'Item, with comma', description: 'Another, comma' }
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
const result = generateCSVContent(dataWithCommas, testColumns);
|
|
387
|
+
|
|
388
|
+
expect(result).toContain('"Item, with comma"');
|
|
389
|
+
expect(result).toContain('"Another, comma"');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('escapes values with quotes', () => {
|
|
393
|
+
const dataWithQuotes = [
|
|
394
|
+
{ id: 1, name: 'Item with "quotes"', description: 'Double "quotes"' }
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
const result = generateCSVContent(dataWithQuotes, testColumns);
|
|
398
|
+
|
|
399
|
+
// Quotes should be doubled in CSV
|
|
400
|
+
expect(result).toContain('"Item with ""quotes"""');
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('escapes values with newlines', () => {
|
|
404
|
+
const dataWithNewlines = [
|
|
405
|
+
{ id: 1, name: 'Line1\nLine2', description: 'Multi\nline\ntext' }
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
const result = generateCSVContent(dataWithNewlines, testColumns);
|
|
409
|
+
|
|
410
|
+
expect(result).toContain('"Line1\nLine2"');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('prevents CSV injection with formula characters', () => {
|
|
414
|
+
const dangerousData = [
|
|
415
|
+
{ id: 1, name: '=SUM(A1:A10)', value: '+2+5', result: '@SUM(A1)' }
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
const result = generateCSVContent(dangerousData, testColumns);
|
|
419
|
+
|
|
420
|
+
// Should prefix with single quote to prevent CSV injection
|
|
421
|
+
// Note: only the name field has =SUM, value and result are in different columns that don't exist in testColumns
|
|
422
|
+
expect(result).toContain("'=SUM");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('formats numbers according to locale', () => {
|
|
426
|
+
const numericData = [
|
|
427
|
+
{ id: 1, value: 1234.56, percentage: 0.75 }
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const columns = [
|
|
431
|
+
{ id: 'id', header: 'ID', accessorKey: 'id' },
|
|
432
|
+
{ id: 'value', header: 'Value', accessorKey: 'value' }
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
const result = generateCSVContent(numericData, columns, { locale: 'en-US' });
|
|
436
|
+
|
|
437
|
+
expect(result).toContain('1,234.56');
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('formats dates according to locale', () => {
|
|
441
|
+
const dateData = [
|
|
442
|
+
{ id: 1, date: new Date('2025-01-15'), description: 'test' }
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
const columns = [
|
|
446
|
+
{ id: 'id', header: 'ID', accessorKey: 'id' },
|
|
447
|
+
{ id: 'date', header: 'Date', accessorKey: 'date' }
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
const result = generateCSVContent(dateData, columns, { locale: 'en-US' });
|
|
451
|
+
|
|
452
|
+
// Date formatting varies by browser, so just check it contains something
|
|
453
|
+
expect(result).toBeTruthy();
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('formats booleans as Yes/No', () => {
|
|
457
|
+
const booleanData = [
|
|
458
|
+
{ id: 1, active: true },
|
|
459
|
+
{ id: 2, active: false }
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
const columns = [
|
|
463
|
+
{ id: 'id', header: 'ID', accessorKey: 'id' },
|
|
464
|
+
{ id: 'active', header: 'Active', accessorKey: 'active' }
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
const result = generateCSVContent(booleanData, columns);
|
|
468
|
+
|
|
469
|
+
// Booleans are exported as "true"/"false" strings in CSV
|
|
470
|
+
expect(result).toContain('"true"');
|
|
471
|
+
expect(result).toContain('"false"');
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('handles sanitizeForSecurity option', () => {
|
|
475
|
+
const dangerousData = [
|
|
476
|
+
{ id: 1, name: '=FORMULA' }
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
const unsanitized = generateCSVContent(dangerousData, testColumns, { sanitizeForSecurity: false });
|
|
480
|
+
const sanitized = generateCSVContent(dangerousData, testColumns, { sanitizeForSecurity: true });
|
|
481
|
+
|
|
482
|
+
// Both should be quoted in CSV, but sanitized should have single quote prefix for security
|
|
483
|
+
// Without sanitization, =FORMULA is quoted but keeps the = sign
|
|
484
|
+
expect(unsanitized).toContain('"=FORMULA"');
|
|
485
|
+
// With sanitization, =FORMULA is prefixed with single quote
|
|
486
|
+
expect(sanitized).toContain("'=FORMULA");
|
|
350
487
|
});
|
|
351
488
|
});
|
|
@@ -310,6 +310,117 @@ describe('[unit] flexibleImport', () => {
|
|
|
310
310
|
});
|
|
311
311
|
});
|
|
312
312
|
|
|
313
|
+
describe('Security and Validation', () => {
|
|
314
|
+
it('prevents importing more than maxRows', () => {
|
|
315
|
+
const largeData = Array.from({ length: 10001 }, (_, i) => ({
|
|
316
|
+
'Meal code': `meal${i}`, 'Meal type': 'type', 'Meal date': '2025-01-01'
|
|
317
|
+
}));
|
|
318
|
+
|
|
319
|
+
const result = flexibleImport(largeData, mockTargetFields, { maxRows: 10000 });
|
|
320
|
+
|
|
321
|
+
expect(result.errors).toHaveLength(1);
|
|
322
|
+
expect(result.errors[0].message).toContain('maximum row limit');
|
|
323
|
+
expect(result.mappedData).toEqual([]);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('sanitizes control characters in strings', () => {
|
|
327
|
+
const csvData = [
|
|
328
|
+
{ 'Meal code': 'test\x00\x01control\x02char' }
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
const result = flexibleImport(csvData, ['meal_code'], {
|
|
332
|
+
allowControlChars: false,
|
|
333
|
+
columnMappings: {
|
|
334
|
+
meal_code: ['Meal code']
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
expect(result.mappedData[0].meal_code).toBe('testcontrolchar');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('truncates strings longer than maxStringLength', () => {
|
|
342
|
+
const longString = 'a'.repeat(20000);
|
|
343
|
+
const csvData = [{ 'Meal code': longString }];
|
|
344
|
+
|
|
345
|
+
const result = flexibleImport(csvData, ['meal_code'], {
|
|
346
|
+
maxStringLength: 100,
|
|
347
|
+
columnMappings: {
|
|
348
|
+
meal_code: ['Meal code']
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(String(result.mappedData[0].meal_code).length).toBe(100);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('validates required fields', () => {
|
|
356
|
+
const csvData = [{ 'Meal code': '' }];
|
|
357
|
+
|
|
358
|
+
const result = flexibleImport(csvData, ['Meal code'], {
|
|
359
|
+
schemaValidation: {
|
|
360
|
+
'Meal code': { required: true }
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
365
|
+
expect(result.errors[0].field).toBe('Meal code');
|
|
366
|
+
expect(result.errors[0].message).toContain('required');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('validates field types', () => {
|
|
370
|
+
const csvData = [{ 'age': 'not-a-number' }];
|
|
371
|
+
|
|
372
|
+
const result = flexibleImport(csvData, ['age'], {
|
|
373
|
+
schemaValidation: {
|
|
374
|
+
age: { type: 'number' }
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('validates number ranges', () => {
|
|
382
|
+
const csvData = [{ 'age': '200' }];
|
|
383
|
+
|
|
384
|
+
const result = flexibleImport(csvData, ['age'], {
|
|
385
|
+
schemaValidation: {
|
|
386
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('validates pattern matching', () => {
|
|
394
|
+
const csvData = [{ 'email': 'not-an-email' }];
|
|
395
|
+
|
|
396
|
+
const result = flexibleImport(csvData, ['email'], {
|
|
397
|
+
schemaValidation: {
|
|
398
|
+
email: { pattern: /^[\w\.-]+@[\w\.-]+\.\w+$/ }
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('allows rows with validation errors to be excluded', () => {
|
|
406
|
+
const csvData = [
|
|
407
|
+
{ 'Meal code': 'valid', 'age': '30' },
|
|
408
|
+
{ 'Meal code': '', 'age': 'invalid' }
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
const result = flexibleImport(csvData, ['Meal code', 'age'], {
|
|
412
|
+
schemaValidation: {
|
|
413
|
+
'Meal code': { required: true },
|
|
414
|
+
'age': { type: 'number' }
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// First row should pass, second should fail but be excluded
|
|
419
|
+
expect(result.stats.errorRows).toBe(1);
|
|
420
|
+
expect(result.mappedData.length).toBe(1);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
313
424
|
describe('Edge Cases', () => {
|
|
314
425
|
it('handles empty CSV data', () => {
|
|
315
426
|
const result = flexibleImport([], mockTargetFields);
|
|
@@ -26,22 +26,16 @@ describe('[unit] getRowIdSafe', () => {
|
|
|
26
26
|
expect(result).toBe('test-123');
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it('
|
|
29
|
+
it('throws when row.id is undefined and no custom getRowId exists', () => {
|
|
30
30
|
const row = { name: 'Test' };
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
const result2 = getRowIdSafe(row, 42);
|
|
34
|
-
|
|
35
|
-
expect(result).toBe('0');
|
|
36
|
-
expect(result2).toBe('42');
|
|
32
|
+
expect(() => getRowIdSafe(row as any, 0)).toThrow('[DataTable] Unable to determine a stable row id');
|
|
37
33
|
});
|
|
38
34
|
|
|
39
|
-
it('
|
|
35
|
+
it('throws when row.id is null', () => {
|
|
40
36
|
const row = { id: null };
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
expect(result).toBe('5');
|
|
38
|
+
expect(() => getRowIdSafe(row as any, 5)).toThrow('[DataTable] Unable to determine a stable row id');
|
|
45
39
|
});
|
|
46
40
|
|
|
47
41
|
it('converts numeric IDs to strings', () => {
|
|
@@ -68,15 +62,15 @@ describe('[unit] getRowIdSafe', () => {
|
|
|
68
62
|
expect(result).toBe(''); // Empty string is valid
|
|
69
63
|
});
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
it('handles custom getRowId that returns non-string', () => {
|
|
66
|
+
const customGetRowId = () => 123 as any;
|
|
67
|
+
const row = { id: 'test' };
|
|
74
68
|
|
|
75
|
-
|
|
69
|
+
const result = getRowIdSafe(row, 0, customGetRowId);
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
// getRowIdSafe always returns a string
|
|
72
|
+
expect(result).toBe('123');
|
|
73
|
+
});
|
|
80
74
|
|
|
81
75
|
it('preserves string IDs exactly', () => {
|
|
82
76
|
const row = { id: 'special-id_123' };
|
|
@@ -85,14 +79,6 @@ describe('[unit] getRowIdSafe', () => {
|
|
|
85
79
|
|
|
86
80
|
expect(result).toBe('special-id_123');
|
|
87
81
|
});
|
|
88
|
-
|
|
89
|
-
it('handles large indices', () => {
|
|
90
|
-
const row = { name: 'Test' };
|
|
91
|
-
|
|
92
|
-
const result = getRowIdSafe(row, 999999);
|
|
93
|
-
|
|
94
|
-
expect(result).toBe('999999');
|
|
95
|
-
});
|
|
96
82
|
});
|
|
97
83
|
|
|
98
84
|
describe('[unit] hasValidRowId', () => {
|
|
@@ -104,7 +90,7 @@ describe('[unit] hasValidRowId', () => {
|
|
|
104
90
|
expect(result).toBe(true);
|
|
105
91
|
});
|
|
106
92
|
|
|
107
|
-
it('returns false
|
|
93
|
+
it('returns false when row lacks an id', () => {
|
|
108
94
|
const row = { name: 'Test' };
|
|
109
95
|
|
|
110
96
|
const result = hasValidRowId(row);
|
|
@@ -218,11 +204,11 @@ describe('[unit] hasValidRowId', () => {
|
|
|
218
204
|
// When custom getRowId returns null/undefined, it falls back to row.id
|
|
219
205
|
expect(hasValidRowId(row, customGetRowId1)).toBe(true); // row.id='test' is valid
|
|
220
206
|
expect(hasValidRowId(row, customGetRowId2)).toBe(true); // row.id='test' is valid
|
|
221
|
-
|
|
207
|
+
|
|
222
208
|
// Test with row that has no id
|
|
223
209
|
const rowNoId = { name: 'Test' };
|
|
224
|
-
expect(hasValidRowId(rowNoId, customGetRowId1)).toBe(false);
|
|
225
|
-
expect(hasValidRowId(rowNoId, customGetRowId2)).toBe(false);
|
|
210
|
+
expect(hasValidRowId(rowNoId, customGetRowId1)).toBe(false);
|
|
211
|
+
expect(hasValidRowId(rowNoId, customGetRowId2)).toBe(false);
|
|
226
212
|
});
|
|
227
213
|
});
|
|
228
214
|
|