From 936cc0ad816350d9bdc61945256ce60e80f235ed Mon Sep 17 00:00:00 2001 From: mymyoux Date: Sat, 12 May 2018 12:50:15 +0200 Subject: [PATCH 1/3] feat(core): v-for to support Iterable objects --- .../instance/render-helpers/render-list.js | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/core/instance/render-helpers/render-list.js b/src/core/instance/render-helpers/render-list.js index abe99db4d6d..f068cfc6765 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,27 @@ 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]) { + const iterator:Iterator = val[Symbol.iterator]() + ret = [] + i = 0 + let next + do { + next = iterator.next() + if (!next.done) { + key = i + ret.push(render(next.value, key, i)) + i++ + } + } + while (!next.done) + } 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)) { From a73e1c6a3c6f850df4821b082aa518de89d00218 Mon Sep 17 00:00:00 2001 From: mymyoux Date: Sat, 12 May 2018 13:55:38 +0200 Subject: [PATCH 2/3] test(v-for): add tests for iterable --- test/unit/features/directives/for.spec.js | 200 ++++++++++++++++++++++ 1 file changed, 200 insertions(+) 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: ` From 91d92038c3061c3be0d8d23da1079a42354dc1fe Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 26 Dec 2018 15:18:34 -0500 Subject: [PATCH 3/3] Update render-list.js --- src/core/instance/render-helpers/render-list.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/core/instance/render-helpers/render-list.js b/src/core/instance/render-helpers/render-list.js index f068cfc6765..8ac388fba16 100644 --- a/src/core/instance/render-helpers/render-list.js +++ b/src/core/instance/render-helpers/render-list.js @@ -26,19 +26,13 @@ export function renderList ( } } else if (isObject(val)) { if (hasSymbol && val[Symbol.iterator]) { - const iterator:Iterator = val[Symbol.iterator]() ret = [] - i = 0 - let next - do { - next = iterator.next() - if (!next.done) { - key = i - ret.push(render(next.value, key, i)) - i++ - } + const iterator: Iterator = val[Symbol.iterator]() + let result = iterator.next() + while (!result.done) { + ret.push(render(result.value, ret.length)) + result = iterator.next() } - while (!next.done) } else { keys = Object.keys(val) ret = new Array(keys.length)