@jmruthers/pace-core 0.5.186 → 0.5.188

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 (290) hide show
  1. package/dist/{DataTable-IX2NBUTP.js → DataTable-GUFUNZ3N.js} +7 -7
  2. package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
  3. package/dist/{PublicPageProvider-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
  4. package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
  5. package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
  6. package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
  7. package/dist/{chunk-HGPQUCBC.js → chunk-2UUZZJFT.js} +3 -3
  8. package/dist/{chunk-445GEP27.js → chunk-3GOZZZYH.js} +33 -8
  9. package/dist/chunk-3GOZZZYH.js.map +1 -0
  10. package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
  11. package/dist/chunk-63FOKYGO.js.map +1 -0
  12. package/dist/{chunk-DAGICKHT.js → chunk-DDM4CCYT.js} +3 -3
  13. package/dist/{chunk-XAUHJD3L.js → chunk-E7UAOUMY.js} +2 -2
  14. package/dist/{chunk-HDCUMOOI.js → chunk-EFCLXK7F.js} +792 -559
  15. package/dist/chunk-EFCLXK7F.js.map +1 -0
  16. package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
  17. package/dist/chunk-HEHYGYOX.js.map +1 -0
  18. package/dist/{chunk-GRIQLQ52.js → chunk-IM4QE42D.js} +27 -23
  19. package/dist/chunk-IM4QE42D.js.map +1 -0
  20. package/dist/{chunk-OALXJH4Y.js → chunk-IPCH26AG.js} +8 -8
  21. package/dist/chunk-IPCH26AG.js.map +1 -0
  22. package/dist/{chunk-UQWSHFVX.js → chunk-SAUPYVLF.js} +1 -1
  23. package/dist/{chunk-UQWSHFVX.js.map → chunk-SAUPYVLF.js.map} +1 -1
  24. package/dist/{chunk-TC7D3CR3.js → chunk-UNOTYLQF.js} +556 -101
  25. package/dist/chunk-UNOTYLQF.js.map +1 -0
  26. package/dist/{chunk-FXFJRTKI.js → chunk-VGZZXKBR.js} +5 -5
  27. package/dist/chunk-VGZZXKBR.js.map +1 -0
  28. package/dist/chunk-YHCN776L.js +447 -0
  29. package/dist/chunk-YHCN776L.js.map +1 -0
  30. package/dist/components.d.ts +4 -4
  31. package/dist/components.js +12 -10
  32. package/dist/components.js.map +1 -1
  33. package/dist/{file-reference-PRTSLxKx.d.ts → file-reference-D037xOFK.d.ts} +0 -1
  34. package/dist/hooks.d.ts +221 -6
  35. package/dist/hooks.js +146 -49
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.d.ts +24 -9
  38. package/dist/index.js +62 -28
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers.js +1 -1
  41. package/dist/rbac/index.d.ts +124 -7
  42. package/dist/rbac/index.js +27 -7
  43. package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
  44. package/dist/types.d.ts +1 -1
  45. package/dist/types.js +1 -1
  46. package/dist/{usePublicRouteParams-D71QLlg4.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +2 -2
  47. package/dist/utils.d.ts +213 -3
  48. package/dist/utils.js +22 -2
  49. package/dist/utils.js.map +1 -1
  50. package/docs/api/classes/ColumnFactory.md +1 -1
  51. package/docs/api/classes/ErrorBoundary.md +1 -1
  52. package/docs/api/classes/InvalidScopeError.md +1 -1
  53. package/docs/api/classes/Logger.md +1 -1
  54. package/docs/api/classes/MissingUserContextError.md +1 -1
  55. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  56. package/docs/api/classes/PermissionDeniedError.md +1 -1
  57. package/docs/api/classes/RBACAuditManager.md +21 -17
  58. package/docs/api/classes/RBACCache.md +31 -23
  59. package/docs/api/classes/RBACEngine.md +5 -5
  60. package/docs/api/classes/RBACError.md +1 -1
  61. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  62. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  63. package/docs/api/classes/StorageUtils.md +1 -1
  64. package/docs/api/enums/FileCategory.md +1 -1
  65. package/docs/api/enums/LogLevel.md +1 -1
  66. package/docs/api/enums/RBACErrorCode.md +1 -1
  67. package/docs/api/enums/RPCFunction.md +1 -1
  68. package/docs/api/interfaces/AddressFieldProps.md +241 -0
  69. package/docs/api/interfaces/AddressFieldRef.md +94 -0
  70. package/docs/api/interfaces/AggregateConfig.md +1 -1
  71. package/docs/api/interfaces/AutocompleteOptions.md +75 -0
  72. package/docs/api/interfaces/BadgeProps.md +1 -1
  73. package/docs/api/interfaces/ButtonProps.md +1 -1
  74. package/docs/api/interfaces/CalendarProps.md +1 -1
  75. package/docs/api/interfaces/CardProps.md +1 -1
  76. package/docs/api/interfaces/ColorPalette.md +1 -1
  77. package/docs/api/interfaces/ColorShade.md +1 -1
  78. package/docs/api/interfaces/ComplianceResult.md +1 -1
  79. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  80. package/docs/api/interfaces/DataRecord.md +1 -1
  81. package/docs/api/interfaces/DataTableAction.md +1 -1
  82. package/docs/api/interfaces/DataTableColumn.md +1 -1
  83. package/docs/api/interfaces/DataTableProps.md +1 -1
  84. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  85. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  86. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  87. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  88. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  90. package/docs/api/interfaces/ExportColumn.md +1 -1
  91. package/docs/api/interfaces/ExportOptions.md +1 -1
  92. package/docs/api/interfaces/FileDisplayProps.md +15 -15
  93. package/docs/api/interfaces/FileMetadata.md +1 -1
  94. package/docs/api/interfaces/FileReference.md +1 -1
  95. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  96. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  97. package/docs/api/interfaces/FileUploadProps.md +1 -1
  98. package/docs/api/interfaces/FooterProps.md +1 -1
  99. package/docs/api/interfaces/FormFieldProps.md +1 -1
  100. package/docs/api/interfaces/FormProps.md +1 -1
  101. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoggerConfig.md +1 -1
  106. package/docs/api/interfaces/LoginFormProps.md +1 -1
  107. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  108. package/docs/api/interfaces/NavigationContextType.md +1 -1
  109. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  110. package/docs/api/interfaces/NavigationItem.md +1 -1
  111. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  112. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  113. package/docs/api/interfaces/Organisation.md +1 -1
  114. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  115. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  116. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  117. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  118. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  119. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  120. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  121. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  122. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  123. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  124. package/docs/api/interfaces/PaletteData.md +1 -1
  125. package/docs/api/interfaces/ParsedAddress.md +120 -0
  126. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  127. package/docs/api/interfaces/ProgressProps.md +1 -1
  128. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  131. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  132. package/docs/api/interfaces/QuickFix.md +1 -1
  133. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  134. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  135. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  136. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  137. package/docs/api/interfaces/RBACConfig.md +26 -3
  138. package/docs/api/interfaces/RBACContext.md +1 -1
  139. package/docs/api/interfaces/RBACLogger.md +5 -5
  140. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  141. package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
  142. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  143. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  144. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  146. package/docs/api/interfaces/RBACResult.md +1 -1
  147. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  148. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  149. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  150. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  151. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  152. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  153. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  154. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  155. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  156. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  157. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  158. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  159. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  160. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  161. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  162. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  163. package/docs/api/interfaces/RouteConfig.md +1 -1
  164. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  165. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  166. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  167. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  168. package/docs/api/interfaces/SetupIssue.md +1 -1
  169. package/docs/api/interfaces/StorageConfig.md +1 -1
  170. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  171. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  172. package/docs/api/interfaces/StorageListOptions.md +1 -1
  173. package/docs/api/interfaces/StorageListResult.md +1 -1
  174. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  175. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  176. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  177. package/docs/api/interfaces/StyleImport.md +1 -1
  178. package/docs/api/interfaces/SwitchProps.md +1 -1
  179. package/docs/api/interfaces/TabsContentProps.md +1 -1
  180. package/docs/api/interfaces/TabsListProps.md +1 -1
  181. package/docs/api/interfaces/TabsProps.md +1 -1
  182. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  183. package/docs/api/interfaces/TextareaProps.md +1 -1
  184. package/docs/api/interfaces/ToastActionElement.md +1 -1
  185. package/docs/api/interfaces/ToastProps.md +1 -1
  186. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  187. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  188. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  189. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  190. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  191. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  192. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  193. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  194. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  196. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  197. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  198. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  199. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  200. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  201. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  202. package/docs/api/interfaces/UserEventAccess.md +1 -1
  203. package/docs/api/interfaces/UserMenuProps.md +1 -1
  204. package/docs/api/interfaces/UserProfile.md +1 -1
  205. package/docs/api/modules.md +318 -59
  206. package/docs/best-practices/performance.md +11 -0
  207. package/docs/getting-started/examples/README.md +2 -2
  208. package/docs/implementation-guides/file-upload-storage.md +29 -0
  209. package/docs/implementation-guides/public-pages.md +140 -1230
  210. package/docs/rbac/README.md +2 -1
  211. package/docs/rbac/api-reference.md +11 -0
  212. package/docs/rbac/performance.md +320 -0
  213. package/docs/standards/01-architecture-standard.md +5 -0
  214. package/docs/standards/05-security-standard.md +14 -0
  215. package/docs/standards/07-rbac-and-rls-standard.md +356 -0
  216. package/package.json +1 -1
  217. package/src/__tests__/public-recipe-view.test.ts +199 -0
  218. package/src/__tests__/rls-policies.test.ts +333 -0
  219. package/src/components/AddressField/AddressField.test.tsx +411 -0
  220. package/src/components/AddressField/AddressField.tsx +323 -0
  221. package/src/components/AddressField/README.md +336 -0
  222. package/src/components/AddressField/index.ts +10 -0
  223. package/src/components/AddressField/types.ts +65 -0
  224. package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
  225. package/src/components/FileDisplay/FileDisplay.tsx +28 -1
  226. package/src/components/index.ts +2 -0
  227. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
  228. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
  229. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
  230. package/src/hooks/index.ts +6 -0
  231. package/src/hooks/public/usePublicFileDisplay.ts +8 -10
  232. package/src/hooks/useAddressAutocomplete.test.ts +318 -0
  233. package/src/hooks/useAddressAutocomplete.ts +268 -0
  234. package/src/hooks/useFileDisplay.ts +3 -15
  235. package/src/hooks/useFileReference.test.ts +20 -3
  236. package/src/hooks/useFileReference.ts +3 -24
  237. package/src/hooks/useFileUrlCache.ts +246 -0
  238. package/src/hooks/useInactivityTracker.ts +31 -20
  239. package/src/hooks/useOrganisationSecurity.test.ts +10 -7
  240. package/src/hooks/useOrganisationSecurity.ts +3 -3
  241. package/src/hooks/useQueryCache.ts +315 -0
  242. package/src/index.ts +2 -0
  243. package/src/providers/services/EventServiceProvider.tsx +4 -1
  244. package/src/rbac/api.test.ts +21 -6
  245. package/src/rbac/api.ts +32 -11
  246. package/src/rbac/audit-batched.ts +223 -0
  247. package/src/rbac/audit-enhanced.ts +2 -2
  248. package/src/rbac/audit.test.ts +6 -5
  249. package/src/rbac/audit.ts +34 -6
  250. package/src/rbac/cache-invalidation.ts +63 -12
  251. package/src/rbac/cache.test.ts +2 -2
  252. package/src/rbac/cache.ts +61 -14
  253. package/src/rbac/components/PagePermissionGuard.tsx +19 -10
  254. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
  255. package/src/rbac/config.ts +9 -0
  256. package/src/rbac/engine.ts +2 -21
  257. package/src/rbac/hooks/usePermissions.ts +21 -5
  258. package/src/rbac/index.ts +19 -0
  259. package/src/rbac/performance.ts +210 -0
  260. package/src/rbac/request-deduplication.ts +87 -0
  261. package/src/rbac/utils/deep-equal.ts +93 -0
  262. package/src/services/OrganisationService.ts +5 -4
  263. package/src/types/file-reference.ts +0 -1
  264. package/src/utils/file-reference/__tests__/file-reference.test.ts +31 -4
  265. package/src/utils/file-reference/index.ts +44 -15
  266. package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
  267. package/src/utils/google-places/googlePlacesUtils.ts +475 -0
  268. package/src/utils/google-places/index.ts +26 -0
  269. package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
  270. package/src/utils/google-places/types.ts +94 -0
  271. package/src/utils/index.ts +23 -0
  272. package/src/utils/request-deduplication.ts +165 -0
  273. package/src/utils/storage/helpers.ts +143 -4
  274. package/dist/chunk-445GEP27.js.map +0 -1
  275. package/dist/chunk-FMUCXFII.js +0 -76
  276. package/dist/chunk-FMUCXFII.js.map +0 -1
  277. package/dist/chunk-FSFQFJCU.js.map +0 -1
  278. package/dist/chunk-FXFJRTKI.js.map +0 -1
  279. package/dist/chunk-GRIQLQ52.js.map +0 -1
  280. package/dist/chunk-HDCUMOOI.js.map +0 -1
  281. package/dist/chunk-OALXJH4Y.js.map +0 -1
  282. package/dist/chunk-TC7D3CR3.js.map +0 -1
  283. package/dist/chunk-U6WNSFX5.js.map +0 -1
  284. /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
  285. /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
  286. /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
  287. /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
  288. /package/dist/{chunk-HGPQUCBC.js.map → chunk-2UUZZJFT.js.map} +0 -0
  289. /package/dist/{chunk-DAGICKHT.js.map → chunk-DDM4CCYT.js.map} +0 -0
  290. /package/dist/{chunk-XAUHJD3L.js.map → chunk-E7UAOUMY.js.map} +0 -0
@@ -0,0 +1,411 @@
1
+ /**
2
+ * @file AddressField Component Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/AddressField/__tests__
5
+ * @since 0.1.0
6
+ */
7
+
8
+ import React from 'react';
9
+ import { screen, waitFor } from '@testing-library/react';
10
+ import userEvent from '@testing-library/user-event';
11
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
12
+ import { AddressField } from './AddressField';
13
+ import { renderWithProviders } from '../../__tests__/helpers/test-utils';
14
+ import * as googlePlacesUtils from '../../utils/google-places';
15
+
16
+ // Mock scrollIntoView for tests
17
+ Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
18
+ value: vi.fn(),
19
+ writable: true,
20
+ });
21
+
22
+ // Mock the Google Places utilities
23
+ vi.mock('../../utils/google-places', () => ({
24
+ fetchPlaceAutocomplete: vi.fn(),
25
+ fetchPlaceDetails: vi.fn(),
26
+ createAddressFromPlaceResult: vi.fn(),
27
+ getAddressByPlaceId: vi.fn(),
28
+ }));
29
+
30
+ // Mock useAddressAutocomplete hook
31
+ vi.mock('../../hooks/useAddressAutocomplete', () => ({
32
+ useAddressAutocomplete: vi.fn(),
33
+ }));
34
+
35
+ import { useAddressAutocomplete } from '../../hooks/useAddressAutocomplete';
36
+
37
+ describe('AddressField Component', () => {
38
+ const mockApiKey = 'test-api-key';
39
+
40
+ beforeEach(() => {
41
+ vi.clearAllMocks();
42
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
43
+ suggestions: [],
44
+ isLoading: false,
45
+ error: null,
46
+ selectAddress: vi.fn(),
47
+ getAddressByPlaceId: vi.fn(),
48
+ clearSuggestions: vi.fn(),
49
+ });
50
+ });
51
+
52
+ afterEach(() => {
53
+ vi.clearAllMocks();
54
+ });
55
+
56
+ describe('Rendering', () => {
57
+ it('renders with default props', () => {
58
+ renderWithProviders(<AddressField apiKey={mockApiKey} />);
59
+ const input = screen.getByRole('combobox');
60
+ expect(input).toBeInTheDocument();
61
+ });
62
+
63
+ it('renders with placeholder', () => {
64
+ renderWithProviders(<AddressField apiKey={mockApiKey} placeholder="Enter address" />);
65
+ expect(screen.getByPlaceholderText('Enter address')).toBeInTheDocument();
66
+ });
67
+
68
+ it('renders with controlled value', () => {
69
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123 Main St" />);
70
+ expect(screen.getByDisplayValue('123 Main St')).toBeInTheDocument();
71
+ });
72
+
73
+ it('renders with defaultValue', () => {
74
+ renderWithProviders(<AddressField apiKey={mockApiKey} defaultValue="123 Main St" />);
75
+ expect(screen.getByDisplayValue('123 Main St')).toBeInTheDocument();
76
+ });
77
+
78
+ it('renders with error state', () => {
79
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
80
+ suggestions: [],
81
+ isLoading: false,
82
+ error: new Error('API error'),
83
+ selectAddress: vi.fn(),
84
+ getAddressByPlaceId: vi.fn(),
85
+ clearSuggestions: vi.fn(),
86
+ });
87
+
88
+ renderWithProviders(<AddressField apiKey={mockApiKey} />);
89
+ expect(screen.getByText('API error')).toBeInTheDocument();
90
+ });
91
+
92
+ it('renders loading spinner when loading', () => {
93
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
94
+ suggestions: [],
95
+ isLoading: true,
96
+ error: null,
97
+ selectAddress: vi.fn(),
98
+ getAddressByPlaceId: vi.fn(),
99
+ clearSuggestions: vi.fn(),
100
+ });
101
+
102
+ renderWithProviders(<AddressField apiKey={mockApiKey} />);
103
+ // Loading spinner should be present (check by test id or class)
104
+ const input = screen.getByRole('combobox');
105
+ expect(input).toBeInTheDocument();
106
+ });
107
+ });
108
+
109
+ describe('Input interactions', () => {
110
+ it('calls onInputChange when input value changes', async () => {
111
+ const user = userEvent.setup();
112
+ const onInputChange = vi.fn();
113
+
114
+ renderWithProviders(
115
+ <AddressField apiKey={mockApiKey} onInputChange={onInputChange} />
116
+ );
117
+
118
+ const input = screen.getByRole('combobox');
119
+ await user.type(input, '123 Main');
120
+
121
+ expect(onInputChange).toHaveBeenCalled();
122
+ });
123
+
124
+ it('handles focus and blur events', async () => {
125
+ const user = userEvent.setup();
126
+
127
+ renderWithProviders(<AddressField apiKey={mockApiKey} />);
128
+
129
+ const input = screen.getByRole('combobox');
130
+ await user.click(input);
131
+ expect(input).toHaveFocus();
132
+
133
+ await user.tab();
134
+ // Input should blur
135
+ });
136
+ });
137
+
138
+ describe('Suggestions display', () => {
139
+ it('displays suggestions when available', async () => {
140
+ const user = userEvent.setup();
141
+ const mockSuggestions = [
142
+ {
143
+ description: '123 Main St, Melbourne VIC, Australia',
144
+ place_id: 'ChIJ123',
145
+ structured_formatting: {
146
+ main_text: '123 Main St',
147
+ secondary_text: 'Melbourne VIC, Australia',
148
+ },
149
+ },
150
+ {
151
+ description: '456 High St, Melbourne VIC, Australia',
152
+ place_id: 'ChIJ456',
153
+ structured_formatting: {
154
+ main_text: '456 High St',
155
+ secondary_text: 'Melbourne VIC, Australia',
156
+ },
157
+ },
158
+ ];
159
+
160
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
161
+ suggestions: mockSuggestions,
162
+ isLoading: false,
163
+ error: null,
164
+ selectAddress: vi.fn(),
165
+ getAddressByPlaceId: vi.fn(),
166
+ clearSuggestions: vi.fn(),
167
+ });
168
+
169
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123 Main" />);
170
+
171
+ // Focus the input to trigger suggestions display
172
+ const input = screen.getByRole('combobox');
173
+ await user.click(input);
174
+
175
+ await waitFor(
176
+ () => {
177
+ expect(screen.getByTestId('address-suggestions')).toBeInTheDocument();
178
+ },
179
+ { timeout: 1000 }
180
+ );
181
+
182
+ expect(screen.getByText('123 Main St')).toBeInTheDocument();
183
+ expect(screen.getByText('456 High St')).toBeInTheDocument();
184
+ });
185
+
186
+ it('hides suggestions when input is empty', () => {
187
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
188
+ suggestions: [
189
+ { description: '123 Main St', place_id: 'ChIJ123' },
190
+ ],
191
+ isLoading: false,
192
+ error: null,
193
+ selectAddress: vi.fn(),
194
+ getAddressByPlaceId: vi.fn(),
195
+ clearSuggestions: vi.fn(),
196
+ });
197
+
198
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="" />);
199
+
200
+ expect(screen.queryByTestId('address-suggestions')).not.toBeInTheDocument();
201
+ });
202
+ });
203
+
204
+ describe('Keyboard navigation', () => {
205
+ it('navigates suggestions with arrow keys', async () => {
206
+ const user = userEvent.setup();
207
+ const mockSuggestions = [
208
+ { description: '123 Main St', place_id: 'ChIJ123' },
209
+ { description: '456 High St', place_id: 'ChIJ456' },
210
+ ];
211
+
212
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
213
+ suggestions: mockSuggestions,
214
+ isLoading: false,
215
+ error: null,
216
+ selectAddress: vi.fn(),
217
+ getAddressByPlaceId: vi.fn(),
218
+ clearSuggestions: vi.fn(),
219
+ });
220
+
221
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123" />);
222
+
223
+ const input = screen.getByRole('combobox');
224
+ await user.click(input);
225
+
226
+ await waitFor(() => {
227
+ expect(screen.getByTestId('address-suggestions')).toBeInTheDocument();
228
+ });
229
+
230
+ // Press ArrowDown
231
+ await user.keyboard('{ArrowDown}');
232
+ const firstItem = screen.getByTestId('address-suggestion-0');
233
+ expect(firstItem).toHaveAttribute('aria-selected', 'true');
234
+
235
+ // Press ArrowDown again
236
+ await user.keyboard('{ArrowDown}');
237
+ const secondItem = screen.getByTestId('address-suggestion-1');
238
+ expect(secondItem).toHaveAttribute('aria-selected', 'true');
239
+ });
240
+
241
+ it('selects suggestion with Enter key', async () => {
242
+ const user = userEvent.setup();
243
+ const selectAddress = vi.fn().mockResolvedValue({
244
+ place_id: 'ChIJ123',
245
+ full_address: '123 Main St',
246
+ street_number: null,
247
+ route: null,
248
+ suburb: null,
249
+ state: null,
250
+ postcode: null,
251
+ country: null,
252
+ lat: null,
253
+ lng: null,
254
+ });
255
+
256
+ const mockSuggestions = [
257
+ { description: '123 Main St', place_id: 'ChIJ123' },
258
+ ];
259
+
260
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
261
+ suggestions: mockSuggestions,
262
+ isLoading: false,
263
+ error: null,
264
+ selectAddress,
265
+ getAddressByPlaceId: vi.fn(),
266
+ clearSuggestions: vi.fn(),
267
+ });
268
+
269
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123" />);
270
+
271
+ const input = screen.getByRole('combobox');
272
+ await user.click(input);
273
+
274
+ await waitFor(() => {
275
+ expect(screen.getByTestId('address-suggestions')).toBeInTheDocument();
276
+ });
277
+
278
+ await user.keyboard('{ArrowDown}{Enter}');
279
+
280
+ expect(selectAddress).toHaveBeenCalledWith('ChIJ123');
281
+ });
282
+
283
+ it('closes suggestions with Escape key', async () => {
284
+ const user = userEvent.setup();
285
+ const mockSuggestions = [
286
+ { description: '123 Main St', place_id: 'ChIJ123' },
287
+ ];
288
+
289
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
290
+ suggestions: mockSuggestions,
291
+ isLoading: false,
292
+ error: null,
293
+ selectAddress: vi.fn(),
294
+ getAddressByPlaceId: vi.fn(),
295
+ clearSuggestions: vi.fn(),
296
+ });
297
+
298
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123" />);
299
+
300
+ const input = screen.getByRole('combobox');
301
+ await user.click(input);
302
+
303
+ await waitFor(() => {
304
+ expect(screen.getByTestId('address-suggestions')).toBeInTheDocument();
305
+ });
306
+
307
+ await user.keyboard('{Escape}');
308
+
309
+ await waitFor(() => {
310
+ expect(screen.queryByTestId('address-suggestions')).not.toBeInTheDocument();
311
+ });
312
+ });
313
+ });
314
+
315
+ describe('Address selection', () => {
316
+ it('calls onChange when address is selected', async () => {
317
+ const user = userEvent.setup();
318
+ const onChange = vi.fn();
319
+ const selectAddress = vi.fn().mockResolvedValue({
320
+ place_id: 'ChIJ123',
321
+ full_address: '123 Main St, Melbourne VIC 3000, Australia',
322
+ street_number: '123',
323
+ route: 'Main Street',
324
+ suburb: 'Melbourne',
325
+ state: 'VIC',
326
+ postcode: '3000',
327
+ country: 'AU',
328
+ lat: -37.8136,
329
+ lng: 144.9631,
330
+ });
331
+
332
+ const mockSuggestions = [
333
+ { description: '123 Main St', place_id: 'ChIJ123' },
334
+ ];
335
+
336
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
337
+ suggestions: mockSuggestions,
338
+ isLoading: false,
339
+ error: null,
340
+ selectAddress,
341
+ getAddressByPlaceId: vi.fn(),
342
+ clearSuggestions: vi.fn(),
343
+ });
344
+
345
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123" onChange={onChange} />);
346
+
347
+ // Focus the input to trigger suggestions display
348
+ const input = screen.getByRole('combobox');
349
+ await user.click(input);
350
+
351
+ await waitFor(
352
+ () => {
353
+ expect(screen.getByTestId('address-suggestions')).toBeInTheDocument();
354
+ },
355
+ { timeout: 1000 }
356
+ );
357
+
358
+ const suggestion = screen.getByTestId('address-suggestion-0');
359
+ await user.click(suggestion);
360
+
361
+ await waitFor(() => {
362
+ expect(selectAddress).toHaveBeenCalledWith('ChIJ123');
363
+ });
364
+
365
+ await waitFor(() => {
366
+ expect(onChange).toHaveBeenCalled();
367
+ });
368
+ });
369
+ });
370
+
371
+ describe('Accessibility', () => {
372
+ it('has proper ARIA attributes', () => {
373
+ renderWithProviders(<AddressField apiKey={mockApiKey} />);
374
+
375
+ const input = screen.getByRole('combobox');
376
+ expect(input).toHaveAttribute('aria-expanded', 'false');
377
+ expect(input).toHaveAttribute('aria-autocomplete', 'list');
378
+ expect(input).toHaveAttribute('aria-haspopup', 'listbox');
379
+ });
380
+
381
+ it('updates aria-expanded when suggestions are open', async () => {
382
+ const mockSuggestions = [
383
+ { description: '123 Main St', place_id: 'ChIJ123' },
384
+ ];
385
+
386
+ vi.mocked(useAddressAutocomplete).mockReturnValue({
387
+ suggestions: mockSuggestions,
388
+ isLoading: false,
389
+ error: null,
390
+ selectAddress: vi.fn(),
391
+ getAddressByPlaceId: vi.fn(),
392
+ clearSuggestions: vi.fn(),
393
+ });
394
+
395
+ renderWithProviders(<AddressField apiKey={mockApiKey} value="123" />);
396
+
397
+ // Focus the input to trigger suggestions display
398
+ const input = screen.getByRole('combobox');
399
+ await userEvent.click(input);
400
+
401
+ await waitFor(
402
+ () => {
403
+ const inputElement = screen.getByRole('combobox');
404
+ expect(inputElement).toHaveAttribute('aria-expanded', 'true');
405
+ },
406
+ { timeout: 1000 }
407
+ );
408
+ });
409
+ });
410
+ });
411
+