Skip to content

Commit

Permalink
feat(v-for): support iterables in v-for (#8179)
Browse files Browse the repository at this point in the history
  • Loading branch information
mymyoux authored and yyx990803 committed Dec 26, 2018
1 parent e1abedb commit d40eb9c
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 6 deletions.
22 changes: 16 additions & 6 deletions src/core/instance/render-helpers/render-list.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<any> = 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)) {
Expand Down
200 changes: 200 additions & 0 deletions test/unit/features/directives/for.spec.js
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down Expand Up @@ -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: `
<div>
<span v-for="item in list">{{item}}</span>
</div>
`,
data: {
list: iterable
}
}).$mount()
expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
Vue.set(vm.list.models, 0, 'd')
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
vm.list.models.push('d')
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span><span>d</span>')
vm.list.models.splice(1, 2)
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')
vm.list.models = ['x', 'y']
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
}).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: `
<div>
<span v-for="(item, i) in list">{{i}}-{{item}}</span>
</div>
`,
data: {
list: iterable
}
}).$mount()
expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
Vue.set(vm.list.models, 0, 'd')
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
vm.list.models.push('d')
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span><span>3-d</span>')
vm.list.models.splice(1, 2)
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')
vm.list.models = ['x', 'y']
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
}).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: `
<div>
<span v-for="item in list">{{item.value}}</span>
</div>
`,
data: {
list: iterable
}
}).$mount()
expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
Vue.set(vm.list.models, 0, { value: 'd' })
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
vm.list.models[0].value = 'e'
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span>')
vm.list.models.push({})
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span><span></span>')
vm.list.models.splice(1, 2)
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')
vm.list.models = [{ value: 'x' }, { value: 'y' }]
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
}).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: `
<div>
<span v-for="(item, i) in list">{{i}}-{{item.value}}</span>
</div>
`,
data: {
list: iterable
}
}).$mount()
expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
Vue.set(vm.list.models, 0, { value: 'd' })
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
vm.list.models[0].value = 'e'
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span>')
vm.list.models.push({})
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>')
vm.list.models.splice(1, 2)
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')
vm.list.models = [{ value: 'x' }, { value: 'y' }]
}).then(() => {
expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
}).then(done)
})
}

it('should render an Object', done => {
const vm = new Vue({
template: `
Expand Down

0 comments on commit d40eb9c

Please sign in to comment.