Skip to content

Commit

Permalink
fix(Suspense): calling hooks before the transition finishes (#9388)
Browse files Browse the repository at this point in the history
close #5844
close #5952
  • Loading branch information
pikax authored Oct 21, 2023
1 parent 7334376 commit 00de3e6
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 3 deletions.
9 changes: 6 additions & 3 deletions packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,17 +491,20 @@ function createSuspenseBoundary(
container
} = suspense

// if there's a transition happening we need to wait it to finish.
let delayEnter: boolean | null = false
if (suspense.isHydrating) {
suspense.isHydrating = false
} else if (!resume) {
const delayEnter =
delayEnter =
activeBranch &&
pendingBranch!.transition &&
pendingBranch!.transition.mode === 'out-in'
if (delayEnter) {
activeBranch!.transition!.afterLeave = () => {
if (pendingId === suspense.pendingId) {
move(pendingBranch!, container, anchor, MoveType.ENTER)
queuePostFlushCb(effects)
}
}
}
Expand Down Expand Up @@ -538,8 +541,8 @@ function createSuspenseBoundary(
}
parent = parent.parent
}
// no pending parent suspense, flush all jobs
if (!hasUnresolvedAncestor) {
// no pending parent suspense nor transition, flush all jobs
if (!hasUnresolvedAncestor && !delayEnter) {
queuePostFlushCb(effects)
}
suspense.effects = []
Expand Down
88 changes: 88 additions & 0 deletions packages/vue/__tests__/e2e/Transition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,94 @@ describe('e2e: Transition', () => {
},
E2E_TIMEOUT
)

// #5844
test('children mount should be called after html changes', async () => {
const fooMountSpy = vi.fn()
const barMountSpy = vi.fn()

await page().exposeFunction('fooMountSpy', fooMountSpy)
await page().exposeFunction('barMountSpy', barMountSpy)

await page().evaluate(() => {
const { fooMountSpy, barMountSpy } = window as any
const { createApp, ref, h, onMounted } = (window as any).Vue
createApp({
template: `
<div id="container">
<transition mode="out-in">
<Suspense>
<Foo v-if="toggle" />
<Bar v-else />
</Suspense>
</transition>
</div>
<button id="toggleBtn" @click="click">button</button>
`,
components: {
Foo: {
setup() {
const el = ref(null)
onMounted(() => {
fooMountSpy(
!!el.value,
!!document.getElementById('foo'),
!!document.getElementById('bar')
)
})

return () => h('div', { ref: el, id: 'foo' }, 'Foo')
}
},
Bar: {
setup() {
const el = ref(null)
onMounted(() => {
barMountSpy(
!!el.value,
!!document.getElementById('foo'),
!!document.getElementById('bar')
)
})

return () => h('div', { ref: el, id: 'bar' }, 'Bar')
}
}
},
setup: () => {
const toggle = ref(true)
const click = () => (toggle.value = !toggle.value)
return { toggle, click }
}
}).mount('#app')
})

await nextFrame()
expect(await html('#container')).toBe('<div id="foo">Foo</div>')
await transitionFinish()

expect(fooMountSpy).toBeCalledTimes(1)
expect(fooMountSpy).toHaveBeenNthCalledWith(1, true, true, false)

await page().evaluate(async () => {
;(document.querySelector('#toggleBtn') as any)!.click()
// nextTrick for patch start
await Promise.resolve()
// nextTrick for Suspense resolve
await Promise.resolve()
// nextTrick for dom transition start
await Promise.resolve()
return document.querySelector('#container div')!.className.split(/\s+/g)
})

await nextFrame()
await transitionFinish()

expect(await html('#container')).toBe('<div id="bar" class="">Bar</div>')

expect(barMountSpy).toBeCalledTimes(1)
expect(barMountSpy).toHaveBeenNthCalledWith(1, true, false, true)
})
})

describe('transition with v-show', () => {
Expand Down

0 comments on commit 00de3e6

Please sign in to comment.