@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.
Files changed (319) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +60 -1
  3. package/core-usage-manifest.json +312 -0
  4. package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
  5. package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
  6. package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
  7. package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
  8. package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
  9. package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
  10. package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
  11. package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
  12. package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
  13. package/dist/chunk-DAGICKHT.js.map +1 -0
  14. package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
  15. package/dist/chunk-FXFJRTKI.js.map +1 -0
  16. package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
  17. package/dist/chunk-GRIQLQ52.js.map +1 -0
  18. package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
  19. package/dist/chunk-HDCUMOOI.js.map +1 -0
  20. package/dist/chunk-HESYZWZW.js +388 -0
  21. package/dist/chunk-HESYZWZW.js.map +1 -0
  22. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  23. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  24. package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
  25. package/dist/chunk-OALXJH4Y.js.map +1 -0
  26. package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
  27. package/dist/chunk-TC7D3CR3.js.map +1 -0
  28. package/dist/chunk-THRPYOFK.js +215 -0
  29. package/dist/chunk-THRPYOFK.js.map +1 -0
  30. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  31. package/dist/chunk-U6WNSFX5.js.map +1 -0
  32. package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
  33. package/dist/chunk-UQWSHFVX.js.map +1 -0
  34. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  35. package/dist/components.d.ts +182 -6
  36. package/dist/components.js +157 -11
  37. package/dist/components.js.map +1 -1
  38. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  39. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  40. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
  41. package/dist/hooks.d.ts +52 -15
  42. package/dist/hooks.js +12 -22
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +12 -12
  45. package/dist/index.js +82 -18
  46. package/dist/index.js.map +1 -1
  47. package/dist/providers.d.ts +1 -1
  48. package/dist/providers.js +3 -1
  49. package/dist/rbac/index.d.ts +206 -15
  50. package/dist/rbac/index.js +28 -6
  51. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  52. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  53. package/dist/types.d.ts +2 -2
  54. package/dist/types.js +1 -1
  55. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
  56. package/dist/utils.d.ts +110 -152
  57. package/dist/utils.js +128 -138
  58. package/dist/utils.js.map +1 -1
  59. package/docs/api/README.md +60 -1
  60. package/docs/api/classes/ColumnFactory.md +1 -1
  61. package/docs/api/classes/ErrorBoundary.md +1 -1
  62. package/docs/api/classes/InvalidScopeError.md +1 -1
  63. package/docs/api/classes/Logger.md +178 -0
  64. package/docs/api/classes/MissingUserContextError.md +1 -1
  65. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  66. package/docs/api/classes/PermissionDeniedError.md +1 -1
  67. package/docs/api/classes/RBACAuditManager.md +2 -2
  68. package/docs/api/classes/RBACCache.md +1 -1
  69. package/docs/api/classes/RBACEngine.md +2 -2
  70. package/docs/api/classes/RBACError.md +1 -1
  71. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  72. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  73. package/docs/api/classes/StorageUtils.md +1 -1
  74. package/docs/api/enums/FileCategory.md +1 -1
  75. package/docs/api/enums/LogLevel.md +54 -0
  76. package/docs/api/enums/RBACErrorCode.md +1 -1
  77. package/docs/api/enums/RPCFunction.md +1 -1
  78. package/docs/api/interfaces/AggregateConfig.md +1 -1
  79. package/docs/api/interfaces/BadgeProps.md +1 -1
  80. package/docs/api/interfaces/ButtonProps.md +1 -1
  81. package/docs/api/interfaces/CalendarProps.md +18 -2
  82. package/docs/api/interfaces/CardProps.md +1 -1
  83. package/docs/api/interfaces/ColorPalette.md +1 -1
  84. package/docs/api/interfaces/ColorShade.md +1 -1
  85. package/docs/api/interfaces/ComplianceResult.md +30 -0
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +1 -1
  88. package/docs/api/interfaces/DataTableAction.md +1 -1
  89. package/docs/api/interfaces/DataTableColumn.md +1 -1
  90. package/docs/api/interfaces/DataTableProps.md +1 -1
  91. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  92. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  93. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  94. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  95. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  97. package/docs/api/interfaces/ExportColumn.md +1 -1
  98. package/docs/api/interfaces/ExportOptions.md +1 -1
  99. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  100. package/docs/api/interfaces/FileMetadata.md +1 -1
  101. package/docs/api/interfaces/FileReference.md +1 -1
  102. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  103. package/docs/api/interfaces/FileUploadOptions.md +48 -8
  104. package/docs/api/interfaces/FileUploadProps.md +46 -13
  105. package/docs/api/interfaces/FooterProps.md +1 -1
  106. package/docs/api/interfaces/FormFieldProps.md +1 -1
  107. package/docs/api/interfaces/FormProps.md +1 -1
  108. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  109. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  110. package/docs/api/interfaces/InputProps.md +1 -1
  111. package/docs/api/interfaces/LabelProps.md +1 -1
  112. package/docs/api/interfaces/LoggerConfig.md +62 -0
  113. package/docs/api/interfaces/LoginFormProps.md +1 -1
  114. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  115. package/docs/api/interfaces/NavigationContextType.md +1 -1
  116. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  117. package/docs/api/interfaces/NavigationItem.md +1 -1
  118. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  119. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  120. package/docs/api/interfaces/Organisation.md +1 -1
  121. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  122. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  123. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  124. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  125. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  126. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  127. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  128. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  129. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  130. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  131. package/docs/api/interfaces/PaletteData.md +1 -1
  132. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  133. package/docs/api/interfaces/ProgressProps.md +1 -1
  134. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  135. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  136. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  138. package/docs/api/interfaces/QuickFix.md +52 -0
  139. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  140. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  141. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  142. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  143. package/docs/api/interfaces/RBACConfig.md +4 -4
  144. package/docs/api/interfaces/RBACContext.md +1 -1
  145. package/docs/api/interfaces/RBACLogger.md +1 -1
  146. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  149. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  151. package/docs/api/interfaces/RBACResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  156. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  157. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  158. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  159. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  160. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  161. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  162. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  163. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  164. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  165. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  166. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  167. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  168. package/docs/api/interfaces/RouteConfig.md +1 -1
  169. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  170. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  171. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  172. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  173. package/docs/api/interfaces/SetupIssue.md +41 -0
  174. package/docs/api/interfaces/StorageConfig.md +1 -1
  175. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  176. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  177. package/docs/api/interfaces/StorageListOptions.md +1 -1
  178. package/docs/api/interfaces/StorageListResult.md +1 -1
  179. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  180. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  181. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  182. package/docs/api/interfaces/StyleImport.md +1 -1
  183. package/docs/api/interfaces/SwitchProps.md +1 -1
  184. package/docs/api/interfaces/TabsContentProps.md +1 -1
  185. package/docs/api/interfaces/TabsListProps.md +1 -1
  186. package/docs/api/interfaces/TabsProps.md +1 -1
  187. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  188. package/docs/api/interfaces/TextareaProps.md +1 -1
  189. package/docs/api/interfaces/ToastActionElement.md +1 -1
  190. package/docs/api/interfaces/ToastProps.md +1 -1
  191. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  192. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  193. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  194. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  195. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  196. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  198. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  202. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  203. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  204. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  205. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  206. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  207. package/docs/api/interfaces/UserEventAccess.md +1 -1
  208. package/docs/api/interfaces/UserMenuProps.md +1 -1
  209. package/docs/api/interfaces/UserProfile.md +1 -1
  210. package/docs/api/modules.md +746 -50
  211. package/docs/api-reference/components.md +26 -12
  212. package/docs/api-reference/hooks.md +111 -0
  213. package/docs/api-reference/rpc-functions.md +1 -1
  214. package/docs/api-reference/utilities.md +184 -0
  215. package/docs/getting-started/installation-guide.md +75 -16
  216. package/docs/getting-started/quick-start.md +61 -11
  217. package/docs/implementation-guides/authentication.md +88 -12
  218. package/docs/implementation-guides/file-reference-system.md +26 -3
  219. package/docs/implementation-guides/file-upload-storage.md +30 -1
  220. package/docs/rbac/README.md +1 -0
  221. package/docs/rbac/compliance/compliance-guide.md +544 -0
  222. package/docs/rbac/getting-started.md +158 -33
  223. package/docs/standards/pace-core-compliance.md +432 -0
  224. package/eslint-config-pace-core.cjs +93 -0
  225. package/package.json +15 -3
  226. package/scripts/analyze-bundle.js +232 -0
  227. package/scripts/build-css.js +56 -0
  228. package/scripts/build-docs-incremental.js +1015 -0
  229. package/scripts/check-pace-core-compliance.cjs +2353 -0
  230. package/scripts/check-pace-core-compliance.js +512 -0
  231. package/scripts/generate-docs.js +157 -0
  232. package/scripts/setup-build-cache.js +73 -0
  233. package/scripts/utils/command-runner.js +131 -0
  234. package/scripts/utils/env.js +33 -0
  235. package/scripts/utils/index.js +10 -0
  236. package/scripts/utils/logger.js +88 -0
  237. package/scripts/utils/path-helpers.js +37 -0
  238. package/scripts/validate-formats.js +133 -0
  239. package/scripts/validate-master.js +155 -0
  240. package/scripts/validate-pre-publish.js +140 -0
  241. package/scripts/validate-theme.js +142 -0
  242. package/src/components/Calendar/Calendar.tsx +8 -1
  243. package/src/components/Card/Card.tsx +47 -8
  244. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  245. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  246. package/src/components/DatePickerWithTimezone/README.md +135 -0
  247. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  248. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  249. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  250. package/src/components/DateTimeField/README.md +148 -0
  251. package/src/components/DateTimeField/index.ts +10 -0
  252. package/src/components/FileUpload/FileUpload.test.tsx +2 -0
  253. package/src/components/FileUpload/FileUpload.tsx +10 -1
  254. package/src/components/Header/Header.test.tsx +47 -18
  255. package/src/components/Header/Header.tsx +22 -7
  256. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  257. package/src/components/PaceAppLayout/README.md +9 -0
  258. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  259. package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
  260. package/src/components/index.ts +8 -0
  261. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  262. package/src/eslint-rules/pace-core-compliance.js +640 -0
  263. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  264. package/src/hooks/index.ts +5 -0
  265. package/src/hooks/useFileReference.test.ts +2 -0
  266. package/src/hooks/useFormDialog.ts +147 -0
  267. package/src/hooks/usePreventTabReload.ts +106 -0
  268. package/src/hooks/useSecureDataAccess.ts +2 -2
  269. package/src/index.ts +27 -0
  270. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  271. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  272. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  273. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  274. package/src/rbac/compliance/database-validator.ts +165 -0
  275. package/src/rbac/compliance/index.ts +38 -0
  276. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  277. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  278. package/src/rbac/compliance/setup-validator.ts +131 -0
  279. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  280. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  281. package/src/rbac/docs/event-based-apps.md +285 -0
  282. package/src/rbac/errors.ts +11 -0
  283. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  284. package/src/rbac/index.ts +30 -0
  285. package/src/services/OrganisationService.ts +4 -0
  286. package/src/styles/core.css +5 -5
  287. package/src/types/database.generated.ts +63 -9
  288. package/src/types/file-reference.ts +9 -0
  289. package/src/utils/__tests__/timezone.test.ts +345 -0
  290. package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
  291. package/src/utils/file-reference/index.ts +13 -2
  292. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  293. package/src/utils/formatting/formatting.ts +179 -0
  294. package/src/utils/index.ts +27 -1
  295. package/src/utils/location/index.ts +16 -0
  296. package/src/utils/location/location.test.ts +286 -0
  297. package/src/utils/location/location.ts +175 -0
  298. package/src/utils/security/secureDataAccess.ts +1 -1
  299. package/src/utils/storage/helpers.ts +68 -0
  300. package/src/utils/timezone/index.ts +17 -0
  301. package/src/utils/timezone/timezone.test.ts +349 -0
  302. package/src/utils/timezone/timezone.ts +281 -0
  303. package/dist/chunk-CSOFYHAG.js.map +0 -1
  304. package/dist/chunk-FUEYYMX5.js.map +0 -1
  305. package/dist/chunk-HKIT6O7W.js +0 -198
  306. package/dist/chunk-HKIT6O7W.js.map +0 -1
  307. package/dist/chunk-KUEN3HFB.js +0 -94
  308. package/dist/chunk-KUEN3HFB.js.map +0 -1
  309. package/dist/chunk-M7W4CP3M.js.map +0 -1
  310. package/dist/chunk-MI7HBHN3.js.map +0 -1
  311. package/dist/chunk-NQPMQGS2.js.map +0 -1
  312. package/dist/chunk-PWAHJW4G.js.map +0 -1
  313. package/dist/chunk-UHNYIBXL.js.map +0 -1
  314. package/dist/chunk-W22JP75J.js.map +0 -1
  315. package/dist/formatting-5wETwiGF.d.ts +0 -162
  316. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  317. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  318. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  319. /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
- const Card = React.forwardRef<
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={link ? handleClick : onClick}
135
- role={link ? "link" : undefined}
136
- tabIndex={link ? 0 : undefined}
137
- onKeyDown={link ? (e) => {
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
- } : undefined}
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
+