for.spec.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. import Vue from 'vue'
  2. import { hasSymbol } from 'core/util/env'
  3. describe('Directive v-for', () => {
  4. it('should render array of primitive values', (done) => {
  5. const vm = new Vue({
  6. template: `
  7. <div>
  8. <span v-for="item in list">{{item}}</span>
  9. </div>
  10. `,
  11. data: {
  12. list: ['a', 'b', 'c']
  13. }
  14. }).$mount()
  15. expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
  16. Vue.set(vm.list, 0, 'd')
  17. waitForUpdate(() => {
  18. expect(vm.$el.innerHTML).toBe(
  19. '<span>d</span><span>b</span><span>c</span>'
  20. )
  21. vm.list.push('d')
  22. })
  23. .then(() => {
  24. expect(vm.$el.innerHTML).toBe(
  25. '<span>d</span><span>b</span><span>c</span><span>d</span>'
  26. )
  27. vm.list.splice(1, 2)
  28. })
  29. .then(() => {
  30. expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')
  31. vm.list = ['x', 'y']
  32. })
  33. .then(() => {
  34. expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
  35. })
  36. .then(done)
  37. })
  38. it('should render array of primitive values with index', (done) => {
  39. const vm = new Vue({
  40. template: `
  41. <div>
  42. <span v-for="(item, i) in list">{{i}}-{{item}}</span>
  43. </div>
  44. `,
  45. data: {
  46. list: ['a', 'b', 'c']
  47. }
  48. }).$mount()
  49. expect(vm.$el.innerHTML).toBe(
  50. '<span>0-a</span><span>1-b</span><span>2-c</span>'
  51. )
  52. Vue.set(vm.list, 0, 'd')
  53. waitForUpdate(() => {
  54. expect(vm.$el.innerHTML).toBe(
  55. '<span>0-d</span><span>1-b</span><span>2-c</span>'
  56. )
  57. vm.list.push('d')
  58. })
  59. .then(() => {
  60. expect(vm.$el.innerHTML).toBe(
  61. '<span>0-d</span><span>1-b</span><span>2-c</span><span>3-d</span>'
  62. )
  63. vm.list.splice(1, 2)
  64. })
  65. .then(() => {
  66. expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')
  67. vm.list = ['x', 'y']
  68. })
  69. .then(() => {
  70. expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
  71. })
  72. .then(done)
  73. })
  74. it('should render array of object values', (done) => {
  75. const vm = new Vue({
  76. template: `
  77. <div>
  78. <span v-for="item in list">{{item.value}}</span>
  79. </div>
  80. `,
  81. data: {
  82. list: [{ value: 'a' }, { value: 'b' }, { value: 'c' }]
  83. }
  84. }).$mount()
  85. expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
  86. Vue.set(vm.list, 0, { value: 'd' })
  87. waitForUpdate(() => {
  88. expect(vm.$el.innerHTML).toBe(
  89. '<span>d</span><span>b</span><span>c</span>'
  90. )
  91. vm.list[0].value = 'e'
  92. })
  93. .then(() => {
  94. expect(vm.$el.innerHTML).toBe(
  95. '<span>e</span><span>b</span><span>c</span>'
  96. )
  97. vm.list.push({})
  98. })
  99. .then(() => {
  100. expect(vm.$el.innerHTML).toBe(
  101. '<span>e</span><span>b</span><span>c</span><span></span>'
  102. )
  103. vm.list.splice(1, 2)
  104. })
  105. .then(() => {
  106. expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')
  107. vm.list = [{ value: 'x' }, { value: 'y' }]
  108. })
  109. .then(() => {
  110. expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
  111. })
  112. .then(done)
  113. })
  114. it('should render array of object values with index', (done) => {
  115. const vm = new Vue({
  116. template: `
  117. <div>
  118. <span v-for="(item, i) in list">{{i}}-{{item.value}}</span>
  119. </div>
  120. `,
  121. data: {
  122. list: [{ value: 'a' }, { value: 'b' }, { value: 'c' }]
  123. }
  124. }).$mount()
  125. expect(vm.$el.innerHTML).toBe(
  126. '<span>0-a</span><span>1-b</span><span>2-c</span>'
  127. )
  128. Vue.set(vm.list, 0, { value: 'd' })
  129. waitForUpdate(() => {
  130. expect(vm.$el.innerHTML).toBe(
  131. '<span>0-d</span><span>1-b</span><span>2-c</span>'
  132. )
  133. vm.list[0].value = 'e'
  134. })
  135. .then(() => {
  136. expect(vm.$el.innerHTML).toBe(
  137. '<span>0-e</span><span>1-b</span><span>2-c</span>'
  138. )
  139. vm.list.push({})
  140. })
  141. .then(() => {
  142. expect(vm.$el.innerHTML).toBe(
  143. '<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>'
  144. )
  145. vm.list.splice(1, 2)
  146. })
  147. .then(() => {
  148. expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')
  149. vm.list = [{ value: 'x' }, { value: 'y' }]
  150. })
  151. .then(() => {
  152. expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
  153. })
  154. .then(done)
  155. })
  156. if (hasSymbol) {
  157. it('should render native iterables (Map)', () => {
  158. const vm = new Vue({
  159. template: `<div><span v-for="[key, val] in list">{{key}},{{val}}</span></div>`,
  160. data: {
  161. list: new Map([
  162. [1, 'foo'],
  163. [2, 'bar']
  164. ])
  165. }
  166. }).$mount()
  167. expect(vm.$el.innerHTML).toBe(`<span>1,foo</span><span>2,bar</span>`)
  168. })
  169. it('should render native iterables (Set)', () => {
  170. const vm = new Vue({
  171. template: `<div><span v-for="val in list">{{val}}</span></div>`,
  172. data: {
  173. list: new Set([1, 2, 3])
  174. }
  175. }).$mount()
  176. expect(vm.$el.innerHTML).toBe(
  177. `<span>1</span><span>2</span><span>3</span>`
  178. )
  179. })
  180. it('should render iterable of primitive values', (done) => {
  181. const iterable = {
  182. models: ['a', 'b', 'c'],
  183. index: 0,
  184. [Symbol.iterator]() {
  185. const iterator = {
  186. index: 0,
  187. models: this.models,
  188. next() {
  189. if (this.index < this.models.length) {
  190. return { value: this.models[this.index++] }
  191. } else {
  192. return { done: true }
  193. }
  194. }
  195. }
  196. return iterator
  197. }
  198. }
  199. const vm = new Vue({
  200. template: `
  201. <div>
  202. <span v-for="item in list">{{item}}</span>
  203. </div>
  204. `,
  205. data: {
  206. list: iterable
  207. }
  208. }).$mount()
  209. expect(vm.$el.innerHTML).toBe(
  210. '<span>a</span><span>b</span><span>c</span>'
  211. )
  212. Vue.set(vm.list.models, 0, 'd')
  213. waitForUpdate(() => {
  214. expect(vm.$el.innerHTML).toBe(
  215. '<span>d</span><span>b</span><span>c</span>'
  216. )
  217. vm.list.models.push('d')
  218. })
  219. .then(() => {
  220. expect(vm.$el.innerHTML).toBe(
  221. '<span>d</span><span>b</span><span>c</span><span>d</span>'
  222. )
  223. vm.list.models.splice(1, 2)
  224. })
  225. .then(() => {
  226. expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')
  227. vm.list.models = ['x', 'y']
  228. })
  229. .then(() => {
  230. expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
  231. })
  232. .then(done)
  233. })
  234. it('should render iterable of primitive values with index', (done) => {
  235. const iterable = {
  236. models: ['a', 'b', 'c'],
  237. index: 0,
  238. [Symbol.iterator]() {
  239. const iterator = {
  240. index: 0,
  241. models: this.models,
  242. next() {
  243. if (this.index < this.models.length) {
  244. return { value: this.models[this.index++] }
  245. } else {
  246. return { done: true }
  247. }
  248. }
  249. }
  250. return iterator
  251. }
  252. }
  253. const vm = new Vue({
  254. template: `
  255. <div>
  256. <span v-for="(item, i) in list">{{i}}-{{item}}</span>
  257. </div>
  258. `,
  259. data: {
  260. list: iterable
  261. }
  262. }).$mount()
  263. expect(vm.$el.innerHTML).toBe(
  264. '<span>0-a</span><span>1-b</span><span>2-c</span>'
  265. )
  266. Vue.set(vm.list.models, 0, 'd')
  267. waitForUpdate(() => {
  268. expect(vm.$el.innerHTML).toBe(
  269. '<span>0-d</span><span>1-b</span><span>2-c</span>'
  270. )
  271. vm.list.models.push('d')
  272. })
  273. .then(() => {
  274. expect(vm.$el.innerHTML).toBe(
  275. '<span>0-d</span><span>1-b</span><span>2-c</span><span>3-d</span>'
  276. )
  277. vm.list.models.splice(1, 2)
  278. })
  279. .then(() => {
  280. expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')
  281. vm.list.models = ['x', 'y']
  282. })
  283. .then(() => {
  284. expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
  285. })
  286. .then(done)
  287. })
  288. it('should render iterable of object values', (done) => {
  289. const iterable = {
  290. models: [{ value: 'a' }, { value: 'b' }, { value: 'c' }],
  291. index: 0,
  292. [Symbol.iterator]() {
  293. const iterator = {
  294. index: 0,
  295. models: this.models,
  296. next() {
  297. if (this.index < this.models.length) {
  298. return { value: this.models[this.index++] }
  299. } else {
  300. return { done: true }
  301. }
  302. }
  303. }
  304. return iterator
  305. }
  306. }
  307. const vm = new Vue({
  308. template: `
  309. <div>
  310. <span v-for="item in list">{{item.value}}</span>
  311. </div>
  312. `,
  313. data: {
  314. list: iterable
  315. }
  316. }).$mount()
  317. expect(vm.$el.innerHTML).toBe(
  318. '<span>a</span><span>b</span><span>c</span>'
  319. )
  320. Vue.set(vm.list.models, 0, { value: 'd' })
  321. waitForUpdate(() => {
  322. expect(vm.$el.innerHTML).toBe(
  323. '<span>d</span><span>b</span><span>c</span>'
  324. )
  325. vm.list.models[0].value = 'e'
  326. })
  327. .then(() => {
  328. expect(vm.$el.innerHTML).toBe(
  329. '<span>e</span><span>b</span><span>c</span>'
  330. )
  331. vm.list.models.push({})
  332. })
  333. .then(() => {
  334. expect(vm.$el.innerHTML).toBe(
  335. '<span>e</span><span>b</span><span>c</span><span></span>'
  336. )
  337. vm.list.models.splice(1, 2)
  338. })
  339. .then(() => {
  340. expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')
  341. vm.list.models = [{ value: 'x' }, { value: 'y' }]
  342. })
  343. .then(() => {
  344. expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
  345. })
  346. .then(done)
  347. })
  348. it('should render iterable of object values with index', (done) => {
  349. const iterable = {
  350. models: [{ value: 'a' }, { value: 'b' }, { value: 'c' }],
  351. index: 0,
  352. [Symbol.iterator]() {
  353. const iterator = {
  354. index: 0,
  355. models: this.models,
  356. next() {
  357. if (this.index < this.models.length) {
  358. return { value: this.models[this.index++] }
  359. } else {
  360. return { done: true }
  361. }
  362. }
  363. }
  364. return iterator
  365. }
  366. }
  367. const vm = new Vue({
  368. template: `
  369. <div>
  370. <span v-for="(item, i) in list">{{i}}-{{item.value}}</span>
  371. </div>
  372. `,
  373. data: {
  374. list: iterable
  375. }
  376. }).$mount()
  377. expect(vm.$el.innerHTML).toBe(
  378. '<span>0-a</span><span>1-b</span><span>2-c</span>'
  379. )
  380. Vue.set(vm.list.models, 0, { value: 'd' })
  381. waitForUpdate(() => {
  382. expect(vm.$el.innerHTML).toBe(
  383. '<span>0-d</span><span>1-b</span><span>2-c</span>'
  384. )
  385. vm.list.models[0].value = 'e'
  386. })
  387. .then(() => {
  388. expect(vm.$el.innerHTML).toBe(
  389. '<span>0-e</span><span>1-b</span><span>2-c</span>'
  390. )
  391. vm.list.models.push({})
  392. })
  393. .then(() => {
  394. expect(vm.$el.innerHTML).toBe(
  395. '<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>'
  396. )
  397. vm.list.models.splice(1, 2)
  398. })
  399. .then(() => {
  400. expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')
  401. vm.list.models = [{ value: 'x' }, { value: 'y' }]
  402. })
  403. .then(() => {
  404. expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
  405. })
  406. .then(done)
  407. })
  408. }
  409. it('should render an Object', (done) => {
  410. const vm = new Vue({
  411. template: `
  412. <div>
  413. <span v-for="val in obj">{{val}}</span>
  414. </div>
  415. `,
  416. data: {
  417. obj: { a: 0, b: 1, c: 2 }
  418. }
  419. }).$mount()
  420. expect(vm.$el.innerHTML).toBe('<span>0</span><span>1</span><span>2</span>')
  421. vm.obj.a = 3
  422. waitForUpdate(() => {
  423. expect(vm.$el.innerHTML).toBe(
  424. '<span>3</span><span>1</span><span>2</span>'
  425. )
  426. Vue.set(vm.obj, 'd', 4)
  427. })
  428. .then(() => {
  429. expect(vm.$el.innerHTML).toBe(
  430. '<span>3</span><span>1</span><span>2</span><span>4</span>'
  431. )
  432. Vue.delete(vm.obj, 'a')
  433. })
  434. .then(() => {
  435. expect(vm.$el.innerHTML).toBe(
  436. '<span>1</span><span>2</span><span>4</span>'
  437. )
  438. })
  439. .then(done)
  440. })
  441. it('should render an Object with key', (done) => {
  442. const vm = new Vue({
  443. template: `
  444. <div>
  445. <span v-for="(val, key) in obj">{{val}}-{{key}}</span>
  446. </div>
  447. `,
  448. data: {
  449. obj: { a: 0, b: 1, c: 2 }
  450. }
  451. }).$mount()
  452. expect(vm.$el.innerHTML).toBe(
  453. '<span>0-a</span><span>1-b</span><span>2-c</span>'
  454. )
  455. vm.obj.a = 3
  456. waitForUpdate(() => {
  457. expect(vm.$el.innerHTML).toBe(
  458. '<span>3-a</span><span>1-b</span><span>2-c</span>'
  459. )
  460. Vue.set(vm.obj, 'd', 4)
  461. })
  462. .then(() => {
  463. expect(vm.$el.innerHTML).toBe(
  464. '<span>3-a</span><span>1-b</span><span>2-c</span><span>4-d</span>'
  465. )
  466. Vue.delete(vm.obj, 'a')
  467. })
  468. .then(() => {
  469. expect(vm.$el.innerHTML).toBe(
  470. '<span>1-b</span><span>2-c</span><span>4-d</span>'
  471. )
  472. })
  473. .then(done)
  474. })
  475. it('should render an Object with key and index', (done) => {
  476. const vm = new Vue({
  477. template: `
  478. <div>
  479. <span v-for="(val, key, i) in obj">{{val}}-{{key}}-{{i}}</span>
  480. </div>
  481. `,
  482. data: {
  483. obj: { a: 0, b: 1, c: 2 }
  484. }
  485. }).$mount()
  486. expect(vm.$el.innerHTML).toBe(
  487. '<span>0-a-0</span><span>1-b-1</span><span>2-c-2</span>'
  488. )
  489. vm.obj.a = 3
  490. waitForUpdate(() => {
  491. expect(vm.$el.innerHTML).toBe(
  492. '<span>3-a-0</span><span>1-b-1</span><span>2-c-2</span>'
  493. )
  494. Vue.set(vm.obj, 'd', 4)
  495. })
  496. .then(() => {
  497. expect(vm.$el.innerHTML).toBe(
  498. '<span>3-a-0</span><span>1-b-1</span><span>2-c-2</span><span>4-d-3</span>'
  499. )
  500. Vue.delete(vm.obj, 'a')
  501. })
  502. .then(() => {
  503. expect(vm.$el.innerHTML).toBe(
  504. '<span>1-b-0</span><span>2-c-1</span><span>4-d-2</span>'
  505. )
  506. })
  507. .then(done)
  508. })
  509. it('should render each key of data', (done) => {
  510. const vm = new Vue({
  511. template: `
  512. <div>
  513. <span v-for="(val, key) in $data">{{val}}-{{key}}</span>
  514. </div>
  515. `,
  516. data: { a: 0, b: 1, c: 2 }
  517. }).$mount()
  518. expect(vm.$el.innerHTML).toBe(
  519. '<span>0-a</span><span>1-b</span><span>2-c</span>'
  520. )
  521. vm.a = 3
  522. waitForUpdate(() => {
  523. expect(vm.$el.innerHTML).toBe(
  524. '<span>3-a</span><span>1-b</span><span>2-c</span>'
  525. )
  526. }).then(done)
  527. })
  528. it('check priorities: v-if before v-for', function () {
  529. const vm = new Vue({
  530. data: {
  531. items: [1, 2, 3]
  532. },
  533. template:
  534. '<div><div v-if="item < 3" v-for="item in items">{{item}}</div></div>'
  535. }).$mount()
  536. expect(vm.$el.textContent).toBe('12')
  537. })
  538. it('check priorities: v-if after v-for', function () {
  539. const vm = new Vue({
  540. data: {
  541. items: [1, 2, 3]
  542. },
  543. template:
  544. '<div><div v-for="item in items" v-if="item < 3">{{item}}</div></div>'
  545. }).$mount()
  546. expect(vm.$el.textContent).toBe('12')
  547. })
  548. it('range v-for', () => {
  549. const vm = new Vue({
  550. template: '<div><div v-for="n in 3">{{n}}</div></div>'
  551. }).$mount()
  552. expect(vm.$el.textContent).toBe('123')
  553. })
  554. it('without key', (done) => {
  555. const vm = new Vue({
  556. data: {
  557. items: [
  558. { id: 1, msg: 'a' },
  559. { id: 2, msg: 'b' },
  560. { id: 3, msg: 'c' }
  561. ]
  562. },
  563. template: '<div><div v-for="item in items">{{ item.msg }}</div></div>'
  564. }).$mount()
  565. expect(vm.$el.textContent).toBe('abc')
  566. const first = vm.$el.children[0]
  567. vm.items.reverse()
  568. waitForUpdate(() => {
  569. expect(vm.$el.textContent).toBe('cba')
  570. // assert reusing DOM element in place
  571. expect(vm.$el.children[0]).toBe(first)
  572. }).then(done)
  573. })
  574. it('with key', (done) => {
  575. const vm = new Vue({
  576. data: {
  577. items: [
  578. { id: 1, msg: 'a' },
  579. { id: 2, msg: 'b' },
  580. { id: 3, msg: 'c' }
  581. ]
  582. },
  583. template:
  584. '<div><div v-for="item in items" :key="item.id">{{ item.msg }}</div></div>'
  585. }).$mount()
  586. expect(vm.$el.textContent).toBe('abc')
  587. const first = vm.$el.children[0]
  588. vm.items.reverse()
  589. waitForUpdate(() => {
  590. expect(vm.$el.textContent).toBe('cba')
  591. // assert moving DOM element
  592. expect(vm.$el.children[0]).not.toBe(first)
  593. expect(vm.$el.children[2]).toBe(first)
  594. }).then(done)
  595. })
  596. it('nested loops', () => {
  597. const vm = new Vue({
  598. data: {
  599. items: [
  600. { items: [{ a: 1 }, { a: 2 }], a: 1 },
  601. { items: [{ a: 3 }, { a: 4 }], a: 2 }
  602. ]
  603. },
  604. template:
  605. '<div>' +
  606. '<div v-for="(item, i) in items">' +
  607. '<p v-for="(subItem, j) in item.items">{{j}} {{subItem.a}} {{i}} {{item.a}}</p>' +
  608. '</div>' +
  609. '</div>'
  610. }).$mount()
  611. expect(vm.$el.innerHTML).toBe(
  612. '<div><p>0 1 0 1</p><p>1 2 0 1</p></div>' +
  613. '<div><p>0 3 1 2</p><p>1 4 1 2</p></div>'
  614. )
  615. })
  616. it('template v-for', (done) => {
  617. const vm = new Vue({
  618. data: {
  619. list: [{ a: 1 }, { a: 2 }, { a: 3 }]
  620. },
  621. template:
  622. '<div>' +
  623. '<template v-for="item in list">' +
  624. '<p>{{item.a}}</p>' +
  625. '<p>{{item.a + 1}}</p>' +
  626. '</template>' +
  627. '</div>'
  628. }).$mount()
  629. assertMarkup()
  630. vm.list.reverse()
  631. waitForUpdate(() => {
  632. assertMarkup()
  633. vm.list.splice(1, 1)
  634. })
  635. .then(() => {
  636. assertMarkup()
  637. vm.list.splice(1, 0, { a: 2 })
  638. })
  639. .then(done)
  640. function assertMarkup() {
  641. const markup = vm.list
  642. .map(function (item) {
  643. return '<p>' + item.a + '</p><p>' + (item.a + 1) + '</p>'
  644. })
  645. .join('')
  646. expect(vm.$el.innerHTML).toBe(markup)
  647. }
  648. })
  649. it('component v-for', (done) => {
  650. const vm = new Vue({
  651. data: {
  652. list: [{ a: 1 }, { a: 2 }, { a: 3 }]
  653. },
  654. template:
  655. '<div>' +
  656. '<test v-for="item in list" :msg="item.a" :key="item.a">' +
  657. '<span>{{item.a}}</span>' +
  658. '</test>' +
  659. '</div>',
  660. components: {
  661. test: {
  662. props: ['msg'],
  663. template: '<p>{{msg}}<slot></slot></p>'
  664. }
  665. }
  666. }).$mount()
  667. assertMarkup()
  668. vm.list.reverse()
  669. waitForUpdate(() => {
  670. assertMarkup()
  671. vm.list.splice(1, 1)
  672. })
  673. .then(() => {
  674. assertMarkup()
  675. vm.list.splice(1, 0, { a: 2 })
  676. })
  677. .then(done)
  678. function assertMarkup() {
  679. const markup = vm.list
  680. .map(function (item) {
  681. return `<p>${item.a}<span>${item.a}</span></p>`
  682. })
  683. .join('')
  684. expect(vm.$el.innerHTML).toBe(markup)
  685. }
  686. })
  687. it('dynamic component v-for', (done) => {
  688. const vm = new Vue({
  689. data: {
  690. list: [{ type: 'one' }, { type: 'two' }]
  691. },
  692. template:
  693. '<div>' +
  694. '<component v-for="item in list" :key="item.type" :is="item.type"></component>' +
  695. '</div>',
  696. components: {
  697. one: {
  698. template: '<p>One!</p>'
  699. },
  700. two: {
  701. template: '<div>Two!</div>'
  702. }
  703. }
  704. }).$mount()
  705. expect(vm.$el.innerHTML).toContain('<p>One!</p><div>Two!</div>')
  706. vm.list.reverse()
  707. waitForUpdate(() => {
  708. expect(vm.$el.innerHTML).toContain('<div>Two!</div><p>One!</p>')
  709. }).then(done)
  710. })
  711. it('should warn component v-for without keys', () => {
  712. new Vue({
  713. template: `<div><test v-for="i in 3"></test></div>`,
  714. components: {
  715. test: {
  716. render() {}
  717. }
  718. }
  719. }).$mount()
  720. expect(
  721. `<test v-for="i in 3">: component lists rendered with v-for should have explicit keys`
  722. ).toHaveBeenTipped()
  723. })
  724. it('multi nested array reactivity', (done) => {
  725. const vm = new Vue({
  726. data: {
  727. list: [[['foo']]]
  728. },
  729. template: `
  730. <div>
  731. <div v-for="i in list">
  732. <div v-for="j in i">
  733. <div v-for="k in j">
  734. {{ k }}
  735. </div>
  736. </div>
  737. </div>
  738. </div>
  739. `
  740. }).$mount()
  741. expect(vm.$el.textContent).toMatch(/\s+foo\s+/)
  742. vm.list[0][0].push('bar')
  743. waitForUpdate(() => {
  744. expect(vm.$el.textContent).toMatch(/\s+foo\s+bar\s+/)
  745. }).then(done)
  746. })
  747. it('should work with strings', (done) => {
  748. const vm = new Vue({
  749. data: {
  750. text: 'foo'
  751. },
  752. template: `
  753. <div>
  754. <span v-for="letter in text">{{ letter }}.</span>
  755. </div>
  756. `
  757. }).$mount()
  758. expect(vm.$el.textContent).toMatch('f.o.o.')
  759. vm.text += 'bar'
  760. waitForUpdate(() => {
  761. expect(vm.$el.textContent).toMatch('f.o.o.b.a.r.')
  762. }).then(done)
  763. })
  764. // #7792
  765. it('should work with multiline expressions', () => {
  766. const vm = new Vue({
  767. data: {
  768. a: [1],
  769. b: [2]
  770. },
  771. template: `
  772. <div>
  773. <span v-for="n in (
  774. a.concat(
  775. b
  776. )
  777. )">{{ n }}</span>
  778. </div>
  779. `
  780. }).$mount()
  781. expect(vm.$el.textContent).toBe('12')
  782. })
  783. // #9181
  784. it('components with v-for and empty list', (done) => {
  785. const vm = new Vue({
  786. template:
  787. '<div attr>' +
  788. '<foo v-for="item in list" :key="item">{{ item }}</foo>' +
  789. '</div>',
  790. data: {
  791. list: undefined
  792. },
  793. components: {
  794. foo: {
  795. template: '<div><slot></slot></div>'
  796. }
  797. }
  798. }).$mount()
  799. expect(vm.$el.innerHTML).toBe('')
  800. vm.list = [1, 2, 3]
  801. waitForUpdate(() => {
  802. expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  803. }).then(done)
  804. })
  805. it('elements with v-for and empty list', (done) => {
  806. const vm = new Vue({
  807. template:
  808. '<div attr>' + '<div v-for="item in list">{{ item }}</div>' + '</div>',
  809. data: {
  810. list: undefined
  811. }
  812. }).$mount()
  813. expect(vm.$el.innerHTML).toBe('')
  814. vm.list = [1, 2, 3]
  815. waitForUpdate(() => {
  816. expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  817. }).then(done)
  818. })
  819. const supportsDestructuring = (() => {
  820. try {
  821. new Function('var { foo } = bar')
  822. return true
  823. } catch (e) {}
  824. })()
  825. if (supportsDestructuring) {
  826. it('should support destructuring syntax in alias position (object)', () => {
  827. const vm = new Vue({
  828. data: { list: [{ foo: 'hi', bar: 'ho' }] },
  829. template:
  830. '<div><div v-for="({ foo, bar }, i) in list">{{ foo }} {{ bar }} {{ i }}</div></div>'
  831. }).$mount()
  832. expect(vm.$el.textContent).toBe('hi ho 0')
  833. })
  834. it('should support destructuring syntax in alias position (array)', () => {
  835. const vm = new Vue({
  836. data: {
  837. list: [
  838. [1, 2],
  839. [3, 4]
  840. ]
  841. },
  842. template:
  843. '<div><div v-for="([ foo, bar ], i) in list">{{ foo }} {{ bar }} {{ i }}</div></div>'
  844. }).$mount()
  845. expect(vm.$el.textContent).toBe('1 2 03 4 1')
  846. })
  847. }
  848. })