Repository: vuejs/vue
Pull request: fix(transition): check existence of `el.parentNode`, fix #8199

Fix #8199 
If the new parentNode gets a `textContent` or `innerHTML` property during patching, the `transition` node would have been detached early, which means `el.parentNode` no longer exists.
So this existential check is necessary.

This is an imperfect fix because while errors are suppressed, transition animations still get lost.
It is due to the different mechanism of `v-html` and VDOM element patching. But fixing that would be much more complex and I don't think we'd bother fixing such an edge case.

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**


Changed files:
filename: src/core/vdom/patch.js
patch: @@ -32,13 +32,20 @@ export const emptyNode = new VNode('', {}, [])
 
 const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
 
+function childrenIgnored (vnode) {
+  return vnode && vnode.data && vnode.data.domProps && (
+    vnode.data.domProps.innerHTML || vnode.data.domProps.textContent
+  )
+}
+
 function sameVnode (a, b) {
   return (
     a.key === b.key && (
       (
         a.tag === b.tag &&
         a.isComment === b.isComment &&
         isDef(a.data) === isDef(b.data) &&
+        !childrenIgnored(a) && !childrenIgnored(b) &&
         sameInputType(a, b)
       ) || (
         isTrue(a.isAsyncPlaceholder) &&

filename: src/platforms/web/runtime/modules/transition.js
patch: @@ -251,7 +251,7 @@ export function leave (vnode: VNodeWithData, rm: Function) {
       return
     }
     // record leaving element
-    if (!vnode.data.show) {
+    if (!vnode.data.show && el.parentNode) {
       (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode
     }
     beforeLeave && beforeLeave(el)

filename: test/unit/features/transition/transition.spec.js
patch: @@ -1167,5 +1167,27 @@ if (!isIE9) {
         expect(vm.$el.innerHTML).toBe('<!---->')
       }).then(done)
     })
+
+    // #8199
+    it('should not throw error when replaced by v-html contents', (done) => {
+      const vm = new Vue({
+        template: `
+          <div>
+            <div v-if="ok" :class="ok">
+              <transition>
+                <span>a</span>
+              </transition>
+            </div>
+            <div v-else v-html="ok"></div>
+          </div>
+        `,
+        data: { ok: true }
+      }).$mount(el)
+
+      vm.ok = false
+      waitForUpdate(() => {
+        expect(vm.$el.children[0].innerHTML).toBe('false')
+      }).then(done)
+    })
   })
 }