repeat_spec.js 15 KB

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