|
| 1 | +import { util } from 'chai'; |
| 2 | +import { createExpect, GLOBAL_EXPECT } from '../../../src/runtime/api/expect'; |
| 3 | +import type { TestCase, WorkerState } from '../../../src/types'; |
| 4 | + |
| 5 | +function createWorkerState(testPath: string): WorkerState { |
| 6 | + return { testPath, runtimeConfig: {} } as WorkerState; |
| 7 | +} |
| 8 | + |
| 9 | +const fakeTest = (name: string) => ({ name }) as unknown as TestCase; |
| 10 | + |
| 11 | +// This test hijacks `globalThis[GLOBAL_EXPECT]` — the same slot the host rstest |
| 12 | +// runtime uses — so each case swaps it only briefly, captures what it needs, |
| 13 | +// then restores it before running the outer assertions. |
| 14 | +function withLiveExpect<T>(live: unknown, fn: () => T): T { |
| 15 | + // @ts-expect-error symbol index |
| 16 | + const prev = globalThis[GLOBAL_EXPECT]; |
| 17 | + // @ts-expect-error symbol index |
| 18 | + globalThis[GLOBAL_EXPECT] = live; |
| 19 | + try { |
| 20 | + return fn(); |
| 21 | + } finally { |
| 22 | + // @ts-expect-error symbol index |
| 23 | + globalThis[GLOBAL_EXPECT] = prev; |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +/** |
| 28 | + * Regression for https://github.com/web-infra-dev/rstest/issues/1376: under |
| 29 | + * `isolate: false` the per-file `expect` (and members value-copied off it) can |
| 30 | + * be captured in a module shared across files. When a later file invokes that |
| 31 | + * stale reference, it must delegate to the current file's live expect |
| 32 | + * (`globalThis[GLOBAL_EXPECT]`) so assertion state and test attribution track |
| 33 | + * the running file — while the per-test local expect must NOT delegate. |
| 34 | + */ |
| 35 | +describe('createExpect cross-file delegation (isolate: false)', () => { |
| 36 | + it('attributes a stale per-file expect to the live file test', () => { |
| 37 | + const file1 = createExpect({ |
| 38 | + workerState: createWorkerState('/f1'), |
| 39 | + getCurrentTest: () => fakeTest('t1'), |
| 40 | + isFileExpect: true, |
| 41 | + }); |
| 42 | + const file2 = createExpect({ |
| 43 | + workerState: createWorkerState('/f2'), |
| 44 | + getCurrentTest: () => fakeTest('t2'), |
| 45 | + isFileExpect: true, |
| 46 | + }); |
| 47 | + |
| 48 | + // File 2 is the running file; the stale file-1 expect must resolve t2. |
| 49 | + const attributed = withLiveExpect(file2, () => |
| 50 | + util.flag(file1(1) as unknown as object, 'vitest-test'), |
| 51 | + ) as TestCase; |
| 52 | + expect(attributed.name).toBe('t2'); |
| 53 | + }); |
| 54 | + |
| 55 | + it('routes assertion state of a stale per-file expect onto the live expect', () => { |
| 56 | + const file1 = createExpect({ |
| 57 | + workerState: createWorkerState('/f1'), |
| 58 | + getCurrentTest: () => undefined, |
| 59 | + isFileExpect: true, |
| 60 | + }); |
| 61 | + const file2 = createExpect({ |
| 62 | + workerState: createWorkerState('/f2'), |
| 63 | + getCurrentTest: () => undefined, |
| 64 | + isFileExpect: true, |
| 65 | + }); |
| 66 | + |
| 67 | + const calls = withLiveExpect(file2, () => { |
| 68 | + file1.setState({ assertionCalls: 7 }); |
| 69 | + // State must land on the live (file 2) key, not file 1's own object. |
| 70 | + return file2.getState().assertionCalls; |
| 71 | + }); |
| 72 | + expect(calls).toBe(7); |
| 73 | + }); |
| 74 | + |
| 75 | + it('does not delegate the per-test local expect (concurrent isolation)', () => { |
| 76 | + const fileExpect = createExpect({ |
| 77 | + workerState: createWorkerState('/f2'), |
| 78 | + getCurrentTest: () => fakeTest('file'), |
| 79 | + isFileExpect: true, |
| 80 | + }); |
| 81 | + const localExpect = createExpect({ |
| 82 | + workerState: createWorkerState('/f2'), |
| 83 | + getCurrentTest: () => fakeTest('local'), |
| 84 | + // isFileExpect omitted -> false: a per-test local expect never delegates. |
| 85 | + }); |
| 86 | + |
| 87 | + const { attributed, localCalls, fileCalls } = withLiveExpect( |
| 88 | + fileExpect, |
| 89 | + () => { |
| 90 | + const a = util.flag( |
| 91 | + localExpect(1) as unknown as object, |
| 92 | + 'vitest-test', |
| 93 | + ) as TestCase; |
| 94 | + localExpect.setState({ assertionCalls: 3 }); |
| 95 | + return { |
| 96 | + attributed: a, |
| 97 | + localCalls: localExpect.getState().assertionCalls, |
| 98 | + fileCalls: fileExpect.getState().assertionCalls, |
| 99 | + }; |
| 100 | + }, |
| 101 | + ); |
| 102 | + |
| 103 | + expect(attributed.name).toBe('local'); |
| 104 | + expect(localCalls).toBe(3); |
| 105 | + // The live file expect's state is untouched by the local expect. |
| 106 | + expect(fileCalls).toBe(0); |
| 107 | + }); |
| 108 | +}); |
0 commit comments