@jmruthers/pace-core 0.5.184 → 0.5.186
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +206 -15
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -13
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +746 -50
- package/docs/api-reference/components.md +26 -12
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/check-pace-core-compliance.js +512 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-MI7HBHN3.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/chunk-W22JP75J.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Timezone-Aware Date Formatting Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Formatting/__tests__
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for timezone-aware date formatting functions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
formatDateTimeForDisplay,
|
|
13
|
+
formatDateOnlyForDisplay,
|
|
14
|
+
formatDateTimeForTable,
|
|
15
|
+
formatDateTimeForMap,
|
|
16
|
+
type DateTimeFormatOptions
|
|
17
|
+
} from './formatting';
|
|
18
|
+
|
|
19
|
+
describe('Timezone-Aware Date Formatting', () => {
|
|
20
|
+
describe('formatDateTimeForDisplay', () => {
|
|
21
|
+
it('formats UTC date with timezone abbreviation', () => {
|
|
22
|
+
const result = formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York');
|
|
23
|
+
expect(result).toMatch(/Jan 15, 2024/);
|
|
24
|
+
expect(result).toMatch(/\(/); // Should include timezone abbreviation in parentheses
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('formats Date object with timezone', () => {
|
|
28
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
29
|
+
const result = formatDateTimeForDisplay(date, 'America/New_York');
|
|
30
|
+
expect(result).toMatch(/Jan 15, 2024/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('excludes timezone when includeTimezone is false', () => {
|
|
34
|
+
const options: DateTimeFormatOptions = { includeTimezone: false };
|
|
35
|
+
const result = formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York', options);
|
|
36
|
+
expect(result).toMatch(/Jan 15, 2024/);
|
|
37
|
+
expect(result).not.toMatch(/\(/); // Should not include parentheses
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('uses custom format string', () => {
|
|
41
|
+
const options: DateTimeFormatOptions = { format: 'yyyy-MM-dd HH:mm' };
|
|
42
|
+
const result = formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York', options);
|
|
43
|
+
expect(result).toMatch(/2024-01-15/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('returns empty string for undefined date', () => {
|
|
47
|
+
const result = formatDateTimeForDisplay(undefined, 'America/New_York');
|
|
48
|
+
expect(result).toBe('');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns empty string for undefined timezone', () => {
|
|
52
|
+
const result = formatDateTimeForDisplay('2024-01-15T10:00:00Z', undefined);
|
|
53
|
+
expect(result).toBe('');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns empty string for invalid date', () => {
|
|
57
|
+
const result = formatDateTimeForDisplay('invalid-date', 'America/New_York');
|
|
58
|
+
expect(result).toBe('');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('handles different timezones correctly', () => {
|
|
62
|
+
const date = '2024-01-15T10:00:00Z';
|
|
63
|
+
const ny = formatDateTimeForDisplay(date, 'America/New_York');
|
|
64
|
+
const la = formatDateTimeForDisplay(date, 'America/Los_Angeles');
|
|
65
|
+
expect(ny).not.toBe(la);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('formatDateOnlyForDisplay', () => {
|
|
70
|
+
it('formats date only without time', () => {
|
|
71
|
+
const result = formatDateOnlyForDisplay('2024-01-15T10:00:00Z');
|
|
72
|
+
expect(result).toMatch(/January/);
|
|
73
|
+
expect(result).toMatch(/2024/);
|
|
74
|
+
expect(result).toMatch(/15/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('formats Date object', () => {
|
|
78
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
79
|
+
const result = formatDateOnlyForDisplay(date);
|
|
80
|
+
expect(result).toMatch(/January/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('returns empty string for undefined', () => {
|
|
84
|
+
const result = formatDateOnlyForDisplay(undefined);
|
|
85
|
+
expect(result).toBe('');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns empty string for invalid date', () => {
|
|
89
|
+
const result = formatDateOnlyForDisplay('invalid-date');
|
|
90
|
+
expect(result).toBe('');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('formats different dates correctly', () => {
|
|
94
|
+
const dates = [
|
|
95
|
+
'2024-01-15T10:00:00Z',
|
|
96
|
+
'2024-06-15T12:00:00Z',
|
|
97
|
+
'2024-12-31T23:59:59Z'
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
dates.forEach(date => {
|
|
101
|
+
const result = formatDateOnlyForDisplay(date);
|
|
102
|
+
expect(result).toBeTruthy();
|
|
103
|
+
expect(result.length).toBeGreaterThan(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('formatDateTimeForTable', () => {
|
|
109
|
+
it('formats date for table display with timezone', () => {
|
|
110
|
+
const result = formatDateTimeForTable('2024-01-15T10:00:00Z', 'America/New_York');
|
|
111
|
+
expect(result).toMatch(/Jan 15, 2024/);
|
|
112
|
+
expect(result).toMatch(/\(/); // Should include timezone
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('returns empty string for undefined date', () => {
|
|
116
|
+
const result = formatDateTimeForTable(undefined, 'America/New_York');
|
|
117
|
+
expect(result).toBe('');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('returns empty string for undefined timezone', () => {
|
|
121
|
+
const result = formatDateTimeForTable('2024-01-15T10:00:00Z', undefined);
|
|
122
|
+
expect(result).toBe('');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('formats Date object', () => {
|
|
126
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
127
|
+
const result = formatDateTimeForTable(date, 'America/New_York');
|
|
128
|
+
expect(result).toMatch(/Jan 15, 2024/);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('formatDateTimeForMap', () => {
|
|
133
|
+
it('formats date for map display', () => {
|
|
134
|
+
const result = formatDateTimeForMap('2024-01-15T10:00:00Z', 'America/New_York');
|
|
135
|
+
expect(result).toMatch(/Jan 15/);
|
|
136
|
+
expect(result).toMatch(/\d{2}:\d{2}/); // Time format
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('includes timezone abbreviation', () => {
|
|
140
|
+
const result = formatDateTimeForMap('2024-01-15T10:00:00Z', 'America/New_York');
|
|
141
|
+
expect(result.length).toBeGreaterThan(0);
|
|
142
|
+
// Should include timezone info (not in parentheses for map format)
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns empty string for undefined date', () => {
|
|
146
|
+
const result = formatDateTimeForMap(undefined, 'America/New_York');
|
|
147
|
+
expect(result).toBe('');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('returns empty string for undefined timezone', () => {
|
|
151
|
+
const result = formatDateTimeForMap('2024-01-15T10:00:00Z', undefined);
|
|
152
|
+
expect(result).toBe('');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('formats Date object', () => {
|
|
156
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
157
|
+
const result = formatDateTimeForMap(date, 'America/New_York');
|
|
158
|
+
expect(result).toMatch(/Jan 15/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('returns empty string for invalid date', () => {
|
|
162
|
+
const result = formatDateTimeForMap('invalid-date', 'America/New_York');
|
|
163
|
+
expect(result).toBe('');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Utility functions for formatting data in the application
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { parseISO, isValid } from 'date-fns';
|
|
6
|
+
import { formatInTimeZone, getTimezoneAbbreviation } from '../timezone';
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Format a date as a readable string in "dd mmm yyyy" format (e.g., "15 Jun 2024")
|
|
7
10
|
*/
|
|
@@ -168,3 +171,179 @@ export function formatFileSize(bytes: number): string {
|
|
|
168
171
|
|
|
169
172
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
170
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Options for formatting date/time with timezone
|
|
177
|
+
*/
|
|
178
|
+
export interface DateTimeFormatOptions {
|
|
179
|
+
/**
|
|
180
|
+
* Include timezone abbreviation (default: true)
|
|
181
|
+
*/
|
|
182
|
+
includeTimezone?: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Custom format string (default: 'MMM dd, yyyy HH:mm')
|
|
185
|
+
*/
|
|
186
|
+
format?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Format a UTC date for display in a specific timezone
|
|
191
|
+
*
|
|
192
|
+
* @param utcDate - UTC date (ISO string, Date object, or undefined)
|
|
193
|
+
* @param timezone - IANA timezone string (e.g., 'America/New_York')
|
|
194
|
+
* @param options - Formatting options
|
|
195
|
+
* @returns Formatted date string or empty string if invalid
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York');
|
|
200
|
+
* // "Jan 15, 2024 05:00 (EST)"
|
|
201
|
+
*
|
|
202
|
+
* formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York', { includeTimezone: false });
|
|
203
|
+
* // "Jan 15, 2024 05:00"
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export function formatDateTimeForDisplay(
|
|
207
|
+
utcDate: string | Date | undefined,
|
|
208
|
+
timezone: string | undefined,
|
|
209
|
+
options: DateTimeFormatOptions = {}
|
|
210
|
+
): string {
|
|
211
|
+
if (!utcDate) {
|
|
212
|
+
return '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!timezone) {
|
|
216
|
+
return '';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const { includeTimezone = true, format: formatStr = 'MMM dd, yyyy HH:mm' } = options;
|
|
221
|
+
|
|
222
|
+
let dateObj: Date;
|
|
223
|
+
if (typeof utcDate === 'string') {
|
|
224
|
+
dateObj = parseISO(utcDate);
|
|
225
|
+
} else {
|
|
226
|
+
dateObj = utcDate;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!isValid(dateObj)) {
|
|
230
|
+
return '';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const formatted = formatInTimeZone(dateObj, timezone, formatStr);
|
|
234
|
+
|
|
235
|
+
if (includeTimezone) {
|
|
236
|
+
const tzAbbr = getTimezoneAbbreviation(dateObj, timezone);
|
|
237
|
+
return `${formatted} (${tzAbbr})`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return formatted;
|
|
241
|
+
} catch {
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Format a UTC date for display (date only, no time)
|
|
248
|
+
*
|
|
249
|
+
* @param utcDate - UTC date (ISO string, Date object, or undefined)
|
|
250
|
+
* @returns Formatted date string or empty string if invalid
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* formatDateOnlyForDisplay('2024-01-15T10:00:00Z');
|
|
255
|
+
* // "15 January 2024"
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export function formatDateOnlyForDisplay(utcDate: string | Date | undefined): string {
|
|
259
|
+
if (!utcDate) {
|
|
260
|
+
return '';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
let dateObj: Date;
|
|
265
|
+
if (typeof utcDate === 'string') {
|
|
266
|
+
dateObj = parseISO(utcDate);
|
|
267
|
+
} else {
|
|
268
|
+
dateObj = utcDate;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!isValid(dateObj)) {
|
|
272
|
+
return '';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Use 'en-GB' locale for "dd mmm yyyy" format
|
|
276
|
+
return dateObj.toLocaleDateString('en-GB', {
|
|
277
|
+
year: 'numeric',
|
|
278
|
+
month: 'long',
|
|
279
|
+
day: 'numeric'
|
|
280
|
+
});
|
|
281
|
+
} catch {
|
|
282
|
+
return '';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Format a UTC date for table display (compact format with timezone)
|
|
288
|
+
*
|
|
289
|
+
* @param utcDate - UTC date (ISO string, Date object, or undefined)
|
|
290
|
+
* @param timezone - IANA timezone string
|
|
291
|
+
* @returns Formatted date string or empty string if invalid
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* formatDateTimeForTable('2024-01-15T10:00:00Z', 'America/New_York');
|
|
296
|
+
* // "Jan 15, 2024 05:00 (EST)"
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export function formatDateTimeForTable(
|
|
300
|
+
utcDate: string | Date | undefined,
|
|
301
|
+
timezone: string | undefined
|
|
302
|
+
): string {
|
|
303
|
+
return formatDateTimeForDisplay(utcDate, timezone, {
|
|
304
|
+
includeTimezone: true,
|
|
305
|
+
format: 'MMM dd, yyyy HH:mm'
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Format a UTC date for map display (compact format)
|
|
311
|
+
*
|
|
312
|
+
* @param utcDate - UTC date (ISO string, Date object, or undefined)
|
|
313
|
+
* @param timezone - IANA timezone string
|
|
314
|
+
* @returns Formatted date string or empty string if invalid
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* formatDateTimeForMap('2024-01-15T10:00:00Z', 'America/New_York');
|
|
319
|
+
* // "Jan 15, 05:00 EST"
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export function formatDateTimeForMap(
|
|
323
|
+
utcDate: string | Date | undefined,
|
|
324
|
+
timezone: string | undefined
|
|
325
|
+
): string {
|
|
326
|
+
if (!utcDate || !timezone) {
|
|
327
|
+
return '';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
let dateObj: Date;
|
|
332
|
+
if (typeof utcDate === 'string') {
|
|
333
|
+
dateObj = parseISO(utcDate);
|
|
334
|
+
} else {
|
|
335
|
+
dateObj = utcDate;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!isValid(dateObj)) {
|
|
339
|
+
return '';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const formatted = formatInTimeZone(dateObj, timezone, 'MMM dd, HH:mm');
|
|
343
|
+
const tzAbbr = getTimezoneAbbreviation(dateObj, timezone);
|
|
344
|
+
|
|
345
|
+
return `${formatted} ${tzAbbr}`;
|
|
346
|
+
} catch {
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -120,8 +120,13 @@ export {
|
|
|
120
120
|
formatNumber,
|
|
121
121
|
formatPercent,
|
|
122
122
|
formatCompactNumber,
|
|
123
|
-
formatFileSize
|
|
123
|
+
formatFileSize,
|
|
124
|
+
formatDateTimeForDisplay,
|
|
125
|
+
formatDateOnlyForDisplay,
|
|
126
|
+
formatDateTimeForTable,
|
|
127
|
+
formatDateTimeForMap
|
|
124
128
|
} from './formatting/formatting';
|
|
129
|
+
export type { DateTimeFormatOptions } from './formatting/formatting';
|
|
125
130
|
|
|
126
131
|
// Organisation context utilities
|
|
127
132
|
export {
|
|
@@ -130,3 +135,24 @@ export {
|
|
|
130
135
|
getOrganisationContext,
|
|
131
136
|
isOrganisationContextAvailable
|
|
132
137
|
} from './context/organisationContext';
|
|
138
|
+
|
|
139
|
+
// Timezone utilities
|
|
140
|
+
export {
|
|
141
|
+
formatInTimeZone,
|
|
142
|
+
getTimezoneAbbreviation,
|
|
143
|
+
formatTimeInTimeZone,
|
|
144
|
+
getUserTimeZone,
|
|
145
|
+
toZonedTime,
|
|
146
|
+
fromZonedTime,
|
|
147
|
+
roundToNearestMinutes,
|
|
148
|
+
getTimeZoneDifference
|
|
149
|
+
} from './timezone';
|
|
150
|
+
|
|
151
|
+
// Location utilities
|
|
152
|
+
export {
|
|
153
|
+
formatCoordinates,
|
|
154
|
+
hasValidCoordinates,
|
|
155
|
+
areCoordinatesEqual,
|
|
156
|
+
getGoogleMapsUrl
|
|
157
|
+
} from './location';
|
|
158
|
+
export type { Coordinates } from './location';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Location Utilities Exports
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Location
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
formatCoordinates,
|
|
10
|
+
hasValidCoordinates,
|
|
11
|
+
areCoordinatesEqual,
|
|
12
|
+
getGoogleMapsUrl
|
|
13
|
+
} from './location';
|
|
14
|
+
|
|
15
|
+
export type { Coordinates } from './location';
|
|
16
|
+
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Location Utilities Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Location/__tests__
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for location utility functions.
|
|
8
|
+
* Tests cover all major functionality, edge cases, and error handling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import {
|
|
13
|
+
formatCoordinates,
|
|
14
|
+
hasValidCoordinates,
|
|
15
|
+
areCoordinatesEqual,
|
|
16
|
+
getGoogleMapsUrl,
|
|
17
|
+
type Coordinates
|
|
18
|
+
} from './location';
|
|
19
|
+
|
|
20
|
+
describe('Location Utilities', () => {
|
|
21
|
+
describe('formatCoordinates', () => {
|
|
22
|
+
it('formats valid coordinates with 6 decimal places', () => {
|
|
23
|
+
const coords: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
24
|
+
const result = formatCoordinates(coords);
|
|
25
|
+
expect(result).toBe('-37.813600, 144.963100');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('formats coordinates with fewer decimal places', () => {
|
|
29
|
+
const coords: Coordinates = { lat: 0, lng: 0 };
|
|
30
|
+
const result = formatCoordinates(coords);
|
|
31
|
+
expect(result).toBe('0.000000, 0.000000');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('formats coordinates with many decimal places', () => {
|
|
35
|
+
const coords: Coordinates = { lat: -37.813612345, lng: 144.963198765 };
|
|
36
|
+
const result = formatCoordinates(coords);
|
|
37
|
+
expect(result).toBe('-37.813612, 144.963199');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns "N/A" for undefined', () => {
|
|
41
|
+
const result = formatCoordinates(undefined);
|
|
42
|
+
expect(result).toBe('N/A');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('returns "N/A" for null', () => {
|
|
46
|
+
// @ts-expect-error - Testing null input
|
|
47
|
+
const result = formatCoordinates(null);
|
|
48
|
+
expect(result).toBe('N/A');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns "N/A" for missing lat', () => {
|
|
52
|
+
// @ts-expect-error - Testing invalid input
|
|
53
|
+
const result = formatCoordinates({ lng: 144.9631 });
|
|
54
|
+
expect(result).toBe('N/A');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns "N/A" for missing lng', () => {
|
|
58
|
+
// @ts-expect-error - Testing invalid input
|
|
59
|
+
const result = formatCoordinates({ lat: -37.8136 });
|
|
60
|
+
expect(result).toBe('N/A');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns "N/A" for NaN values', () => {
|
|
64
|
+
const coords: Coordinates = { lat: NaN, lng: 144.9631 };
|
|
65
|
+
const result = formatCoordinates(coords);
|
|
66
|
+
expect(result).toBe('N/A');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns "N/A" for Infinity values', () => {
|
|
70
|
+
const coords: Coordinates = { lat: Infinity, lng: 144.9631 };
|
|
71
|
+
const result = formatCoordinates(coords);
|
|
72
|
+
expect(result).toBe('N/A');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns "N/A" for non-number lat', () => {
|
|
76
|
+
// @ts-expect-error - Testing invalid input
|
|
77
|
+
const result = formatCoordinates({ lat: 'invalid', lng: 144.9631 });
|
|
78
|
+
expect(result).toBe('N/A');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns "N/A" for non-number lng', () => {
|
|
82
|
+
// @ts-expect-error - Testing invalid input
|
|
83
|
+
const result = formatCoordinates({ lat: -37.8136, lng: 'invalid' });
|
|
84
|
+
expect(result).toBe('N/A');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('hasValidCoordinates', () => {
|
|
89
|
+
it('returns true for valid coordinates', () => {
|
|
90
|
+
const coords: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
91
|
+
expect(hasValidCoordinates(coords)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns true for coordinates at boundaries', () => {
|
|
95
|
+
expect(hasValidCoordinates({ lat: -90, lng: -180 })).toBe(true);
|
|
96
|
+
expect(hasValidCoordinates({ lat: 90, lng: 180 })).toBe(true);
|
|
97
|
+
expect(hasValidCoordinates({ lat: 0, lng: 0 })).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('returns false for lat out of range (too low)', () => {
|
|
101
|
+
expect(hasValidCoordinates({ lat: -91, lng: 0 })).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns false for lat out of range (too high)', () => {
|
|
105
|
+
expect(hasValidCoordinates({ lat: 91, lng: 0 })).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('returns false for lng out of range (too low)', () => {
|
|
109
|
+
expect(hasValidCoordinates({ lat: 0, lng: -181 })).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('returns false for lng out of range (too high)', () => {
|
|
113
|
+
expect(hasValidCoordinates({ lat: 0, lng: 181 })).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('returns false for undefined', () => {
|
|
117
|
+
expect(hasValidCoordinates(undefined)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('returns false for null', () => {
|
|
121
|
+
// @ts-expect-error - Testing null input
|
|
122
|
+
expect(hasValidCoordinates(null)).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('returns false for missing lat', () => {
|
|
126
|
+
expect(hasValidCoordinates({ lng: 144.9631 })).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('returns false for missing lng', () => {
|
|
130
|
+
expect(hasValidCoordinates({ lat: -37.8136 })).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('returns false for NaN lat', () => {
|
|
134
|
+
expect(hasValidCoordinates({ lat: NaN, lng: 144.9631 })).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('returns false for NaN lng', () => {
|
|
138
|
+
expect(hasValidCoordinates({ lat: -37.8136, lng: NaN })).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns false for Infinity lat', () => {
|
|
142
|
+
expect(hasValidCoordinates({ lat: Infinity, lng: 144.9631 })).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('returns false for Infinity lng', () => {
|
|
146
|
+
expect(hasValidCoordinates({ lat: -37.8136, lng: Infinity })).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('returns false for non-number lat', () => {
|
|
150
|
+
// @ts-expect-error - Testing invalid input
|
|
151
|
+
expect(hasValidCoordinates({ lat: 'invalid', lng: 144.9631 })).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('returns false for non-number lng', () => {
|
|
155
|
+
// @ts-expect-error - Testing invalid input
|
|
156
|
+
expect(hasValidCoordinates({ lat: -37.8136, lng: 'invalid' })).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('areCoordinatesEqual', () => {
|
|
161
|
+
it('returns true for identical coordinates', () => {
|
|
162
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
163
|
+
const coords2: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
164
|
+
expect(areCoordinatesEqual(coords1, coords2)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns true for coordinates within default tolerance', () => {
|
|
168
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
169
|
+
const coords2: Coordinates = { lat: -37.8137, lng: 144.9632 };
|
|
170
|
+
expect(areCoordinatesEqual(coords1, coords2)).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('returns false for coordinates outside default tolerance', () => {
|
|
174
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
175
|
+
const coords2: Coordinates = { lat: -37.8150, lng: 144.9650 };
|
|
176
|
+
expect(areCoordinatesEqual(coords1, coords2)).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('returns true for coordinates within custom tolerance', () => {
|
|
180
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
181
|
+
const coords2: Coordinates = { lat: -37.8137, lng: 144.9632 };
|
|
182
|
+
expect(areCoordinatesEqual(coords1, coords2, 0.001)).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('returns false for coordinates outside custom tolerance', () => {
|
|
186
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
187
|
+
const coords2: Coordinates = { lat: -37.8137, lng: 144.9632 };
|
|
188
|
+
expect(areCoordinatesEqual(coords1, coords2, 0.00001)).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('returns true when both are null', () => {
|
|
192
|
+
expect(areCoordinatesEqual(null, null)).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('returns true when both are undefined', () => {
|
|
196
|
+
expect(areCoordinatesEqual(undefined, undefined)).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('returns false when one is null and other is valid', () => {
|
|
200
|
+
const coords: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
201
|
+
expect(areCoordinatesEqual(null, coords)).toBe(false);
|
|
202
|
+
expect(areCoordinatesEqual(coords, null)).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('returns false when one is undefined and other is valid', () => {
|
|
206
|
+
const coords: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
207
|
+
expect(areCoordinatesEqual(undefined, coords)).toBe(false);
|
|
208
|
+
expect(areCoordinatesEqual(coords, undefined)).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('returns false for invalid coordinates', () => {
|
|
212
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
213
|
+
// @ts-expect-error - Testing invalid input
|
|
214
|
+
const coords2 = { lat: 91, lng: 0 };
|
|
215
|
+
expect(areCoordinatesEqual(coords1, coords2)).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('handles edge case: exactly at tolerance boundary', () => {
|
|
219
|
+
const coords1: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
220
|
+
const coords2: Coordinates = { lat: -37.8136 + 0.0001, lng: 144.9631 };
|
|
221
|
+
expect(areCoordinatesEqual(coords1, coords2, 0.0001)).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('getGoogleMapsUrl', () => {
|
|
226
|
+
it('generates URL for valid coordinates', () => {
|
|
227
|
+
const coords: Coordinates = { lat: -37.8136, lng: 144.9631 };
|
|
228
|
+
const result = getGoogleMapsUrl(coords);
|
|
229
|
+
expect(result).toBe('https://www.google.com/maps/search/?api=1&query=-37.8136,144.9631');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('generates URL with negative coordinates', () => {
|
|
233
|
+
const coords: Coordinates = { lat: -90, lng: -180 };
|
|
234
|
+
const result = getGoogleMapsUrl(coords);
|
|
235
|
+
expect(result).toBe('https://www.google.com/maps/search/?api=1&query=-90,-180');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('generates URL with positive coordinates', () => {
|
|
239
|
+
const coords: Coordinates = { lat: 90, lng: 180 };
|
|
240
|
+
const result = getGoogleMapsUrl(coords);
|
|
241
|
+
expect(result).toBe('https://www.google.com/maps/search/?api=1&query=90,180');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('returns empty string for undefined', () => {
|
|
245
|
+
const result = getGoogleMapsUrl(undefined);
|
|
246
|
+
expect(result).toBe('');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('returns empty string for null', () => {
|
|
250
|
+
// @ts-expect-error - Testing null input
|
|
251
|
+
const result = getGoogleMapsUrl(null);
|
|
252
|
+
expect(result).toBe('');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('returns empty string for invalid coordinates', () => {
|
|
256
|
+
// @ts-expect-error - Testing invalid input
|
|
257
|
+
const result = getGoogleMapsUrl({ lat: 91, lng: 0 });
|
|
258
|
+
expect(result).toBe('');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('returns empty string for missing lat', () => {
|
|
262
|
+
// @ts-expect-error - Testing invalid input
|
|
263
|
+
const result = getGoogleMapsUrl({ lng: 144.9631 });
|
|
264
|
+
expect(result).toBe('');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('returns empty string for missing lng', () => {
|
|
268
|
+
// @ts-expect-error - Testing invalid input
|
|
269
|
+
const result = getGoogleMapsUrl({ lat: -37.8136 });
|
|
270
|
+
expect(result).toBe('');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('returns empty string for NaN values', () => {
|
|
274
|
+
const coords: Coordinates = { lat: NaN, lng: 144.9631 };
|
|
275
|
+
const result = getGoogleMapsUrl(coords);
|
|
276
|
+
expect(result).toBe('');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('returns empty string for Infinity values', () => {
|
|
280
|
+
const coords: Coordinates = { lat: Infinity, lng: 144.9631 };
|
|
281
|
+
const result = getGoogleMapsUrl(coords);
|
|
282
|
+
expect(result).toBe('');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|