repeat_spec.js 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  1. var _ = require('../../../../src/util')
  2. var Vue = require('../../../../src/vue')
  3. if (_.inBrowser) {
  4. describe('v-repeat', function () {
  5. var el
  6. beforeEach(function () {
  7. el = document.createElement('div')
  8. spyOn(_, 'warn')
  9. })
  10. it('objects', function (done) {
  11. var vm = new Vue({
  12. el: el,
  13. data: {
  14. items: [{a: 1}, {a: 2}]
  15. },
  16. template: '<div v-repeat="items">{{$index}} {{a}}</div>'
  17. })
  18. assertMutations(vm, el, done)
  19. })
  20. it('primitive values', function (done) {
  21. var vm = new Vue({
  22. el: el,
  23. data: {
  24. items: [2, 1, 2]
  25. },
  26. template: '<div v-repeat="items">{{$index}} {{$value}}</div>'
  27. })
  28. assertPrimitiveMutations(vm, el, done)
  29. })
  30. it('objects with identifier', function (done) {
  31. var vm = new Vue({
  32. el: el,
  33. data: {
  34. items: [{a: 1}, {a: 2}]
  35. },
  36. template: '<div v-repeat="item:items">{{$index}} {{item.a}}</div>'
  37. })
  38. assertMutations(vm, el, done)
  39. })
  40. it('item in list syntax', function (done) {
  41. var vm = new Vue({
  42. el: el,
  43. data: {
  44. items: [{a: 1}, {a: 2}]
  45. },
  46. template: '<div v-repeat="item in items">{{$index}} {{item.a}}</div>'
  47. })
  48. assertMutations(vm, el, done)
  49. })
  50. it('primitive with identifier', function (done) {
  51. var vm = new Vue({
  52. el: el,
  53. data: {
  54. items: [2, 1, 2]
  55. },
  56. template: '<div v-repeat="item:items">{{$index}} {{item}}</div>'
  57. })
  58. assertPrimitiveMutations(vm, el, done)
  59. })
  60. it('repeating an object of objects', function (done) {
  61. var vm = new Vue({
  62. el: el,
  63. data: {
  64. items: {
  65. a: {a: 1},
  66. b: {a: 2}
  67. }
  68. },
  69. template: '<div v-repeat="items">{{$index}} {{$key}} {{a}}</div>'
  70. })
  71. assertObjectMutations(vm, el, done)
  72. })
  73. it('repeating an object of primitives', function (done) {
  74. var vm = new Vue({
  75. el: el,
  76. data: {
  77. items: {
  78. a: 1,
  79. b: 2
  80. }
  81. },
  82. template: '<div v-repeat="items">{{$index}} {{$key}} {{$value}}</div>'
  83. })
  84. assertObjectPrimitiveMutations(vm, el, done)
  85. })
  86. it('repeating an object of objects with identifier', function (done) {
  87. var vm = new Vue({
  88. el: el,
  89. data: {
  90. items: {
  91. a: {a: 1},
  92. b: {a: 2}
  93. }
  94. },
  95. template: '<div v-repeat="item:items">{{$index}} {{$key}} {{item.a}}</div>'
  96. })
  97. assertObjectMutations(vm, el, done)
  98. })
  99. it('repeating an object of primitives with identifier', function (done) {
  100. var vm = new Vue({
  101. el: el,
  102. data: {
  103. items: {
  104. a: 1,
  105. b: 2
  106. }
  107. },
  108. template: '<div v-repeat="item:items">{{$index}} {{$key}} {{item}}</div>'
  109. })
  110. assertObjectPrimitiveMutations(vm, el, done)
  111. })
  112. it('array of arrays', function () {
  113. var vm = new Vue({
  114. el: el,
  115. data: {
  116. items: [[1, 1], [2, 2], [3, 3]]
  117. },
  118. template: '<div v-repeat="items">{{$index}} {{$value}}</div>'
  119. })
  120. var markup = vm.items.map(function (item, i) {
  121. return '<div>' + i + ' ' + item.toString() + '</div>'
  122. }).join('')
  123. expect(el.innerHTML).toBe(markup)
  124. })
  125. it('repeating object with filter', function () {
  126. new Vue({
  127. el: el,
  128. data: {
  129. items: {
  130. a: { msg: 'aaa' },
  131. b: { msg: 'bbb' }
  132. }
  133. },
  134. template: '<div v-repeat="items | filterBy \'aaa\'">{{msg}}</div>'
  135. })
  136. expect(el.innerHTML).toBe('<div>aaa</div>')
  137. })
  138. it('component', function (done) {
  139. var vm = new Vue({
  140. el: el,
  141. data: {
  142. items: [{a: 1}, {a: 2}]
  143. },
  144. template: '<test v-repeat="items"></test>',
  145. components: {
  146. test: {
  147. template: '<div>{{$index}} {{a}}</div>',
  148. replace: true
  149. }
  150. }
  151. })
  152. assertMutations(vm, el, done)
  153. })
  154. it('v-component', function (done) {
  155. var vm = new Vue({
  156. el: el,
  157. data: {
  158. items: [{a: 1}, {a: 2}]
  159. },
  160. template: '<p v-repeat="items" v-component="test"></p>',
  161. components: {
  162. test: {
  163. template: '<div>{{$index}} {{a}}</div>',
  164. replace: true
  165. }
  166. }
  167. })
  168. assertMutations(vm, el, done)
  169. })
  170. it('component with inline-template', function (done) {
  171. var vm = new Vue({
  172. el: el,
  173. data: {
  174. items: [{a: 1}, {a: 2}]
  175. },
  176. template:
  177. '<test v-repeat="items" inline-template>' +
  178. '{{$index}} {{a}}' +
  179. '</test>',
  180. components: {
  181. test: {}
  182. }
  183. })
  184. assertMutations(vm, el, done)
  185. })
  186. it('component with primitive values', function (done) {
  187. var vm = new Vue({
  188. el: el,
  189. data: {
  190. items: [2, 1, 2]
  191. },
  192. template: '<test v-repeat="items"></test>',
  193. components: {
  194. test: {
  195. template: '<div>{{$index}} {{$value}}</div>',
  196. replace: true
  197. }
  198. }
  199. })
  200. assertPrimitiveMutations(vm, el, done)
  201. })
  202. it('component with object of objects', function (done) {
  203. var vm = new Vue({
  204. el: el,
  205. data: {
  206. items: {
  207. a: {a: 1},
  208. b: {a: 2}
  209. }
  210. },
  211. template: '<test v-repeat="items"></test>',
  212. components: {
  213. test: {
  214. template: '<div>{{$index}} {{$key}} {{a}}</div>',
  215. replace: true
  216. }
  217. }
  218. })
  219. assertObjectMutations(vm, el, done)
  220. })
  221. it('nested repeats', function () {
  222. new Vue({
  223. el: el,
  224. data: {
  225. items: [
  226. { items: [{a: 1}, {a: 2}], a: 1 },
  227. { items: [{a: 3}, {a: 4}], a: 2 }
  228. ]
  229. },
  230. template: '<div v-repeat="items">' +
  231. '<p v-repeat="items">{{$index}} {{a}} {{$parent.$index}} {{$parent.a}}</p>' +
  232. '</div>'
  233. })
  234. expect(el.innerHTML).toBe(
  235. '<div><p>0 1 0 1</p><p>1 2 0 1</p></div>' +
  236. '<div><p>0 3 1 2</p><p>1 4 1 2</p></div>'
  237. )
  238. })
  239. it('nested repeats on object', function () {
  240. new Vue({
  241. el: el,
  242. data: {
  243. listHash: {
  244. listA: [{a: 1}, {a: 2}],
  245. listB: [{a: 1}, {a: 2}]
  246. }
  247. },
  248. template: '<div v-repeat="listHash">{{$key}}' +
  249. '<p v-repeat="$value">{{a}}</p>' +
  250. '</div>'
  251. })
  252. function output (key) {
  253. var key1 = key === 'listA' ? 'listB' : 'listA'
  254. return '<div>' + key + '<p>1</p><p>2</p></div>' +
  255. '<div>' + key1 + '<p>1</p><p>2</p></div>'
  256. }
  257. expect(el.innerHTML === output('listA') || el.innerHTML === output('listB')).toBeTruthy()
  258. })
  259. it('dynamic component type based on instance data', function () {
  260. new Vue({
  261. el: el,
  262. template: '<component v-repeat="list" is="view-{{type}}"></component>',
  263. data: {
  264. list: [
  265. { type: 'a' },
  266. { type: 'b' },
  267. { type: 'c' }
  268. ]
  269. },
  270. components: {
  271. 'view-a': {
  272. template: 'AAA'
  273. },
  274. 'view-b': {
  275. template: 'BBB'
  276. },
  277. 'view-c': {
  278. template: 'CCC'
  279. }
  280. }
  281. })
  282. expect(el.innerHTML).toBe('<component>AAA</component><component>BBB</component><component>CCC</component>')
  283. // #458 meta properties
  284. new Vue({
  285. el: el,
  286. template: '<component v-repeat="list" is="view-{{$value}}"></component>',
  287. data: {
  288. list: ['a', 'b', 'c']
  289. },
  290. components: {
  291. 'view-a': {
  292. template: 'AAA'
  293. },
  294. 'view-b': {
  295. template: 'BBB'
  296. },
  297. 'view-c': {
  298. template: 'CCC'
  299. }
  300. }
  301. })
  302. expect(el.innerHTML).toBe('<component>AAA</component><component>BBB</component><component>CCC</component>')
  303. })
  304. it('block repeat', function (done) {
  305. var vm = new Vue({
  306. el: el,
  307. template: '<template v-repeat="list"><p>{{a}}</p><p>{{a + 1}}</p></template>',
  308. data: {
  309. list: [
  310. { a: 1 },
  311. { a: 2 },
  312. { a: 3 }
  313. ]
  314. }
  315. })
  316. assertMarkup()
  317. vm.list.reverse()
  318. _.nextTick(function () {
  319. assertMarkup()
  320. vm.list.splice(1, 1)
  321. _.nextTick(function () {
  322. assertMarkup()
  323. vm.list.splice(1, 0, { a: 2 })
  324. _.nextTick(function () {
  325. assertMarkup()
  326. done()
  327. })
  328. })
  329. })
  330. function assertMarkup () {
  331. var markup = vm.list.map(function (item) {
  332. return '<p>' + item.a + '</p><p>' + (item.a + 1) + '</p>'
  333. }).join('')
  334. expect(el.innerHTML).toBe(markup)
  335. }
  336. })
  337. it('block repeat with component', function (done) {
  338. var vm = new Vue({
  339. el: el,
  340. template: '<template v-repeat="list"><test a="{{a}}"></test></template>',
  341. data: {
  342. list: [
  343. { a: 1 },
  344. { a: 2 },
  345. { a: 3 }
  346. ]
  347. },
  348. components: {
  349. test: {
  350. props: ['a'],
  351. template: '{{a}}'
  352. }
  353. }
  354. })
  355. assertMarkup()
  356. vm.list.reverse()
  357. _.nextTick(function () {
  358. assertMarkup()
  359. vm.list.splice(1, 1)
  360. _.nextTick(function () {
  361. assertMarkup()
  362. vm.list.splice(1, 0, { a: 2 })
  363. _.nextTick(function () {
  364. assertMarkup()
  365. done()
  366. })
  367. })
  368. })
  369. function assertMarkup () {
  370. var markup = vm.list.map(function (item) {
  371. return '<test>' + item.a + '</test>'
  372. }).join('')
  373. expect(el.innerHTML).toBe(markup)
  374. }
  375. })
  376. it('array filters', function (done) {
  377. var vm = new Vue({
  378. el: el,
  379. template: '<div v-repeat="list | filterBy filterKey | orderBy sortKey -1">{{id}}</div>',
  380. data: {
  381. filterKey: 'hi!',
  382. sortKey: 'id',
  383. list: [
  384. { id: 1, id2: 4, msg: 'hi!' },
  385. { id: 2, id2: 3, msg: 'na' },
  386. { id: 3, id2: 2, msg: 'hi!' },
  387. { id: 4, id2: 1, msg: 'na' }
  388. ]
  389. }
  390. })
  391. assertMarkup()
  392. go(
  393. function () {
  394. vm.filterKey = 'na'
  395. }, assertMarkup
  396. )
  397. .then(
  398. function () {
  399. vm.sortKey = 'id2'
  400. }, assertMarkup
  401. )
  402. .then(
  403. function () {
  404. vm.list[0].id2 = 0
  405. }, assertMarkup
  406. )
  407. .then(
  408. function () {
  409. vm.list.push({ id: 0, id2: 4, msg: 'na' })
  410. }, assertMarkup
  411. )
  412. .then(
  413. function () {
  414. vm.list = [
  415. { id: 33, id2: 4, msg: 'hi!' },
  416. { id: 44, id2: 3, msg: 'na' }
  417. ]
  418. }, assertMarkup
  419. )
  420. .run(done)
  421. function assertMarkup () {
  422. var markup = vm.list
  423. .filter(function (item) {
  424. return item.msg === vm.filterKey
  425. })
  426. .sort(function (a, b) {
  427. return a[vm.sortKey] > b[vm.sortKey] ? -1 : 1
  428. })
  429. .map(function (item) {
  430. return '<div>' + item.id + '</div>'
  431. }).join('')
  432. expect(el.innerHTML).toBe(markup)
  433. }
  434. })
  435. it('orderBy supporting $key for object repeaters', function (done) {
  436. var vm = new Vue({
  437. el: el,
  438. template: '<div v-repeat="obj | orderBy sortKey">{{$value}}</div>',
  439. data: {
  440. sortKey: '$key',
  441. obj: {
  442. c: 1,
  443. a: 3,
  444. b: 2
  445. }
  446. }
  447. })
  448. expect(el.innerHTML).toBe('<div>3</div><div>2</div><div>1</div>')
  449. vm.sortKey = '$value'
  450. _.nextTick(function () {
  451. expect(el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  452. done()
  453. })
  454. })
  455. it('orderBy supporting $value for primitive arrays', function () {
  456. new Vue({
  457. el: el,
  458. template: '<div v-repeat="list | orderBy \'$value\'">{{$value}}</div>',
  459. data: {
  460. list: [3, 2, 1]
  461. }
  462. })
  463. expect(el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  464. })
  465. it('track by id', function (done) {
  466. assertTrackBy('<test v-repeat="list" track-by="id"></test>', '{{msg}}', function () {
  467. assertTrackBy('<test v-repeat="item:list" track-by="id"></test>', '{{item.msg}}', done)
  468. })
  469. function assertTrackBy (template, componentTemplate, next) {
  470. var vm = new Vue({
  471. el: el,
  472. template: template,
  473. data: {
  474. list: [
  475. { id: 1, msg: 'hi' },
  476. { id: 2, msg: 'ha' },
  477. { id: 3, msg: 'ho' }
  478. ]
  479. },
  480. components: {
  481. test: {
  482. template: componentTemplate
  483. }
  484. }
  485. })
  486. assertMarkup()
  487. var oldVms = vm.$children.slice()
  488. // swap the data with different objects, but with
  489. // the same ID!
  490. vm.list = [
  491. { id: 1, msg: 'wa' },
  492. { id: 2, msg: 'wo' }
  493. ]
  494. _.nextTick(function () {
  495. assertMarkup()
  496. // should reuse old vms!
  497. var i = 2
  498. while (i--) {
  499. expect(vm.$children[i]).toBe(oldVms[i])
  500. }
  501. next()
  502. })
  503. function assertMarkup () {
  504. var markup = vm.list.map(function (item) {
  505. return '<test>' + item.msg + '</test>'
  506. }).join('')
  507. expect(el.innerHTML).toBe(markup)
  508. }
  509. }
  510. })
  511. it('track by $index', function (done) {
  512. var vm = new Vue({
  513. el: el,
  514. data: {
  515. items: [{a: 1}, {a: 2}]
  516. },
  517. template: '<div v-repeat="items" track-by="$index">{{$index}} {{a}}</div>'
  518. })
  519. assertMarkup()
  520. var el1 = el.children[0]
  521. var el2 = el.children[1]
  522. vm.items = [{a: 3}, {a: 2}, {a: 1}]
  523. _.nextTick(function () {
  524. assertMarkup()
  525. // should mutate the DOM in-place
  526. expect(el.children[0]).toBe(el1)
  527. expect(el.children[1]).toBe(el2)
  528. done()
  529. })
  530. function assertMarkup () {
  531. expect(el.innerHTML).toBe(vm.items.map(function (item, i) {
  532. return '<div>' + i + ' ' + item.a + '</div>'
  533. }).join(''))
  534. }
  535. })
  536. it('warn duplicate objects on initial render', function () {
  537. var obj = {}
  538. new Vue({
  539. el: el,
  540. template: '<div v-repeat="items"></div>',
  541. data: {
  542. items: [obj, obj]
  543. }
  544. })
  545. expect(hasWarned(_, 'Duplicate objects')).toBe(true)
  546. })
  547. it('warn duplicate objects on diff', function (done) {
  548. var obj = {}
  549. var vm = new Vue({
  550. el: el,
  551. template: '<div v-repeat="items"></div>',
  552. data: {
  553. items: [obj]
  554. }
  555. })
  556. expect(_.warn).not.toHaveBeenCalled()
  557. vm.items.push(obj)
  558. _.nextTick(function () {
  559. expect(hasWarned(_, 'Duplicate objects')).toBe(true)
  560. done()
  561. })
  562. })
  563. it('warn duplicate trackby id', function () {
  564. new Vue({
  565. el: el,
  566. template: '<div v-repeat="items" track-by="id"></div>',
  567. data: {
  568. items: [{id: 1}, {id: 1}]
  569. }
  570. })
  571. expect(hasWarned(_, 'Duplicate objects with the same track-by key')).toBe(true)
  572. })
  573. it('warn v-if', function () {
  574. new Vue({
  575. el: el,
  576. template: '<div v-repeat="items" v-if="aaa"></div>',
  577. data: {
  578. items: []
  579. }
  580. })
  581. expect(hasWarned(_, 'Don\'t use v-if')).toBe(true)
  582. })
  583. it('repeat number', function () {
  584. new Vue({
  585. el: el,
  586. template: '<div v-repeat="3">{{$index}} {{$value}}</div>'
  587. })
  588. expect(el.innerHTML).toBe('<div>0 0</div><div>1 1</div><div>2 2</div>')
  589. })
  590. it('repeat string', function () {
  591. new Vue({
  592. el: el,
  593. template: '<div v-repeat="\'vue\'">{{$index}} {{$value}}</div>'
  594. })
  595. expect(el.innerHTML).toBe('<div>0 v</div><div>1 u</div><div>2 e</div>')
  596. })
  597. it('teardown', function () {
  598. var vm = new Vue({
  599. el: el,
  600. template: '<div v-repeat="items"></div>',
  601. data: {
  602. items: [{a: 1}, {a: 2}]
  603. }
  604. })
  605. vm._directives[0].unbind()
  606. expect(vm.$children.length).toBe(0)
  607. })
  608. it('with transition', function (done) {
  609. document.body.appendChild(el)
  610. var vm = new Vue({
  611. el: el,
  612. template: '<div v-repeat="items" v-transition="test">{{a}}</div>',
  613. data: {
  614. items: [{a: 1}, {a: 2}, {a: 3}]
  615. },
  616. transitions: {
  617. test: {
  618. leave: function (el, done) {
  619. setTimeout(done, 0)
  620. }
  621. }
  622. }
  623. })
  624. vm.items.splice(1, 1, {a: 4})
  625. setTimeout(function () {
  626. expect(el.innerHTML).toBe(
  627. '<div class="test-transition">1</div>' +
  628. '<div class="test-transition">4</div>' +
  629. '<div class="test-transition">3</div>'
  630. )
  631. document.body.removeChild(el)
  632. done()
  633. }, 100)
  634. })
  635. it('sync $value/alias changes back to original array/object', function (done) {
  636. var vm = new Vue({
  637. el: el,
  638. template:
  639. '<div v-repeat="items">{{$value}}</div>' +
  640. '<div v-repeat="obj">{{$value}}</div>' +
  641. '<div v-repeat="val:vals">{{val}}</div>',
  642. data: {
  643. items: ['a', true],
  644. obj: { foo: 'a', bar: 'b' },
  645. vals: [1, null]
  646. }
  647. })
  648. vm.$children[0].$value = 'c'
  649. vm.$children[1].$value = 'd'
  650. var key = vm.$children[2].$key
  651. vm.$children[2].$value = 'e'
  652. vm.$children[4].val = 3
  653. vm.$children[5].val = 4
  654. _.nextTick(function () {
  655. expect(vm.items[0]).toBe('c')
  656. expect(vm.items[1]).toBe('d')
  657. expect(vm.obj[key]).toBe('e')
  658. expect(vm.vals[0]).toBe(3)
  659. expect(vm.vals[1]).toBe(4)
  660. done()
  661. })
  662. })
  663. it('warn $value sync with filters', function (done) {
  664. var vm = new Vue({
  665. el: el,
  666. template: '<div v-repeat="items | orderBy \'$value\'"></div>',
  667. data: {
  668. items: ['a', 'b']
  669. }
  670. })
  671. vm.$children[0].$value = 'c'
  672. _.nextTick(function () {
  673. expect(hasWarned(_, 'use an Array of Objects instead')).toBe(true)
  674. done()
  675. })
  676. })
  677. it('nested track by', function (done) {
  678. assertTrackBy('<div v-repeat="list" track-by="id">{{msg}}<div v-repeat="list" track-by="id">{{msg}}</div></div>', function () {
  679. assertTrackBy('<div v-repeat="list" track-by="id">{{msg}}<div v-repeat="list" track-by="id">{{msg}}</div></div>', done)
  680. })
  681. function assertTrackBy (template, next) {
  682. var vm = new Vue({
  683. el: el,
  684. data: {
  685. list: [
  686. { id: 1, msg: 'hi', list: [
  687. { id: 1, msg: 'hi foo' }
  688. ] },
  689. { id: 2, msg: 'ha', list: [] },
  690. { id: 3, msg: 'ho', list: [] }
  691. ]
  692. },
  693. template: template
  694. })
  695. assertMarkup()
  696. var oldVms = vm.$children.slice()
  697. vm.list = [
  698. { id: 1, msg: 'wa', list: [
  699. { id: 1, msg: 'hi foo' },
  700. { id: 2, msg: 'hi bar' }
  701. ] },
  702. { id: 2, msg: 'wo', list: [] }
  703. ]
  704. _.nextTick(function () {
  705. assertMarkup()
  706. // should reuse old vms!
  707. var i = 2
  708. while (i--) {
  709. expect(vm.$children[i]).toBe(oldVms[i])
  710. }
  711. expect(vm.$children[0].$children[0]).toBe(oldVms[0].$children[0])
  712. next()
  713. })
  714. function assertMarkup () {
  715. var markup = vm.list.map(function (item) {
  716. var sublist = item.list.map(function (item) {
  717. return '<div>' + item.msg + '</div>'
  718. }).join('')
  719. return '<div>' + item.msg + sublist + '</div>'
  720. }).join('')
  721. expect(el.innerHTML).toBe(markup)
  722. }
  723. }
  724. })
  725. it('switch between object-converted & array mode', function (done) {
  726. var obj = {
  727. a: { msg: 'AA' },
  728. b: { msg: 'BB' }
  729. }
  730. var arr = [obj.b, obj.a]
  731. var vm = new Vue({
  732. el: el,
  733. template: '<div v-repeat="obj">{{msg}}</div>',
  734. data: {
  735. obj: obj
  736. }
  737. })
  738. expect(el.innerHTML).toBe(Object.keys(obj).map(function (key) {
  739. return '<div>' + obj[key].msg + '</div>'
  740. }).join(''))
  741. vm.obj = arr
  742. _.nextTick(function () {
  743. expect(el.innerHTML).toBe('<div>BB</div><div>AA</div>')
  744. // make sure it cleared the cache
  745. expect(vm._directives[0].cache.a).toBeNull()
  746. expect(vm._directives[0].cache.b).toBeNull()
  747. done()
  748. })
  749. })
  750. })
  751. }
  752. /**
  753. * Simple helper for chained async asssertions
  754. *
  755. * @param {Function} fn - the data manipulation function
  756. * @param {Function} cb - the assertion fn to be called on nextTick
  757. */
  758. function go (fn, cb) {
  759. return {
  760. stack: [{fn: fn, cb: cb}],
  761. then: function (fn, cb) {
  762. this.stack.push({fn: fn, cb: cb})
  763. return this
  764. },
  765. run: function (done) {
  766. var self = this
  767. var step = this.stack.shift()
  768. if (!step) return done()
  769. step.fn()
  770. _.nextTick(function () {
  771. step.cb()
  772. self.run(done)
  773. })
  774. }
  775. }
  776. }
  777. /**
  778. * Assert mutation and markup correctness for v-repeat on
  779. * an Array of Objects
  780. */
  781. function assertMutations (vm, el, done) {
  782. assertMarkup()
  783. var poppedItem
  784. go(
  785. function () {
  786. vm.items.push({a: 3})
  787. },
  788. assertMarkup
  789. )
  790. .then(
  791. function () {
  792. vm.items.shift()
  793. },
  794. assertMarkup
  795. )
  796. .then(
  797. function () {
  798. vm.items.reverse()
  799. },
  800. assertMarkup
  801. )
  802. .then(
  803. function () {
  804. poppedItem = vm.items.pop()
  805. },
  806. assertMarkup
  807. )
  808. .then(
  809. function () {
  810. vm.items.unshift(poppedItem)
  811. },
  812. assertMarkup
  813. )
  814. .then(
  815. function () {
  816. vm.items.sort(function (a, b) {
  817. return a.a > b.a ? 1 : -1
  818. })
  819. },
  820. assertMarkup
  821. )
  822. .then(
  823. function () {
  824. vm.items.splice(1, 1, {a: 5})
  825. },
  826. assertMarkup
  827. )
  828. // test swapping the array
  829. .then(
  830. function () {
  831. vm.items = [{a: 0}, {a: 1}, {a: 2}]
  832. },
  833. assertMarkup
  834. )
  835. .run(done)
  836. function assertMarkup () {
  837. var tag = el.children[0].tagName.toLowerCase()
  838. var markup = vm.items.map(function (item, i) {
  839. var el = '<' + tag + '>' + i + ' ' + item.a + '</' + tag + '>'
  840. return el
  841. }).join('')
  842. expect(el.innerHTML).toBe(markup)
  843. }
  844. }
  845. /**
  846. * Assert mutation and markup correctness for v-repeat on
  847. * an Array of primitive values
  848. */
  849. function assertPrimitiveMutations (vm, el, done) {
  850. assertMarkup()
  851. go(
  852. function () {
  853. // check duplicate
  854. vm.items.push(2, 2, 3)
  855. },
  856. assertMarkup
  857. )
  858. .then(
  859. function () {
  860. vm.items.shift()
  861. },
  862. assertMarkup
  863. )
  864. .then(
  865. function () {
  866. vm.items.reverse()
  867. },
  868. assertMarkup
  869. )
  870. .then(
  871. function () {
  872. vm.items.pop()
  873. },
  874. assertMarkup
  875. )
  876. .then(
  877. function () {
  878. vm.items.unshift(3)
  879. },
  880. assertMarkup
  881. )
  882. .then(
  883. function () {
  884. vm.items.sort(function (a, b) {
  885. return a > b ? 1 : -1
  886. })
  887. },
  888. assertMarkup
  889. )
  890. .then(
  891. function () {
  892. vm.items.splice(1, 1, 5)
  893. },
  894. assertMarkup
  895. )
  896. // test swapping the array
  897. .then(
  898. function () {
  899. vm.items = [1, 2, 2]
  900. },
  901. assertMarkup
  902. )
  903. .run(done)
  904. function assertMarkup () {
  905. var markup = vm.items.map(function (item, i) {
  906. return '<div>' + i + ' ' + item + '</div>'
  907. }).join('')
  908. expect(el.innerHTML).toBe(markup)
  909. }
  910. }
  911. /**
  912. * Assert mutation and markup correctness for v-repeat on
  913. * an Object of Objects
  914. */
  915. function assertObjectMutations (vm, el, done) {
  916. assertMarkup()
  917. go(
  918. function () {
  919. vm.items.a = {a: 3}
  920. },
  921. assertMarkup
  922. )
  923. .then(
  924. function () {
  925. vm.items = {
  926. c: {a: 1},
  927. d: {a: 2}
  928. }
  929. },
  930. assertMarkup
  931. )
  932. .then(
  933. function () {
  934. vm.items.$add('a', {a: 3})
  935. },
  936. assertMarkup
  937. )
  938. .run(done)
  939. function assertMarkup () {
  940. var markup = Object.keys(vm.items).map(function (key, i) {
  941. return '<div>' + i + ' ' + key + ' ' + vm.items[key].a + '</div>'
  942. }).join('')
  943. expect(el.innerHTML).toBe(markup)
  944. }
  945. }
  946. /**
  947. * Assert mutation and markup correctness for v-repeat on
  948. * an Object of primitive values
  949. */
  950. function assertObjectPrimitiveMutations (vm, el, done) {
  951. assertMarkup()
  952. go(
  953. function () {
  954. vm.items.a = 3
  955. },
  956. assertMarkup
  957. )
  958. .then(
  959. function () {
  960. vm.items = {
  961. c: 1,
  962. d: 2
  963. }
  964. },
  965. assertMarkup
  966. )
  967. .then(
  968. function () {
  969. vm.items.$add('a', 3)
  970. },
  971. assertMarkup
  972. )
  973. .run(done)
  974. function assertMarkup () {
  975. var markup = Object.keys(vm.items).map(function (key, i) {
  976. return '<div>' + i + ' ' + key + ' ' + vm.items[key] + '</div>'
  977. }).join('')
  978. expect(el.innerHTML).toBe(markup)
  979. }
  980. }