diff --git a/src/core/instance/render-helpers/render-list.js b/src/core/instance/render-helpers/render-list.js index abe99db4d6d..8ac388fba16 100644 --- a/src/core/instance/render-helpers/render-list.js +++ b/src/core/instance/render-helpers/render-list.js @@ -1,6 +1,6 @@ /* @flow */ -import { isObject, isDef } from 'core/util/index' +import { isObject, isDef, hasSymbol } from 'core/util/index' /** * Runtime helper for rendering v-for lists. @@ -25,11 +25,21 @@ export function renderList ( ret[i] = render(i + 1, i) } } else if (isObject(val)) { - keys = Object.keys(val) - ret = new Array(keys.length) - for (i = 0, l = keys.length; i < l; i++) { - key = keys[i] - ret[i] = render(val[key], key, i) + if (hasSymbol && val[Symbol.iterator]) { + ret = [] + const iterator: Iterator = val[Symbol.iterator]() + let result = iterator.next() + while (!result.done) { + ret.push(render(result.value, ret.length)) + result = iterator.next() + } + } else { + keys = Object.keys(val) + ret = new Array(keys.length) + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i] + ret[i] = render(val[key], key, i) + } } } if (isDef(ret)) { diff --git a/test/unit/features/directives/for.spec.js b/test/unit/features/directives/for.spec.js index 22528be62fc..fc8c05c924e 100644 --- a/test/unit/features/directives/for.spec.js +++ b/test/unit/features/directives/for.spec.js @@ -1,4 +1,5 @@ import Vue from 'vue' +import { hasSymbol } from 'core/util/env' describe('Directive v-for', () => { it('should render array of primitive values', done => { @@ -123,6 +124,205 @@ describe('Directive v-for', () => { }).then(done) }) + if (hasSymbol) { + it('should render iterable of primitive values', done => { + const iterable = { + models: ['a', 'b', 'c'], + index: 0, + [Symbol.iterator] () { + const iterator = { + index: 0, + models: this.models, + next () { + if (this.index < this.models.length) { + return { value: this.models[this.index++] } + } else { + return { done: true } + } + } + } + return iterator + } + } + const vm = new Vue({ + template: ` +
+ {{item}} +
+ `, + data: { + list: iterable + } + }).$mount() + expect(vm.$el.innerHTML).toBe('abc') + Vue.set(vm.list.models, 0, 'd') + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('dbc') + vm.list.models.push('d') + }).then(() => { + expect(vm.$el.innerHTML).toBe('dbcd') + vm.list.models.splice(1, 2) + }).then(() => { + expect(vm.$el.innerHTML).toBe('dd') + vm.list.models = ['x', 'y'] + }).then(() => { + expect(vm.$el.innerHTML).toBe('xy') + }).then(done) + }) + + it('should render iterable of primitive values with index', done => { + const iterable = { + models: ['a', 'b', 'c'], + index: 0, + [Symbol.iterator] () { + const iterator = { + index: 0, + models: this.models, + next () { + if (this.index < this.models.length) { + return { value: this.models[this.index++] } + } else { + return { done: true } + } + } + } + return iterator + } + } + + const vm = new Vue({ + template: ` +
+ {{i}}-{{item}} +
+ `, + data: { + list: iterable + } + }).$mount() + expect(vm.$el.innerHTML).toBe('0-a1-b2-c') + Vue.set(vm.list.models, 0, 'd') + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('0-d1-b2-c') + vm.list.models.push('d') + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-d1-b2-c3-d') + vm.list.models.splice(1, 2) + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-d1-d') + vm.list.models = ['x', 'y'] + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-x1-y') + }).then(done) + }) + + it('should render iterable of object values', done => { + const iterable = { + models: [ + { value: 'a' }, + { value: 'b' }, + { value: 'c' } + ], + index: 0, + [Symbol.iterator] () { + const iterator = { + index: 0, + models: this.models, + next () { + if (this.index < this.models.length) { + return { value: this.models[this.index++] } + } else { + return { done: true } + } + } + } + return iterator + } + } + + const vm = new Vue({ + template: ` +
+ {{item.value}} +
+ `, + data: { + list: iterable + } + }).$mount() + expect(vm.$el.innerHTML).toBe('abc') + Vue.set(vm.list.models, 0, { value: 'd' }) + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('dbc') + vm.list.models[0].value = 'e' + }).then(() => { + expect(vm.$el.innerHTML).toBe('ebc') + vm.list.models.push({}) + }).then(() => { + expect(vm.$el.innerHTML).toBe('ebc') + vm.list.models.splice(1, 2) + }).then(() => { + expect(vm.$el.innerHTML).toBe('e') + vm.list.models = [{ value: 'x' }, { value: 'y' }] + }).then(() => { + expect(vm.$el.innerHTML).toBe('xy') + }).then(done) + }) + + it('should render iterable of object values with index', done => { + const iterable = { + models: [ + { value: 'a' }, + { value: 'b' }, + { value: 'c' } + ], + index: 0, + [Symbol.iterator] () { + const iterator = { + index: 0, + models: this.models, + next () { + if (this.index < this.models.length) { + return { value: this.models[this.index++] } + } else { + return { done: true } + } + } + } + return iterator + } + } + + const vm = new Vue({ + template: ` +
+ {{i}}-{{item.value}} +
+ `, + data: { + list: iterable + } + }).$mount() + expect(vm.$el.innerHTML).toBe('0-a1-b2-c') + Vue.set(vm.list.models, 0, { value: 'd' }) + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe('0-d1-b2-c') + vm.list.models[0].value = 'e' + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-e1-b2-c') + vm.list.models.push({}) + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-e1-b2-c3-') + vm.list.models.splice(1, 2) + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-e1-') + vm.list.models = [{ value: 'x' }, { value: 'y' }] + }).then(() => { + expect(vm.$el.innerHTML).toBe('0-x1-y') + }).then(done) + }) + } + it('should render an Object', done => { const vm = new Vue({ template: `