component-keep-alive.spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. import Vue from 'vue'
  2. describe('Component keep-alive', () => {
  3. let components, one, two, el
  4. beforeEach(() => {
  5. one = {
  6. template: '<div>one</div>',
  7. created: vi.fn(),
  8. mounted: vi.fn(),
  9. activated: vi.fn(),
  10. deactivated: vi.fn(),
  11. destroyed: vi.fn()
  12. }
  13. two = {
  14. template: '<div>two</div>',
  15. created: vi.fn(),
  16. mounted: vi.fn(),
  17. activated: vi.fn(),
  18. deactivated: vi.fn(),
  19. destroyed: vi.fn()
  20. }
  21. components = {
  22. one,
  23. two
  24. }
  25. el = document.createElement('div')
  26. document.body.appendChild(el)
  27. })
  28. function assertHookCalls(component, callCounts) {
  29. expect([
  30. component.created.mock.calls.length,
  31. component.mounted.mock.calls.length,
  32. component.activated.mock.calls.length,
  33. component.deactivated.mock.calls.length,
  34. component.destroyed.mock.calls.length
  35. ]).toEqual(callCounts)
  36. }
  37. it('should work', done => {
  38. const vm = new Vue({
  39. template: `
  40. <div v-if="ok">
  41. <keep-alive>
  42. <component :is="view"></component>
  43. </keep-alive>
  44. </div>
  45. `,
  46. data: {
  47. view: 'one',
  48. ok: true
  49. },
  50. components
  51. }).$mount()
  52. expect(vm.$el.textContent).toBe('one')
  53. assertHookCalls(one, [1, 1, 1, 0, 0])
  54. assertHookCalls(two, [0, 0, 0, 0, 0])
  55. vm.view = 'two'
  56. waitForUpdate(() => {
  57. expect(vm.$el.textContent).toBe('two')
  58. assertHookCalls(one, [1, 1, 1, 1, 0])
  59. assertHookCalls(two, [1, 1, 1, 0, 0])
  60. vm.view = 'one'
  61. })
  62. .then(() => {
  63. expect(vm.$el.textContent).toBe('one')
  64. assertHookCalls(one, [1, 1, 2, 1, 0])
  65. assertHookCalls(two, [1, 1, 1, 1, 0])
  66. vm.view = 'two'
  67. })
  68. .then(() => {
  69. expect(vm.$el.textContent).toBe('two')
  70. assertHookCalls(one, [1, 1, 2, 2, 0])
  71. assertHookCalls(two, [1, 1, 2, 1, 0])
  72. vm.ok = false // teardown
  73. })
  74. .then(() => {
  75. expect(vm.$el.textContent).toBe('')
  76. assertHookCalls(one, [1, 1, 2, 2, 1])
  77. assertHookCalls(two, [1, 1, 2, 2, 1])
  78. })
  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. })
  107. .then(() => {
  108. expect(vm.$el.textContent).toBe('two')
  109. assertHookCalls(one, [1, 1, 2, 1, 0])
  110. assertHookCalls(two, [1, 1, 2, 1, 0])
  111. vm.ok = false
  112. })
  113. .then(() => {
  114. expect(vm.$el.textContent).toBe('')
  115. assertHookCalls(one, [1, 1, 2, 2, 0])
  116. assertHookCalls(two, [1, 1, 2, 2, 0])
  117. })
  118. .then(done)
  119. })
  120. it('should handle nested keep-alive hooks properly', done => {
  121. one.template = '<keep-alive><two v-if="ok" /></keep-alive>'
  122. one.data = () => ({ ok: true })
  123. one.components = { two }
  124. const vm = new Vue({
  125. template: `
  126. <div>
  127. <keep-alive>
  128. <one v-if="ok" ref="one" />
  129. </keep-alive>
  130. </div>
  131. `,
  132. data: {
  133. ok: true
  134. },
  135. components
  136. }).$mount()
  137. const oneInstance = vm.$refs.one
  138. expect(vm.$el.textContent).toBe('two')
  139. assertHookCalls(one, [1, 1, 1, 0, 0])
  140. assertHookCalls(two, [1, 1, 1, 0, 0])
  141. vm.ok = false
  142. waitForUpdate(() => {
  143. expect(vm.$el.textContent).toBe('')
  144. assertHookCalls(one, [1, 1, 1, 1, 0])
  145. assertHookCalls(two, [1, 1, 1, 1, 0])
  146. })
  147. .then(() => {
  148. vm.ok = true
  149. })
  150. .then(() => {
  151. expect(vm.$el.textContent).toBe('two')
  152. assertHookCalls(one, [1, 1, 2, 1, 0])
  153. assertHookCalls(two, [1, 1, 2, 1, 0])
  154. })
  155. .then(() => {
  156. // toggle sub component when activated
  157. oneInstance.ok = false
  158. })
  159. .then(() => {
  160. expect(vm.$el.textContent).toBe('')
  161. assertHookCalls(one, [1, 1, 2, 1, 0])
  162. assertHookCalls(two, [1, 1, 2, 2, 0])
  163. })
  164. .then(() => {
  165. oneInstance.ok = true
  166. })
  167. .then(() => {
  168. expect(vm.$el.textContent).toBe('two')
  169. assertHookCalls(one, [1, 1, 2, 1, 0])
  170. assertHookCalls(two, [1, 1, 3, 2, 0])
  171. })
  172. .then(() => {
  173. vm.ok = false
  174. })
  175. .then(() => {
  176. expect(vm.$el.textContent).toBe('')
  177. assertHookCalls(one, [1, 1, 2, 2, 0])
  178. assertHookCalls(two, [1, 1, 3, 3, 0])
  179. })
  180. .then(() => {
  181. // toggle sub component when parent is deactivated
  182. oneInstance.ok = false
  183. })
  184. .then(() => {
  185. expect(vm.$el.textContent).toBe('')
  186. assertHookCalls(one, [1, 1, 2, 2, 0])
  187. assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
  188. })
  189. .then(() => {
  190. oneInstance.ok = true
  191. })
  192. .then(() => {
  193. expect(vm.$el.textContent).toBe('')
  194. assertHookCalls(one, [1, 1, 2, 2, 0])
  195. assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected
  196. })
  197. .then(() => {
  198. vm.ok = true
  199. })
  200. .then(() => {
  201. expect(vm.$el.textContent).toBe('two')
  202. assertHookCalls(one, [1, 1, 3, 2, 0])
  203. assertHookCalls(two, [1, 1, 4, 3, 0])
  204. })
  205. .then(() => {
  206. oneInstance.ok = false
  207. vm.ok = false
  208. })
  209. .then(() => {
  210. expect(vm.$el.textContent).toBe('')
  211. assertHookCalls(one, [1, 1, 3, 3, 0])
  212. assertHookCalls(two, [1, 1, 4, 4, 0])
  213. })
  214. .then(() => {
  215. vm.ok = true
  216. })
  217. .then(() => {
  218. expect(vm.$el.textContent).toBe('')
  219. assertHookCalls(one, [1, 1, 4, 3, 0])
  220. assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive
  221. })
  222. .then(done)
  223. })
  224. function sharedAssertions(vm, done) {
  225. expect(vm.$el.textContent).toBe('one')
  226. assertHookCalls(one, [1, 1, 1, 0, 0])
  227. assertHookCalls(two, [0, 0, 0, 0, 0])
  228. vm.view = 'two'
  229. waitForUpdate(() => {
  230. expect(vm.$el.textContent).toBe('two')
  231. assertHookCalls(one, [1, 1, 1, 1, 0])
  232. assertHookCalls(two, [1, 1, 0, 0, 0])
  233. vm.view = 'one'
  234. })
  235. .then(() => {
  236. expect(vm.$el.textContent).toBe('one')
  237. assertHookCalls(one, [1, 1, 2, 1, 0])
  238. assertHookCalls(two, [1, 1, 0, 0, 1])
  239. vm.view = 'two'
  240. })
  241. .then(() => {
  242. expect(vm.$el.textContent).toBe('two')
  243. assertHookCalls(one, [1, 1, 2, 2, 0])
  244. assertHookCalls(two, [2, 2, 0, 0, 1])
  245. vm.ok = false // teardown
  246. })
  247. .then(() => {
  248. expect(vm.$el.textContent).toBe('')
  249. assertHookCalls(one, [1, 1, 2, 2, 1])
  250. assertHookCalls(two, [2, 2, 0, 0, 2])
  251. })
  252. .then(done)
  253. }
  254. it('include (string)', done => {
  255. const vm = new Vue({
  256. template: `
  257. <div v-if="ok">
  258. <keep-alive include="one">
  259. <component :is="view"></component>
  260. </keep-alive>
  261. </div>
  262. `,
  263. data: {
  264. view: 'one',
  265. ok: true
  266. },
  267. components
  268. }).$mount()
  269. sharedAssertions(vm, done)
  270. })
  271. it('include (regex)', done => {
  272. const vm = new Vue({
  273. template: `
  274. <div v-if="ok">
  275. <keep-alive :include="/^one$/">
  276. <component :is="view"></component>
  277. </keep-alive>
  278. </div>
  279. `,
  280. data: {
  281. view: 'one',
  282. ok: true
  283. },
  284. components
  285. }).$mount()
  286. sharedAssertions(vm, done)
  287. })
  288. it('include (array)', done => {
  289. const vm = new Vue({
  290. template: `
  291. <div v-if="ok">
  292. <keep-alive :include="['one']">
  293. <component :is="view"></component>
  294. </keep-alive>
  295. </div>
  296. `,
  297. data: {
  298. view: 'one',
  299. ok: true
  300. },
  301. components
  302. }).$mount()
  303. sharedAssertions(vm, done)
  304. })
  305. it('exclude (string)', done => {
  306. const vm = new Vue({
  307. template: `
  308. <div v-if="ok">
  309. <keep-alive exclude="two">
  310. <component :is="view"></component>
  311. </keep-alive>
  312. </div>
  313. `,
  314. data: {
  315. view: 'one',
  316. ok: true
  317. },
  318. components
  319. }).$mount()
  320. sharedAssertions(vm, done)
  321. })
  322. it('exclude (regex)', done => {
  323. const vm = new Vue({
  324. template: `
  325. <div v-if="ok">
  326. <keep-alive :exclude="/^two$/">
  327. <component :is="view"></component>
  328. </keep-alive>
  329. </div>
  330. `,
  331. data: {
  332. view: 'one',
  333. ok: true
  334. },
  335. components
  336. }).$mount()
  337. sharedAssertions(vm, done)
  338. })
  339. it('exclude (array)', done => {
  340. const vm = new Vue({
  341. template: `
  342. <div v-if="ok">
  343. <keep-alive :exclude="['two']">
  344. <component :is="view"></component>
  345. </keep-alive>
  346. </div>
  347. `,
  348. data: {
  349. view: 'one',
  350. ok: true
  351. },
  352. components
  353. }).$mount()
  354. sharedAssertions(vm, done)
  355. })
  356. it('include + exclude', done => {
  357. const vm = new Vue({
  358. template: `
  359. <div v-if="ok">
  360. <keep-alive include="one,two" exclude="two">
  361. <component :is="view"></component>
  362. </keep-alive>
  363. </div>
  364. `,
  365. data: {
  366. view: 'one',
  367. ok: true
  368. },
  369. components
  370. }).$mount()
  371. sharedAssertions(vm, done)
  372. })
  373. it('prune cache on include/exclude change', done => {
  374. const vm = new Vue({
  375. template: `
  376. <div>
  377. <keep-alive :include="include">
  378. <component :is="view"></component>
  379. </keep-alive>
  380. </div>
  381. `,
  382. data: {
  383. view: 'one',
  384. include: 'one,two'
  385. },
  386. components
  387. }).$mount()
  388. vm.view = 'two'
  389. waitForUpdate(() => {
  390. assertHookCalls(one, [1, 1, 1, 1, 0])
  391. assertHookCalls(two, [1, 1, 1, 0, 0])
  392. vm.include = 'two'
  393. })
  394. .then(() => {
  395. assertHookCalls(one, [1, 1, 1, 1, 1])
  396. assertHookCalls(two, [1, 1, 1, 0, 0])
  397. vm.view = 'one'
  398. })
  399. .then(() => {
  400. assertHookCalls(one, [2, 2, 1, 1, 1])
  401. assertHookCalls(two, [1, 1, 1, 1, 0])
  402. })
  403. .then(done)
  404. })
  405. it('prune cache on include/exclude change + view switch', done => {
  406. const vm = new Vue({
  407. template: `
  408. <div>
  409. <keep-alive :include="include">
  410. <component :is="view"></component>
  411. </keep-alive>
  412. </div>
  413. `,
  414. data: {
  415. view: 'one',
  416. include: 'one,two'
  417. },
  418. components
  419. }).$mount()
  420. vm.view = 'two'
  421. waitForUpdate(() => {
  422. assertHookCalls(one, [1, 1, 1, 1, 0])
  423. assertHookCalls(two, [1, 1, 1, 0, 0])
  424. vm.include = 'one'
  425. vm.view = 'one'
  426. })
  427. .then(() => {
  428. assertHookCalls(one, [1, 1, 2, 1, 0])
  429. // two should be pruned
  430. assertHookCalls(two, [1, 1, 1, 1, 1])
  431. })
  432. .then(done)
  433. })
  434. it('should not prune currently active instance', done => {
  435. const vm = new Vue({
  436. template: `
  437. <div>
  438. <keep-alive :include="include">
  439. <component :is="view"></component>
  440. </keep-alive>
  441. </div>
  442. `,
  443. data: {
  444. view: 'one',
  445. include: 'one,two'
  446. },
  447. components
  448. }).$mount()
  449. vm.include = 'two'
  450. waitForUpdate(() => {
  451. assertHookCalls(one, [1, 1, 1, 0, 0])
  452. assertHookCalls(two, [0, 0, 0, 0, 0])
  453. vm.view = 'two'
  454. })
  455. .then(() => {
  456. assertHookCalls(one, [1, 1, 1, 0, 1])
  457. assertHookCalls(two, [1, 1, 1, 0, 0])
  458. })
  459. .then(done)
  460. })
  461. // #3882
  462. it('deeply nested keep-alive should be destroyed properly', done => {
  463. one.template = `<div><keep-alive><two></two></keep-alive></div>`
  464. one.components = { two }
  465. const vm = new Vue({
  466. template: `<div><parent v-if="ok"></parent></div>`,
  467. data: { ok: true },
  468. components: {
  469. parent: {
  470. template: `<div><keep-alive><one></one></keep-alive></div>`,
  471. components: { one }
  472. }
  473. }
  474. }).$mount()
  475. assertHookCalls(one, [1, 1, 1, 0, 0])
  476. assertHookCalls(two, [1, 1, 1, 0, 0])
  477. vm.ok = false
  478. waitForUpdate(() => {
  479. assertHookCalls(one, [1, 1, 1, 1, 1])
  480. assertHookCalls(two, [1, 1, 1, 1, 1])
  481. }).then(done)
  482. })
  483. // #4237
  484. it('should update latest props/listeners for a re-activated component', done => {
  485. const one = {
  486. props: ['prop'],
  487. template: `<div>one {{ prop }}</div>`
  488. }
  489. const two = {
  490. props: ['prop'],
  491. template: `<div>two {{ prop }}</div>`
  492. }
  493. const vm = new Vue({
  494. data: { view: 'one', n: 1 },
  495. template: `
  496. <div>
  497. <keep-alive>
  498. <component :is="view" :prop="n"></component>
  499. </keep-alive>
  500. </div>
  501. `,
  502. components: { one, two }
  503. }).$mount()
  504. expect(vm.$el.textContent).toBe('one 1')
  505. vm.n++
  506. waitForUpdate(() => {
  507. expect(vm.$el.textContent).toBe('one 2')
  508. vm.view = 'two'
  509. })
  510. .then(() => {
  511. expect(vm.$el.textContent).toBe('two 2')
  512. })
  513. .then(done)
  514. })
  515. it('max', done => {
  516. const spyA = vi.fn()
  517. const spyB = vi.fn()
  518. const spyC = vi.fn()
  519. const spyAD = vi.fn()
  520. const spyBD = vi.fn()
  521. const spyCD = vi.fn()
  522. function assertCount(calls) {
  523. expect([
  524. spyA.mock.calls.length,
  525. spyAD.mock.calls.length,
  526. spyB.mock.calls.length,
  527. spyBD.mock.calls.length,
  528. spyC.mock.calls.length,
  529. spyCD.mock.calls.length
  530. ]).toEqual(calls)
  531. }
  532. const vm = new Vue({
  533. template: `
  534. <keep-alive max="2">
  535. <component :is="n"></component>
  536. </keep-alive>
  537. `,
  538. data: {
  539. n: 'aa'
  540. },
  541. components: {
  542. aa: {
  543. template: '<div>a</div>',
  544. created: spyA,
  545. destroyed: spyAD
  546. },
  547. bb: {
  548. template: '<div>bbb</div>',
  549. created: spyB,
  550. destroyed: spyBD
  551. },
  552. cc: {
  553. template: '<div>ccc</div>',
  554. created: spyC,
  555. destroyed: spyCD
  556. }
  557. }
  558. }).$mount()
  559. assertCount([1, 0, 0, 0, 0, 0])
  560. vm.n = 'bb'
  561. waitForUpdate(() => {
  562. assertCount([1, 0, 1, 0, 0, 0])
  563. vm.n = 'cc'
  564. })
  565. .then(() => {
  566. // should prune A because max cache reached
  567. assertCount([1, 1, 1, 0, 1, 0])
  568. vm.n = 'bb'
  569. })
  570. .then(() => {
  571. // B should be reused, and made latest
  572. assertCount([1, 1, 1, 0, 1, 0])
  573. vm.n = 'aa'
  574. })
  575. .then(() => {
  576. // C should be pruned because B was used last so C is the oldest cached
  577. assertCount([2, 1, 1, 0, 1, 1])
  578. })
  579. .then(done)
  580. })
  581. it('max=1', done => {
  582. const spyA = vi.fn()
  583. const spyB = vi.fn()
  584. const spyC = vi.fn()
  585. const spyAD = vi.fn()
  586. const spyBD = vi.fn()
  587. const spyCD = vi.fn()
  588. function assertCount(calls) {
  589. expect([
  590. spyA.mock.calls.length,
  591. spyAD.mock.calls.length,
  592. spyB.mock.calls.length,
  593. spyBD.mock.calls.length,
  594. spyC.mock.calls.length,
  595. spyCD.mock.calls.length
  596. ]).toEqual(calls)
  597. }
  598. const vm = new Vue({
  599. template: `
  600. <keep-alive max="1">
  601. <component :is="n"></component>
  602. </keep-alive>
  603. `,
  604. data: {
  605. n: 'aa'
  606. },
  607. components: {
  608. aa: {
  609. template: '<div>a</div>',
  610. created: spyA,
  611. destroyed: spyAD
  612. },
  613. bb: {
  614. template: '<div>bbb</div>',
  615. created: spyB,
  616. destroyed: spyBD
  617. },
  618. cc: {
  619. template: '<div>ccc</div>',
  620. created: spyC,
  621. destroyed: spyCD
  622. }
  623. }
  624. }).$mount()
  625. assertCount([1, 0, 0, 0, 0, 0])
  626. vm.n = 'bb'
  627. waitForUpdate(() => {
  628. // should prune A because max cache reached
  629. assertCount([1, 1, 1, 0, 0, 0])
  630. vm.n = 'cc'
  631. })
  632. .then(() => {
  633. // should prune B because max cache reached
  634. assertCount([1, 1, 1, 1, 1, 0])
  635. vm.n = 'bb'
  636. })
  637. .then(() => {
  638. // B is recreated
  639. assertCount([1, 1, 2, 1, 1, 1])
  640. vm.n = 'aa'
  641. })
  642. .then(() => {
  643. // B is destroyed and A recreated
  644. assertCount([2, 1, 2, 2, 1, 1])
  645. })
  646. .then(done)
  647. })
  648. it('should warn unknown component inside', () => {
  649. new Vue({
  650. template: `<keep-alive><foo/></keep-alive>`
  651. }).$mount()
  652. expect(`Unknown custom element: <foo>`).toHaveBeenWarned()
  653. })
  654. // #6938
  655. it('should not cache anonymous component when include is specified', done => {
  656. const Foo = {
  657. name: 'foo',
  658. template: `<div>foo</div>`,
  659. created: vi.fn()
  660. }
  661. const Bar = {
  662. template: `<div>bar</div>`,
  663. created: vi.fn()
  664. }
  665. const Child = {
  666. functional: true,
  667. render(h, ctx) {
  668. return h(ctx.props.view ? Foo : Bar)
  669. }
  670. }
  671. const vm = new Vue({
  672. template: `
  673. <keep-alive include="foo">
  674. <child :view="view"></child>
  675. </keep-alive>
  676. `,
  677. data: {
  678. view: true
  679. },
  680. components: { Child }
  681. }).$mount()
  682. function assert(foo, bar) {
  683. expect(Foo.created.mock.calls.length).toBe(foo)
  684. expect(Bar.created.mock.calls.length).toBe(bar)
  685. }
  686. expect(vm.$el.textContent).toBe('foo')
  687. assert(1, 0)
  688. vm.view = false
  689. waitForUpdate(() => {
  690. expect(vm.$el.textContent).toBe('bar')
  691. assert(1, 1)
  692. vm.view = true
  693. })
  694. .then(() => {
  695. expect(vm.$el.textContent).toBe('foo')
  696. assert(1, 1)
  697. vm.view = false
  698. })
  699. .then(() => {
  700. expect(vm.$el.textContent).toBe('bar')
  701. assert(1, 2)
  702. })
  703. .then(done)
  704. })
  705. it('should cache anonymous components if include is not specified', done => {
  706. const Foo = {
  707. template: `<div>foo</div>`,
  708. created: vi.fn()
  709. }
  710. const Bar = {
  711. template: `<div>bar</div>`,
  712. created: vi.fn()
  713. }
  714. const Child = {
  715. functional: true,
  716. render(h, ctx) {
  717. return h(ctx.props.view ? Foo : Bar)
  718. }
  719. }
  720. const vm = new Vue({
  721. template: `
  722. <keep-alive>
  723. <child :view="view"></child>
  724. </keep-alive>
  725. `,
  726. data: {
  727. view: true
  728. },
  729. components: { Child }
  730. }).$mount()
  731. function assert(foo, bar) {
  732. expect(Foo.created.mock.calls.length).toBe(foo)
  733. expect(Bar.created.mock.calls.length).toBe(bar)
  734. }
  735. expect(vm.$el.textContent).toBe('foo')
  736. assert(1, 0)
  737. vm.view = false
  738. waitForUpdate(() => {
  739. expect(vm.$el.textContent).toBe('bar')
  740. assert(1, 1)
  741. vm.view = true
  742. })
  743. .then(() => {
  744. expect(vm.$el.textContent).toBe('foo')
  745. assert(1, 1)
  746. vm.view = false
  747. })
  748. .then(() => {
  749. expect(vm.$el.textContent).toBe('bar')
  750. assert(1, 1)
  751. })
  752. .then(done)
  753. })
  754. // #7105
  755. it('should not destroy active instance when pruning cache', done => {
  756. const Foo = {
  757. template: `<div>foo</div>`,
  758. destroyed: vi.fn()
  759. }
  760. const vm = new Vue({
  761. template: `
  762. <div>
  763. <keep-alive :include="include">
  764. <foo/>
  765. </keep-alive>
  766. </div>
  767. `,
  768. data: {
  769. include: ['foo']
  770. },
  771. components: { Foo }
  772. }).$mount()
  773. // condition: a render where a previous component is reused
  774. vm.include = ['foo']
  775. waitForUpdate(() => {
  776. vm.include = ['']
  777. })
  778. .then(() => {
  779. expect(Foo.destroyed).not.toHaveBeenCalled()
  780. })
  781. .then(done)
  782. })
  783. })