for_spec.js 23 KB

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