repeat_spec.js 17 KB

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