repeat_spec.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  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', 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 trackby id', function () {
  548. new Vue({
  549. el: el,
  550. template: '<div v-repeat="items" track-by="id"></div>',
  551. data: {
  552. items: [{id: 1}, {id: 1}]
  553. }
  554. })
  555. expect(hasWarned(_, 'Duplicate track-by key')).toBe(true)
  556. })
  557. it('warn v-if', function () {
  558. new Vue({
  559. el: el,
  560. template: '<div v-repeat="items" v-if="aaa"></div>',
  561. data: {
  562. items: []
  563. }
  564. })
  565. expect(hasWarned(_, 'Don\'t use v-if')).toBe(true)
  566. })
  567. it('repeat number', function () {
  568. new Vue({
  569. el: el,
  570. template: '<div v-repeat="3">{{$index}} {{$value}}</div>'
  571. })
  572. expect(el.innerHTML).toBe('<div>0 0</div><div>1 1</div><div>2 2</div>')
  573. })
  574. it('repeat string', function () {
  575. new Vue({
  576. el: el,
  577. template: '<div v-repeat="\'vue\'">{{$index}} {{$value}}</div>'
  578. })
  579. expect(el.innerHTML).toBe('<div>0 v</div><div>1 u</div><div>2 e</div>')
  580. })
  581. it('teardown', function () {
  582. var vm = new Vue({
  583. el: el,
  584. template: '<div v-repeat="items"></div>',
  585. data: {
  586. items: [{a: 1}, {a: 2}]
  587. }
  588. })
  589. vm._directives[0].unbind()
  590. expect(vm.$children.length).toBe(0)
  591. })
  592. it('with transition', function (done) {
  593. document.body.appendChild(el)
  594. var vm = new Vue({
  595. el: el,
  596. template: '<div v-repeat="items" v-transition="test">{{a}}</div>',
  597. data: {
  598. items: [{a: 1}, {a: 2}, {a: 3}]
  599. },
  600. transitions: {
  601. test: {
  602. leave: function (el, done) {
  603. setTimeout(done, 0)
  604. }
  605. }
  606. }
  607. })
  608. vm.items.splice(1, 1, {a: 4})
  609. setTimeout(function () {
  610. expect(el.innerHTML).toBe(
  611. '<div class="test-transition">1</div>' +
  612. '<div class="test-transition">4</div>' +
  613. '<div class="test-transition">3</div>'
  614. )
  615. document.body.removeChild(el)
  616. done()
  617. }, 100)
  618. })
  619. it('sync $value/alias changes back to original array/object', function (done) {
  620. var vm = new Vue({
  621. el: el,
  622. template:
  623. '<div v-repeat="items">{{$value}}</div>' +
  624. '<div v-repeat="obj">{{$value}}</div>' +
  625. '<div v-repeat="val:vals">{{val}}</div>',
  626. data: {
  627. items: ['a', true],
  628. obj: { foo: 'a', bar: 'b' },
  629. vals: [1, null]
  630. }
  631. })
  632. vm.$children[0].$value = 'c'
  633. vm.$children[1].$value = 'd'
  634. var key = vm.$children[2].$key
  635. vm.$children[2].$value = 'e'
  636. vm.$children[4].val = 3
  637. vm.$children[5].val = 4
  638. _.nextTick(function () {
  639. expect(vm.items[0]).toBe('c')
  640. expect(vm.items[1]).toBe('d')
  641. expect(vm.obj[key]).toBe('e')
  642. expect(vm.vals[0]).toBe(3)
  643. expect(vm.vals[1]).toBe(4)
  644. done()
  645. })
  646. })
  647. it('warn $value sync with filters', function (done) {
  648. var vm = new Vue({
  649. el: el,
  650. template: '<div v-repeat="items | orderBy \'$value\'"></div>',
  651. data: {
  652. items: ['a', 'b']
  653. }
  654. })
  655. vm.$children[0].$value = 'c'
  656. _.nextTick(function () {
  657. expect(hasWarned(_, 'use an Array of Objects instead')).toBe(true)
  658. done()
  659. })
  660. })
  661. it('nested track by', function (done) {
  662. assertTrackBy('<div v-repeat="list" track-by="id">{{msg}}<div v-repeat="list" track-by="id">{{msg}}</div></div>', function () {
  663. assertTrackBy('<div v-repeat="list" track-by="id">{{msg}}<div v-repeat="list" track-by="id">{{msg}}</div></div>', done)
  664. })
  665. function assertTrackBy (template, next) {
  666. var vm = new Vue({
  667. el: el,
  668. data: {
  669. list: [
  670. { id: 1, msg: 'hi', list: [
  671. { id: 1, msg: 'hi foo' }
  672. ] },
  673. { id: 2, msg: 'ha', list: [] },
  674. { id: 3, msg: 'ho', list: [] }
  675. ]
  676. },
  677. template: template
  678. })
  679. assertMarkup()
  680. var oldVms = vm.$children.slice()
  681. vm.list = [
  682. { id: 1, msg: 'wa', list: [
  683. { id: 1, msg: 'hi foo' },
  684. { id: 2, msg: 'hi bar' }
  685. ] },
  686. { id: 2, msg: 'wo', list: [] }
  687. ]
  688. _.nextTick(function () {
  689. assertMarkup()
  690. // should reuse old vms!
  691. var i = 2
  692. while (i--) {
  693. expect(vm.$children[i]).toBe(oldVms[i])
  694. }
  695. expect(vm.$children[0].$children[0]).toBe(oldVms[0].$children[0])
  696. next()
  697. })
  698. function assertMarkup () {
  699. var markup = vm.list.map(function (item) {
  700. var sublist = item.list.map(function (item) {
  701. return '<div>' + item.msg + '</div>'
  702. }).join('')
  703. return '<div>' + item.msg + sublist + '</div>'
  704. }).join('')
  705. expect(el.innerHTML).toBe(markup)
  706. }
  707. }
  708. })
  709. it('switch between object-converted & array mode', function (done) {
  710. var obj = {
  711. a: { msg: 'AA' },
  712. b: { msg: 'BB' }
  713. }
  714. var arr = [obj.b, obj.a]
  715. var vm = new Vue({
  716. el: el,
  717. template: '<div v-repeat="obj">{{msg}}</div>',
  718. data: {
  719. obj: obj
  720. }
  721. })
  722. expect(el.innerHTML).toBe(Object.keys(obj).map(function (key) {
  723. return '<div>' + obj[key].msg + '</div>'
  724. }).join(''))
  725. vm.obj = arr
  726. _.nextTick(function () {
  727. expect(el.innerHTML).toBe('<div>BB</div><div>AA</div>')
  728. // make sure it cleared the cache
  729. expect(vm._directives[0].cache.a).toBeNull()
  730. expect(vm._directives[0].cache.b).toBeNull()
  731. done()
  732. })
  733. })
  734. })
  735. }
  736. /**
  737. * Simple helper for chained async asssertions
  738. *
  739. * @param {Function} fn - the data manipulation function
  740. * @param {Function} cb - the assertion fn to be called on nextTick
  741. */
  742. function go (fn, cb) {
  743. return {
  744. stack: [{fn: fn, cb: cb}],
  745. then: function (fn, cb) {
  746. this.stack.push({fn: fn, cb: cb})
  747. return this
  748. },
  749. run: function (done) {
  750. var self = this
  751. var step = this.stack.shift()
  752. if (!step) return done()
  753. step.fn()
  754. _.nextTick(function () {
  755. step.cb()
  756. self.run(done)
  757. })
  758. }
  759. }
  760. }
  761. /**
  762. * Assert mutation and markup correctness for v-repeat on
  763. * an Array of Objects
  764. */
  765. function assertMutations (vm, el, done) {
  766. assertMarkup()
  767. var poppedItem
  768. go(
  769. function () {
  770. vm.items.push({a: 3})
  771. },
  772. assertMarkup
  773. )
  774. .then(
  775. function () {
  776. vm.items.shift()
  777. },
  778. assertMarkup
  779. )
  780. .then(
  781. function () {
  782. vm.items.reverse()
  783. },
  784. assertMarkup
  785. )
  786. .then(
  787. function () {
  788. poppedItem = vm.items.pop()
  789. },
  790. assertMarkup
  791. )
  792. .then(
  793. function () {
  794. vm.items.unshift(poppedItem)
  795. },
  796. assertMarkup
  797. )
  798. .then(
  799. function () {
  800. vm.items.sort(function (a, b) {
  801. return a.a > b.a ? 1 : -1
  802. })
  803. },
  804. assertMarkup
  805. )
  806. .then(
  807. function () {
  808. vm.items.splice(1, 1, {a: 5})
  809. },
  810. assertMarkup
  811. )
  812. // test swapping the array
  813. .then(
  814. function () {
  815. vm.items = [{a: 0}, {a: 1}, {a: 2}]
  816. },
  817. assertMarkup
  818. )
  819. .run(done)
  820. function assertMarkup () {
  821. var tag = el.children[0].tagName.toLowerCase()
  822. var markup = vm.items.map(function (item, i) {
  823. var el = '<' + tag + '>' + i + ' ' + item.a + '</' + tag + '>'
  824. return el
  825. }).join('')
  826. expect(el.innerHTML).toBe(markup)
  827. }
  828. }
  829. /**
  830. * Assert mutation and markup correctness for v-repeat on
  831. * an Array of primitive values
  832. */
  833. function assertPrimitiveMutations (vm, el, done) {
  834. assertMarkup()
  835. go(
  836. function () {
  837. // check duplicate
  838. vm.items.push(2, 2, 3)
  839. },
  840. assertMarkup
  841. )
  842. .then(
  843. function () {
  844. vm.items.shift()
  845. },
  846. assertMarkup
  847. )
  848. .then(
  849. function () {
  850. vm.items.reverse()
  851. },
  852. assertMarkup
  853. )
  854. .then(
  855. function () {
  856. vm.items.pop()
  857. },
  858. assertMarkup
  859. )
  860. .then(
  861. function () {
  862. vm.items.unshift(3)
  863. },
  864. assertMarkup
  865. )
  866. .then(
  867. function () {
  868. vm.items.sort(function (a, b) {
  869. return a > b ? 1 : -1
  870. })
  871. },
  872. assertMarkup
  873. )
  874. .then(
  875. function () {
  876. vm.items.splice(1, 1, 5)
  877. },
  878. assertMarkup
  879. )
  880. // test swapping the array
  881. .then(
  882. function () {
  883. vm.items = [1, 2, 2]
  884. },
  885. assertMarkup
  886. )
  887. .run(done)
  888. function assertMarkup () {
  889. var markup = vm.items.map(function (item, i) {
  890. return '<div>' + i + ' ' + item + '</div>'
  891. }).join('')
  892. expect(el.innerHTML).toBe(markup)
  893. }
  894. }
  895. /**
  896. * Assert mutation and markup correctness for v-repeat on
  897. * an Object of Objects
  898. */
  899. function assertObjectMutations (vm, el, done) {
  900. assertMarkup()
  901. go(
  902. function () {
  903. vm.items.a = {a: 3}
  904. },
  905. assertMarkup
  906. )
  907. .then(
  908. function () {
  909. vm.items = {
  910. c: {a: 1},
  911. d: {a: 2}
  912. }
  913. },
  914. assertMarkup
  915. )
  916. .then(
  917. function () {
  918. vm.items.$add('a', {a: 3})
  919. },
  920. assertMarkup
  921. )
  922. .run(done)
  923. function assertMarkup () {
  924. var markup = Object.keys(vm.items).map(function (key, i) {
  925. return '<div>' + i + ' ' + key + ' ' + vm.items[key].a + '</div>'
  926. }).join('')
  927. expect(el.innerHTML).toBe(markup)
  928. }
  929. }
  930. /**
  931. * Assert mutation and markup correctness for v-repeat on
  932. * an Object of primitive values
  933. */
  934. function assertObjectPrimitiveMutations (vm, el, done) {
  935. assertMarkup()
  936. go(
  937. function () {
  938. vm.items.a = 3
  939. },
  940. assertMarkup
  941. )
  942. .then(
  943. function () {
  944. vm.items = {
  945. c: 1,
  946. d: 2
  947. }
  948. },
  949. assertMarkup
  950. )
  951. .then(
  952. function () {
  953. vm.items.$add('a', 3)
  954. },
  955. assertMarkup
  956. )
  957. .run(done)
  958. function assertMarkup () {
  959. var markup = Object.keys(vm.items).map(function (key, i) {
  960. return '<div>' + i + ' ' + key + ' ' + vm.items[key] + '</div>'
  961. }).join('')
  962. expect(el.innerHTML).toBe(markup)
  963. }
  964. }