@jmruthers/pace-core 0.5.184 → 0.5.186
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 +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +206 -15
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- 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/ComplianceResult.md +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.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 +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -13
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
- 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/LoggerConfig.md +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- 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/QuickFix.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- 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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- 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/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +746 -50
- package/docs/api-reference/components.md +26 -12
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/check-pace-core-compliance.js +512 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-MI7HBHN3.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/chunk-W22JP75J.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-publish Validation Script
|
|
5
|
+
* Now uses standardized test execution consistent with npm run test
|
|
6
|
+
* Simplified to rely on vitest exit codes rather than output parsing
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import {
|
|
12
|
+
runCommand,
|
|
13
|
+
runStandardizedTests,
|
|
14
|
+
} from './utils/command-runner.js';
|
|
15
|
+
import { getPackageRoot, getRepoRoot } from './utils/path-helpers.js';
|
|
16
|
+
import { getDefaultTimeout, getTestTimeout } from './utils/env.js';
|
|
17
|
+
import { logger } from './utils/logger.js';
|
|
18
|
+
|
|
19
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
20
|
+
const repoRoot = getRepoRoot(import.meta.url);
|
|
21
|
+
const DEFAULT_TIMEOUT = getDefaultTimeout(30000, 120000);
|
|
22
|
+
const TEST_TIMEOUT = getTestTimeout(300000, 300000);
|
|
23
|
+
|
|
24
|
+
logger.header('Running pre-publish validation');
|
|
25
|
+
|
|
26
|
+
// Check if typedoc and its plugins are installed
|
|
27
|
+
function checkTypedocInstallation() {
|
|
28
|
+
const requiredPackages = [
|
|
29
|
+
'typedoc',
|
|
30
|
+
'typedoc-plugin-markdown',
|
|
31
|
+
'typedoc-plugin-merge-modules'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
logger.info('Checking TypeDoc installation...');
|
|
35
|
+
|
|
36
|
+
// Check for specific packages
|
|
37
|
+
const missingPackages = requiredPackages.filter(pkg => {
|
|
38
|
+
const pkgPath = join(packageRoot, 'node_modules', pkg);
|
|
39
|
+
return !existsSync(pkgPath);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (missingPackages.length > 0) {
|
|
43
|
+
logger.error(`Missing required packages: ${missingPackages.join(', ')}`);
|
|
44
|
+
logger.info(`Please run: npm install --save-dev ${missingPackages.join(' ')}`);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
logger.success('TypeDoc installation verified');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Main validation function
|
|
53
|
+
async function runPrePublishValidation() {
|
|
54
|
+
logger.info('Running validation steps...');
|
|
55
|
+
logger.newline();
|
|
56
|
+
|
|
57
|
+
// Step 1: Type check
|
|
58
|
+
logger.step('Step 1: Type check');
|
|
59
|
+
const typeCheck = await runCommand('npm', ['run', 'type-check'], {
|
|
60
|
+
cwd: packageRoot,
|
|
61
|
+
timeout: DEFAULT_TIMEOUT,
|
|
62
|
+
});
|
|
63
|
+
if (!typeCheck.success) {
|
|
64
|
+
logger.error('Type check failed');
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Step 2: Build validation
|
|
69
|
+
logger.newline();
|
|
70
|
+
logger.step('Step 2: Build validation');
|
|
71
|
+
const build = await runCommand('npm', ['run', 'build'], {
|
|
72
|
+
cwd: packageRoot,
|
|
73
|
+
timeout: getDefaultTimeout(60000, 180000),
|
|
74
|
+
});
|
|
75
|
+
if (!build.success) {
|
|
76
|
+
logger.error('Build failed');
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Step 3: Test validation - now uses standardized test execution
|
|
81
|
+
logger.newline();
|
|
82
|
+
logger.step('Step 3: Test validation');
|
|
83
|
+
const tests = await runStandardizedTests(packageRoot, repoRoot, TEST_TIMEOUT);
|
|
84
|
+
if (!tests.success) {
|
|
85
|
+
logger.error('Tests failed');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Step 4: Documentation generation
|
|
90
|
+
logger.newline();
|
|
91
|
+
logger.step('Step 4: Documentation generation');
|
|
92
|
+
if (!checkTypedocInstallation()) {
|
|
93
|
+
logger.error('TypeDoc validation failed');
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const docs = await runCommand('npm', ['run', 'build:docs'], {
|
|
98
|
+
cwd: packageRoot,
|
|
99
|
+
timeout: getDefaultTimeout(120000, 240000),
|
|
100
|
+
});
|
|
101
|
+
if (!docs.success) {
|
|
102
|
+
logger.error('Documentation generation failed');
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 5: Format validation
|
|
107
|
+
logger.newline();
|
|
108
|
+
logger.step('Step 5: Format validation');
|
|
109
|
+
const format = await runCommand('npm', ['run', 'format:check'], {
|
|
110
|
+
cwd: packageRoot,
|
|
111
|
+
timeout: DEFAULT_TIMEOUT,
|
|
112
|
+
});
|
|
113
|
+
if (!format.success) {
|
|
114
|
+
logger.error('Format validation failed');
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.newline();
|
|
119
|
+
logger.header('PRE-PUBLISH VALIDATION SUMMARY');
|
|
120
|
+
logger.success('All validation steps passed successfully!');
|
|
121
|
+
logger.success('Package meets publication requirements');
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Run the validation
|
|
126
|
+
(async () => {
|
|
127
|
+
const success = await runPrePublishValidation();
|
|
128
|
+
if (!success) {
|
|
129
|
+
logger.newline();
|
|
130
|
+
logger.info('Required fixes:');
|
|
131
|
+
logger.info('- Fix validation failures');
|
|
132
|
+
logger.info('- Resolve build or type check errors');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
} else {
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
})().catch(error => {
|
|
138
|
+
logger.error(`Pre-publish validation error: ${error.message}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Theme Validation Script
|
|
5
|
+
*
|
|
6
|
+
* Validates that the Tailwind v4 theme file is properly structured
|
|
7
|
+
* and doesn't have common issues like circular references or missing colors.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { getPackageRoot } from './utils/path-helpers.js';
|
|
13
|
+
import { logger } from './utils/logger.js';
|
|
14
|
+
|
|
15
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
16
|
+
const THEME_FILE = join(packageRoot, 'src/styles/core.css');
|
|
17
|
+
|
|
18
|
+
// Required semantic colors for Tailwind v4
|
|
19
|
+
const REQUIRED_SEMANTIC_COLORS = [
|
|
20
|
+
'--color-ring',
|
|
21
|
+
'--color-border',
|
|
22
|
+
'--color-input',
|
|
23
|
+
'--color-background',
|
|
24
|
+
'--color-foreground',
|
|
25
|
+
'--color-primary',
|
|
26
|
+
'--color-primary-foreground',
|
|
27
|
+
'--color-secondary',
|
|
28
|
+
'--color-secondary-foreground',
|
|
29
|
+
'--color-destructive',
|
|
30
|
+
'--color-destructive-foreground',
|
|
31
|
+
'--color-muted',
|
|
32
|
+
'--color-muted-foreground',
|
|
33
|
+
'--color-accent',
|
|
34
|
+
'--color-accent-foreground',
|
|
35
|
+
'--color-popover',
|
|
36
|
+
'--color-popover-foreground',
|
|
37
|
+
'--color-card',
|
|
38
|
+
'--color-card-foreground',
|
|
39
|
+
'--color-ring-offset'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Component classes that should not have circular references
|
|
43
|
+
const COMPONENT_CLASSES = [
|
|
44
|
+
'.pace-button',
|
|
45
|
+
'.pace-button-primary',
|
|
46
|
+
'.pace-button-secondary',
|
|
47
|
+
'.pace-button-outline',
|
|
48
|
+
'.pace-button-ghost',
|
|
49
|
+
'.pace-button-destructive',
|
|
50
|
+
'.pace-input',
|
|
51
|
+
'.pace-card',
|
|
52
|
+
'.pace-card-header',
|
|
53
|
+
'.pace-card-title',
|
|
54
|
+
'.pace-card-description',
|
|
55
|
+
'.pace-card-content',
|
|
56
|
+
'.pace-card-footer'
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function validateThemeFile() {
|
|
60
|
+
logger.info('Validating Tailwind v4 theme file...');
|
|
61
|
+
logger.newline();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const themeContent = readFileSync(THEME_FILE, 'utf8');
|
|
65
|
+
let errors = [];
|
|
66
|
+
let warnings = [];
|
|
67
|
+
|
|
68
|
+
// Check for required semantic colors
|
|
69
|
+
logger.success('Checking semantic colors...');
|
|
70
|
+
for (const color of REQUIRED_SEMANTIC_COLORS) {
|
|
71
|
+
if (!themeContent.includes(color)) {
|
|
72
|
+
errors.push(`Missing required semantic color: ${color}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for circular @apply references
|
|
77
|
+
logger.success('Checking for circular @apply references...');
|
|
78
|
+
for (const className of COMPONENT_CLASSES) {
|
|
79
|
+
const classRegex = new RegExp(`${className}\\s*\\{[^}]*@apply[^}]*${className}[^}]*\\}`, 's');
|
|
80
|
+
if (classRegex.test(themeContent)) {
|
|
81
|
+
errors.push(`Circular @apply reference found in ${className}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for undefined color references (skip if --color-*: initial is used)
|
|
86
|
+
logger.success('Checking for undefined color references...');
|
|
87
|
+
if (!themeContent.includes('--color-*: initial')) {
|
|
88
|
+
const colorRefs = themeContent.match(/--color-[a-zA-Z-]+/g) || [];
|
|
89
|
+
const definedColors = themeContent.match(/--color-[a-zA-Z-]+:/g) || [];
|
|
90
|
+
const definedColorNames = definedColors.map(c => c.replace(':', ''));
|
|
91
|
+
|
|
92
|
+
for (const ref of colorRefs) {
|
|
93
|
+
if (!definedColorNames.includes(ref)) {
|
|
94
|
+
warnings.push(`Potentially undefined color reference: ${ref}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check for @theme declaration
|
|
100
|
+
logger.success('Checking for @theme declaration...');
|
|
101
|
+
if (!themeContent.includes('@theme {')) {
|
|
102
|
+
errors.push('Missing @theme declaration');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for @import tailwindcss
|
|
106
|
+
logger.success('Checking for Tailwind import...');
|
|
107
|
+
if (!themeContent.includes('@import "tailwindcss"')) {
|
|
108
|
+
errors.push('Missing @import "tailwindcss"');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Report results
|
|
112
|
+
logger.newline();
|
|
113
|
+
logger.header('Validation Results');
|
|
114
|
+
|
|
115
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
116
|
+
logger.success('All checks passed! Theme file is valid.');
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
logger.newline();
|
|
122
|
+
logger.error('Errors found:');
|
|
123
|
+
errors.forEach(error => console.log(` - ${error}`));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (warnings.length > 0) {
|
|
127
|
+
logger.newline();
|
|
128
|
+
logger.warning('Warnings:');
|
|
129
|
+
warnings.forEach(warning => console.log(` - ${warning}`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return errors.length === 0;
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
logger.error(`Failed to read theme file: ${error.message}`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Run validation
|
|
141
|
+
const isValid = validateThemeFile();
|
|
142
|
+
process.exit(isValid ? 0 : 1);
|
|
@@ -63,7 +63,7 @@ import { cn } from '../../utils/core/cn';
|
|
|
63
63
|
// CALENDAR COMPONENT
|
|
64
64
|
// ============================================================================
|
|
65
65
|
|
|
66
|
-
export interface CalendarProps extends Omit<DayPickerProps, 'className' | 'classNames' | 'styles'> {
|
|
66
|
+
export interface CalendarProps extends Omit<DayPickerProps, 'className' | 'classNames' | 'styles' | 'onSelect'> {
|
|
67
67
|
/**
|
|
68
68
|
* Additional CSS classes to apply to the calendar wrapper
|
|
69
69
|
*/
|
|
@@ -72,6 +72,13 @@ export interface CalendarProps extends Omit<DayPickerProps, 'className' | 'class
|
|
|
72
72
|
* Custom classNames for DayPicker sub-components
|
|
73
73
|
*/
|
|
74
74
|
classNames?: DayPickerProps['classNames'];
|
|
75
|
+
/**
|
|
76
|
+
* Date selection handler. Signature depends on mode:
|
|
77
|
+
* - mode="single": (date: Date | undefined) => void
|
|
78
|
+
* - mode="range": (range: { from: Date; to?: Date } | undefined) => void
|
|
79
|
+
* - mode="multiple": (dates: Date[]) => void
|
|
80
|
+
*/
|
|
81
|
+
onSelect?: ((date: Date | undefined) => void) | ((range: { from: Date; to?: Date } | undefined) => void) | ((dates: Date[]) => void);
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
/**
|
|
@@ -97,9 +97,10 @@ function getCardClasses(variant: CardProps['variant'] = 'default', size: CardPro
|
|
|
97
97
|
return `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
// Internal component for cards with navigation (requires Router context)
|
|
101
|
+
const CardWithNavigation = React.forwardRef<
|
|
101
102
|
HTMLElement,
|
|
102
|
-
CardProps
|
|
103
|
+
CardProps & { link: string }
|
|
103
104
|
>(({ className, variant, size, isLink, link, onClick, ...props }, ref) => {
|
|
104
105
|
const navigate = useNavigate();
|
|
105
106
|
|
|
@@ -131,19 +132,57 @@ const Card = React.forwardRef<
|
|
|
131
132
|
shouldShowLinkStyles && "cursor-pointer hover:bg-acc-200 hover:shadow-xl/30",
|
|
132
133
|
className
|
|
133
134
|
)}
|
|
134
|
-
onClick={
|
|
135
|
-
role=
|
|
136
|
-
tabIndex={
|
|
137
|
-
onKeyDown={
|
|
135
|
+
onClick={handleClick}
|
|
136
|
+
role="link"
|
|
137
|
+
tabIndex={0}
|
|
138
|
+
onKeyDown={(e) => {
|
|
138
139
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
139
140
|
e.preventDefault();
|
|
140
141
|
handleClick(e as unknown as React.MouseEvent<HTMLElement>);
|
|
141
142
|
}
|
|
142
|
-
}
|
|
143
|
+
}}
|
|
143
144
|
{...props}
|
|
144
145
|
/>
|
|
145
146
|
);
|
|
146
|
-
})
|
|
147
|
+
});
|
|
148
|
+
CardWithNavigation.displayName = "CardWithNavigation";
|
|
149
|
+
|
|
150
|
+
// Internal component for cards without navigation (no Router context required)
|
|
151
|
+
const CardWithoutNavigation = React.forwardRef<
|
|
152
|
+
HTMLElement,
|
|
153
|
+
Omit<CardProps, 'link'>
|
|
154
|
+
>(({ className, variant, size, isLink, onClick, ...props }, ref) => {
|
|
155
|
+
// Automatically enable link styles if isLink is true
|
|
156
|
+
const shouldShowLinkStyles = isLink;
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<article
|
|
160
|
+
ref={ref}
|
|
161
|
+
className={cn(
|
|
162
|
+
"grid grid-rows-[auto_1fr_auto] min-w-0 overflow-visible",
|
|
163
|
+
getCardClasses(variant, size),
|
|
164
|
+
shouldShowLinkStyles && "cursor-pointer hover:bg-acc-200 hover:shadow-xl/30",
|
|
165
|
+
className
|
|
166
|
+
)}
|
|
167
|
+
onClick={onClick}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
CardWithoutNavigation.displayName = "CardWithoutNavigation";
|
|
173
|
+
|
|
174
|
+
// Main Card component that conditionally renders the appropriate variant
|
|
175
|
+
const Card = React.forwardRef<
|
|
176
|
+
HTMLElement,
|
|
177
|
+
CardProps
|
|
178
|
+
>(({ link, ...props }, ref) => {
|
|
179
|
+
// Only use navigation component if link is provided
|
|
180
|
+
// This prevents useNavigate() from being called when Router context is not available
|
|
181
|
+
if (link) {
|
|
182
|
+
return <CardWithNavigation ref={ref} link={link} {...props} />;
|
|
183
|
+
}
|
|
184
|
+
return <CardWithoutNavigation ref={ref} {...props} />;
|
|
185
|
+
});
|
|
147
186
|
Card.displayName = "Card"
|
|
148
187
|
|
|
149
188
|
const CardHeader = React.forwardRef<
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DatePickerWithTimezone Component Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DatePickerWithTimezone/__tests__
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive test suite for DatePickerWithTimezone component.
|
|
8
|
+
* Tests cover all major functionality, edge cases, and accessibility.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
13
|
+
import userEvent from '@testing-library/user-event';
|
|
14
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
15
|
+
import { DatePickerWithTimezone } from './DatePickerWithTimezone';
|
|
16
|
+
import { Calendar } from '../Calendar';
|
|
17
|
+
|
|
18
|
+
// Mock timezone utilities
|
|
19
|
+
vi.mock('../../utils/timezone', () => ({
|
|
20
|
+
getUserTimeZone: vi.fn(() => 'America/New_York')
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Mock Calendar component
|
|
24
|
+
vi.mock('../Calendar', () => ({
|
|
25
|
+
Calendar: vi.fn(({ selected, onSelect, ...props }) => (
|
|
26
|
+
<div data-testid="calendar" {...props}>
|
|
27
|
+
<button
|
|
28
|
+
data-testid="calendar-day"
|
|
29
|
+
onClick={() => onSelect?.(new Date('2024-01-15'))}
|
|
30
|
+
>
|
|
31
|
+
Day 15
|
|
32
|
+
</button>
|
|
33
|
+
{selected && <span data-testid="selected-date">{selected.toISOString()}</span>}
|
|
34
|
+
</div>
|
|
35
|
+
))
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Mock Button component
|
|
39
|
+
vi.mock('../Button', () => ({
|
|
40
|
+
Button: vi.fn(({ children, onClick, ...props }) => (
|
|
41
|
+
<button data-testid="done-button" onClick={onClick} {...props}>
|
|
42
|
+
{children}
|
|
43
|
+
</button>
|
|
44
|
+
))
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
// Mock lucide-react Clock icon
|
|
48
|
+
vi.mock('lucide-react', () => ({
|
|
49
|
+
Clock: ({ className, 'aria-hidden': ariaHidden, ...props }: any) => (
|
|
50
|
+
<svg
|
|
51
|
+
data-testid="clock-icon"
|
|
52
|
+
className={className}
|
|
53
|
+
aria-hidden={ariaHidden}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
<path d="M12 2v10l4 4" />
|
|
57
|
+
</svg>
|
|
58
|
+
)
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
describe('DatePickerWithTimezone Component', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
vi.clearAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('Rendering', () => {
|
|
67
|
+
it('renders with default props', () => {
|
|
68
|
+
const onSelect = vi.fn();
|
|
69
|
+
render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
|
|
70
|
+
|
|
71
|
+
expect(screen.getByTestId('calendar')).toBeInTheDocument();
|
|
72
|
+
expect(screen.getByText(/Timezone:/)).toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('renders with selected date', () => {
|
|
76
|
+
const onSelect = vi.fn();
|
|
77
|
+
const selected = new Date('2024-01-15');
|
|
78
|
+
render(<DatePickerWithTimezone selected={selected} onSelect={onSelect} />);
|
|
79
|
+
|
|
80
|
+
expect(screen.getByTestId('selected-date')).toBeInTheDocument();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('renders with custom timezone', () => {
|
|
84
|
+
const onSelect = vi.fn();
|
|
85
|
+
render(
|
|
86
|
+
<DatePickerWithTimezone
|
|
87
|
+
selected={undefined}
|
|
88
|
+
onSelect={onSelect}
|
|
89
|
+
timezone="America/Los_Angeles"
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(screen.getByText('America/Los_Angeles')).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('displays "Local" when timezone matches user timezone', () => {
|
|
97
|
+
const onSelect = vi.fn();
|
|
98
|
+
render(
|
|
99
|
+
<DatePickerWithTimezone
|
|
100
|
+
selected={undefined}
|
|
101
|
+
onSelect={onSelect}
|
|
102
|
+
timezone="America/New_York"
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(screen.getByText('Local')).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('renders with Done button when onDone is provided', () => {
|
|
110
|
+
const onSelect = vi.fn();
|
|
111
|
+
const onDone = vi.fn();
|
|
112
|
+
render(
|
|
113
|
+
<DatePickerWithTimezone selected={undefined} onSelect={onSelect} onDone={onDone} />
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(screen.getByTestId('done-button')).toBeInTheDocument();
|
|
117
|
+
expect(screen.getByText('Done')).toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('does not render Done button when onDone is not provided', () => {
|
|
121
|
+
const onSelect = vi.fn();
|
|
122
|
+
render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
|
|
123
|
+
|
|
124
|
+
expect(screen.queryByTestId('done-button')).not.toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('renders with custom className', () => {
|
|
128
|
+
const onSelect = vi.fn();
|
|
129
|
+
const { container } = render(
|
|
130
|
+
<DatePickerWithTimezone
|
|
131
|
+
selected={undefined}
|
|
132
|
+
onSelect={onSelect}
|
|
133
|
+
className="custom-class"
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(container.firstChild).toHaveClass('custom-class');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Date Selection', () => {
|
|
142
|
+
it('calls onSelect when date is selected', async () => {
|
|
143
|
+
const onSelect = vi.fn();
|
|
144
|
+
const user = userEvent.setup();
|
|
145
|
+
|
|
146
|
+
render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
|
|
147
|
+
|
|
148
|
+
const dayButton = screen.getByTestId('calendar-day');
|
|
149
|
+
await user.click(dayButton);
|
|
150
|
+
|
|
151
|
+
expect(onSelect).toHaveBeenCalled();
|
|
152
|
+
expect(onSelect).toHaveBeenCalledWith(expect.any(Date));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('handles date selection with existing selected date', async () => {
|
|
156
|
+
const onSelect = vi.fn();
|
|
157
|
+
const user = userEvent.setup();
|
|
158
|
+
const selected = new Date('2024-01-10');
|
|
159
|
+
|
|
160
|
+
render(<DatePickerWithTimezone selected={selected} onSelect={onSelect} />);
|
|
161
|
+
|
|
162
|
+
const dayButton = screen.getByTestId('calendar-day');
|
|
163
|
+
await user.click(dayButton);
|
|
164
|
+
|
|
165
|
+
expect(onSelect).toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('Done Button', () => {
|
|
170
|
+
it('calls onDone when Done button is clicked', async () => {
|
|
171
|
+
const onSelect = vi.fn();
|
|
172
|
+
const onDone = vi.fn();
|
|
173
|
+
const user = userEvent.setup();
|
|
174
|
+
|
|
175
|
+
render(
|
|
176
|
+
<DatePickerWithTimezone selected={undefined} onSelect={onSelect} onDone={onDone} />
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const doneButton = screen.getByTestId('done-button');
|
|
180
|
+
await user.click(doneButton);
|
|
181
|
+
|
|
182
|
+
expect(onDone).toHaveBeenCalledTimes(1);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('does not call onSelect when Done button is clicked', async () => {
|
|
186
|
+
const onSelect = vi.fn();
|
|
187
|
+
const onDone = vi.fn();
|
|
188
|
+
const user = userEvent.setup();
|
|
189
|
+
|
|
190
|
+
render(
|
|
191
|
+
<DatePickerWithTimezone selected={undefined} onSelect={onSelect} onDone={onDone} />
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const doneButton = screen.getByTestId('done-button');
|
|
195
|
+
await user.click(doneButton);
|
|
196
|
+
|
|
197
|
+
expect(onSelect).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('Timezone Display', () => {
|
|
202
|
+
it('displays user timezone as "Local" when no timezone provided', () => {
|
|
203
|
+
const onSelect = vi.fn();
|
|
204
|
+
render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
|
|
205
|
+
|
|
206
|
+
expect(screen.getByText('Local')).toBeInTheDocument();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('displays timezone name when different from user timezone', () => {
|
|
210
|
+
const onSelect = vi.fn();
|
|
211
|
+
render(
|
|
212
|
+
<DatePickerWithTimezone
|
|
213
|
+
selected={undefined}
|
|
214
|
+
onSelect={onSelect}
|
|
215
|
+
timezone="Europe/London"
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
expect(screen.getByText('Europe/London')).toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('displays Clock icon', () => {
|
|
223
|
+
const onSelect = vi.fn();
|
|
224
|
+
render(
|
|
225
|
+
<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
expect(screen.getByTestId('clock-icon')).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('Edge Cases', () => {
|
|
233
|
+
it('handles undefined selected date', () => {
|
|
234
|
+
const onSelect = vi.fn();
|
|
235
|
+
render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
|
|
236
|
+
|
|
237
|
+
expect(screen.getByTestId('calendar')).toBeInTheDocument();
|
|
238
|
+
expect(screen.queryByTestId('selected-date')).not.toBeInTheDocument();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('handles null selected date', () => {
|
|
242
|
+
const onSelect = vi.fn();
|
|
243
|
+
// @ts-expect-error - Testing edge case
|
|
244
|
+
render(<DatePickerWithTimezone selected={null} onSelect={onSelect} />);
|
|
245
|
+
|
|
246
|
+
expect(screen.getByTestId('calendar')).toBeInTheDocument();
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Accessibility', () => {
|
|
251
|
+
it('has proper ARIA labels for timezone', () => {
|
|
252
|
+
const onSelect = vi.fn();
|
|
253
|
+
render(
|
|
254
|
+
<DatePickerWithTimezone
|
|
255
|
+
selected={undefined}
|
|
256
|
+
onSelect={onSelect}
|
|
257
|
+
timezone="America/Los_Angeles"
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const timezoneLabel = screen.getByLabelText('Timezone America/Los_Angeles');
|
|
262
|
+
expect(timezoneLabel).toBeInTheDocument();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('marks Clock icon as decorative', () => {
|
|
266
|
+
const onSelect = vi.fn();
|
|
267
|
+
render(
|
|
268
|
+
<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const clockIcon = screen.getByTestId('clock-icon');
|
|
272
|
+
expect(clockIcon).toBeInTheDocument();
|
|
273
|
+
expect(clockIcon).toHaveAttribute('aria-hidden', 'true');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('supports keyboard navigation', async () => {
|
|
277
|
+
const onSelect = vi.fn();
|
|
278
|
+
const user = userEvent.setup();
|
|
279
|
+
|
|
280
|
+
render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
|
|
281
|
+
|
|
282
|
+
const dayButton = screen.getByTestId('calendar-day');
|
|
283
|
+
await user.tab();
|
|
284
|
+
|
|
285
|
+
// Calendar should be keyboard accessible
|
|
286
|
+
expect(dayButton).toBeInTheDocument();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('Calendar Integration', () => {
|
|
291
|
+
it('passes correct props to Calendar component', () => {
|
|
292
|
+
const onSelect = vi.fn();
|
|
293
|
+
const selected = new Date('2024-01-15');
|
|
294
|
+
const MockedCalendar = vi.mocked(Calendar);
|
|
295
|
+
|
|
296
|
+
render(<DatePickerWithTimezone selected={selected} onSelect={onSelect} />);
|
|
297
|
+
|
|
298
|
+
expect(MockedCalendar).toHaveBeenCalledWith(
|
|
299
|
+
expect.objectContaining({
|
|
300
|
+
mode: 'single',
|
|
301
|
+
selected: selected,
|
|
302
|
+
onSelect: onSelect,
|
|
303
|
+
initialFocus: true,
|
|
304
|
+
captionLayout: 'dropdown-buttons',
|
|
305
|
+
fromYear: 1900,
|
|
306
|
+
toYear: 2100,
|
|
307
|
+
className: 'p-0'
|
|
308
|
+
}),
|
|
309
|
+
expect.anything()
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|