repeat_spec.js 24 KB

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