@jmruthers/pace-core 0.5.68 → 0.5.69

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 (375) hide show
  1. package/dist/{DataTable-4IUY7BXB.js → DataTable-MPBSXUC6.js} +5 -6
  2. package/dist/{PublicLoadingSpinner-DdKXTkCZ.d.ts → PublicLoadingSpinner-BOdyU3u-.d.ts} +1 -1
  3. package/dist/{chunk-PXWEDX7Y.js → chunk-2ARQW6VX.js} +3 -3
  4. package/dist/{chunk-MOJXHWDE.js → chunk-6JILXFEA.js} +335 -5
  5. package/dist/chunk-6JILXFEA.js.map +1 -0
  6. package/dist/{chunk-D7ARGIA3.js → chunk-6RBH67W7.js} +23 -6
  7. package/dist/chunk-6RBH67W7.js.map +1 -0
  8. package/dist/{chunk-ZMS23NS5.js → chunk-FJTAWPAQ.js} +3 -5
  9. package/dist/{chunk-ZMS23NS5.js.map → chunk-FJTAWPAQ.js.map} +1 -1
  10. package/dist/{chunk-OPCWH3A4.js → chunk-NO5QHMDX.js} +7 -6
  11. package/dist/chunk-NO5QHMDX.js.map +1 -0
  12. package/dist/{chunk-ZPK5656W.js → chunk-O3NWNXDY.js} +4 -5
  13. package/dist/chunk-O3NWNXDY.js.map +1 -0
  14. package/dist/{chunk-UYA6U6H7.js → chunk-Q2UP3ZWQ.js} +4 -4
  15. package/dist/{chunk-KRCRNXPD.js → chunk-RVYGJPOD.js} +79 -18
  16. package/dist/chunk-RVYGJPOD.js.map +1 -0
  17. package/dist/{chunk-NN45OBIS.js → chunk-UCMHBF7Y.js} +3 -5
  18. package/dist/{chunk-NN45OBIS.js.map → chunk-UCMHBF7Y.js.map} +1 -1
  19. package/dist/{chunk-ZPG4XPV5.js → chunk-V3QO3LL7.js} +5 -7
  20. package/dist/chunk-V3QO3LL7.js.map +1 -0
  21. package/dist/{chunk-U6GPOF6J.js → chunk-ZXJGZLLO.js} +17 -17
  22. package/dist/{chunk-U6GPOF6J.js.map → chunk-ZXJGZLLO.js.map} +1 -1
  23. package/dist/components.d.ts +1 -1
  24. package/dist/components.js +8 -9
  25. package/dist/components.js.map +1 -1
  26. package/dist/hooks.d.ts +1 -1
  27. package/dist/hooks.js +9 -6
  28. package/dist/hooks.js.map +1 -1
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +16 -16
  31. package/dist/index.js.map +1 -1
  32. package/dist/providers.js +5 -7
  33. package/dist/rbac/index.js +5 -6
  34. package/dist/{usePublicRouteParams-CdoFxnJK.d.ts → usePublicRouteParams-Ua1Vz-HG.d.ts} +35 -1
  35. package/dist/utils.d.ts +4 -1
  36. package/dist/utils.js +3 -3
  37. package/docs/DOCUMENTATION_CHECKLIST.md +281 -0
  38. package/docs/README.md +22 -10
  39. package/docs/api/classes/ColumnFactory.md +1 -1
  40. package/docs/api/classes/ErrorBoundary.md +1 -1
  41. package/docs/api/classes/InvalidScopeError.md +1 -1
  42. package/docs/api/classes/MissingUserContextError.md +1 -1
  43. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  44. package/docs/api/classes/PermissionDeniedError.md +1 -1
  45. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  46. package/docs/api/classes/RBACAuditManager.md +1 -1
  47. package/docs/api/classes/RBACCache.md +1 -1
  48. package/docs/api/classes/RBACEngine.md +1 -1
  49. package/docs/api/classes/RBACError.md +1 -1
  50. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  51. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  52. package/docs/api/classes/StorageUtils.md +1 -1
  53. package/docs/api/interfaces/AggregateConfig.md +1 -1
  54. package/docs/api/interfaces/ButtonProps.md +1 -1
  55. package/docs/api/interfaces/CardProps.md +1 -1
  56. package/docs/api/interfaces/ColorPalette.md +1 -1
  57. package/docs/api/interfaces/ColorShade.md +1 -1
  58. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  59. package/docs/api/interfaces/DataTableAction.md +1 -1
  60. package/docs/api/interfaces/DataTableColumn.md +1 -1
  61. package/docs/api/interfaces/DataTableProps.md +1 -1
  62. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  63. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  64. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  65. package/docs/api/interfaces/EventContextType.md +1 -1
  66. package/docs/api/interfaces/EventLogoProps.md +1 -1
  67. package/docs/api/interfaces/EventProviderProps.md +1 -1
  68. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  69. package/docs/api/interfaces/FileUploadProps.md +1 -1
  70. package/docs/api/interfaces/FooterProps.md +1 -1
  71. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  72. package/docs/api/interfaces/InputProps.md +1 -1
  73. package/docs/api/interfaces/LabelProps.md +1 -1
  74. package/docs/api/interfaces/LoginFormProps.md +1 -1
  75. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  76. package/docs/api/interfaces/NavigationContextType.md +1 -1
  77. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  78. package/docs/api/interfaces/NavigationItem.md +1 -1
  79. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  80. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  81. package/docs/api/interfaces/Organisation.md +1 -1
  82. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  83. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  84. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  85. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  86. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  87. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  88. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  89. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  90. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  91. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  92. package/docs/api/interfaces/PaletteData.md +1 -1
  93. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  94. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  95. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  96. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  97. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  98. package/docs/api/interfaces/PublicPageHeaderProps.md +2 -2
  99. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  100. package/docs/api/interfaces/RBACConfig.md +1 -1
  101. package/docs/api/interfaces/RBACContextType.md +1 -1
  102. package/docs/api/interfaces/RBACLogger.md +1 -1
  103. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  104. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  105. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  106. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  107. package/docs/api/interfaces/RouteConfig.md +1 -1
  108. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  109. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  110. package/docs/api/interfaces/StorageConfig.md +1 -1
  111. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  112. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  113. package/docs/api/interfaces/StorageListOptions.md +1 -1
  114. package/docs/api/interfaces/StorageListResult.md +1 -1
  115. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  116. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  117. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  118. package/docs/api/interfaces/StyleImport.md +1 -1
  119. package/docs/api/interfaces/SwitchProps.md +1 -1
  120. package/docs/api/interfaces/ToastActionElement.md +1 -1
  121. package/docs/api/interfaces/ToastProps.md +1 -1
  122. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  123. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  124. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  125. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  126. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  127. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  128. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  129. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  130. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  131. package/docs/api/interfaces/UserEventAccess.md +1 -1
  132. package/docs/api/interfaces/UserMenuProps.md +1 -1
  133. package/docs/api/interfaces/UserProfile.md +1 -1
  134. package/docs/api/modules.md +39 -14
  135. package/docs/architecture/services.md +374 -0
  136. package/docs/best-practices/README.md +1 -1
  137. package/docs/best-practices/testing.md +1 -1
  138. package/docs/breaking-changes.md +182 -0
  139. package/docs/common-patterns.md +445 -0
  140. package/docs/core-concepts/authentication.md +26 -11
  141. package/docs/core-concepts/events.md +2 -0
  142. package/docs/core-concepts/organisations.md +2 -0
  143. package/docs/core-concepts/permissions.md +2 -0
  144. package/docs/{INDEX.md → documentation-index.md} +26 -38
  145. package/docs/faq.md +286 -0
  146. package/docs/{FILE_REFERENCE_SYSTEM.md → file-reference-system.md} +1 -1
  147. package/docs/getting-started/installation-guide.md +284 -0
  148. package/docs/getting-started/quick-start.md +8 -1
  149. package/docs/implementation-guides/app-layout.md +3 -1
  150. package/docs/implementation-guides/data-tables.md +2 -0
  151. package/docs/implementation-guides/dynamic-colors.md +47 -2
  152. package/docs/implementation-guides/event-theming-summary.md +220 -0
  153. package/docs/implementation-guides/forms.md +9 -7
  154. package/docs/implementation-guides/navigation.md +2 -0
  155. package/docs/migration/service-architecture.md +351 -0
  156. package/docs/rbac/README-rbac-rls-integration.md +2 -2
  157. package/docs/rbac/README.md +1 -1
  158. package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
  159. package/docs/rbac/quick-start.md +2 -0
  160. package/docs/rbac/rbac-rls-integration.md +2 -2
  161. package/docs/style-guide.md +136 -1
  162. package/docs/testing/README.md +1 -1
  163. package/docs/troubleshooting/authentication-issues.md +334 -0
  164. package/docs/troubleshooting/common-issues.md +2 -0
  165. package/docs/troubleshooting/styling-issues.md +199 -144
  166. package/docs/usage.md +23 -2
  167. package/package.json +1 -1
  168. package/src/__tests__/{TESTING_GUIDELINES.md → TEST_GUIDE_CURSOR.md} +20 -0
  169. package/src/__tests__/TEST_GUIDE_HUMAN.md +103 -0
  170. package/src/__tests__/fixtures/test-data.ts +90 -0
  171. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +260 -0
  172. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +224 -0
  173. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +273 -0
  174. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +98 -0
  175. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +436 -0
  176. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +371 -0
  177. package/src/__tests__/helpers/component-test-utils.tsx +14 -4
  178. package/src/__tests__/helpers/optimized-test-setup.ts +68 -0
  179. package/src/__tests__/helpers/test-providers.tsx +329 -0
  180. package/src/__tests__/helpers/test-utils.tsx +91 -45
  181. package/src/__tests__/helpers/timer-utils.ts +71 -0
  182. package/src/__tests__/hooks/usePermissions.test.ts +1 -5
  183. package/src/__tests__/integration/UserProfile.test.tsx +1 -5
  184. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +42 -12
  185. package/src/__tests__/setup.ts +34 -28
  186. package/src/components/Alert/Alert.test.tsx +1 -5
  187. package/src/components/Avatar/Avatar.test.tsx +1 -5
  188. package/src/components/Button/Button.test.tsx +4 -20
  189. package/src/components/Card/Card.test.tsx +1 -5
  190. package/src/components/Checkbox/Checkbox.test.tsx +1 -5
  191. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +1 -5
  192. package/src/components/DataTable/__tests__/DataTable.test.tsx +45 -49
  193. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +1 -5
  194. package/src/components/DataTable/__tests__/styles.test.ts +382 -0
  195. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +409 -0
  196. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +634 -0
  197. package/src/components/DataTable/core/__tests__/DataManager.test.ts +519 -0
  198. package/src/components/DataTable/core/__tests__/StateManager.test.ts +714 -0
  199. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +592 -0
  200. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +354 -0
  201. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +539 -0
  202. package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +1 -5
  203. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +1 -8
  204. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +34 -38
  205. package/src/components/Footer/Footer.test.tsx +1 -5
  206. package/src/components/Form/Form.test.tsx +22 -35
  207. package/src/components/Header/Header.test.tsx +1 -9
  208. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +1 -5
  209. package/src/components/Input/Input.test.tsx +2 -10
  210. package/src/components/LoginForm/LoginForm.test.tsx +1 -5
  211. package/src/components/NavigationMenu/NavigationMenu.test.tsx +24 -24
  212. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +1 -6
  213. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -16
  214. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +1 -5
  215. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +1 -5
  216. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +1 -7
  217. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +1 -9
  218. package/src/components/PasswordReset/PasswordResetForm.test.tsx +1 -9
  219. package/src/components/PublicLayout/PublicErrorBoundary.tsx +4 -5
  220. package/src/components/PublicLayout/PublicPageHeader.tsx +13 -9
  221. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +666 -0
  222. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +457 -0
  223. package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +393 -0
  224. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +351 -0
  225. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +374 -0
  226. package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +388 -0
  227. package/src/components/Select/Select.bug-test.tsx +69 -0
  228. package/src/components/Select/Select.refactored.tsx +497 -0
  229. package/src/components/Select/Select.test.tsx +42 -49
  230. package/src/components/Select/Select.tsx +5 -2
  231. package/src/components/Select/hooks.ts +254 -0
  232. package/src/components/Switch/Switch.test.tsx +1 -5
  233. package/src/components/Table/__tests__/Table.test.tsx +775 -0
  234. package/src/components/Toast/Toast.test.tsx +15 -8
  235. package/src/components/Tooltip/Tooltip.test.tsx +1 -5
  236. package/src/components/UserMenu/UserMenu.test.tsx +3 -15
  237. package/src/components/__tests__/FileDisplay.test.tsx +575 -0
  238. package/src/components/__tests__/FileUpload.test.tsx +446 -0
  239. package/src/components/__tests__/SuperAdminGuard.test.tsx +422 -354
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +613 -0
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +1 -10
  242. package/src/hooks/__tests__/useApiFetch.unit.test.ts +10 -14
  243. package/src/hooks/__tests__/useAppConfig.unit.test.ts +307 -0
  244. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +1 -6
  245. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +1 -5
  246. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +6 -9
  247. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +321 -0
  248. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
  249. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +640 -0
  250. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +435 -0
  251. package/src/hooks/__tests__/useRBAC.unit.test.ts +10 -10
  252. package/src/hooks/__tests__/useStorage.unit.test.ts +751 -0
  253. package/src/hooks/index.ts +3 -0
  254. package/src/hooks/public/usePublicEvent.ts +30 -9
  255. package/src/hooks/public/usePublicRouteParams.ts +13 -3
  256. package/src/hooks/services/useAuth.ts +50 -0
  257. package/src/hooks/services/useAuthService.ts +30 -0
  258. package/src/hooks/services/useCurrentEvent.ts +36 -0
  259. package/src/hooks/services/useCurrentOrganisation.ts +52 -0
  260. package/src/hooks/services/useEventService.ts +30 -0
  261. package/src/hooks/services/useInactivityService.ts +30 -0
  262. package/src/hooks/services/useOrganisationService.ts +30 -0
  263. package/src/hooks/services/usePermissions.ts +70 -0
  264. package/src/hooks/services/useRBACService.ts +30 -0
  265. package/src/hooks/useCounter.test.ts +1 -5
  266. package/src/hooks/useEventTheme.ts +86 -0
  267. package/src/hooks/useOrganisationPermissions.test.ts +2 -5
  268. package/src/hooks/useOrganisationSecurity.test.ts +1 -5
  269. package/src/hooks/usePermissionCache.test.ts +1 -5
  270. package/src/hooks/usePermissionCheck.ts +150 -0
  271. package/src/hooks/useSecureDataAccess.test.ts +1 -5
  272. package/src/index.ts +1 -0
  273. package/src/providers/OrganisationProvider.test.tsx +1 -5
  274. package/src/providers/OrganisationProvider.tsx +56 -4
  275. package/src/providers/UnifiedAuthProvider.test.tsx +1 -5
  276. package/src/providers/__tests__/AuthProvider.test.tsx +105 -439
  277. package/src/providers/__tests__/AuthProvider.test.tsx.backup +771 -0
  278. package/src/providers/__tests__/EventProvider.test.tsx +211 -110
  279. package/src/providers/__tests__/EventProvider.test.tsx.backup +824 -0
  280. package/src/providers/__tests__/InactivityProvider.test.tsx +1 -5
  281. package/src/providers/__tests__/OrganisationProvider.test.tsx +97 -261
  282. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +820 -0
  283. package/src/providers/__tests__/ServiceProviders.test.tsx +477 -0
  284. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +72 -504
  285. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +911 -0
  286. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +166 -0
  287. package/src/providers/services/AuthServiceProvider.tsx +65 -0
  288. package/src/providers/services/EventServiceProvider.tsx +83 -0
  289. package/src/providers/services/InactivityServiceProvider.tsx +83 -0
  290. package/src/providers/services/OrganisationServiceProvider.tsx +77 -0
  291. package/src/providers/services/RBACServiceProvider.tsx +79 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +368 -0
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +210 -0
  294. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +269 -0
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +892 -0
  296. package/src/rbac/__tests__/engine.comprehensive.test.ts +954 -0
  297. package/src/rbac/__tests__/integration.authflow.test.tsx +1 -5
  298. package/src/rbac/__tests__/integration.navigation.test.tsx +1 -4
  299. package/src/rbac/__tests__/rbac-core.test.tsx +2 -7
  300. package/src/rbac/__tests__/rbac-functions.test.ts +1 -9
  301. package/src/rbac/__tests__/rbac-integration.test.ts +1 -9
  302. package/src/rbac/api.test.ts +1 -9
  303. package/src/rbac/cache.test.ts +10 -8
  304. package/src/rbac/cli/__tests__/policy-manager.test.ts +339 -0
  305. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -5
  306. package/src/rbac/components/NavigationProvider.test.tsx +1 -5
  307. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -5
  308. package/src/rbac/components/SecureDataProvider.test.tsx +1 -5
  309. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +25 -29
  310. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +27 -30
  311. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +23 -27
  312. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +18 -22
  313. package/src/rbac/config.test.ts +1 -5
  314. package/src/rbac/hooks/useCan.test.ts +262 -9
  315. package/src/rbac/hooks/usePermissions.test.ts +246 -6
  316. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -5
  317. package/src/rbac/hooks/useRBAC.test.ts +472 -198
  318. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +1 -9
  319. package/src/services/AuthService.ts +416 -0
  320. package/src/services/EventService.ts +366 -0
  321. package/src/services/InactivityService.ts +388 -0
  322. package/src/services/OrganisationService.ts +592 -0
  323. package/src/services/RBACService.ts +522 -0
  324. package/src/services/__tests__/AuthService.test.ts +356 -0
  325. package/src/services/__tests__/BaseService.test.ts +314 -0
  326. package/src/services/__tests__/EventService.test.ts +489 -0
  327. package/src/services/__tests__/InactivityService.test.ts +403 -0
  328. package/src/services/__tests__/OrganisationService.test.ts +660 -0
  329. package/src/services/__tests__/RBACService.test.ts +492 -0
  330. package/src/services/base/BaseService.ts +87 -0
  331. package/src/services/interfaces/IAuthService.ts +39 -0
  332. package/src/services/interfaces/IEventService.ts +30 -0
  333. package/src/services/interfaces/IInactivityService.ts +31 -0
  334. package/src/services/interfaces/IOrganisationService.ts +41 -0
  335. package/src/services/interfaces/IRBACService.ts +62 -0
  336. package/src/theming/__tests__/runtime.test.ts +540 -0
  337. package/src/types/__tests__/file-reference.test.ts +447 -0
  338. package/src/types/__tests__/organisation.test.ts +1133 -0
  339. package/src/types/__tests__/theme.test.ts +830 -0
  340. package/src/types/__tests__/type-validation.test.ts +527 -0
  341. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +1 -5
  342. package/src/utils/__tests__/debugLogger.test.ts +417 -0
  343. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -6
  344. package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -5
  345. package/src/utils/__tests__/lazyLoad.unit.test.tsx +35 -35
  346. package/src/utils/__tests__/organisationContext.unit.test.ts +1 -5
  347. package/src/utils/__tests__/performanceBudgets.unit.test.ts +5 -11
  348. package/src/utils/__tests__/secureErrors.unit.test.ts +1 -6
  349. package/src/utils/__tests__/secureStorage.unit.test.ts +1 -5
  350. package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -5
  351. package/src/utils/__tests__/sessionTracking.unit.test.ts +1 -5
  352. package/src/utils/appIdResolver.test.ts +6 -10
  353. package/src/utils/appNameResolver.simple.test.ts +142 -0
  354. package/src/utils/appNameResolver.test.ts +31 -458
  355. package/src/utils/appNameResolver.test.ts.backup +494 -0
  356. package/src/utils/debugLogger.ts +26 -5
  357. package/src/utils/formatDate.test.ts +1 -5
  358. package/src/utils/organisationContext.test.ts +1 -5
  359. package/src/utils/performanceBudgets.ts +3 -4
  360. package/src/utils/secureDataAccess.test.ts +1 -5
  361. package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -5
  362. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +1 -5
  363. package/dist/chunk-D7ARGIA3.js.map +0 -1
  364. package/dist/chunk-IPCH4YPT.js +0 -315
  365. package/dist/chunk-IPCH4YPT.js.map +0 -1
  366. package/dist/chunk-KRCRNXPD.js.map +0 -1
  367. package/dist/chunk-MOJXHWDE.js.map +0 -1
  368. package/dist/chunk-OPCWH3A4.js.map +0 -1
  369. package/dist/chunk-ZPG4XPV5.js.map +0 -1
  370. package/dist/chunk-ZPK5656W.js.map +0 -1
  371. package/docs/getting-started/installation.md +0 -269
  372. package/src/__tests__/REBUILD_PLAN.md +0 -223
  373. /package/dist/{DataTable-4IUY7BXB.js.map → DataTable-MPBSXUC6.js.map} +0 -0
  374. /package/dist/{chunk-PXWEDX7Y.js.map → chunk-2ARQW6VX.js.map} +0 -0
  375. /package/dist/{chunk-UYA6U6H7.js.map → chunk-Q2UP3ZWQ.js.map} +0 -0
@@ -0,0 +1,497 @@
1
+ /**
2
+ * @file Select Component - Refactored SOLID Implementation
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/Select
5
+ * @since 0.4.0
6
+ *
7
+ * Refactored Select component following SOLID principles:
8
+ * - Single Responsibility: Each component has one clear purpose
9
+ * - Open/Closed: Easy to extend without modification
10
+ * - Liskov Substitution: Components can be substituted
11
+ * - Interface Segregation: Small, focused interfaces
12
+ * - Dependency Inversion: Depends on abstractions, not concretions
13
+ */
14
+
15
+ import * as React from "react";
16
+ import { Search, X, ChevronDown, Check } from "lucide-react";
17
+ import { Button, type ButtonProps } from "../Button/Button";
18
+ import { cn } from "../../utils/cn";
19
+ import {
20
+ useSelectState,
21
+ useSelectEvents,
22
+ useSelectSearch,
23
+ type SelectState,
24
+ type SelectActions,
25
+ type UseSelectStateProps
26
+ } from "./hooks";
27
+
28
+ // ============================================================================
29
+ // TYPES AND INTERFACES
30
+ // ============================================================================
31
+
32
+ export interface SelectContextValue extends SelectState {
33
+ actions: SelectActions;
34
+ }
35
+
36
+ export interface SelectProps extends Omit<React.HTMLAttributes<HTMLFormElement>, 'onChange' | 'onKeyDown' | 'onFocus' | 'onBlur'> {
37
+ children: React.ReactNode;
38
+ className?: string;
39
+ }
40
+
41
+ export interface SelectTriggerProps extends Omit<ButtonProps, 'onClick' | 'onKeyDown'> {
42
+ children: React.ReactNode;
43
+ asChild?: boolean;
44
+ }
45
+
46
+ export interface SelectValueProps {
47
+ placeholder?: string;
48
+ children?: React.ReactNode;
49
+ }
50
+
51
+ export interface SelectContentProps {
52
+ children: React.ReactNode;
53
+ className?: string;
54
+ searchable?: boolean;
55
+ searchPlaceholder?: string;
56
+ maxHeight?: string;
57
+ style?: React.CSSProperties;
58
+ }
59
+
60
+ export interface SelectItemProps {
61
+ value: string;
62
+ children: React.ReactNode;
63
+ disabled?: boolean;
64
+ className?: string;
65
+ onClick?: (e: React.MouseEvent) => void;
66
+ }
67
+
68
+ // ============================================================================
69
+ // CONTEXT
70
+ // ============================================================================
71
+
72
+ const SelectContext = React.createContext<SelectContextValue | null>(null);
73
+
74
+ const useSelectContext = () => {
75
+ const context = React.useContext(SelectContext);
76
+ if (!context) {
77
+ throw new Error('Select components must be used within a Select');
78
+ }
79
+ return context;
80
+ };
81
+
82
+ // ============================================================================
83
+ // ROOT COMPONENT
84
+ // ============================================================================
85
+
86
+ export const Select = React.forwardRef<HTMLFormElement, SelectProps & UseSelectStateProps>(
87
+ ({
88
+ children,
89
+ className,
90
+ ...selectProps
91
+ }, ref) => {
92
+ const internalRef = React.useRef<HTMLFormElement>(null);
93
+ const selectRef = React.useMemo(() => {
94
+ if (ref && typeof ref === 'object' && 'current' in ref) {
95
+ return ref as React.RefObject<HTMLFormElement>;
96
+ }
97
+ return internalRef;
98
+ }, [ref]);
99
+
100
+ // Use custom hooks for state management
101
+ const { state, actions } = useSelectState(selectProps);
102
+ const { isSelecting } = useSelectEvents({ state, actions, selectRef });
103
+
104
+ // Find selected text when value changes
105
+ React.useEffect(() => {
106
+ if (state.value && !state.selectedText) {
107
+ // Find the SelectItem with the matching value and extract its text
108
+ const selectElement = selectRef.current;
109
+ if (selectElement) {
110
+ const selectItem = selectElement.querySelector(`[data-value="${state.value}"]`);
111
+ if (selectItem) {
112
+ const textContent = selectItem.textContent?.trim() || '';
113
+ if (textContent) {
114
+ actions.setSelectedText(textContent);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }, [state.value, state.selectedText, actions, selectRef]);
120
+
121
+ const contextValue = React.useMemo<SelectContextValue>(() => ({
122
+ ...state,
123
+ actions,
124
+ }), [state, actions]);
125
+
126
+ return (
127
+ <form
128
+ ref={selectRef}
129
+ className={cn("relative", className)}
130
+ data-value={state.value}
131
+ data-testid="select-root"
132
+ >
133
+ <SelectContext.Provider value={contextValue}>
134
+ {children}
135
+ </SelectContext.Provider>
136
+ </form>
137
+ );
138
+ }
139
+ );
140
+ Select.displayName = "Select";
141
+
142
+ // ============================================================================
143
+ // TRIGGER COMPONENT
144
+ // ============================================================================
145
+
146
+ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
147
+ ({ children, className, variant = "outline", size = "default", asChild = false, ...props }, ref) => {
148
+ const { open, disabled, value, actions } = useSelectContext();
149
+
150
+ const handleClick = () => {
151
+ actions.setOpen(!open);
152
+ };
153
+
154
+ const handleKeyDown = (e: React.KeyboardEvent) => {
155
+ if (disabled) return;
156
+
157
+ switch (e.key) {
158
+ case 'Enter':
159
+ case ' ':
160
+ case 'ArrowDown':
161
+ case 'ArrowUp':
162
+ e.preventDefault();
163
+ actions.setOpen(true);
164
+ break;
165
+ case 'Escape':
166
+ if (open) {
167
+ e.preventDefault();
168
+ actions.setOpen(false);
169
+ }
170
+ break;
171
+ }
172
+ };
173
+
174
+ const triggerProps = {
175
+ ref,
176
+ type: "button" as const,
177
+ role: "combobox",
178
+ "aria-expanded": open,
179
+ "aria-haspopup": "listbox",
180
+ disabled,
181
+ className: cn(
182
+ "!justify-between relative w-full",
183
+ "[&_svg]:pointer-events-none",
184
+ open && "!rounded-b-none !border-b-0",
185
+ className
186
+ ),
187
+ style: {
188
+ ...props.style,
189
+ overflow: 'hidden',
190
+ textOverflow: 'ellipsis',
191
+ whiteSpace: 'nowrap'
192
+ },
193
+ onClick: handleClick,
194
+ onKeyDown: handleKeyDown,
195
+ "data-testid": "select-trigger",
196
+ "data-value": value,
197
+ ...props
198
+ };
199
+
200
+ if (asChild) {
201
+ const childChildren = React.Children.toArray((children as React.ReactElement).props.children);
202
+ const hasChevron = childChildren.some(child =>
203
+ React.isValidElement(child) &&
204
+ (child.type === ChevronDown ||
205
+ (child.type === 'svg' && child.props['data-testid'] === 'chevron-down') ||
206
+ (typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown'))
207
+ );
208
+
209
+ return React.cloneElement(children as React.ReactElement, {
210
+ ...triggerProps,
211
+ children: hasChevron ? childChildren : [
212
+ ...childChildren,
213
+ <ChevronDown
214
+ key="chevron-down"
215
+ className={cn(
216
+ "h-4 w-4 opacity-50 transition-transform pointer-events-none float-right",
217
+ open && "rotate-180"
218
+ )}
219
+ />
220
+ ]
221
+ });
222
+ }
223
+
224
+ return (
225
+ <Button
226
+ ref={ref}
227
+ type="button"
228
+ role="combobox"
229
+ aria-expanded={open}
230
+ aria-haspopup="listbox"
231
+ disabled={disabled}
232
+ variant={variant}
233
+ size={size}
234
+ className={cn(
235
+ "!justify-between relative w-full",
236
+ "[&_svg]:pointer-events-none",
237
+ open && "!rounded-b-none !border-b-0",
238
+ className
239
+ )}
240
+ style={{
241
+ ...props.style,
242
+ overflow: 'hidden',
243
+ textOverflow: 'ellipsis',
244
+ whiteSpace: 'nowrap'
245
+ }}
246
+ onClick={handleClick}
247
+ onKeyDown={handleKeyDown}
248
+ data-testid="select-trigger"
249
+ data-value={value}
250
+ {...props}
251
+ >
252
+ {children}
253
+ <ChevronDown
254
+ className={cn(
255
+ "h-4 w-4 opacity-50 transition-transform pointer-events-none float-right",
256
+ open && "rotate-180"
257
+ )}
258
+ />
259
+ </Button>
260
+ );
261
+ }
262
+ );
263
+ SelectTrigger.displayName = "SelectTrigger";
264
+
265
+ // ============================================================================
266
+ // VALUE COMPONENT
267
+ // ============================================================================
268
+
269
+ export const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(
270
+ ({ placeholder = "Select an option...", children }, ref) => {
271
+ const { selectedText } = useSelectContext();
272
+
273
+ return (
274
+ <span ref={ref} data-testid="select-value">
275
+ {children || (selectedText ? selectedText : placeholder)}
276
+ </span>
277
+ );
278
+ }
279
+ );
280
+ SelectValue.displayName = "SelectValue";
281
+
282
+ // ============================================================================
283
+ // CONTENT COMPONENT
284
+ // ============================================================================
285
+
286
+ export const SelectContent = React.forwardRef<HTMLUListElement, SelectContentProps>(
287
+ ({
288
+ children,
289
+ className,
290
+ searchable = false,
291
+ searchPlaceholder = "Search...",
292
+ maxHeight = "20rem",
293
+ style
294
+ }, ref) => {
295
+ const { open, actions } = useSelectContext();
296
+ const { searchTerm, setSearchTerm, filteredChildren, searchInputRef } = useSelectSearch({
297
+ children,
298
+ searchable,
299
+ });
300
+
301
+ // Focus search input when dropdown opens
302
+ React.useEffect(() => {
303
+ if (open && searchable && searchInputRef.current) {
304
+ searchInputRef.current.focus();
305
+ }
306
+ }, [open, searchable]);
307
+
308
+ if (!open) {
309
+ return null;
310
+ }
311
+
312
+ return (
313
+ <ul
314
+ ref={ref}
315
+ className={cn(
316
+ "absolute z-[99999] w-full overflow-y-auto rounded-b-md border border-t-0 border-main-300 bg-main-50 shadow-lg",
317
+ "list-none p-0 m-0",
318
+ className
319
+ )}
320
+ style={{
321
+ top: '100%',
322
+ left: 0,
323
+ right: 0,
324
+ maxHeight,
325
+ position: 'absolute',
326
+ zIndex: 99999,
327
+ ...style
328
+ }}
329
+ data-testid="select-content"
330
+ role="listbox"
331
+ >
332
+ {searchable && (
333
+ <div className="p-2 border-b border-main-200">
334
+ <div className="relative">
335
+ <Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-main-400" />
336
+ <input
337
+ ref={searchInputRef}
338
+ type="text"
339
+ placeholder={searchPlaceholder}
340
+ value={searchTerm}
341
+ onChange={(e) => setSearchTerm(e.target.value)}
342
+ onKeyDown={(e) => {
343
+ if (e.key === 'Escape') {
344
+ e.preventDefault();
345
+ setSearchTerm('');
346
+ }
347
+ }}
348
+ className="w-full pl-8 pr-8 py-1 text-sm border border-main-200 rounded focus:outline-none focus:ring-2 focus:ring-main-500"
349
+ data-testid="select-search-input"
350
+ aria-label="Search options"
351
+ />
352
+ {searchTerm && (
353
+ <button
354
+ type="button"
355
+ onClick={() => setSearchTerm('')}
356
+ className="absolute right-2 top-1/2 transform -translate-y-1/2 text-main-400 hover:text-main-600"
357
+ data-testid="select-clear-search"
358
+ aria-label="Clear search"
359
+ >
360
+ <X className="h-4 w-4" />
361
+ </button>
362
+ )}
363
+ </div>
364
+ </div>
365
+ )}
366
+ {filteredChildren}
367
+ </ul>
368
+ );
369
+ }
370
+ );
371
+ SelectContent.displayName = "SelectContent";
372
+
373
+ // ============================================================================
374
+ // ITEM COMPONENT
375
+ // ============================================================================
376
+
377
+ export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
378
+ ({ value, children, disabled = false, className, onClick }, ref) => {
379
+ const { value: selectedValue, actions } = useSelectContext();
380
+ const isSelected = selectedValue === value;
381
+
382
+ // Extract text content from children for display
383
+ const getTextContent = (children: React.ReactNode): string => {
384
+ if (typeof children === 'string') return children;
385
+ if (typeof children === 'number') return children.toString();
386
+ if (React.isValidElement(children) && children.props.children) {
387
+ return getTextContent(children.props.children);
388
+ }
389
+ if (Array.isArray(children)) {
390
+ return children.map(getTextContent).join('');
391
+ }
392
+ return '';
393
+ };
394
+
395
+ const itemText = getTextContent(children);
396
+
397
+ const handleMouseDown = (e: React.MouseEvent) => {
398
+ if (!disabled) {
399
+ const event = new CustomEvent('selectItemMouseDown', { detail: { value } });
400
+ document.dispatchEvent(event);
401
+ }
402
+ };
403
+
404
+ const handleClick = (e: React.MouseEvent) => {
405
+ if (!disabled) {
406
+ if (onClick) {
407
+ onClick(e);
408
+ }
409
+ actions.setValue(value, itemText);
410
+ actions.setOpen(false);
411
+ }
412
+ };
413
+
414
+ const handleKeyDown = (e: React.KeyboardEvent) => {
415
+ if (disabled) return;
416
+
417
+ switch (e.key) {
418
+ case 'Enter':
419
+ case ' ':
420
+ e.preventDefault();
421
+ if (onClick) {
422
+ onClick(e as any);
423
+ }
424
+ actions.setValue(value, itemText);
425
+ actions.setOpen(false);
426
+ break;
427
+ }
428
+ };
429
+
430
+ return (
431
+ <li
432
+ ref={ref}
433
+ data-value={value}
434
+ className={cn(
435
+ "relative flex cursor-pointer select-none items-start rounded-sm px-2 py-1.5 text-sm outline-none",
436
+ "hover:bg-main-100 focus:bg-main-100",
437
+ "break-words min-w-0",
438
+ isSelected && "bg-main-100 text-main-900",
439
+ disabled && "pointer-events-none opacity-50",
440
+ className
441
+ )}
442
+ onMouseDown={handleMouseDown}
443
+ onClick={handleClick}
444
+ onKeyDown={handleKeyDown}
445
+ data-testid="select-item"
446
+ data-disabled={disabled ? "true" : undefined}
447
+ role="option"
448
+ aria-selected={isSelected}
449
+ >
450
+ {children}
451
+ {isSelected && (
452
+ <Check className="absolute right-2 h-4 w-4 flex-shrink-0 mt-0.5" />
453
+ )}
454
+ </li>
455
+ );
456
+ }
457
+ );
458
+ SelectItem.displayName = "SelectItem";
459
+
460
+ // ============================================================================
461
+ // ADDITIONAL COMPONENTS (for backward compatibility)
462
+ // ============================================================================
463
+
464
+ export const SelectGroup = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
465
+ ({ children, className }, ref) => {
466
+ return (
467
+ <div ref={ref} className={cn("p-1", className)} data-testid="select-group">
468
+ {children}
469
+ </div>
470
+ );
471
+ }
472
+ );
473
+ SelectGroup.displayName = "SelectGroup";
474
+
475
+ export const SelectLabel = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
476
+ ({ children, className }, ref) => {
477
+ return (
478
+ <div ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} data-testid="select-label">
479
+ {children}
480
+ </div>
481
+ );
482
+ }
483
+ );
484
+ SelectLabel.displayName = "SelectLabel";
485
+
486
+ export const SelectSeparator = React.forwardRef<HTMLDivElement, { className?: string }>(
487
+ ({ className }, ref) => {
488
+ return (
489
+ <div
490
+ ref={ref}
491
+ className={cn("my-1 h-px bg-sec-200", className)}
492
+ data-testid="select-separator"
493
+ />
494
+ );
495
+ }
496
+ );
497
+ SelectSeparator.displayName = "SelectSeparator";
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import React from 'react';
8
- import { screen, waitFor } from '@testing-library/react';
8
+ import { screen, waitFor, act } from '@testing-library/react';
9
9
  import userEvent from '@testing-library/user-event';
10
10
  import { describe, it, expect, vi, beforeEach } from 'vitest';
11
11
  import {
@@ -66,11 +66,7 @@ describe('Select Component', () => {
66
66
  );
67
67
  };
68
68
 
69
- beforeEach(() => {
70
- vi.clearAllMocks();
71
- });
72
-
73
- describe('Basic Rendering', () => {
69
+ describe('Basic Rendering', () => {
74
70
  it('renders with default props', () => {
75
71
  renderSelect();
76
72
 
@@ -366,7 +362,7 @@ describe('Select Component', () => {
366
362
 
367
363
  await waitFor(() => {
368
364
  expect(screen.queryByTestId('select-content')).not.toBeInTheDocument();
369
- });
365
+ }, { interval: 10 });
370
366
  });
371
367
 
372
368
  it('updates value on item selection', async () => {
@@ -397,7 +393,7 @@ describe('Select Component', () => {
397
393
 
398
394
  await waitFor(() => {
399
395
  expect(screen.queryByTestId('select-content')).not.toBeInTheDocument();
400
- });
396
+ }, { interval: 10 });
401
397
  });
402
398
  });
403
399
 
@@ -459,7 +455,7 @@ describe('Select Component', () => {
459
455
 
460
456
  await waitFor(() => {
461
457
  expect(screen.queryByTestId('select-content')).not.toBeInTheDocument();
462
- });
458
+ }, { interval: 10 });
463
459
  });
464
460
 
465
461
  it('selects item on click', async () => {
@@ -513,29 +509,40 @@ describe('Select Component', () => {
513
509
  expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
514
510
  });
515
511
 
516
- it.skip('filters options based on search term', async () => {
512
+ it('filters options based on search term', async () => {
517
513
  const user = userEvent.setup();
514
+ const options = [
515
+ { value: 'apple', label: 'Apple' },
516
+ { value: 'banana', label: 'Banana' },
517
+ { value: 'cherry', label: 'Cherry' },
518
+ ];
519
+
518
520
  renderWithProviders(
519
- <Select>
521
+ <Select defaultOpen>
520
522
  <SelectTrigger>
521
- <SelectValue />
523
+ <SelectValue placeholder="Select an option..." />
522
524
  </SelectTrigger>
523
525
  <SelectContent searchable>
524
- <SelectItem value="apple">Apple</SelectItem>
525
- <SelectItem value="banana">Banana</SelectItem>
526
- <SelectItem value="cherry">Cherry</SelectItem>
526
+ {options.map(option => (
527
+ <SelectItem key={option.value} value={option.value}>
528
+ {option.label}
529
+ </SelectItem>
530
+ ))}
527
531
  </SelectContent>
528
532
  </Select>
529
533
  );
530
534
 
531
535
  const trigger = screen.getByTestId('select-trigger');
532
- await user.click(trigger);
533
536
 
534
- // Wait for dropdown to open and search input to be available
535
- await waitFor(() => {
536
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
537
- });
537
+ // Get search input - should be visible once dropdown opens
538
+ expect(screen.getByTestId('select-search-input')).toBeInTheDocument();
539
+
540
+ // Verify options are visible
541
+ expect(screen.getByText('Apple')).toBeInTheDocument();
542
+ expect(screen.getByText('Banana')).toBeInTheDocument();
543
+ expect(screen.getByText('Cherry')).toBeInTheDocument();
538
544
 
545
+ // Type in search input to filter
539
546
  const searchInput = screen.getByTestId('select-search-input');
540
547
  await user.type(searchInput, 'app');
541
548
 
@@ -547,10 +554,10 @@ describe('Select Component', () => {
547
554
  });
548
555
  });
549
556
 
550
- it.skip('shows clear search button when search term exists', async () => {
557
+ it('shows clear search button when search term exists', async () => {
551
558
  const user = userEvent.setup();
552
559
  renderWithProviders(
553
- <Select>
560
+ <Select defaultOpen>
554
561
  <SelectTrigger>
555
562
  <SelectValue />
556
563
  </SelectTrigger>
@@ -560,14 +567,6 @@ describe('Select Component', () => {
560
567
  </Select>
561
568
  );
562
569
 
563
- const trigger = screen.getByTestId('select-trigger');
564
- await user.click(trigger);
565
-
566
- // Wait for dropdown to open and search input to be available
567
- await waitFor(() => {
568
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
569
- });
570
-
571
570
  const searchInput = screen.getByTestId('select-search-input');
572
571
  await user.type(searchInput, 'test');
573
572
 
@@ -577,10 +576,10 @@ describe('Select Component', () => {
577
576
  });
578
577
  });
579
578
 
580
- it.skip('clears search when clear button is clicked', async () => {
579
+ it('clears search when clear button is clicked', async () => {
581
580
  const user = userEvent.setup();
582
581
  renderWithProviders(
583
- <Select>
582
+ <Select defaultOpen>
584
583
  <SelectTrigger>
585
584
  <SelectValue />
586
585
  </SelectTrigger>
@@ -591,14 +590,6 @@ describe('Select Component', () => {
591
590
  </Select>
592
591
  );
593
592
 
594
- const trigger = screen.getByTestId('select-trigger');
595
- await user.click(trigger);
596
-
597
- // Wait for dropdown to open and search input to be available
598
- await waitFor(() => {
599
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
600
- });
601
-
602
593
  const searchInput = screen.getByTestId('select-search-input');
603
594
  await user.type(searchInput, 'app');
604
595
 
@@ -607,7 +598,11 @@ describe('Select Component', () => {
607
598
  expect(screen.queryByText('Banana')).not.toBeInTheDocument();
608
599
  });
609
600
 
610
- const clearButton = screen.getByTestId('select-clear-search');
601
+ // Wait for clear button to appear
602
+ const clearButton = await waitFor(() => {
603
+ return screen.getByTestId('select-clear-search');
604
+ });
605
+
611
606
  await user.click(clearButton);
612
607
 
613
608
  // Wait for search to be cleared and all options to be visible again
@@ -617,10 +612,10 @@ describe('Select Component', () => {
617
612
  });
618
613
  });
619
614
 
620
- it.skip('clears search on Escape key', async () => {
615
+ it('clears search on Escape key', async () => {
621
616
  const user = userEvent.setup();
622
617
  renderWithProviders(
623
- <Select>
618
+ <Select defaultOpen>
624
619
  <SelectTrigger>
625
620
  <SelectValue />
626
621
  </SelectTrigger>
@@ -631,16 +626,14 @@ describe('Select Component', () => {
631
626
  </Select>
632
627
  );
633
628
 
634
- const trigger = screen.getByTestId('select-trigger');
635
- await user.click(trigger);
629
+ const searchInput = screen.getByTestId('select-search-input');
630
+ await user.type(searchInput, 'app');
636
631
 
637
- // Wait for dropdown to open and search input to be available
632
+ // Wait for filtering to complete
638
633
  await waitFor(() => {
639
- expect(screen.getByTestId('select-content')).toBeInTheDocument();
634
+ expect(screen.queryByText('Banana')).not.toBeInTheDocument();
640
635
  });
641
636
 
642
- const searchInput = screen.getByTestId('select-search-input');
643
- await user.type(searchInput, 'app');
644
637
  await user.keyboard('{Escape}');
645
638
 
646
639
  // Wait for search to be cleared and all options to be visible again