]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(transition/keep-alive): fix unmount bug for component with out-in transition...
author被雨水过滤的空气(Rairn) <958414905@qq.com>
Tue, 8 Nov 2022 15:01:43 +0000 (23:01 +0800)
committerGitHub <noreply@github.com>
Tue, 8 Nov 2022 15:01:43 +0000 (10:01 -0500)
fix #6835

packages/runtime-core/__tests__/components/BaseTransition.spec.ts
packages/runtime-core/src/components/BaseTransition.ts

index ade8fffa7d72da1c6b29a5b2cf96d35265bc3af7..7b2fb38954050a383424880e146965e122312905 100644 (file)
@@ -19,13 +19,20 @@ function mount(
   withKeepAlive = false
 ) {
   const root = nodeOps.createElement('div')
-  render(
-    h(BaseTransition, props, () => {
-      return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
-    }),
-    root
-  )
-  return root
+  const show = ref(true)
+  const unmount = () => (show.value = false)
+  const App = {
+    render() {
+      return show.value
+        ? h(BaseTransition, props, () => {
+            return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
+          })
+        : null
+    }
+  }
+  render(h(App), root)
+
+  return { root, unmount }
 }
 
 function mockProps(extra: BaseTransitionProps = {}, withKeepAlive = false) {
@@ -258,7 +265,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({ mode })
-      const root = mount(props, () =>
+      const { root } = mount(props, () =>
         toggle.value ? trueBranch() : falseBranch()
       )
 
@@ -347,7 +354,7 @@ describe('BaseTransition', () => {
     }: ToggleOptions) {
       const toggle = ref(false)
       const { props, cbs } = mockProps()
-      const root = mount(props, () =>
+      const { root } = mount(props, () =>
         toggle.value ? trueBranch() : falseBranch()
       )
 
@@ -431,7 +438,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({}, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
@@ -537,7 +544,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({}, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
@@ -670,7 +677,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
@@ -763,6 +770,89 @@ describe('BaseTransition', () => {
     })
   })
 
+
+  // #6835
+  describe('mode: "out-in" toggle again after unmounted', () => {
+    async function testOutIn(
+      {
+        trueBranch,
+        falseBranch,
+        trueSerialized,
+        falseSerialized
+      }: ToggleOptions,
+      withKeepAlive = false
+    ) {
+      const toggle = ref(true)
+      const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
+      const { root, unmount } = mount(
+        props,
+        () => (toggle.value ? trueBranch() : falseBranch()),
+        withKeepAlive
+      )
+
+      // trigger toggle
+      toggle.value = false
+      await nextTick()
+      // a placeholder is injected until the leave finishes
+      expect(serializeInner(root)).toBe(`${trueSerialized}<!---->`)
+      expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
+      assertCalledWithEl(props.onBeforeLeave, trueSerialized)
+      expect(props.onLeave).toHaveBeenCalledTimes(1)
+      assertCalledWithEl(props.onLeave, trueSerialized)
+      expect(props.onAfterLeave).not.toHaveBeenCalled()
+      // enter should not have started
+      expect(props.onBeforeEnter).not.toHaveBeenCalled()
+      expect(props.onEnter).not.toHaveBeenCalled()
+      expect(props.onAfterEnter).not.toHaveBeenCalled()
+
+      cbs.doneLeave[trueSerialized]()
+      expect(props.onAfterLeave).toHaveBeenCalledTimes(1)
+      assertCalledWithEl(props.onAfterLeave, trueSerialized)
+      // have to wait for a tick because this triggers an update
+      await nextTick()
+      expect(serializeInner(root)).toBe(falseSerialized)
+      // enter should start
+      expect(props.onBeforeEnter).toHaveBeenCalledTimes(1)
+      assertCalledWithEl(props.onBeforeEnter, falseSerialized)
+      expect(props.onEnter).toHaveBeenCalledTimes(1)
+      assertCalledWithEl(props.onEnter, falseSerialized)
+      expect(props.onAfterEnter).not.toHaveBeenCalled()
+      // finish enter
+      cbs.doneEnter[falseSerialized]()
+      expect(props.onAfterEnter).toHaveBeenCalledTimes(1)
+      assertCalledWithEl(props.onAfterEnter, falseSerialized)
+
+      unmount()
+      // toggle again after unmounted should not throw error
+      toggle.value = true
+      await nextTick()
+      expect(serializeInner(root)).toBe(`<!---->`)
+
+      assertCalls(props, {
+        onBeforeEnter: 1,
+        onEnter: 1,
+        onAfterEnter: 1,
+        onEnterCancelled: 0,
+        onBeforeLeave: 1,
+        onLeave: 1,
+        onAfterLeave: 1,
+        onLeaveCancelled: 0
+      })
+    }
+
+    test('w/ elements', async () => {
+      await runTestWithElements(testOutIn)
+    })
+
+    test('w/ components', async () => {
+      await runTestWithComponents(testOutIn)
+    })
+
+    test('w/ KeepAlive', async () => {
+      await runTestWithKeepAlive(testOutIn)
+    })
+  })
+
   describe('mode: "out-in" toggle before finish', () => {
     async function testOutInBeforeFinish(
       { trueBranch, falseBranch, trueSerialized }: ToggleOptions,
@@ -770,7 +860,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
@@ -847,7 +937,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
@@ -925,7 +1015,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
@@ -1029,7 +1119,7 @@ describe('BaseTransition', () => {
     ) {
       const toggle = ref(true)
       const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
-      const root = mount(
+      const { root } = mount(
         props,
         () => (toggle.value ? trueBranch() : falseBranch()),
         withKeepAlive
index 5e5e216d34abb85ea2a315bae301a0de9a603b0a..affa069c2352db41548cd44991386d9481c63765 100644 (file)
@@ -238,7 +238,11 @@ const BaseTransitionImpl: ComponentOptions = {
           // return placeholder node and queue update when leave finishes
           leavingHooks.afterLeave = () => {
             state.isLeaving = false
-            instance.update()
+            // #6835
+            // it also needs to be updated when active is undefined
+            if (instance.update.active !== false) {
+              instance.update()
+            }
           }
           return emptyPlaceholder(child)
         } else if (mode === 'in-out' && innerChild.type !== Comment) {