Skip to content

Commit

Permalink
fix(transition): ensure Transition enterHooks are updated after clone (
Browse files Browse the repository at this point in the history
  • Loading branch information
edison1105 authored Jun 4, 2024
1 parent ef2e737 commit 671cf29
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 4 deletions.
15 changes: 13 additions & 2 deletions packages/runtime-core/src/components/BaseTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,13 @@ const BaseTransitionImpl: ComponentOptions = {
return emptyPlaceholder(child)
}

const enterHooks = resolveTransitionHooks(
let enterHooks = resolveTransitionHooks(
innerChild,
rawProps,
state,
instance,
// #11061, ensure enterHooks is fresh after clone
hooks => (enterHooks = hooks),
)
setTransitionHooks(innerChild, enterHooks)

Expand Down Expand Up @@ -305,6 +307,7 @@ export function resolveTransitionHooks(
props: BaseTransitionProps<any>,
state: TransitionState,
instance: ComponentInternalInstance,
postClone?: (hooks: TransitionHooks) => void,
): TransitionHooks {
const {
appear,
Expand Down Expand Up @@ -445,7 +448,15 @@ export function resolveTransitionHooks(
},

clone(vnode) {
return resolveTransitionHooks(vnode, props, state, instance)
const hooks = resolveTransitionHooks(
vnode,
props,
state,
instance,
postClone,
)
if (postClone) postClone(hooks)
return hooks
},
}

Expand Down
10 changes: 8 additions & 2 deletions packages/runtime-core/src/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import {
isSuspense,
} from './components/Suspense'
import type { DirectiveBinding } from './directives'
import type { TransitionHooks } from './components/BaseTransition'
import {
type TransitionHooks,
setTransitionHooks,
} from './components/BaseTransition'
import { warn } from './warning'
import {
type Teleport,
Expand Down Expand Up @@ -691,7 +694,10 @@ export function cloneVNode<T, U>(
// to clone the transition to ensure that the vnode referenced within
// the transition hooks is fresh.
if (transition && cloneTransition) {
cloned.transition = transition.clone(cloned as VNode)
setTransitionHooks(
cloned as VNode,
transition.clone(cloned as VNode) as TransitionHooks,
)
}

if (__COMPAT__) {
Expand Down
92 changes: 92 additions & 0 deletions packages/vue/__tests__/e2e/Transition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,98 @@ describe('e2e: Transition', () => {
E2E_TIMEOUT,
)

// #11061
test(
'transition + fallthrough attrs (in-out mode)',
async () => {
const beforeLeaveSpy = vi.fn()
const onLeaveSpy = vi.fn()
const afterLeaveSpy = vi.fn()
const beforeEnterSpy = vi.fn()
const onEnterSpy = vi.fn()
const afterEnterSpy = vi.fn()

await page().exposeFunction('onLeaveSpy', onLeaveSpy)
await page().exposeFunction('onEnterSpy', onEnterSpy)
await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
await page().exposeFunction('afterEnterSpy', afterEnterSpy)

await page().evaluate(() => {
const { onEnterSpy, onLeaveSpy } = window as any
const { createApp, ref } = (window as any).Vue
createApp({
components: {
one: {
template: '<div>one</div>',
},
two: {
template: '<div>two</div>',
},
},
template: `
<div id="container">
<transition foo="1" name="test" mode="in-out"
@before-enter="beforeEnterSpy()"
@enter="onEnterSpy()"
@after-enter="afterEnterSpy()"
@before-leave="beforeLeaveSpy()"
@leave="onLeaveSpy()"
@after-leave="afterLeaveSpy()">
<component :is="view"></component>
</transition>
</div>
<button id="toggleBtn" @click="click">button</button>
`,
setup: () => {
const view = ref('one')
const click = () =>
(view.value = view.value === 'one' ? 'two' : 'one')
return {
view,
click,
beforeEnterSpy,
onEnterSpy,
afterEnterSpy,
beforeLeaveSpy,
onLeaveSpy,
afterLeaveSpy,
}
},
}).mount('#app')
})
expect(await html('#container')).toBe('<div foo="1">one</div>')

// toggle
await click('#toggleBtn')
await nextTick()
await transitionFinish()
expect(beforeEnterSpy).toBeCalledTimes(1)
expect(onEnterSpy).toBeCalledTimes(1)
expect(afterEnterSpy).toBeCalledTimes(1)
expect(beforeLeaveSpy).toBeCalledTimes(1)
expect(onLeaveSpy).toBeCalledTimes(1)
expect(afterLeaveSpy).toBeCalledTimes(1)

expect(await html('#container')).toBe('<div foo="1" class="">two</div>')

// toggle back
await click('#toggleBtn')
await nextTick()
await transitionFinish()
expect(beforeEnterSpy).toBeCalledTimes(2)
expect(onEnterSpy).toBeCalledTimes(2)
expect(afterEnterSpy).toBeCalledTimes(2)
expect(beforeLeaveSpy).toBeCalledTimes(2)
expect(onLeaveSpy).toBeCalledTimes(2)
expect(afterLeaveSpy).toBeCalledTimes(2)

expect(await html('#container')).toBe('<div foo="1" class="">one</div>')
},
E2E_TIMEOUT,
)

test(
'w/ KeepAlive + unmount innerChild',
async () => {
Expand Down

0 comments on commit 671cf29

Please sign in to comment.