From 085da57670b3efbc768f9b7ab84c865b3a1cf81c Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Sat, 7 Feb 2026 20:52:18 -0800 Subject: [PATCH 1/3] feat(edge-apps-library): add optional latitude and longitude parameters to getTimeZone Allow callers to provide custom coordinates to getTimeZone() instead of always relying on metadata coordinates. This provides flexibility for testing and use cases where coordinates are known in advance. Co-Authored-By: Claude Sonnet 4.5 --- edge-apps/edge-apps-library/src/utils/locale.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/edge-apps/edge-apps-library/src/utils/locale.ts b/edge-apps/edge-apps-library/src/utils/locale.ts index 4cb034ac2..e23882e05 100644 --- a/edge-apps/edge-apps-library/src/utils/locale.ts +++ b/edge-apps/edge-apps-library/src/utils/locale.ts @@ -65,8 +65,14 @@ function isValidLocale(locale: string): boolean { /** * Resolve timezone configuration with fallback chain * Fallback order: override setting (validated) → GPS-based detection → 'UTC' + * + * @param latitude - Optional latitude coordinate (overrides metadata coordinates) + * @param longitude - Optional longitude coordinate (overrides metadata coordinates) */ -export async function getTimeZone(): Promise { +export async function getTimeZone( + latitude?: number, + longitude?: number, +): Promise { // Priority 1: Use override setting if provided and valid const overrideTimezone = getSettingWithDefault( 'override_timezone', @@ -83,8 +89,12 @@ export async function getTimeZone(): Promise { } try { - const [latitude, longitude] = getMetadata().coordinates - return tzlookup(latitude, longitude) + // Use provided coordinates or fall back to metadata coordinates + const [lat, lng] = + latitude !== undefined && longitude !== undefined + ? [latitude, longitude] + : getMetadata().coordinates + return tzlookup(lat, lng) } catch (error) { console.warn('Failed to get timezone from coordinates, using UTC:', error) return 'UTC' From bdc9d123f236f3d23b26adad643f9e732d684afd Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Sat, 7 Feb 2026 21:10:11 -0800 Subject: [PATCH 2/3] test(edge-apps-library): add tests for getTimeZone optional parameters and refactor - Add tests for new optional latitude/longitude parameters - Extract repeated coordinate constants to reduce duplication - Create helper function for coordinate-to-timezone tests - Simplify test structure using coordinate constants throughout Co-Authored-By: Claude Sonnet 4.5 --- .../src/utils/locale.test.ts | 148 ++++++++---------- 1 file changed, 66 insertions(+), 82 deletions(-) diff --git a/edge-apps/edge-apps-library/src/utils/locale.test.ts b/edge-apps/edge-apps-library/src/utils/locale.test.ts index f6f1fb72d..634cbc24c 100644 --- a/edge-apps/edge-apps-library/src/utils/locale.test.ts +++ b/edge-apps/edge-apps-library/src/utils/locale.test.ts @@ -2,6 +2,23 @@ import { describe, test, expect, beforeEach, afterEach } from 'bun:test' import { getTimeZone, formatCoordinates, getLocale } from './locale' import { setupScreenlyMock, resetScreenlyMock } from '../test/mock' +// Test coordinates +const COORDS = { + LA: [37.3861, -122.0839] as [number, number], + LONDON: [51.5074, -0.1278] as [number, number], + TOKYO: [35.6762, 139.6503] as [number, number], +} + +// Helper to test coordinate-to-timezone mapping +async function testCoordinateTimezone( + coords: [number, number], + expectedTimezone: string, +) { + setupScreenlyMock({ coordinates: coords }) + const timezone = await getTimeZone() + expect(timezone).toBe(expectedTimezone) +} + // eslint-disable-next-line max-lines-per-function describe('locale utilities', () => { beforeEach(() => { @@ -13,41 +30,19 @@ describe('locale utilities', () => { }) describe('getTimeZone', () => { - test('should return timezone for coordinates', async () => { - setupScreenlyMock({ - coordinates: [37.3861, -122.0839], // Mountain View, CA - }) - - const timezone = await getTimeZone() - expect(timezone).toBe('America/Los_Angeles') - }) + test('should return timezone for coordinates', () => + testCoordinateTimezone(COORDS.LA, 'America/Los_Angeles')) - test('should return timezone for London coordinates', async () => { - setupScreenlyMock({ - coordinates: [51.5074, -0.1278], // London, UK - }) + test('should return timezone for London coordinates', () => + testCoordinateTimezone(COORDS.LONDON, 'Europe/London')) - const timezone = await getTimeZone() - expect(timezone).toBe('Europe/London') - }) - - test('should return timezone for Tokyo coordinates', async () => { - setupScreenlyMock({ - coordinates: [35.6762, 139.6503], // Tokyo, Japan - }) - - const timezone = await getTimeZone() - expect(timezone).toBe('Asia/Tokyo') - }) + test('should return timezone for Tokyo coordinates', () => + testCoordinateTimezone(COORDS.TOKYO, 'Asia/Tokyo')) test('should use valid override_timezone setting', async () => { setupScreenlyMock( - { - coordinates: [37.3861, -122.0839], - }, - { - override_timezone: 'Europe/Paris', - }, + { coordinates: COORDS.LA }, + { override_timezone: 'Europe/Paris' }, ) const timezone = await getTimeZone() @@ -56,12 +51,8 @@ describe('locale utilities', () => { test('should fallback to GPS detection for invalid override_timezone', async () => { setupScreenlyMock( - { - coordinates: [51.5074, -0.1278], - }, - { - override_timezone: 'Invalid/Timezone', - }, + { coordinates: COORDS.LONDON }, + { override_timezone: 'Invalid/Timezone' }, ) const timezone = await getTimeZone() @@ -69,18 +60,40 @@ describe('locale utilities', () => { }) test('should fallback to UTC when coordinates are missing', async () => { - setupScreenlyMock({ - coordinates: undefined, - }) + setupScreenlyMock({ coordinates: undefined }) const timezone = await getTimeZone() expect(timezone).toBe('UTC') }) + + test('should use provided coordinates instead of metadata', async () => { + setupScreenlyMock({ coordinates: COORDS.LA }) + + const timezone = await getTimeZone(...COORDS.TOKYO) + expect(timezone).toBe('Asia/Tokyo') + }) + + test('should respect override_timezone even with provided coordinates', async () => { + setupScreenlyMock( + { coordinates: COORDS.LA }, + { override_timezone: 'Europe/Paris' }, + ) + + const timezone = await getTimeZone(...COORDS.TOKYO) + expect(timezone).toBe('Europe/Paris') + }) + + test('should use metadata when only one coordinate provided', async () => { + setupScreenlyMock({ coordinates: COORDS.LONDON }) + + const timezone = await getTimeZone(COORDS.TOKYO[0], undefined) + expect(timezone).toBe('Europe/London') + }) }) describe('formatCoordinates', () => { test('should format positive coordinates correctly', () => { - const formatted = formatCoordinates([37.3861, -122.0839]) + const formatted = formatCoordinates(COORDS.LA) expect(formatted).toBe('37.3861° N, 122.0839° W') }) @@ -90,7 +103,7 @@ describe('locale utilities', () => { }) test('should format coordinates with proper precision', () => { - const formatted = formatCoordinates([51.5074, -0.1278]) // London + const formatted = formatCoordinates(COORDS.LONDON) expect(formatted).toBe('51.5074° N, 0.1278° W') }) @@ -102,16 +115,11 @@ describe('locale utilities', () => { }) }) - // eslint-disable-next-line max-lines-per-function describe('getLocale', () => { test('should normalize single underscore in override_locale', async () => { setupScreenlyMock( - { - coordinates: [37.3861, -122.0839], - }, - { - override_locale: 'en_US', - }, + { coordinates: COORDS.LA }, + { override_locale: 'en_US' }, ) const locale = await getLocale() @@ -120,12 +128,8 @@ describe('locale utilities', () => { test('should normalize multiple underscores in override_locale', async () => { setupScreenlyMock( - { - coordinates: [37.3861, -122.0839], - }, - { - override_locale: 'de_DE', - }, + { coordinates: COORDS.LA }, + { override_locale: 'de_DE' }, ) const locale = await getLocale() @@ -134,61 +138,41 @@ describe('locale utilities', () => { test('should fallback to GPS detection for invalid override_locale', async () => { setupScreenlyMock( - { - coordinates: [35.6762, 139.6503], // Tokyo, Japan - }, - { - override_locale: 'invalid_locale_xyz', - }, + { coordinates: COORDS.TOKYO }, + { override_locale: 'invalid_locale_xyz' }, ) const locale = await getLocale() - // Should fallback to GPS-based locale detection (ja for Japan) expect(locale.startsWith('ja')).toBe(true) }) test('should reject malformed locale with trailing hyphen', async () => { setupScreenlyMock( - { - coordinates: [35.6762, 139.6503], - }, - { - override_locale: 'en-', - }, + { coordinates: COORDS.TOKYO }, + { override_locale: 'en-' }, ) const locale = await getLocale() - // Should fallback to GPS-based locale detection expect(locale.startsWith('ja')).toBe(true) }) test('should reject malformed locale where region code is invalid', async () => { setupScreenlyMock( - { - coordinates: [35.6762, 139.6503], - }, - { - override_locale: 'en-INVALID', - }, + { coordinates: COORDS.TOKYO }, + { override_locale: 'en-INVALID' }, ) const locale = await getLocale() - // Should fallback to GPS-based locale detection (not use 'en') expect(locale.startsWith('ja')).toBe(true) }) test('should reject locale with underscores and invalid parts', async () => { setupScreenlyMock( - { - coordinates: [35.6762, 139.6503], - }, - { - override_locale: 'en_US_INVALID_EXTRA', - }, + { coordinates: COORDS.TOKYO }, + { override_locale: 'en_US_INVALID_EXTRA' }, ) const locale = await getLocale() - // Should fallback to GPS-based locale detection expect(locale.startsWith('ja')).toBe(true) }) }) From 18dc29c5f5e3d4e8c1dc5fe0860ff4f5e22ac82d Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Sat, 7 Feb 2026 21:13:59 -0800 Subject: [PATCH 3/3] test(edge-apps-library): revert changes to locale test cases --- .../src/utils/locale.test.ts | 148 ++++++++++-------- 1 file changed, 82 insertions(+), 66 deletions(-) diff --git a/edge-apps/edge-apps-library/src/utils/locale.test.ts b/edge-apps/edge-apps-library/src/utils/locale.test.ts index 634cbc24c..f6f1fb72d 100644 --- a/edge-apps/edge-apps-library/src/utils/locale.test.ts +++ b/edge-apps/edge-apps-library/src/utils/locale.test.ts @@ -2,23 +2,6 @@ import { describe, test, expect, beforeEach, afterEach } from 'bun:test' import { getTimeZone, formatCoordinates, getLocale } from './locale' import { setupScreenlyMock, resetScreenlyMock } from '../test/mock' -// Test coordinates -const COORDS = { - LA: [37.3861, -122.0839] as [number, number], - LONDON: [51.5074, -0.1278] as [number, number], - TOKYO: [35.6762, 139.6503] as [number, number], -} - -// Helper to test coordinate-to-timezone mapping -async function testCoordinateTimezone( - coords: [number, number], - expectedTimezone: string, -) { - setupScreenlyMock({ coordinates: coords }) - const timezone = await getTimeZone() - expect(timezone).toBe(expectedTimezone) -} - // eslint-disable-next-line max-lines-per-function describe('locale utilities', () => { beforeEach(() => { @@ -30,19 +13,41 @@ describe('locale utilities', () => { }) describe('getTimeZone', () => { - test('should return timezone for coordinates', () => - testCoordinateTimezone(COORDS.LA, 'America/Los_Angeles')) + test('should return timezone for coordinates', async () => { + setupScreenlyMock({ + coordinates: [37.3861, -122.0839], // Mountain View, CA + }) + + const timezone = await getTimeZone() + expect(timezone).toBe('America/Los_Angeles') + }) - test('should return timezone for London coordinates', () => - testCoordinateTimezone(COORDS.LONDON, 'Europe/London')) + test('should return timezone for London coordinates', async () => { + setupScreenlyMock({ + coordinates: [51.5074, -0.1278], // London, UK + }) - test('should return timezone for Tokyo coordinates', () => - testCoordinateTimezone(COORDS.TOKYO, 'Asia/Tokyo')) + const timezone = await getTimeZone() + expect(timezone).toBe('Europe/London') + }) + + test('should return timezone for Tokyo coordinates', async () => { + setupScreenlyMock({ + coordinates: [35.6762, 139.6503], // Tokyo, Japan + }) + + const timezone = await getTimeZone() + expect(timezone).toBe('Asia/Tokyo') + }) test('should use valid override_timezone setting', async () => { setupScreenlyMock( - { coordinates: COORDS.LA }, - { override_timezone: 'Europe/Paris' }, + { + coordinates: [37.3861, -122.0839], + }, + { + override_timezone: 'Europe/Paris', + }, ) const timezone = await getTimeZone() @@ -51,8 +56,12 @@ describe('locale utilities', () => { test('should fallback to GPS detection for invalid override_timezone', async () => { setupScreenlyMock( - { coordinates: COORDS.LONDON }, - { override_timezone: 'Invalid/Timezone' }, + { + coordinates: [51.5074, -0.1278], + }, + { + override_timezone: 'Invalid/Timezone', + }, ) const timezone = await getTimeZone() @@ -60,40 +69,18 @@ describe('locale utilities', () => { }) test('should fallback to UTC when coordinates are missing', async () => { - setupScreenlyMock({ coordinates: undefined }) + setupScreenlyMock({ + coordinates: undefined, + }) const timezone = await getTimeZone() expect(timezone).toBe('UTC') }) - - test('should use provided coordinates instead of metadata', async () => { - setupScreenlyMock({ coordinates: COORDS.LA }) - - const timezone = await getTimeZone(...COORDS.TOKYO) - expect(timezone).toBe('Asia/Tokyo') - }) - - test('should respect override_timezone even with provided coordinates', async () => { - setupScreenlyMock( - { coordinates: COORDS.LA }, - { override_timezone: 'Europe/Paris' }, - ) - - const timezone = await getTimeZone(...COORDS.TOKYO) - expect(timezone).toBe('Europe/Paris') - }) - - test('should use metadata when only one coordinate provided', async () => { - setupScreenlyMock({ coordinates: COORDS.LONDON }) - - const timezone = await getTimeZone(COORDS.TOKYO[0], undefined) - expect(timezone).toBe('Europe/London') - }) }) describe('formatCoordinates', () => { test('should format positive coordinates correctly', () => { - const formatted = formatCoordinates(COORDS.LA) + const formatted = formatCoordinates([37.3861, -122.0839]) expect(formatted).toBe('37.3861° N, 122.0839° W') }) @@ -103,7 +90,7 @@ describe('locale utilities', () => { }) test('should format coordinates with proper precision', () => { - const formatted = formatCoordinates(COORDS.LONDON) + const formatted = formatCoordinates([51.5074, -0.1278]) // London expect(formatted).toBe('51.5074° N, 0.1278° W') }) @@ -115,11 +102,16 @@ describe('locale utilities', () => { }) }) + // eslint-disable-next-line max-lines-per-function describe('getLocale', () => { test('should normalize single underscore in override_locale', async () => { setupScreenlyMock( - { coordinates: COORDS.LA }, - { override_locale: 'en_US' }, + { + coordinates: [37.3861, -122.0839], + }, + { + override_locale: 'en_US', + }, ) const locale = await getLocale() @@ -128,8 +120,12 @@ describe('locale utilities', () => { test('should normalize multiple underscores in override_locale', async () => { setupScreenlyMock( - { coordinates: COORDS.LA }, - { override_locale: 'de_DE' }, + { + coordinates: [37.3861, -122.0839], + }, + { + override_locale: 'de_DE', + }, ) const locale = await getLocale() @@ -138,41 +134,61 @@ describe('locale utilities', () => { test('should fallback to GPS detection for invalid override_locale', async () => { setupScreenlyMock( - { coordinates: COORDS.TOKYO }, - { override_locale: 'invalid_locale_xyz' }, + { + coordinates: [35.6762, 139.6503], // Tokyo, Japan + }, + { + override_locale: 'invalid_locale_xyz', + }, ) const locale = await getLocale() + // Should fallback to GPS-based locale detection (ja for Japan) expect(locale.startsWith('ja')).toBe(true) }) test('should reject malformed locale with trailing hyphen', async () => { setupScreenlyMock( - { coordinates: COORDS.TOKYO }, - { override_locale: 'en-' }, + { + coordinates: [35.6762, 139.6503], + }, + { + override_locale: 'en-', + }, ) const locale = await getLocale() + // Should fallback to GPS-based locale detection expect(locale.startsWith('ja')).toBe(true) }) test('should reject malformed locale where region code is invalid', async () => { setupScreenlyMock( - { coordinates: COORDS.TOKYO }, - { override_locale: 'en-INVALID' }, + { + coordinates: [35.6762, 139.6503], + }, + { + override_locale: 'en-INVALID', + }, ) const locale = await getLocale() + // Should fallback to GPS-based locale detection (not use 'en') expect(locale.startsWith('ja')).toBe(true) }) test('should reject locale with underscores and invalid parts', async () => { setupScreenlyMock( - { coordinates: COORDS.TOKYO }, - { override_locale: 'en_US_INVALID_EXTRA' }, + { + coordinates: [35.6762, 139.6503], + }, + { + override_locale: 'en_US_INVALID_EXTRA', + }, ) const locale = await getLocale() + // Should fallback to GPS-based locale detection expect(locale.startsWith('ja')).toBe(true) }) })