repeat_spec.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  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('repeating object with filter', function () {
  103. var vm = new Vue({
  104. el: el,
  105. data: {
  106. items: {
  107. a: { msg: 'aaa' },
  108. b: { msg: 'bbb' }
  109. }
  110. },
  111. template: '<div v-repeat="items | filterBy \'aaa\'">{{msg}}</div>'
  112. })
  113. expect(el.innerHTML).toBe('<div>aaa</div><!--v-repeat-->')
  114. })
  115. it('v-component', function () {
  116. var vm = new Vue({
  117. el: el,
  118. data: {
  119. items: [{a:1}, {a:2}, {a:3}]
  120. },
  121. template: '<div v-repeat="items" v-component="test"></div>',
  122. components: {
  123. test: {
  124. template: '<p>{{$index}} {{a}}</p>',
  125. replace: true
  126. }
  127. }
  128. })
  129. expect(el.innerHTML).toBe('<p>0 1</p><p>1 2</p><p>2 3</p><!--v-repeat-->')
  130. })
  131. it('nested repeats', function () {
  132. var vm = new Vue({
  133. el: el,
  134. data: {
  135. items: [
  136. { items: [{a:1}, {a:2}], a: 1 },
  137. { items: [{a:3}, {a:4}], a: 2 }
  138. ]
  139. },
  140. template: '<div v-repeat="items">' +
  141. '<p v-repeat="items">{{$index}} {{a}} {{$parent.$index}} {{$parent.a}}</p>' +
  142. '</div>'
  143. })
  144. expect(el.innerHTML).toBe(
  145. '<div><p>0 1 0 1</p><p>1 2 0 1</p><!--v-repeat--></div>' +
  146. '<div><p>0 3 1 2</p><p>1 4 1 2</p><!--v-repeat--></div>' +
  147. '<!--v-repeat-->'
  148. )
  149. })
  150. it('dynamic component type based on instance data', function () {
  151. var vm = new Vue({
  152. el: el,
  153. template: '<div v-repeat="list" v-component="view-{{type}}"></div>',
  154. data: {
  155. list: [
  156. { type: 'a' },
  157. { type: 'b' },
  158. { type: 'c' }
  159. ]
  160. },
  161. components: {
  162. 'view-a': {
  163. template: 'AAA'
  164. },
  165. 'view-b': {
  166. template: 'BBB'
  167. },
  168. 'view-c': {
  169. template: 'CCC'
  170. }
  171. }
  172. })
  173. expect(el.innerHTML).toBe('<div>AAA</div><div>BBB</div><div>CCC</div><!--v-repeat-->')
  174. // #458 meta properties
  175. vm = new Vue({
  176. el: el,
  177. template: '<div v-repeat="list" v-component="view-{{$value}}"></div>',
  178. data: {
  179. list: ['a', 'b', 'c']
  180. },
  181. components: {
  182. 'view-a': {
  183. template: 'AAA'
  184. },
  185. 'view-b': {
  186. template: 'BBB'
  187. },
  188. 'view-c': {
  189. template: 'CCC'
  190. }
  191. }
  192. })
  193. expect(el.innerHTML).toBe('<div>AAA</div><div>BBB</div><div>CCC</div><!--v-repeat-->')
  194. })
  195. it('block repeat', function () {
  196. var vm = new Vue({
  197. el: el,
  198. template: '<template v-repeat="list"><p>{{a}}</p><p>{{a + 1}}</p></template>',
  199. data: {
  200. list: [
  201. { a: 1 },
  202. { a: 2 },
  203. { a: 3 }
  204. ]
  205. }
  206. })
  207. var markup = vm.list.map(function (item) {
  208. return '<!--v-start--><p>' + item.a + '</p><p>' + (item.a + 1) + '</p><!--v-end-->'
  209. }).join('')
  210. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  211. })
  212. it('component + parent directive + transclusion', function (done) {
  213. var vm = new Vue({
  214. el: el,
  215. template: '<div v-repeat="list" v-component="test" v-class="cls">{{msg}}</div>',
  216. data: {
  217. cls: 'parent',
  218. msg: 'hi',
  219. list: [{a:1},{a:2},{a:3}]
  220. },
  221. components: {
  222. test: {
  223. replace: true,
  224. template: '<div class="child">{{a}} <content></content></div>'
  225. }
  226. }
  227. })
  228. var markup = vm.list.map(function (item) {
  229. return '<div class="child parent">' + item.a + ' hi</div>'
  230. }).join('')
  231. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  232. vm.msg = 'ho'
  233. markup = vm.list.map(function (item) {
  234. return '<div class="child parent">' + item.a + ' ho</div>'
  235. }).join('')
  236. _.nextTick(function () {
  237. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  238. done()
  239. })
  240. })
  241. it('array filters', function (done) {
  242. var vm = new Vue({
  243. el: el,
  244. template: '<div v-repeat="list | filterBy filterKey | orderBy sortKey -1">{{id}}</div>',
  245. data: {
  246. filterKey: 'hi!',
  247. sortKey: 'id',
  248. list: [
  249. { id: 1, id2: 4, msg: 'hi!' },
  250. { id: 2, id2: 3, msg: 'na' },
  251. { id: 3, id2: 2, msg: 'hi!' },
  252. { id: 4, id2: 1, msg: 'na' }
  253. ]
  254. }
  255. })
  256. assertMarkup()
  257. go(
  258. function () {
  259. vm.filterKey = 'na'
  260. }, assertMarkup
  261. )
  262. .then(
  263. function () {
  264. vm.sortKey = 'id2'
  265. }, assertMarkup
  266. )
  267. .then(
  268. function () {
  269. vm.list[0].id2 = 0
  270. }, assertMarkup
  271. )
  272. .then(
  273. function () {
  274. vm.list.push({ id: 0, id2: 4, msg: 'na' })
  275. }, assertMarkup
  276. )
  277. .then(
  278. function () {
  279. vm.list = [
  280. { id: 33, id2: 4, msg: 'hi!' },
  281. { id: 44, id2: 3, msg: 'na' }
  282. ]
  283. }, assertMarkup
  284. )
  285. .run(done)
  286. function assertMarkup () {
  287. var markup = vm.list
  288. .filter(function (item) {
  289. return item.msg === vm.filterKey
  290. })
  291. .sort(function (a, b) {
  292. return a[vm.sortKey] > b[vm.sortKey] ? -1 : 1
  293. })
  294. .map(function (item) {
  295. return '<div>' + item.id + '</div>'
  296. }).join('')
  297. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  298. }
  299. })
  300. it('track by id', function (done) {
  301. assertTrackBy('<div v-repeat="list" track-by="id">{{msg}}</div>', function () {
  302. assertTrackBy('<div v-repeat="item:list" track-by="id">{{item.msg}}</div>', done)
  303. })
  304. function assertTrackBy (template, next) {
  305. var vm = new Vue({
  306. el: el,
  307. template: template,
  308. data: {
  309. list: [
  310. { id: 1, msg: 'hi' },
  311. { id: 2, msg: 'ha' },
  312. { id: 3, msg: 'ho' }
  313. ]
  314. }
  315. })
  316. assertMarkup()
  317. var oldVms = vm._children.slice()
  318. // swap the data with different objects, but with
  319. // the same ID!
  320. vm.list = [
  321. { id: 1, msg: 'wa' },
  322. { id: 2, msg: 'wo' }
  323. ]
  324. _.nextTick(function () {
  325. assertMarkup()
  326. // should reuse old vms!
  327. var i = 2
  328. while (i--) {
  329. expect(vm._children[i]).toBe(oldVms[i])
  330. }
  331. next()
  332. })
  333. function assertMarkup () {
  334. var markup = vm.list.map(function (item) {
  335. return '<div>' + item.msg + '</div>'
  336. }).join('')
  337. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  338. }
  339. }
  340. })
  341. it('warn duplicate objects', function () {
  342. var obj = {}
  343. var vm = new Vue({
  344. el: el,
  345. template: '<div v-repeat="items"></div>',
  346. data: {
  347. items: [obj, obj]
  348. }
  349. })
  350. expect(_.warn).toHaveBeenCalled()
  351. })
  352. it('warn duplicate trackby id', function () {
  353. var vm = new Vue({
  354. el: el,
  355. template: '<div v-repeat="items" trackby="id"></div>',
  356. data: {
  357. items: [{id:1}, {id:1}]
  358. }
  359. })
  360. expect(_.warn).toHaveBeenCalled()
  361. })
  362. it('warn v-if', function () {
  363. var vm = new Vue({
  364. el: el,
  365. template: '<div v-repeat="items" v-if="aaa"></div>',
  366. data: {
  367. items: []
  368. }
  369. })
  370. expect(_.warn).toHaveBeenCalled()
  371. })
  372. it('repeat number', function () {
  373. var vm = new Vue({
  374. el: el,
  375. template: '<div v-repeat="3">{{$index}} {{$value}}</div>'
  376. })
  377. expect(el.innerHTML).toBe('<div>0 0</div><div>1 1</div><div>2 2</div><!--v-repeat-->')
  378. })
  379. it('repeat string', function () {
  380. var vm = new Vue({
  381. el: el,
  382. template: '<div v-repeat="\'vue\'">{{$index}} {{$value}}</div>'
  383. })
  384. expect(el.innerHTML).toBe('<div>0 v</div><div>1 u</div><div>2 e</div><!--v-repeat-->')
  385. })
  386. it('teardown', function () {
  387. var vm = new Vue({
  388. el: el,
  389. template: '<div v-repeat="items">{{a}}</div>',
  390. data: {
  391. items: [{a:1}, {a:2}]
  392. }
  393. })
  394. vm._directives[0].unbind()
  395. expect(vm._children.length).toBe(0)
  396. })
  397. it('with transition', function (done) {
  398. // === IMPORTANT ===
  399. // PhantomJS always returns false when calling
  400. // Element.contains() on a comment node. This causes
  401. // transitions to be skipped. Monkey patching here
  402. // isn't ideal but does the job...
  403. var inDoc = _.inDoc
  404. _.inDoc = function () {
  405. return true
  406. }
  407. var vm = new Vue({
  408. el: el,
  409. template: '<div v-repeat="items" v-transition="test">{{a}}</div>',
  410. data: {
  411. items: [{a:1}, {a:2}, {a:3}]
  412. },
  413. transitions: {
  414. test: {
  415. leave: function (el, done) {
  416. setTimeout(done, 1)
  417. }
  418. }
  419. }
  420. })
  421. vm.items.splice(1, 1, {a:4})
  422. setTimeout(function () {
  423. expect(el.innerHTML).toBe('<div>1</div><div>4</div><div>3</div><!--v-repeat-->')
  424. // clean up
  425. _.inDoc = inDoc
  426. done()
  427. }, 30)
  428. })
  429. })
  430. }
  431. /**
  432. * Simple helper for chained async asssertions
  433. *
  434. * @param {Function} fn - the data manipulation function
  435. * @param {Function} cb - the assertion fn to be called on nextTick
  436. */
  437. function go (fn, cb) {
  438. return {
  439. stack: [{fn:fn, cb:cb}],
  440. then: function (fn, cb) {
  441. this.stack.push({fn:fn, cb:cb})
  442. return this
  443. },
  444. run: function (done) {
  445. var self = this
  446. var step = this.stack.shift()
  447. if (!step) return done()
  448. step.fn()
  449. _.nextTick(function () {
  450. step.cb()
  451. self.run(done)
  452. })
  453. }
  454. }
  455. }
  456. /**
  457. * Assert mutation and markup correctness for v-repeat on
  458. * an Array of Objects
  459. */
  460. function assertMutations (vm, el, done) {
  461. assertMarkup()
  462. var poppedItem
  463. go(
  464. function () {
  465. vm.items.push({a:3})
  466. },
  467. assertMarkup
  468. )
  469. .then(
  470. function () {
  471. vm.items.shift()
  472. },
  473. assertMarkup
  474. )
  475. .then(
  476. function () {
  477. vm.items.reverse()
  478. },
  479. assertMarkup
  480. )
  481. .then(
  482. function () {
  483. poppedItem = vm.items.pop()
  484. },
  485. assertMarkup
  486. )
  487. .then(
  488. function () {
  489. vm.items.unshift(poppedItem)
  490. },
  491. assertMarkup
  492. )
  493. .then(
  494. function () {
  495. vm.items.sort(function (a, b) {
  496. return a.a > b.a ? 1 : -1
  497. })
  498. },
  499. assertMarkup
  500. )
  501. .then(
  502. function () {
  503. vm.items.splice(1, 1, {a:5})
  504. },
  505. assertMarkup
  506. )
  507. // test swapping the array
  508. .then(
  509. function () {
  510. vm.items = [{a:0}, {a:1}, {a:2}]
  511. },
  512. assertMarkup
  513. )
  514. .run(done)
  515. function assertMarkup () {
  516. var markup = vm.items.map(function (item, i) {
  517. return '<div>' + i + ' ' + item.a + '</div>'
  518. }).join('')
  519. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  520. }
  521. }
  522. /**
  523. * Assert mutation and markup correctness for v-repeat on
  524. * an Array of primitive values
  525. */
  526. function assertPrimitiveMutations (vm, el, done) {
  527. assertMarkup()
  528. go(
  529. function () {
  530. // check duplicate
  531. vm.items.push(2, 2, 3)
  532. },
  533. assertMarkup
  534. )
  535. .then(
  536. function () {
  537. vm.items.shift()
  538. },
  539. assertMarkup
  540. )
  541. .then(
  542. function () {
  543. vm.items.reverse()
  544. },
  545. assertMarkup
  546. )
  547. .then(
  548. function () {
  549. vm.items.pop()
  550. },
  551. assertMarkup
  552. )
  553. .then(
  554. function () {
  555. vm.items.unshift(3)
  556. },
  557. assertMarkup
  558. )
  559. .then(
  560. function () {
  561. vm.items.sort(function (a, b) {
  562. return a > b ? 1 : -1
  563. })
  564. },
  565. assertMarkup
  566. )
  567. .then(
  568. function () {
  569. vm.items.splice(1, 1, 5)
  570. },
  571. assertMarkup
  572. )
  573. // test swapping the array
  574. .then(
  575. function () {
  576. vm.items = [1, 2, 2]
  577. },
  578. assertMarkup
  579. )
  580. .run(done)
  581. function assertMarkup () {
  582. var markup = vm.items.map(function (item, i) {
  583. return '<div>' + i + ' ' + item + '</div>'
  584. }).join('')
  585. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  586. }
  587. }
  588. /**
  589. * Assert mutation and markup correctness for v-repeat on
  590. * an Object of Objects
  591. */
  592. function assertObjectMutations (vm, el, done) {
  593. assertMarkup()
  594. go(
  595. function () {
  596. vm.items.a = {a:3}
  597. },
  598. assertMarkup
  599. )
  600. .then(
  601. function () {
  602. vm.items = {
  603. c: {a:1},
  604. d: {a:2}
  605. }
  606. },
  607. assertMarkup
  608. )
  609. .then(
  610. function () {
  611. vm.items.$add('a', {a:3})
  612. },
  613. assertMarkup
  614. )
  615. .run(done)
  616. function assertMarkup () {
  617. var markup = Object.keys(vm.items).map(function (key, i) {
  618. return '<div>' + i + ' ' + key + ' ' + vm.items[key].a + '</div>'
  619. }).join('')
  620. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  621. }
  622. }
  623. /**
  624. * Assert mutation and markup correctness for v-repeat on
  625. * an Object of primitive values
  626. */
  627. function assertObjectPrimitiveMutations (vm, el, done) {
  628. assertMarkup()
  629. go(
  630. function () {
  631. vm.items.a = 3
  632. },
  633. assertMarkup
  634. )
  635. .then(
  636. function () {
  637. vm.items = {
  638. c: 1,
  639. d: 2
  640. }
  641. },
  642. assertMarkup
  643. )
  644. .then(
  645. function () {
  646. vm.items.$add('a', 3)
  647. },
  648. assertMarkup
  649. )
  650. .run(done)
  651. function assertMarkup () {
  652. var markup = Object.keys(vm.items).map(function (key, i) {
  653. return '<div>' + i + ' ' + key + ' ' + vm.items[key] + '</div>'
  654. }).join('')
  655. expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
  656. }
  657. }