var _ = require('../../../../src/util') var Vue = require('../../../../src/vue') if (_.inBrowser) { describe('v-repeat', function () { var el beforeEach(function () { el = document.createElement('div') spyOn(_, 'warn') }) it('objects', function (done) { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}] }, template: '
{{$index}} {{a}}
' }) assertMutations(vm, el, done) }) it('primitive values', function (done) { var vm = new Vue({ el: el, data: { items: [2, 1, 2] }, template: '
{{$index}} {{$value}}
' }) assertPrimitiveMutations(vm, el, done) }) it('objects with identifier', function (done) { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}] }, template: '
{{$index}} {{item.a}}
' }) assertMutations(vm, el, done) }) it('primitive with identifier', function (done) { var vm = new Vue({ el: el, data: { items: [2, 1, 2] }, template: '
{{$index}} {{item}}
' }) assertPrimitiveMutations(vm, el, done) }) it('repeating an object of objects', function (done) { var vm = new Vue({ el: el, data: { items: { a: {a:1}, b: {a:2} } }, template: '
{{$index}} {{$key}} {{a}}
' }) assertObjectMutations(vm, el, done) }) it('repeating an object of primitives', function (done) { var vm = new Vue({ el: el, data: { items: { a: 1, b: 2 } }, template: '
{{$index}} {{$key}} {{$value}}
' }) assertObjectPrimitiveMutations(vm, el, done) }) it('repeating an object of objects with identifier', function (done) { var vm = new Vue({ el: el, data: { items: { a: {a:1}, b: {a:2} } }, template: '
{{$index}} {{$key}} {{item.a}}
' }) assertObjectMutations(vm, el, done) }) it('repeating an object of primitives with identifier', function (done) { var vm = new Vue({ el: el, data: { items: { a: 1, b: 2 } }, template: '
{{$index}} {{$key}} {{item}}
' }) assertObjectPrimitiveMutations(vm, el, done) }) it('array of arrays', function () { var vm = new Vue({ el: el, data: { items: [[1,1], [2,2], [3,3]] }, template: '
{{$index}} {{$value}}
' }) var markup = vm.items.map(function (item, i) { return '
' + i + ' ' + item.toString() + '
' }).join('') + '' expect(el.innerHTML).toBe(markup) }) it('repeating object with filter', function () { var vm = new Vue({ el: el, data: { items: { a: { msg: 'aaa' }, b: { msg: 'bbb' } } }, template: '
{{msg}}
' }) expect(el.innerHTML).toBe('
aaa
') }) it('v-component', function (done) { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}] }, template: '

', components: { test: { template: '
{{$index}} {{a}}
', replace: true } } }) assertMutations(vm, el, done) }) it('v-component with inline-template', function (done) { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}] }, template: '
' + '{{$index}} {{a}}' + '
', components: { test: {} } }) assertMutations(vm, el, done) }) it('v-component with primitive values', function (done) { var vm = new Vue({ el: el, data: { items: [2, 1, 2] }, template: '

', components: { test: { template: '
{{$index}} {{$value}}
', replace: true } } }) assertPrimitiveMutations(vm, el, done) }) it('v-component with object of objects', function (done) { var vm = new Vue({ el: el, data: { items: { a: {a:1}, b: {a:2} } }, template: '

', components: { test: { template: '
{{$index}} {{$key}} {{a}}
', replace: true } } }) assertObjectMutations(vm, el, done) }) it('custom element component', function () { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}, {a:3}] }, template: '', components: { 'test-component': { template: '{{$index}} {{a}}' } } }) expect(el.innerHTML).toBe( '0 1' + '1 2' + '2 3' + '' ) }) it('custom element component with replace:true', function () { var vm = new Vue({ el: el, data: { items: [{a:1}, {a:2}, {a:3}] }, template: '', components: { 'test-component': { template: '

{{$index}} {{a}}

', replace: true } } }) expect(el.innerHTML).toBe('

0 1

1 2

2 3

') }) it('nested repeats', function () { var vm = new Vue({ el: el, data: { items: [ { items: [{a:1}, {a:2}], a: 1 }, { items: [{a:3}, {a:4}], a: 2 } ] }, template: '
' + '

{{$index}} {{a}} {{$parent.$index}} {{$parent.a}}

' + '
' }) expect(el.innerHTML).toBe( '

0 1 0 1

1 2 0 1

' + '

0 3 1 2

1 4 1 2

' + '' ) }) it('nested repeats on object', function(){ var vm = new Vue({ el: el, data: { listHash: { listA: [{a: 1},{a: 2}], listB: [{a: 1},{a: 2}] } }, template: '
{{$key}}' + '

{{a}}

' + '
' }) function output(key){ var key1 = key === 'listA' ? 'listB' : 'listA' return '
'+ key +'

1

2

' + '
'+ key1 +'

1

2

' + '' } expect(el.innerHTML === output('listA') || el.innerHTML === output('listB')).toBeTruthy() }) it('dynamic component type based on instance data', function () { var vm = new Vue({ el: el, template: '
', data: { list: [ { type: 'a' }, { type: 'b' }, { type: 'c' } ] }, components: { 'view-a': { template: 'AAA' }, 'view-b': { template: 'BBB' }, 'view-c': { template: 'CCC' } } }) expect(el.innerHTML).toBe('
AAA
BBB
CCC
') // #458 meta properties vm = new Vue({ el: el, template: '
', data: { list: ['a', 'b', 'c'] }, components: { 'view-a': { template: 'AAA' }, 'view-b': { template: 'BBB' }, 'view-c': { template: 'CCC' } } }) expect(el.innerHTML).toBe('
AAA
BBB
CCC
') }) it('block repeat', function (done) { var vm = new Vue({ el: el, template: '', data: { list: [ { a: 1 }, { a: 2 }, { a: 3 } ] } }) assertMarkup() vm.list.reverse() _.nextTick(function () { assertMarkup() done() }) function assertMarkup () { var markup = vm.list.map(function (item) { return '

' + item.a + '

' + (item.a + 1) + '

' }).join('') expect(el.innerHTML).toBe(markup + '') } }) // added for #799 it('block repeat with diff', function (done) { var vm = new Vue({ el: el, template: '', data: { list: [ { a: 1 }, { a: 2 }, { a: 3 } ] }, components: { test: { template: '

{{a}}

{{a + 1}}

' } } }) assertMarkup() vm.list.reverse() _.nextTick(function () { assertMarkup() done() }) function assertMarkup () { var markup = vm.list.map(function (item) { return '

' + item.a + '

' + (item.a + 1) + '

' }).join('') expect(el.innerHTML).toBe(markup + '') } }) it('component + parent directive + transclusion', function (done) { var vm = new Vue({ el: el, template: '
{{msg}}
', data: { cls: 'parent', msg: 'hi', list: [{a:1},{a:2},{a:3}] }, components: { test: { replace: true, template: '
{{a}}
' } } }) var markup = vm.list.map(function (item) { return '
' + item.a + ' hi
' }).join('') expect(el.innerHTML).toBe(markup + '') vm.msg = 'ho' markup = vm.list.map(function (item) { return '
' + item.a + ' ho
' }).join('') _.nextTick(function () { expect(el.innerHTML).toBe(markup + '') done() }) }) it('array filters', function (done) { var vm = new Vue({ el: el, template: '
{{id}}
', data: { filterKey: 'hi!', sortKey: 'id', list: [ { id: 1, id2: 4, msg: 'hi!' }, { id: 2, id2: 3, msg: 'na' }, { id: 3, id2: 2, msg: 'hi!' }, { id: 4, id2: 1, msg: 'na' } ] } }) assertMarkup() go( function () { vm.filterKey = 'na' }, assertMarkup ) .then( function () { vm.sortKey = 'id2' }, assertMarkup ) .then( function () { vm.list[0].id2 = 0 }, assertMarkup ) .then( function () { vm.list.push({ id: 0, id2: 4, msg: 'na' }) }, assertMarkup ) .then( function () { vm.list = [ { id: 33, id2: 4, msg: 'hi!' }, { id: 44, id2: 3, msg: 'na' } ] }, assertMarkup ) .run(done) function assertMarkup () { var markup = vm.list .filter(function (item) { return item.msg === vm.filterKey }) .sort(function (a, b) { return a[vm.sortKey] > b[vm.sortKey] ? -1 : 1 }) .map(function (item) { return '
' + item.id + '
' }).join('') expect(el.innerHTML).toBe(markup + '') } }) it('orderBy supporting $key for object repeaters', function (done) { var vm = new Vue({ el: el, template: '
{{$value}}
', data: { sortKey: '$key', obj: { c: 1, a: 3, b: 2 } } }) expect(el.innerHTML).toBe('
3
2
1
') vm.sortKey = '$value' _.nextTick(function () { expect(el.innerHTML).toBe('
1
2
3
') done() }) }) it('orderBy supporting $value for primitive arrays', function () { var vm = new Vue({ el: el, template: '
{{$value}}
', data: { list: [3, 2, 1] } }) expect(el.innerHTML).toBe('
1
2
3
') }) it('track by id', function (done) { assertTrackBy('
', '{{msg}}', function () { assertTrackBy('
', '{{item.msg}}', done) }) function assertTrackBy (template, componentTemplate, next) { var vm = new Vue({ el: el, template: template, data: { list: [ { id: 1, msg: 'hi' }, { id: 2, msg: 'ha' }, { id: 3, msg: 'ho' } ] }, components: { test: { template: componentTemplate } } }) assertMarkup() var oldVms = vm._children.slice() // swap the data with different objects, but with // the same ID! vm.list = [ { id: 1, msg: 'wa' }, { id: 2, msg: 'wo' } ] _.nextTick(function () { assertMarkup() // should reuse old vms! var i = 2 while (i--) { expect(vm._children[i]).toBe(oldVms[i]) } next() }) function assertMarkup () { var markup = vm.list.map(function (item) { return '
' + item.msg + '
' }).join('') expect(el.innerHTML).toBe(markup + '') } } }) it('warn duplicate objects', function () { var obj = {} var vm = new Vue({ el: el, template: '
', data: { items: [obj, obj] }, components: { test: {} } }) expect(_.warn).toHaveBeenCalled() }) it('warn duplicate trackby id', function () { var vm = new Vue({ el: el, template: '
', data: { items: [{id:1}, {id:1}] }, components: { test: {} } }) expect(_.warn).toHaveBeenCalled() }) it('warn v-if', function () { var vm = new Vue({ el: el, template: '
', data: { items: [] } }) expect(_.warn).toHaveBeenCalled() }) it('repeat number', function () { var vm = new Vue({ el: el, template: '
{{$index}} {{$value}}
' }) expect(el.innerHTML).toBe('
0 0
1 1
2 2
') }) it('repeat string', function () { var vm = new Vue({ el: el, template: '
{{$index}} {{$value}}
' }) expect(el.innerHTML).toBe('
0 v
1 u
2 e
') }) it('teardown', function () { var vm = new Vue({ el: el, template: '
', data: { items: [{a:1}, {a:2}] }, components: { test: {} } }) vm._directives[0].unbind() expect(vm._children.length).toBe(0) }) it('with transition', function (done) { document.body.appendChild(el) var vm = new Vue({ el: el, template: '
{{a}}
', data: { items: [{a:1}, {a:2}, {a:3}] }, transitions: { test: { leave: function (el, done) { setTimeout(done, 1) } } } }) vm.items.splice(1, 1, {a:4}) setTimeout(function () { expect(el.innerHTML).toBe('
1
4
3
') document.body.removeChild(el) done() }, 30) }) it('sync $value changes back to original array/object', function (done) { var vm = new Vue({ el: el, template: '
{{$value}}
' + '
{{$value}}
', data: { items: ['a', 'b'], obj: { foo: 'a', bar: 'b' } } }) vm._children[0].$value = 'c' var key = vm._children[2].$key vm._children[2].$value = 'd' _.nextTick(function () { expect(vm.items[0]).toBe('c') expect(vm.obj[key]).toBe('d') done() }) }) it('nested track by', function (done) { assertTrackBy('
{{msg}}
{{msg}}
', function () { assertTrackBy('
{{msg}}
{{msg}}
', done) }) function assertTrackBy(template, next) { var vm = new Vue({ el: el, data: { list: [ { id: 1, msg: 'hi', list: [ { id: 1, msg: 'hi foo' } ] }, { id: 2, msg: 'ha', list: [] }, { id: 3, msg: 'ho', list: [] } ] }, template: template }) assertMarkup() var oldVms = vm._children.slice() vm.list = [ { id: 1, msg: 'wa', list: [ { id: 1, msg: 'hi foo' }, { id: 2, msg: 'hi bar' } ] }, { id: 2, msg: 'wo', list: [] } ] _.nextTick(function () { assertMarkup() // should reuse old vms! var i = 2 while (i--) { expect(vm._children[i]).toBe(oldVms[i]) } expect(vm._children[0]._children[0]).toBe(oldVms[0]._children[0]) next() }) function assertMarkup () { var markup = vm.list.map(function (item) { var sublist = item.list.map(function (item) { return '
' + item.msg + '
' }).join('') + '' return '
' + item.msg + sublist + '
' }).join('') + '' expect(el.innerHTML).toBe(markup) } } }) }) } /** * Simple helper for chained async asssertions * * @param {Function} fn - the data manipulation function * @param {Function} cb - the assertion fn to be called on nextTick */ function go (fn, cb) { return { stack: [{fn:fn, cb:cb}], then: function (fn, cb) { this.stack.push({fn:fn, cb:cb}) return this }, run: function (done) { var self = this var step = this.stack.shift() if (!step) return done() step.fn() _.nextTick(function () { step.cb() self.run(done) }) } } } /** * Assert mutation and markup correctness for v-repeat on * an Array of Objects */ function assertMutations (vm, el, done) { assertMarkup() var poppedItem go( function () { vm.items.push({a:3}) }, assertMarkup ) .then( function () { vm.items.shift() }, assertMarkup ) .then( function () { vm.items.reverse() }, assertMarkup ) .then( function () { poppedItem = vm.items.pop() }, assertMarkup ) .then( function () { vm.items.unshift(poppedItem) }, assertMarkup ) .then( function () { vm.items.sort(function (a, b) { return a.a > b.a ? 1 : -1 }) }, assertMarkup ) .then( function () { vm.items.splice(1, 1, {a:5}) }, assertMarkup ) // test swapping the array .then( function () { vm.items = [{a:0}, {a:1}, {a:2}] }, assertMarkup ) .run(done) function assertMarkup () { var markup = vm.items.map(function (item, i) { return '
' + i + ' ' + item.a + '
' }).join('') expect(el.innerHTML).toBe(markup + '') } } /** * Assert mutation and markup correctness for v-repeat on * an Array of primitive values */ function assertPrimitiveMutations (vm, el, done) { assertMarkup() go( function () { // check duplicate vm.items.push(2, 2, 3) }, assertMarkup ) .then( function () { vm.items.shift() }, assertMarkup ) .then( function () { vm.items.reverse() }, assertMarkup ) .then( function () { vm.items.pop() }, assertMarkup ) .then( function () { vm.items.unshift(3) }, assertMarkup ) .then( function () { vm.items.sort(function (a, b) { return a > b ? 1 : -1 }) }, assertMarkup ) .then( function () { vm.items.splice(1, 1, 5) }, assertMarkup ) // test swapping the array .then( function () { vm.items = [1, 2, 2] }, assertMarkup ) .run(done) function assertMarkup () { var markup = vm.items.map(function (item, i) { return '
' + i + ' ' + item + '
' }).join('') expect(el.innerHTML).toBe(markup + '') } } /** * Assert mutation and markup correctness for v-repeat on * an Object of Objects */ function assertObjectMutations (vm, el, done) { assertMarkup() go( function () { vm.items.a = {a:3} }, assertMarkup ) .then( function () { vm.items = { c: {a:1}, d: {a:2} } }, assertMarkup ) .then( function () { vm.items.$add('a', {a:3}) }, assertMarkup ) .run(done) function assertMarkup () { var markup = Object.keys(vm.items).map(function (key, i) { return '
' + i + ' ' + key + ' ' + vm.items[key].a + '
' }).join('') expect(el.innerHTML).toBe(markup + '') } } /** * Assert mutation and markup correctness for v-repeat on * an Object of primitive values */ function assertObjectPrimitiveMutations (vm, el, done) { assertMarkup() go( function () { vm.items.a = 3 }, assertMarkup ) .then( function () { vm.items = { c: 1, d: 2 } }, assertMarkup ) .then( function () { vm.items.$add('a', 3) }, assertMarkup ) .run(done) function assertMarkup () { var markup = Object.keys(vm.items).map(function (key, i) { return '
' + i + ' ' + key + ' ' + vm.items[key] + '
' }).join('') expect(el.innerHTML).toBe(markup + '') } }