Skip to content

Commit

Permalink
fix: incorrect execution order in nested describe
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Sep 24, 2023
1 parent 219144c commit cc85b1e
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 10 deletions.
34 changes: 25 additions & 9 deletions packages/runner/src/fixture.ts
Expand Up @@ -45,13 +45,17 @@ export function mergeContextFixtures(fixtures: Record<string, any>, context: { f
}

const fixtureValueMap = new Map<FixtureItem, any>()
const fixtureCleanupSet = new Set<() => void | Promise<void>>()
const fixtureCleanupFnMap = new Map<string, Array<() => void | Promise<void>>>()

export async function callFixtureCleanup() {
await Promise.all([...fixtureCleanupSet].map(async (fn) => {
await fn()
}))
fixtureValueMap.clear()
export async function callFixtureCleanup(id: string) {
const cleanupFnArray = fixtureCleanupFnMap.get(id)
if (!cleanupFnArray)
return

for (const cleanup of cleanupFnArray.reverse())
await cleanup()

fixtureCleanupFnMap.delete(id)
}

export function withFixtures(fn: Function, testContext?: TestContext) {
Expand All @@ -61,6 +65,12 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
if (!context)
return fn({})

let cleanupFnArray = fixtureCleanupFnMap.get(context.task.suite.id)!
if (!cleanupFnArray) {
cleanupFnArray = []
fixtureCleanupFnMap.set(context.task.suite.id, cleanupFnArray)
}

const fixtures = getFixture(context)
if (!fixtures?.length)
return fn(context)
Expand All @@ -77,7 +87,13 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
async function use(fixtureValue: any) {
const fixture = pendingFixtures[cursor++]
context![fixture.prop] = fixtureValue
fixtureValueMap.set(fixture, fixtureValue)

if (!fixtureValueMap.has(fixture)) {
fixtureValueMap.set(fixture, fixtureValue)
cleanupFnArray.unshift(() => {
fixtureValueMap.delete(fixture)
})
}

if (cursor < pendingFixtures.length) {
await next()
Expand All @@ -91,7 +107,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
reject(err)
}
return new Promise<void>((resolve) => {
fixtureCleanupSet.add(resolve)
cleanupFnArray.push(resolve)
})
}
}
Expand All @@ -106,7 +122,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
}

const setupFixturePromise = next()
fixtureCleanupSet.add(() => setupFixturePromise)
cleanupFnArray.unshift(() => setupFixturePromise)
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/run.ts
Expand Up @@ -322,7 +322,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner) {
}

try {
await callFixtureCleanup()
await callFixtureCleanup(suite.id)
await callSuiteHook(suite, suite, 'afterAll', runner, [suite])
await callCleanupHooks(beforeAllCleanups)
}
Expand Down
78 changes: 78 additions & 0 deletions test/core/test/test-extend.test.ts
Expand Up @@ -199,4 +199,82 @@ describe('test.extend()', () => {
expect(teardownFn).toBeCalledTimes(2)
})
})

describe('fixture in nested describe', () => {
interface Fixture {
foo: number
bar: number
}

const fooFn = vi.fn(() => 0)
const fooCleanup = vi.fn()

const barFn = vi.fn(() => 0)
const barCleanup = vi.fn()

const nestedTest = test.extend<Fixture>({
async foo({}, use) {
await use(fooFn())
fooCleanup()
},
async bar({}, use) {
await use(barFn())
barCleanup()
},
})

beforeEach<Fixture>(({ foo }) => {
expect(foo).toBe(0)
})

nestedTest('should only initialize foo', ({ foo }) => {
expect(foo).toBe(0)
expect(fooFn).toBeCalledTimes(1)
expect(barFn).toBeCalledTimes(0)
})

describe('level 2, using both foo and bar together', () => {
beforeEach<Fixture>(({ foo, bar }) => {
expect(foo).toBe(0)
expect(bar).toBe(0)
})

nestedTest('should only initialize bar', ({ foo, bar }) => {
expect(foo).toBe(0)
expect(bar).toBe(0)
expect(fooFn).toBeCalledTimes(1)
expect(barFn).toBeCalledTimes(1)
})

afterEach<Fixture>(({ foo, bar }) => {
expect(foo).toBe(0)
expect(bar).toBe(0)
})

afterAll(() => {
// foo setup in outside describe
// cleanup also called in outside describe
expect(fooCleanup).toHaveBeenCalledTimes(0)
// bar setup in inside describe
// cleanup also called in inside describe
expect(barCleanup).toHaveBeenCalledTimes(1)
})
})

nestedTest('level 2 will not call foo cleanup', ({ foo }) => {
expect(foo).toBe(0)
expect(fooFn).toBeCalledTimes(1)
})

afterEach<Fixture>(({ foo }) => {
expect(foo).toBe(0)
})

afterAll(() => {
// foo setup in this describe
// cleanup also called in this describe
expect(fooCleanup).toHaveBeenCalledTimes(1)
expect(barCleanup).toHaveBeenCalledTimes(1)
})
})
})

0 comments on commit cc85b1e

Please sign in to comment.