Skip to content

Commit f33a39b

Browse files
committed
fix(core): return OS-native testPath/filepath at user-facing surfaces
`expect.getState().testPath` and the lifecycle hook `ctx.filepath` are real on-disk paths surfaced to users, but they were emitted in POSIX form (forward slashes), so on Windows they diverged from `import.meta.filename` / `__filename` (which the rspack plugin injects in native form). A user comparing them — e.g. `expect.getState().testPath === import.meta.filename` — failed on Windows. Internally `testPath` must stay POSIX: the snapshot client, reporter relative paths, and the related-graph lookup all run it through `pathe`, which only understands `/`. So convert to native separators only at the two user-facing boundaries (the `setState` testPath and the hook context `filepath`) via a small `toNativePath` helper, leaving every internal consumer untouched. Closes #1465
1 parent dfdd527 commit f33a39b

6 files changed

Lines changed: 58 additions & 8 deletions

File tree

e2e/expect/test/index.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,11 @@ describe('Expect API', () => {
107107
expect(1 + 2).toBe(3);
108108
expect(1 + 3).toBe(4);
109109
});
110+
111+
// testPath must be the OS-native absolute path so it equals
112+
// `import.meta.filename`/`__filename` on every platform (incl. Windows).
113+
// https://github.com/web-infra-dev/rstest/issues/1465
114+
it('expect.getState().testPath should be the native file path', () => {
115+
expect(expect.getState().testPath).toBe(import.meta.filename);
116+
});
110117
});

e2e/lifecycle/fixtures/beforeAll.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { beforeAll, describe, expect, it } from '@rstest/core';
2-
import pathe from 'pathe';
32
import { sleep } from '../../scripts';
43

54
beforeAll((ctx) => {
65
console.log('[beforeAll] root');
7-
expect(ctx.filepath).toBe(pathe.normalize(__filename));
6+
// `ctx.filepath` is the OS-native test path, equal to `__filename` (#1465).
7+
expect(ctx.filepath).toBe(__filename);
88
});
99

1010
beforeAll(async () => {

e2e/setup/index.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,13 @@ describe('test setup file', async () => {
9898
const logs = cli.stdout.split('\n');
9999
await expectExecSuccess();
100100

101+
// `ctx.filepath` is OS-native (#1465), so build the expected suffix with
102+
// `join` to match backslashes on Windows and forward slashes on POSIX.
101103
expect(
102-
logs.filter((log) => log.endsWith('no-isolate/foo.test.ts')),
104+
logs.filter((log) => log.endsWith(join('no-isolate', 'foo.test.ts'))),
103105
).toHaveLength(1);
104106
expect(
105-
logs.filter((log) => log.endsWith('no-isolate/bar.test.ts')),
107+
logs.filter((log) => log.endsWith(join('no-isolate', 'bar.test.ts'))),
106108
).toHaveLength(1);
107109
expect(logs.filter((log) => log.endsWith('[beforeAll] root'))).toHaveLength(
108110
2,

packages/core/src/runtime/runner/runner.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import type {
2121
WorkerState,
2222
} from '../../types';
2323
import { SYNTHETIC_STACK_ERROR_MESSAGE } from '../../utils/constants';
24-
import { getFileTaskId, getTaskNameWithPrefix } from '../../utils/helper';
24+
import {
25+
getFileTaskId,
26+
getTaskNameWithPrefix,
27+
toNativePath,
28+
} from '../../utils/helper';
2529
import { createExpect } from '../api/expect';
2630
import { formatTestError, TestSkipError } from '../util';
2731
import type { TaskContext } from '../worker/taskContext';
@@ -409,7 +413,9 @@ export class TestRunner {
409413
try {
410414
for (const fn of test.beforeAllListeners) {
411415
const cleanupFn = await fn({
412-
filepath: testPath,
416+
// `ctx.filepath` is user-facing; expose the OS-native path
417+
// so it matches `__filename`/`import.meta.filename` (#1465).
418+
filepath: toNativePath(testPath),
413419
});
414420
if (cleanupFn) cleanups.push(cleanupFn);
415421
}
@@ -440,7 +446,9 @@ export class TestRunner {
440446
try {
441447
for (const fn of afterAllFns) {
442448
await fn({
443-
filepath: testPath,
449+
// `ctx.filepath` is user-facing; expose the OS-native path
450+
// so it matches `__filename`/`import.meta.filename` (#1465).
451+
filepath: toNativePath(testPath),
444452
});
445453
}
446454
} catch (error) {
@@ -763,7 +771,10 @@ export class TestRunner {
763771
isExpectingAssertionsError: null,
764772
expectedAssertionsNumber: null,
765773
expectedAssertionsNumberErrorGen: null,
766-
testPath: test.testPath,
774+
// `expect.getState().testPath` is user-facing; expose the OS-native
775+
// path (equal to `import.meta.filename`). Internal consumers
776+
// (snapshot, reporter, related) keep the POSIX `test.testPath` (#1465).
777+
testPath: toNativePath(test.testPath),
767778
snapshotState,
768779
currentTestName: getTaskNameWithPrefix(test),
769780
},

packages/core/src/utils/helper.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { normalize as nativeNormalize } from 'node:path';
12
import {
23
isAbsolute,
34
join,
@@ -61,6 +62,18 @@ export const displayPath = (filePath: string, rootPath: string): string => {
6162
return rel && !rel.startsWith('..') ? rel : filePath;
6263
};
6364

65+
/**
66+
* Convert a path to the OS-native separator form (`\` on Windows, no-op on
67+
* POSIX). Internally `testPath` stays POSIX so the pathe-based consumers
68+
* (snapshot, reporter relative paths, related-graph lookup) keep working, but
69+
* the user-facing surfaces (`expect.getState().testPath`, hook `ctx.filepath`)
70+
* must match `import.meta.filename`/`__filename`, which the rspack plugin
71+
* injects in native form. Apply this only at those user-facing boundaries.
72+
* See https://github.com/web-infra-dev/rstest/issues/1465.
73+
*/
74+
export const toNativePath = (filePath: string): string =>
75+
nativeNormalize(filePath);
76+
6477
export const parsePosix = (filePath: string): { dir: string; base: string } => {
6578
const { dir, base } = parse(filePath);
6679

packages/core/tests/utils/helper.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
needFlagExperimentalDetectModule,
66
parsePosix,
77
prettyTime,
8+
toNativePath,
89
} from '../../src/utils/helper';
910

1011
it('getFileTaskId builds the file: task-id grammar', () => {
@@ -13,6 +14,22 @@ it('getFileTaskId builds the file: task-id grammar', () => {
1314
expect(getFileTaskId('/abs/foo.test.ts')).toBe('file:/abs/foo.test.ts');
1415
});
1516

17+
it('toNativePath converts forward-slash paths to OS-native separators', () => {
18+
// The runtime `testPath` must use OS-native separators so it matches
19+
// `import.meta.filename` on Windows (#1465). `join(sep)` keeps this
20+
// assertion meaningful on both platforms: on Windows the result must use
21+
// backslashes, on POSIX it stays `/` (native == POSIX there).
22+
expect(toNativePath('packages/core/a.test.ts')).toBe(
23+
'packages/core/a.test.ts'.replaceAll('/', sep),
24+
);
25+
// Absolute paths are converted too.
26+
expect(toNativePath('/abs/foo.test.ts')).toBe(
27+
'/abs/foo.test.ts'.replaceAll('/', sep),
28+
);
29+
// The output must never keep `/` unless `/` is the native separator.
30+
expect(toNativePath('a/b/c').includes('/')).toBe(sep === '/');
31+
});
32+
1633
it('parsePosix correctly', () => {
1734
const splitPaths = ['packages', 'core', 'tests', 'index.test.ts'];
1835

0 commit comments

Comments
 (0)