component-keep-alive.spec.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. import Vue from 'vue'
  2. import injectStyles from '../transition/inject-styles'
  3. import { isIE9 } from 'core/util/env'
  4. import { nextFrame } from 'web/runtime/transition-util'
  5. describe('Component keep-alive', () => {
  6. const { duration, buffer } = injectStyles()
  7. let components, one, two, el
  8. beforeEach(() => {
  9. one = {
  10. template: '<div>one</div>',
  11. created: jasmine.createSpy('one created'),
  12. mounted: jasmine.createSpy('one mounted'),
  13. activated: jasmine.createSpy('one activated'),
  14. deactivated: jasmine.createSpy('one deactivated'),
  15. destroyed: jasmine.createSpy('one destroyed')
  16. }
  17. two = {
  18. template: '<div>two</div>',
  19. created: jasmine.createSpy('two created'),
  20. mounted: jasmine.createSpy('two mounted'),
  21. activated: jasmine.createSpy('two activated'),
  22. deactivated: jasmine.createSpy('two deactivated'),
  23. destroyed: jasmine.createSpy('two destroyed')
  24. }
  25. components = {
  26. one,
  27. two
  28. }
  29. el = document.createElement('div')
  30. document.body.appendChild(el)
  31. })
  32. function assertHookCalls (component, callCounts) {
  33. expect([
  34. component.created.calls.count(),
  35. component.mounted.calls.count(),
  36. component.activated.calls.count(),
  37. component.deactivated.calls.count(),
  38. component.destroyed.calls.count()
  39. ]).toEqual(callCounts)
  40. }
  41. it('should work', done => {
  42. const vm = new Vue({
  43. template: `
  44. <div v-if="ok">
  45. <keep-alive>
  46. <component :is="view"></component>
  47. </keep-alive>
  48. </div>
  49. `,
  50. data: {
  51. view: 'one',
  52. ok: true
  53. },
  54. components
  55. }).$mount()
  56. expect(vm.$el.textContent).toBe('one')
  57. assertHookCalls(one, [1, 1, 1, 0, 0])
  58. assertHookCalls(two, [0, 0, 0, 0, 0])
  59. vm.view = 'two'
  60. waitForUpdate(() => {
  61. expect(vm.$el.textContent).toBe('two')
  62. assertHookCalls(one, [1, 1, 1, 1, 0])
  63. assertHookCalls(two, [1, 1, 1, 0, 0])
  64. vm.view = 'one'
  65. }).then(() => {
  66. expect(vm.$el.textContent).toBe('one')
  67. assertHookCalls(one, [1, 1, 2, 1, 0])
  68. assertHookCalls(two, [1, 1, 1, 1, 0])
  69. vm.view = 'two'
  70. }).then(() => {
  71. expect(vm.$el.textContent).toBe('two')
  72. assertHookCalls(one, [1, 1, 2, 2, 0])
  73. assertHookCalls(two, [1, 1, 2, 1, 0])
  74. vm.ok = false // teardown
  75. }).then(() => {
  76. expect(vm.$el.textContent).toBe('')
  77. assertHookCalls(one, [1, 1, 2, 2, 1])
  78. assertHookCalls(two, [1, 1, 2, 2, 1])
  79. }).then(done)
  80. })
  81. it('should invoke hooks on the entire sub tree', done => {
  82. one.template = '<two/>'
  83. one.components = { two }
  84. const vm = new Vue({
  85. template: `
  86. <div>
  87. <keep-alive>
  88. <one v-if="ok"/>
  89. </keep-alive>
  90. </div>
  91. `,
  92. data: {
  93. ok: true
  94. },
  95. components
  96. }).$mount()
  97. expect(vm.$el.textContent).toBe('two')
  98. assertHookCalls(one, [1, 1, 1, 0, 0])
  99. assertHookCalls(two, [1, 1, 1, 0, 0])
  100. vm.ok = false
  101. waitForUpdate(() => {
  102. expect(vm.$el.textContent).toBe('')
  103. assertHookCalls(one, [1, 1, 1, 1, 0])
  104. assertHookCalls(two, [1, 1, 1, 1, 0])
  105. vm.ok = true
  106. }).then(() => {
  107. expect(vm.$el.textContent).toBe('two')
  108. assertHookCalls(one, [1, 1, 2, 1, 0])
  109. assertHookCalls(two, [1, 1, 2, 1, 0])
  110. vm.ok = false
  111. }).then(() => {
  112. expect(vm.$el.textContent).toBe('')
  113. assertHookCalls(one, [1, 1, 2, 2, 0])
  114. assertHookCalls(two, [1, 1, 2, 2, 0])
  115. }).then(done)
  116. })
  117. it('should handle nested keep-alive hooks properly', done => {
  118. one.template = '<keep-alive><two v-if="ok" /></keep-alive>'
  119. one.data = () => ({ ok: true })
  120. one.components = { two }
  121. const vm = new Vue({
  122. template: `
  123. <div>
  124. <keep-alive>
  125. <one v-if="ok" ref="one" />
  126. </keep-alive>
  127. </div>
  128. `,
  129. data: {
  130. ok: true
  131. },
  132. components
  133. }).$mount()
  134. const oneInstance = vm.$refs.one
  135. expect(vm.$el.textContent).toBe('two')
  136. assertHookCalls(one, [1, 1, 1, 0, 0])
  137. assertHookCalls(two, [1, 1, 1, 0, 0])
  138. vm.ok = false
  139. waitForUpdate(() => {
  140. expect(vm.$el.textContent).toBe('')
  141. assertHookCalls(one, [1, 1, 1, 1, 0])
  142. assertHookCalls(two, [1, 1, 1, 1, 0])
  143. }).then(() => {
  144. vm.ok = true
  145. }).then(() => {
  146. expect(vm.$el.textContent).toBe('two')
  147. assertHookCalls(one, [1, 1, 2, 1, 0])
  148. assertHookCalls(two, [1, 1, 2, 1, 0])
  149. }).then(() => {
  150. // toggle sub component when activated
  151. oneInstance.ok = false
  152. }).then(() => {
  153. expect(vm.$el.textContent).toBe('')
  154. assertHookCalls(one, [1, 1, 2, 1, 0])
  155. assertHookCalls(two, [1, 1, 2, 2, 0])
  156. }).then(() => {
  157. oneInstance.ok = true
  158. }).then(() => {
  159. expect(vm.$el.textContent).toBe('two')
  160. assertHookCalls(one, [1, 1, 2, 1, 0])
  161. assertHookCalls(two, [1, 1, 3, 2, 0])
  162. }).then(() => {
  163. vm.ok = false
  164. }).then(() => {
  165. expect(vm.$el.textContent).toBe('')
  166. assertHookCalls(one, [1, 1, 2, 2, 0])
  167. assertHookCalls(two, [1, 1, 3, 3, 0])
  168. }).then(() => {
  169. // toggle sub component when parent is deactivated
  170. oneInstance.ok = false
  171. }).then(() => {
  172. expect(vm.$el.textContent).toBe('')
  173. assertHookCalls(one, [1, 1, 2, 2, 0])
  174. assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
  175. }).then(() => {
  176. oneInstance.ok = true
  177. }).then(() => {
  178. expect(vm.$el.textContent).toBe('')
  179. assertHookCalls(one, [1, 1, 2, 2, 0])
  180. assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
  181. }).then(() => {
  182. vm.ok = true
  183. }).then(() => {
  184. expect(vm.$el.textContent).toBe('two')
  185. assertHookCalls(one, [1, 1, 3, 2, 0])
  186. assertHookCalls(two, [1, 1, 4, 3, 0])
  187. }).then(() => {
  188. oneInstance.ok = false
  189. vm.ok = false
  190. }).then(() => {
  191. expect(vm.$el.textContent).toBe('')
  192. assertHookCalls(one, [1, 1, 3, 3, 0])
  193. assertHookCalls(two, [1, 1, 4, 4, 0])
  194. }).then(() => {
  195. vm.ok = true
  196. }).then(() => {
  197. expect(vm.$el.textContent).toBe('')
  198. assertHookCalls(one, [1, 1, 4, 3, 0])
  199. assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive
  200. }).then(done)
  201. })
  202. function sharedAssertions (vm, done) {
  203. expect(vm.$el.textContent).toBe('one')
  204. assertHookCalls(one, [1, 1, 1, 0, 0])
  205. assertHookCalls(two, [0, 0, 0, 0, 0])
  206. vm.view = 'two'
  207. waitForUpdate(() => {
  208. expect(vm.$el.textContent).toBe('two')
  209. assertHookCalls(one, [1, 1, 1, 1, 0])
  210. assertHookCalls(two, [1, 1, 0, 0, 0])
  211. vm.view = 'one'
  212. }).then(() => {
  213. expect(vm.$el.textContent).toBe('one')
  214. assertHookCalls(one, [1, 1, 2, 1, 0])
  215. assertHookCalls(two, [1, 1, 0, 0, 1])
  216. vm.view = 'two'
  217. }).then(() => {
  218. expect(vm.$el.textContent).toBe('two')
  219. assertHookCalls(one, [1, 1, 2, 2, 0])
  220. assertHookCalls(two, [2, 2, 0, 0, 1])
  221. vm.ok = false // teardown
  222. }).then(() => {
  223. expect(vm.$el.textContent).toBe('')
  224. assertHookCalls(one, [1, 1, 2, 2, 1])
  225. assertHookCalls(two, [2, 2, 0, 0, 2])
  226. }).then(done)
  227. }
  228. it('include (string)', done => {
  229. const vm = new Vue({
  230. template: `
  231. <div v-if="ok">
  232. <keep-alive include="one">
  233. <component :is="view"></component>
  234. </keep-alive>
  235. </div>
  236. `,
  237. data: {
  238. view: 'one',
  239. ok: true
  240. },
  241. components
  242. }).$mount()
  243. sharedAssertions(vm, done)
  244. })
  245. it('include (regex)', done => {
  246. const vm = new Vue({
  247. template: `
  248. <div v-if="ok">
  249. <keep-alive :include="/^one$/">
  250. <component :is="view"></component>
  251. </keep-alive>
  252. </div>
  253. `,
  254. data: {
  255. view: 'one',
  256. ok: true
  257. },
  258. components
  259. }).$mount()
  260. sharedAssertions(vm, done)
  261. })
  262. it('include (array)', done => {
  263. const vm = new Vue({
  264. template: `
  265. <div v-if="ok">
  266. <keep-alive :include="['one']">
  267. <component :is="view"></component>
  268. </keep-alive>
  269. </div>
  270. `,
  271. data: {
  272. view: 'one',
  273. ok: true
  274. },
  275. components
  276. }).$mount()
  277. sharedAssertions(vm, done)
  278. })
  279. it('exclude (string)', done => {
  280. const vm = new Vue({
  281. template: `
  282. <div v-if="ok">
  283. <keep-alive exclude="two">
  284. <component :is="view"></component>
  285. </keep-alive>
  286. </div>
  287. `,
  288. data: {
  289. view: 'one',
  290. ok: true
  291. },
  292. components
  293. }).$mount()
  294. sharedAssertions(vm, done)
  295. })
  296. it('exclude (regex)', done => {
  297. const vm = new Vue({
  298. template: `
  299. <div v-if="ok">
  300. <keep-alive :exclude="/^two$/">
  301. <component :is="view"></component>
  302. </keep-alive>
  303. </div>
  304. `,
  305. data: {
  306. view: 'one',
  307. ok: true
  308. },
  309. components
  310. }).$mount()
  311. sharedAssertions(vm, done)
  312. })
  313. it('exclude (array)', done => {
  314. const vm = new Vue({
  315. template: `
  316. <div v-if="ok">
  317. <keep-alive :exclude="['two']">
  318. <component :is="view"></component>
  319. </keep-alive>
  320. </div>
  321. `,
  322. data: {
  323. view: 'one',
  324. ok: true
  325. },
  326. components
  327. }).$mount()
  328. sharedAssertions(vm, done)
  329. })
  330. it('include + exclude', done => {
  331. const vm = new Vue({
  332. template: `
  333. <div v-if="ok">
  334. <keep-alive include="one,two" exclude="two">
  335. <component :is="view"></component>
  336. </keep-alive>
  337. </div>
  338. `,
  339. data: {
  340. view: 'one',
  341. ok: true
  342. },
  343. components
  344. }).$mount()
  345. sharedAssertions(vm, done)
  346. })
  347. it('prune cache on include/exclude change', done => {
  348. const vm = new Vue({
  349. template: `
  350. <div>
  351. <keep-alive :include="include">
  352. <component :is="view"></component>
  353. </keep-alive>
  354. </div>
  355. `,
  356. data: {
  357. view: 'one',
  358. include: 'one,two'
  359. },
  360. components
  361. }).$mount()
  362. vm.view = 'two'
  363. waitForUpdate(() => {
  364. assertHookCalls(one, [1, 1, 1, 1, 0])
  365. assertHookCalls(two, [1, 1, 1, 0, 0])
  366. vm.include = 'two'
  367. }).then(() => {
  368. assertHookCalls(one, [1, 1, 1, 1, 1])
  369. assertHookCalls(two, [1, 1, 1, 0, 0])
  370. vm.view = 'one'
  371. }).then(() => {
  372. assertHookCalls(one, [2, 2, 1, 1, 1])
  373. assertHookCalls(two, [1, 1, 1, 1, 0])
  374. }).then(done)
  375. })
  376. it('prune cache on include/exclude change + view switch', done => {
  377. const vm = new Vue({
  378. template: `
  379. <div>
  380. <keep-alive :include="include">
  381. <component :is="view"></component>
  382. </keep-alive>
  383. </div>
  384. `,
  385. data: {
  386. view: 'one',
  387. include: 'one,two'
  388. },
  389. components
  390. }).$mount()
  391. vm.view = 'two'
  392. waitForUpdate(() => {
  393. assertHookCalls(one, [1, 1, 1, 1, 0])
  394. assertHookCalls(two, [1, 1, 1, 0, 0])
  395. vm.include = 'one'
  396. vm.view = 'one'
  397. }).then(() => {
  398. assertHookCalls(one, [1, 1, 2, 1, 0])
  399. // two should be pruned
  400. assertHookCalls(two, [1, 1, 1, 1, 1])
  401. }).then(done)
  402. })
  403. it('should not prune currently active instance', done => {
  404. const vm = new Vue({
  405. template: `
  406. <div>
  407. <keep-alive :include="include">
  408. <component :is="view"></component>
  409. </keep-alive>
  410. </div>
  411. `,
  412. data: {
  413. view: 'one',
  414. include: 'one,two'
  415. },
  416. components
  417. }).$mount()
  418. vm.include = 'two'
  419. waitForUpdate(() => {
  420. assertHookCalls(one, [1, 1, 1, 0, 0])
  421. assertHookCalls(two, [0, 0, 0, 0, 0])
  422. vm.view = 'two'
  423. }).then(() => {
  424. assertHookCalls(one, [1, 1, 1, 0, 1])
  425. assertHookCalls(two, [1, 1, 1, 0, 0])
  426. }).then(done)
  427. })
  428. // #3882
  429. it('deeply nested keep-alive should be destroyed properly', done => {
  430. one.template = `<div><keep-alive><two></two></keep-alive></div>`
  431. one.components = { two }
  432. const vm = new Vue({
  433. template: `<div><parent v-if="ok"></parent></div>`,
  434. data: { ok: true },
  435. components: {
  436. parent: {
  437. template: `<div><keep-alive><one></one></keep-alive></div>`,
  438. components: { one }
  439. }
  440. }
  441. }).$mount()
  442. assertHookCalls(one, [1, 1, 1, 0, 0])
  443. assertHookCalls(two, [1, 1, 1, 0, 0])
  444. vm.ok = false
  445. waitForUpdate(() => {
  446. assertHookCalls(one, [1, 1, 1, 1, 1])
  447. assertHookCalls(two, [1, 1, 1, 1, 1])
  448. }).then(done)
  449. })
  450. // #4237
  451. it('should update latest props/listeners for a re-activated component', done => {
  452. const one = {
  453. props: ['prop'],
  454. template: `<div>one {{ prop }}</div>`
  455. }
  456. const two = {
  457. props: ['prop'],
  458. template: `<div>two {{ prop }}</div>`
  459. }
  460. const vm = new Vue({
  461. data: { view: 'one', n: 1 },
  462. template: `
  463. <div>
  464. <keep-alive>
  465. <component :is="view" :prop="n"></component>
  466. </keep-alive>
  467. </div>
  468. `,
  469. components: { one, two }
  470. }).$mount()
  471. expect(vm.$el.textContent).toBe('one 1')
  472. vm.n++
  473. waitForUpdate(() => {
  474. expect(vm.$el.textContent).toBe('one 2')
  475. vm.view = 'two'
  476. }).then(() => {
  477. expect(vm.$el.textContent).toBe('two 2')
  478. }).then(done)
  479. })
  480. it('max', done => {
  481. const spyA = jasmine.createSpy()
  482. const spyB = jasmine.createSpy()
  483. const spyC = jasmine.createSpy()
  484. const spyAD = jasmine.createSpy()
  485. const spyBD = jasmine.createSpy()
  486. const spyCD = jasmine.createSpy()
  487. function assertCount (calls) {
  488. expect([
  489. spyA.calls.count(),
  490. spyAD.calls.count(),
  491. spyB.calls.count(),
  492. spyBD.calls.count(),
  493. spyC.calls.count(),
  494. spyCD.calls.count()
  495. ]).toEqual(calls)
  496. }
  497. const vm = new Vue({
  498. template: `
  499. <keep-alive max="2">
  500. <component :is="n"></component>
  501. </keep-alive>
  502. `,
  503. data: {
  504. n: 'aa'
  505. },
  506. components: {
  507. aa: {
  508. template: '<div>a</div>',
  509. created: spyA,
  510. destroyed: spyAD
  511. },
  512. bb: {
  513. template: '<div>bbb</div>',
  514. created: spyB,
  515. destroyed: spyBD
  516. },
  517. cc: {
  518. template: '<div>ccc</div>',
  519. created: spyC,
  520. destroyed: spyCD
  521. }
  522. }
  523. }).$mount()
  524. assertCount([1, 0, 0, 0, 0, 0])
  525. vm.n = 'bb'
  526. waitForUpdate(() => {
  527. assertCount([1, 0, 1, 0, 0, 0])
  528. vm.n = 'cc'
  529. }).then(() => {
  530. // should prune A because max cache reached
  531. assertCount([1, 1, 1, 0, 1, 0])
  532. vm.n = 'bb'
  533. }).then(() => {
  534. // B should be reused, and made latest
  535. assertCount([1, 1, 1, 0, 1, 0])
  536. vm.n = 'aa'
  537. }).then(() => {
  538. // C should be pruned because B was used last so C is the oldest cached
  539. assertCount([2, 1, 1, 0, 1, 1])
  540. }).then(done)
  541. })
  542. it('should warn unknown component inside', () => {
  543. new Vue({
  544. template: `<keep-alive><foo/></keep-alive>`
  545. }).$mount()
  546. expect(`Unknown custom element: <foo>`).toHaveBeenWarned()
  547. })
  548. // #6938
  549. it('should not cache anonymous component when include is specified', done => {
  550. const Foo = {
  551. name: 'foo',
  552. template: `<div>foo</div>`,
  553. created: jasmine.createSpy('foo')
  554. }
  555. const Bar = {
  556. template: `<div>bar</div>`,
  557. created: jasmine.createSpy('bar')
  558. }
  559. const Child = {
  560. functional: true,
  561. render (h, ctx) {
  562. return h(ctx.props.view ? Foo : Bar)
  563. }
  564. }
  565. const vm = new Vue({
  566. template: `
  567. <keep-alive include="foo">
  568. <child :view="view"></child>
  569. </keep-alive>
  570. `,
  571. data: {
  572. view: true
  573. },
  574. components: { Child }
  575. }).$mount()
  576. function assert (foo, bar) {
  577. expect(Foo.created.calls.count()).toBe(foo)
  578. expect(Bar.created.calls.count()).toBe(bar)
  579. }
  580. expect(vm.$el.textContent).toBe('foo')
  581. assert(1, 0)
  582. vm.view = false
  583. waitForUpdate(() => {
  584. expect(vm.$el.textContent).toBe('bar')
  585. assert(1, 1)
  586. vm.view = true
  587. }).then(() => {
  588. expect(vm.$el.textContent).toBe('foo')
  589. assert(1, 1)
  590. vm.view = false
  591. }).then(() => {
  592. expect(vm.$el.textContent).toBe('bar')
  593. assert(1, 2)
  594. }).then(done)
  595. })
  596. it('should cache anonymous components if include is not specified', done => {
  597. const Foo = {
  598. template: `<div>foo</div>`,
  599. created: jasmine.createSpy('foo')
  600. }
  601. const Bar = {
  602. template: `<div>bar</div>`,
  603. created: jasmine.createSpy('bar')
  604. }
  605. const Child = {
  606. functional: true,
  607. render (h, ctx) {
  608. return h(ctx.props.view ? Foo : Bar)
  609. }
  610. }
  611. const vm = new Vue({
  612. template: `
  613. <keep-alive>
  614. <child :view="view"></child>
  615. </keep-alive>
  616. `,
  617. data: {
  618. view: true
  619. },
  620. components: { Child }
  621. }).$mount()
  622. function assert (foo, bar) {
  623. expect(Foo.created.calls.count()).toBe(foo)
  624. expect(Bar.created.calls.count()).toBe(bar)
  625. }
  626. expect(vm.$el.textContent).toBe('foo')
  627. assert(1, 0)
  628. vm.view = false
  629. waitForUpdate(() => {
  630. expect(vm.$el.textContent).toBe('bar')
  631. assert(1, 1)
  632. vm.view = true
  633. }).then(() => {
  634. expect(vm.$el.textContent).toBe('foo')
  635. assert(1, 1)
  636. vm.view = false
  637. }).then(() => {
  638. expect(vm.$el.textContent).toBe('bar')
  639. assert(1, 1)
  640. }).then(done)
  641. })
  642. // #7105
  643. it('should not destroy active instance when pruning cache', done => {
  644. const Foo = {
  645. template: `<div>foo</div>`,
  646. destroyed: jasmine.createSpy('destroyed')
  647. }
  648. const vm = new Vue({
  649. template: `
  650. <div>
  651. <keep-alive :include="include">
  652. <foo/>
  653. </keep-alive>
  654. </div>
  655. `,
  656. data: {
  657. include: ['foo']
  658. },
  659. components: { Foo }
  660. }).$mount()
  661. // condition: a render where a previous component is reused
  662. vm.include = ['foo']
  663. waitForUpdate(() => {
  664. vm.include = ['']
  665. }).then(() => {
  666. expect(Foo.destroyed).not.toHaveBeenCalled()
  667. }).then(done)
  668. })
  669. if (!isIE9) {
  670. it('with transition-mode out-in', done => {
  671. let next
  672. const vm = new Vue({
  673. template: `<div>
  674. <transition name="test" mode="out-in" @after-leave="afterLeave">
  675. <keep-alive>
  676. <component :is="view" class="test"></component>
  677. </keep-alive>
  678. </transition>
  679. </div>`,
  680. data: {
  681. view: 'one'
  682. },
  683. components,
  684. methods: {
  685. afterLeave () {
  686. next()
  687. }
  688. }
  689. }).$mount(el)
  690. expect(vm.$el.textContent).toBe('one')
  691. assertHookCalls(one, [1, 1, 1, 0, 0])
  692. assertHookCalls(two, [0, 0, 0, 0, 0])
  693. vm.view = 'two'
  694. waitForUpdate(() => {
  695. expect(vm.$el.innerHTML).toBe(
  696. '<div class="test test-leave test-leave-active">one</div><!---->'
  697. )
  698. assertHookCalls(one, [1, 1, 1, 1, 0])
  699. assertHookCalls(two, [0, 0, 0, 0, 0])
  700. }).thenWaitFor(nextFrame).then(() => {
  701. expect(vm.$el.innerHTML).toBe(
  702. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  703. )
  704. }).thenWaitFor(_next => { next = _next }).then(() => {
  705. expect(vm.$el.innerHTML).toBe('<!---->')
  706. }).thenWaitFor(nextFrame).then(() => {
  707. expect(vm.$el.innerHTML).toBe(
  708. '<div class="test test-enter test-enter-active">two</div>'
  709. )
  710. assertHookCalls(one, [1, 1, 1, 1, 0])
  711. assertHookCalls(two, [1, 1, 1, 0, 0])
  712. }).thenWaitFor(nextFrame).then(() => {
  713. expect(vm.$el.innerHTML).toBe(
  714. '<div class="test test-enter-active test-enter-to">two</div>'
  715. )
  716. }).thenWaitFor(duration + buffer).then(() => {
  717. expect(vm.$el.innerHTML).toBe(
  718. '<div class="test">two</div>'
  719. )
  720. assertHookCalls(one, [1, 1, 1, 1, 0])
  721. assertHookCalls(two, [1, 1, 1, 0, 0])
  722. }).then(() => {
  723. vm.view = 'one'
  724. }).then(() => {
  725. expect(vm.$el.innerHTML).toBe(
  726. '<div class="test test-leave test-leave-active">two</div><!---->'
  727. )
  728. assertHookCalls(one, [1, 1, 1, 1, 0])
  729. assertHookCalls(two, [1, 1, 1, 1, 0])
  730. }).thenWaitFor(nextFrame).then(() => {
  731. expect(vm.$el.innerHTML).toBe(
  732. '<div class="test test-leave-active test-leave-to">two</div><!---->'
  733. )
  734. }).thenWaitFor(_next => { next = _next }).then(() => {
  735. expect(vm.$el.innerHTML).toBe('<!---->')
  736. }).thenWaitFor(nextFrame).then(() => {
  737. expect(vm.$el.innerHTML).toBe(
  738. '<div class="test test-enter test-enter-active">one</div>'
  739. )
  740. assertHookCalls(one, [1, 1, 2, 1, 0])
  741. assertHookCalls(two, [1, 1, 1, 1, 0])
  742. }).thenWaitFor(nextFrame).then(() => {
  743. expect(vm.$el.innerHTML).toBe(
  744. '<div class="test test-enter-active test-enter-to">one</div>'
  745. )
  746. }).thenWaitFor(duration + buffer).then(() => {
  747. expect(vm.$el.innerHTML).toBe(
  748. '<div class="test">one</div>'
  749. )
  750. assertHookCalls(one, [1, 1, 2, 1, 0])
  751. assertHookCalls(two, [1, 1, 1, 1, 0])
  752. }).then(done)
  753. })
  754. it('with transition-mode out-in + include', done => {
  755. let next
  756. const vm = new Vue({
  757. template: `<div>
  758. <transition name="test" mode="out-in" @after-leave="afterLeave">
  759. <keep-alive include="one">
  760. <component :is="view" class="test"></component>
  761. </keep-alive>
  762. </transition>
  763. </div>`,
  764. data: {
  765. view: 'one'
  766. },
  767. components,
  768. methods: {
  769. afterLeave () {
  770. next()
  771. }
  772. }
  773. }).$mount(el)
  774. expect(vm.$el.textContent).toBe('one')
  775. assertHookCalls(one, [1, 1, 1, 0, 0])
  776. assertHookCalls(two, [0, 0, 0, 0, 0])
  777. vm.view = 'two'
  778. waitForUpdate(() => {
  779. expect(vm.$el.innerHTML).toBe(
  780. '<div class="test test-leave test-leave-active">one</div><!---->'
  781. )
  782. assertHookCalls(one, [1, 1, 1, 1, 0])
  783. assertHookCalls(two, [0, 0, 0, 0, 0])
  784. }).thenWaitFor(nextFrame).then(() => {
  785. expect(vm.$el.innerHTML).toBe(
  786. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  787. )
  788. }).thenWaitFor(_next => { next = _next }).then(() => {
  789. expect(vm.$el.innerHTML).toBe('<!---->')
  790. }).thenWaitFor(nextFrame).then(() => {
  791. expect(vm.$el.innerHTML).toBe(
  792. '<div class="test test-enter test-enter-active">two</div>'
  793. )
  794. assertHookCalls(one, [1, 1, 1, 1, 0])
  795. assertHookCalls(two, [1, 1, 0, 0, 0])
  796. }).thenWaitFor(nextFrame).then(() => {
  797. expect(vm.$el.innerHTML).toBe(
  798. '<div class="test test-enter-active test-enter-to">two</div>'
  799. )
  800. }).thenWaitFor(duration + buffer).then(() => {
  801. expect(vm.$el.innerHTML).toBe(
  802. '<div class="test">two</div>'
  803. )
  804. assertHookCalls(one, [1, 1, 1, 1, 0])
  805. assertHookCalls(two, [1, 1, 0, 0, 0])
  806. }).then(() => {
  807. vm.view = 'one'
  808. }).then(() => {
  809. expect(vm.$el.innerHTML).toBe(
  810. '<div class="test test-leave test-leave-active">two</div><!---->'
  811. )
  812. assertHookCalls(one, [1, 1, 1, 1, 0])
  813. assertHookCalls(two, [1, 1, 0, 0, 1])
  814. }).thenWaitFor(nextFrame).then(() => {
  815. expect(vm.$el.innerHTML).toBe(
  816. '<div class="test test-leave-active test-leave-to">two</div><!---->'
  817. )
  818. }).thenWaitFor(_next => { next = _next }).then(() => {
  819. expect(vm.$el.innerHTML).toBe('<!---->')
  820. }).thenWaitFor(nextFrame).then(() => {
  821. expect(vm.$el.innerHTML).toBe(
  822. '<div class="test test-enter test-enter-active">one</div>'
  823. )
  824. assertHookCalls(one, [1, 1, 2, 1, 0])
  825. assertHookCalls(two, [1, 1, 0, 0, 1])
  826. }).thenWaitFor(nextFrame).then(() => {
  827. expect(vm.$el.innerHTML).toBe(
  828. '<div class="test test-enter-active test-enter-to">one</div>'
  829. )
  830. }).thenWaitFor(duration + buffer).then(() => {
  831. expect(vm.$el.innerHTML).toBe(
  832. '<div class="test">one</div>'
  833. )
  834. assertHookCalls(one, [1, 1, 2, 1, 0])
  835. assertHookCalls(two, [1, 1, 0, 0, 1])
  836. }).then(done)
  837. })
  838. it('with transition-mode in-out', done => {
  839. let next
  840. const vm = new Vue({
  841. template: `<div>
  842. <transition name="test" mode="in-out" @after-enter="afterEnter">
  843. <keep-alive>
  844. <component :is="view" class="test"></component>
  845. </keep-alive>
  846. </transition>
  847. </div>`,
  848. data: {
  849. view: 'one'
  850. },
  851. components,
  852. methods: {
  853. afterEnter () {
  854. next()
  855. }
  856. }
  857. }).$mount(el)
  858. expect(vm.$el.textContent).toBe('one')
  859. assertHookCalls(one, [1, 1, 1, 0, 0])
  860. assertHookCalls(two, [0, 0, 0, 0, 0])
  861. vm.view = 'two'
  862. waitForUpdate(() => {
  863. expect(vm.$el.innerHTML).toBe(
  864. '<div class="test">one</div>' +
  865. '<div class="test test-enter test-enter-active">two</div>'
  866. )
  867. assertHookCalls(one, [1, 1, 1, 1, 0])
  868. assertHookCalls(two, [1, 1, 1, 0, 0])
  869. }).thenWaitFor(nextFrame).then(() => {
  870. expect(vm.$el.innerHTML).toBe(
  871. '<div class="test">one</div>' +
  872. '<div class="test test-enter-active test-enter-to">two</div>'
  873. )
  874. }).thenWaitFor(_next => { next = _next }).then(() => {
  875. expect(vm.$el.innerHTML).toBe(
  876. '<div class="test">one</div>' +
  877. '<div class="test">two</div>'
  878. )
  879. }).then(() => {
  880. expect(vm.$el.innerHTML).toBe(
  881. '<div class="test test-leave test-leave-active">one</div>' +
  882. '<div class="test">two</div>'
  883. )
  884. }).thenWaitFor(nextFrame).then(() => {
  885. expect(vm.$el.innerHTML).toBe(
  886. '<div class="test test-leave-active test-leave-to">one</div>' +
  887. '<div class="test">two</div>'
  888. )
  889. }).thenWaitFor(duration + buffer).then(() => {
  890. expect(vm.$el.innerHTML).toBe(
  891. '<div class="test">two</div>'
  892. )
  893. assertHookCalls(one, [1, 1, 1, 1, 0])
  894. assertHookCalls(two, [1, 1, 1, 0, 0])
  895. }).then(() => {
  896. vm.view = 'one'
  897. }).then(() => {
  898. expect(vm.$el.innerHTML).toBe(
  899. '<div class="test">two</div>' +
  900. '<div class="test test-enter test-enter-active">one</div>'
  901. )
  902. assertHookCalls(one, [1, 1, 2, 1, 0])
  903. assertHookCalls(two, [1, 1, 1, 1, 0])
  904. }).thenWaitFor(nextFrame).then(() => {
  905. expect(vm.$el.innerHTML).toBe(
  906. '<div class="test">two</div>' +
  907. '<div class="test test-enter-active test-enter-to">one</div>'
  908. )
  909. }).thenWaitFor(_next => { next = _next }).then(() => {
  910. expect(vm.$el.innerHTML).toBe(
  911. '<div class="test">two</div>' +
  912. '<div class="test">one</div>'
  913. )
  914. }).then(() => {
  915. expect(vm.$el.innerHTML).toBe(
  916. '<div class="test test-leave test-leave-active">two</div>' +
  917. '<div class="test">one</div>'
  918. )
  919. }).thenWaitFor(nextFrame).then(() => {
  920. expect(vm.$el.innerHTML).toBe(
  921. '<div class="test test-leave-active test-leave-to">two</div>' +
  922. '<div class="test">one</div>'
  923. )
  924. }).thenWaitFor(duration + buffer).then(() => {
  925. expect(vm.$el.innerHTML).toBe(
  926. '<div class="test">one</div>'
  927. )
  928. assertHookCalls(one, [1, 1, 2, 1, 0])
  929. assertHookCalls(two, [1, 1, 1, 1, 0])
  930. }).then(done)
  931. })
  932. it('dynamic components, in-out with early cancel', done => {
  933. let next
  934. const vm = new Vue({
  935. template: `<div>
  936. <transition name="test" mode="in-out" @after-enter="afterEnter">
  937. <keep-alive>
  938. <component :is="view" class="test"></component>
  939. </keep-alive>
  940. </transition>
  941. </div>`,
  942. data: { view: 'one' },
  943. components,
  944. methods: {
  945. afterEnter () {
  946. next()
  947. }
  948. }
  949. }).$mount(el)
  950. expect(vm.$el.textContent).toBe('one')
  951. vm.view = 'two'
  952. waitForUpdate(() => {
  953. expect(vm.$el.innerHTML).toBe(
  954. '<div class="test">one</div>' +
  955. '<div class="test test-enter test-enter-active">two</div>'
  956. )
  957. }).thenWaitFor(nextFrame).then(() => {
  958. expect(vm.$el.innerHTML).toBe(
  959. '<div class="test">one</div>' +
  960. '<div class="test test-enter-active test-enter-to">two</div>'
  961. )
  962. // switch again before enter finishes,
  963. // this cancels both enter and leave.
  964. vm.view = 'one'
  965. }).then(() => {
  966. // 1. the pending leaving "one" should be removed instantly.
  967. // 2. the entering "two" should be placed into its final state instantly.
  968. // 3. a new "one" is created and entering
  969. expect(vm.$el.innerHTML).toBe(
  970. '<div class="test">two</div>' +
  971. '<div class="test test-enter test-enter-active">one</div>'
  972. )
  973. }).thenWaitFor(nextFrame).then(() => {
  974. expect(vm.$el.innerHTML).toBe(
  975. '<div class="test">two</div>' +
  976. '<div class="test test-enter-active test-enter-to">one</div>'
  977. )
  978. }).thenWaitFor(_next => { next = _next }).then(() => {
  979. expect(vm.$el.innerHTML).toBe(
  980. '<div class="test">two</div>' +
  981. '<div class="test">one</div>'
  982. )
  983. }).then(() => {
  984. expect(vm.$el.innerHTML).toBe(
  985. '<div class="test test-leave test-leave-active">two</div>' +
  986. '<div class="test">one</div>'
  987. )
  988. }).thenWaitFor(nextFrame).then(() => {
  989. expect(vm.$el.innerHTML).toBe(
  990. '<div class="test test-leave-active test-leave-to">two</div>' +
  991. '<div class="test">one</div>'
  992. )
  993. }).thenWaitFor(duration + buffer).then(() => {
  994. expect(vm.$el.innerHTML).toBe(
  995. '<div class="test">one</div>'
  996. )
  997. }).then(done).then(done)
  998. })
  999. // #4339
  1000. it('component with inner transition', done => {
  1001. const vm = new Vue({
  1002. template: `
  1003. <div>
  1004. <keep-alive>
  1005. <component ref="test" :is="view"></component>
  1006. </keep-alive>
  1007. </div>
  1008. `,
  1009. data: { view: 'foo' },
  1010. components: {
  1011. foo: { template: '<transition><div class="test">foo</div></transition>' },
  1012. bar: { template: '<transition name="test"><div class="test">bar</div></transition>' }
  1013. }
  1014. }).$mount(el)
  1015. // should not apply transition on initial render by default
  1016. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  1017. vm.view = 'bar'
  1018. waitForUpdate(() => {
  1019. expect(vm.$el.innerHTML).toBe(
  1020. '<div class="test v-leave v-leave-active">foo</div>' +
  1021. '<div class="test test-enter test-enter-active">bar</div>'
  1022. )
  1023. }).thenWaitFor(nextFrame).then(() => {
  1024. expect(vm.$el.innerHTML).toBe(
  1025. '<div class="test v-leave-active v-leave-to">foo</div>' +
  1026. '<div class="test test-enter-active test-enter-to">bar</div>'
  1027. )
  1028. }).thenWaitFor(duration + buffer).then(() => {
  1029. expect(vm.$el.innerHTML).toBe(
  1030. '<div class="test">bar</div>'
  1031. )
  1032. vm.view = 'foo'
  1033. }).then(() => {
  1034. expect(vm.$el.innerHTML).toBe(
  1035. '<div class="test test-leave test-leave-active">bar</div>' +
  1036. '<div class="test v-enter v-enter-active">foo</div>'
  1037. )
  1038. }).thenWaitFor(nextFrame).then(() => {
  1039. expect(vm.$el.innerHTML).toBe(
  1040. '<div class="test test-leave-active test-leave-to">bar</div>' +
  1041. '<div class="test v-enter-active v-enter-to">foo</div>'
  1042. )
  1043. }).thenWaitFor(duration + buffer).then(() => {
  1044. expect(vm.$el.innerHTML).toBe(
  1045. '<div class="test">foo</div>'
  1046. )
  1047. }).then(done)
  1048. })
  1049. it('async components with transition-mode out-in', done => {
  1050. const barResolve = jasmine.createSpy('bar resolved')
  1051. let next
  1052. const foo = (resolve) => {
  1053. setTimeout(() => {
  1054. resolve(one)
  1055. Vue.nextTick(next)
  1056. }, duration / 2)
  1057. }
  1058. const bar = (resolve) => {
  1059. setTimeout(() => {
  1060. resolve(two)
  1061. barResolve()
  1062. }, duration / 2)
  1063. }
  1064. components = {
  1065. foo,
  1066. bar
  1067. }
  1068. const vm = new Vue({
  1069. template: `<div>
  1070. <transition name="test" mode="out-in" @after-enter="afterEnter" @after-leave="afterLeave">
  1071. <keep-alive>
  1072. <component :is="view" class="test"></component>
  1073. </keep-alive>
  1074. </transition>
  1075. </div>`,
  1076. data: {
  1077. view: 'foo'
  1078. },
  1079. components,
  1080. methods: {
  1081. afterEnter () {
  1082. next()
  1083. },
  1084. afterLeave () {
  1085. next()
  1086. }
  1087. }
  1088. }).$mount(el)
  1089. expect(vm.$el.textContent).toBe('')
  1090. next = () => {
  1091. assertHookCalls(one, [1, 1, 1, 0, 0])
  1092. assertHookCalls(two, [0, 0, 0, 0, 0])
  1093. waitForUpdate(() => {
  1094. expect(vm.$el.innerHTML).toBe(
  1095. '<div class="test test-enter test-enter-active">one</div>'
  1096. )
  1097. }).thenWaitFor(nextFrame).then(() => {
  1098. expect(vm.$el.innerHTML).toBe(
  1099. '<div class="test test-enter-active test-enter-to">one</div>'
  1100. )
  1101. }).thenWaitFor(_next => { next = _next }).then(() => {
  1102. // foo afterEnter get called
  1103. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  1104. vm.view = 'bar'
  1105. }).thenWaitFor(nextFrame).then(() => {
  1106. assertHookCalls(one, [1, 1, 1, 1, 0])
  1107. assertHookCalls(two, [0, 0, 0, 0, 0])
  1108. expect(vm.$el.innerHTML).toBe(
  1109. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  1110. )
  1111. }).thenWaitFor(_next => { next = _next }).then(() => {
  1112. // foo afterLeave get called
  1113. // and bar has already been resolved before afterLeave get called
  1114. expect(barResolve.calls.count()).toBe(1)
  1115. expect(vm.$el.innerHTML).toBe('<!---->')
  1116. }).thenWaitFor(nextFrame).then(() => {
  1117. expect(vm.$el.innerHTML).toBe(
  1118. '<div class="test test-enter test-enter-active">two</div>'
  1119. )
  1120. assertHookCalls(one, [1, 1, 1, 1, 0])
  1121. assertHookCalls(two, [1, 1, 1, 0, 0])
  1122. }).thenWaitFor(nextFrame).then(() => {
  1123. expect(vm.$el.innerHTML).toBe(
  1124. '<div class="test test-enter-active test-enter-to">two</div>'
  1125. )
  1126. }).thenWaitFor(_next => { next = _next }).then(() => {
  1127. // bar afterEnter get called
  1128. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  1129. }).then(done)
  1130. }
  1131. })
  1132. }
  1133. })