Repository: vuejs/vue
Pull request: Fix: nested child elements can not be updated correctly, fix #5618

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

https://github.com/vuejs/vue/issues/5618

<!-- 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/instance/render-helpers/render-list.js
patch: @@ -1,6 +1,6 @@
 /* @flow */
 
-import { isObject } from 'core/util/index'
+import { isObject, isDef } from 'core/util/index'
 
 /**
  * Runtime helper for rendering v-for lists.
@@ -28,5 +28,8 @@ export function renderList (
       ret[i] = render(val[key], key, i)
     }
   }
+  if (isDef(ret)) {
+    (ret: any)._isVList = true
+  }
   return ret
 }

filename: src/core/vdom/helpers/normalize-children.js
patch: @@ -1,7 +1,7 @@
 /* @flow */
 
 import VNode, { createTextVNode } from 'core/vdom/vnode'
-import { isFalse, isDef, isUndef, isPrimitive } from 'shared/util'
+import { isFalse, isTrue, isDef, isUndef, isPrimitive } from 'shared/util'
 
 // The template compiler attempts to minimize the need for normalization by
 // statically analyzing the template at compile time.
@@ -58,7 +58,10 @@ function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNo
         res[res.length - 1] = createTextVNode(last.text + c.text)
       } else {
         // default key for nested array children (likely generated by v-for)
-        if (isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) {
+        if (isTrue(children._isVList) &&
+          isDef(c.tag) &&
+          isUndef(c.key) &&
+          isDef(nestedIndex)) {
           c.key = `__vlist${nestedIndex}_${i}__`
         }
         res.push(c)

filename: test/unit/features/component/component-slot.spec.js
patch: @@ -639,4 +639,25 @@ describe('Component slot', () => {
       expect(vm.$el.textContent).toBe('2foo')
     }).then(done)
   })
+
+  it('the elements of slot should be updated correctly', done => {
+    const vm = new Vue({
+      data: { n: 1 },
+      template: '<div><test><span v-for="i in n" :key="i">{{ i }}</span><input value="a"/></test></div>',
+      components: {
+        test: {
+          template: '<div><slot></slot></div>'
+        }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<div><span>1</span><input value="a"></div>')
+    const input = vm.$el.querySelector('input')
+    input.value = 'b'
+    vm.n++
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<div><span>1</span><span>2</span><input value="a"></div>')
+      expect(vm.$el.querySelector('input')).toBe(input)
+      expect(vm.$el.querySelector('input').value).toBe('b')
+    }).then(done)
+  })
 })

filename: test/unit/modules/vdom/create-element.spec.js
patch: @@ -152,4 +152,32 @@ describe('create-element', () => {
     }).$mount()
     expect('Avoid using observed data object as vnode data').toHaveBeenWarned()
   })
+
+  it('nested child elements should be updated correctly', done => {
+    const vm = new Vue({
+      data: { n: 1 },
+      render (h) {
+        const list = []
+        for (let i = 0; i < this.n; i++) {
+          list.push(h('span', i))
+        }
+        const input = h('input', {
+          attrs: {
+            value: 'a',
+            type: 'text'
+          }
+        })
+        return h('div', [[...list, input]])
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>0</span><input value="a" type="text">')
+    const el = vm.$el.querySelector('input')
+    el.value = 'b'
+    vm.n++
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>0</span><span>1</span><input value="a" type="text">')
+      expect(vm.$el.querySelector('input')).toBe(el)
+      expect(vm.$el.querySelector('input').value).toBe('b')
+    }).then(done)
+  })
 })