fix(Suspense): calling hooks before the transition finishes (#9388)

close #5844
close #5952
This commit is contained in:
Carlos Rodrigues 2023-10-21 14:24:30 +01:00 committed by GitHub
parent 733437691f
commit 00de3e61ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 3 deletions

View File

@ -491,10 +491,12 @@ 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'
@ -502,6 +504,7 @@ function createSuspenseBoundary(
activeBranch!.transition!.afterLeave = () => {
if (pendingId === suspense.pendingId) {
move(pendingBranch!, container, anchor, MoveType.ENTER)
queuePostFlushCb(effects)
}
}
}
@ -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 = []

View File

@ -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', () => {