Skip to content

Commit 300cdbc

Browse files
authored
fix(#324): runs xpath tests with the same timezone (#323)
Adds a global beforeEach and AfterEach hook to set the tests' timezone Adds a new optional environment variable for the tests' locale
1 parent 021c661 commit 300cdbc

File tree

5 files changed

+53
-3
lines changed

5 files changed

+53
-3
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ defaults:
2121

2222
env:
2323
TZ: 'America/Phoenix'
24+
LOCALE_ID: 'en-US'
2425

2526
jobs:
2627
install-and-build:

packages/xpath/test/helpers.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,33 @@ type AnyParentNode =
1010
| XMLDocument;
1111

1212
declare global {
13+
/**
14+
* The timezone identifier used for all date and time operations in tests.
15+
* This string follows the IANA Time Zone Database format (e.g., 'America/Phoenix').
16+
* It determines the offset and DST behavior for `Date` objects and
17+
* related functions.
18+
*
19+
* @example 'America/Phoenix' // Fixed UTC-7, no DST
20+
* @example 'Europe/London' // UTC+0 (GMT) or UTC+1 (BST) with DST
21+
*/
1322
// eslint-disable-next-line no-var
1423
var TZ: string | undefined;
24+
/**
25+
* The locale string defining the language and regional formatting for tests.
26+
* This follows the BCP 47 language tag format (e.g., 'en-US'). It ensures consistent formatting
27+
* across tests.
28+
*
29+
* @example 'en-US' // American English
30+
*/
31+
// eslint-disable-next-line no-var
32+
var LOCALE_ID: string | undefined;
1533
// eslint-disable-next-line no-var
1634
var IMPLEMENTATION: string | undefined;
1735
}
1836

1937
globalThis.IMPLEMENTATION = typeof IMPLEMENTATION === 'string' ? IMPLEMENTATION : undefined;
2038
globalThis.TZ = typeof TZ === 'string' ? TZ : undefined;
39+
globalThis.LOCALE_ID = typeof LOCALE_ID === 'string' ? LOCALE_ID : undefined;
2140

2241
const namespaces: Record<string, string> = {
2342
xhtml: 'http://www.w3.org/1999/xhtml',
@@ -338,3 +357,7 @@ export const getNonNamespaceAttributes = (element: Element): readonly Attr[] =>
338357

339358
return attrs.filter(({ name }) => name !== 'xmlns' && !name.startsWith('xmlns:'));
340359
};
360+
361+
export const getDefaultDateTimeLocale = (): string => {
362+
return new Date().toLocaleString(LOCALE_ID, { timeZone: TZ });
363+
};

packages/xpath/test/setup.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { afterEach, beforeEach } from 'vitest';
2+
import { vi } from 'vitest';
3+
import { getDefaultDateTimeLocale } from './helpers.ts';
4+
5+
beforeEach(() => {
6+
const dateOnTimezone = getDefaultDateTimeLocale();
7+
vi.useFakeTimers({
8+
now: new Date(dateOnTimezone).getTime(),
9+
});
10+
});
11+
12+
afterEach(() => {
13+
vi.useRealTimers();
14+
});

packages/xpath/test/xforms/now.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ describe('#now()', () => {
1414
// > including timezone offset (i.e. not normalized to UTC) as described
1515
// > under the dateTime datatype.
1616
it('should return a timestamp for this instant', () => {
17-
// this check might fail if run at precisely midnight ;-)
18-
1917
// given
2018
const now = new Date();
2119
const today = `${now.getFullYear()}-${(1 + now.getMonth()).toString().padStart(2, '0')}-${now

packages/xpath/vite.config.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,26 @@ const TEST_ENVIRONMENT = BROWSER_ENABLED ? 'node' : 'jsdom';
4444
*/
4545
const TEST_TIME_ZONE = 'America/Phoenix';
4646

47+
/**
48+
* The locale used for formatting dates and times in all test cases.
49+
* This ensures consistent language and regional settings across tests.
50+
* Currently set to 'en-US' (American English), which affects date formats
51+
* (e.g., MM/DD/YYYY) and time separators.
52+
*
53+
* @constant
54+
* @default 'en-US'
55+
*/
56+
const TEST_LOCALE = 'en-US';
57+
4758
export default defineConfig(({ mode }) => {
4859
const isTest = mode === 'test';
4960

5061
let timeZoneId: string | null = process.env.TZ ?? null;
62+
let localeId: string | null = process.env.LOCALE_ID ?? null;
5163

5264
if (isTest) {
5365
timeZoneId = timeZoneId ?? TEST_TIME_ZONE;
66+
localeId = localeId ?? TEST_LOCALE;
5467
}
5568

5669
// `expressionParser.ts` is built as a separate entry so it can be consumed
@@ -83,6 +96,7 @@ export default defineConfig(({ mode }) => {
8396
},
8497
define: {
8598
TZ: JSON.stringify(timeZoneId),
99+
LOCALE_ID: JSON.stringify(localeId),
86100
},
87101
esbuild: {
88102
target: 'esnext',
@@ -122,7 +136,7 @@ export default defineConfig(({ mode }) => {
122136
headless: true,
123137
screenshotFailures: false,
124138
},
125-
139+
setupFiles: ['test/setup.ts'],
126140
environment: TEST_ENVIRONMENT,
127141
globals: false,
128142
include: ['test/**/*.test.ts'],

0 commit comments

Comments
 (0)