1import Vue from 'vue'23describe('Component keep-alive', () => {4 let components, one, two, el5 beforeEach(() => {6 one = {7 template: '<div>one</div>',8 created: vi.fn(),9 mounted: vi.fn(),10 activated: vi.fn(),11 deactivated: vi.fn(),12 destroyed: vi.fn()13 }14 two = {15 template: '<div>two</div>',16 created: vi.fn(),17 mounted: vi.fn(),18 activated: vi.fn(),19 deactivated: vi.fn(),20 destroyed: vi.fn()21 }22 components = {23 one,24 two25 }26 el = document.createElement('div')27 document.body.appendChild(el)28 })2930 function assertHookCalls(component, callCounts) {31 expect([32 component.created.mock.calls.length,33 component.mounted.mock.calls.length,34 component.activated.mock.calls.length,35 component.deactivated.mock.calls.length,36 component.destroyed.mock.calls.length37 ]).toEqual(callCounts)38 }3940 it('should work', done => {41 const vm = new Vue({42 template: `43 <div v-if="ok">44 <keep-alive>45 <component :is="view"></component>46 </keep-alive>47 </div>48 `,49 data: {50 view: 'one',51 ok: true52 },53 components54 }).$mount()55 expect(vm.$el.textContent).toBe('one')56 assertHookCalls(one, [1, 1, 1, 0, 0])57 assertHookCalls(two, [0, 0, 0, 0, 0])58 vm.view = 'two'59 waitForUpdate(() => {60 expect(vm.$el.textContent).toBe('two')61 assertHookCalls(one, [1, 1, 1, 1, 0])62 assertHookCalls(two, [1, 1, 1, 0, 0])63 vm.view = 'one'64 })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 })71 .then(() => {72 expect(vm.$el.textContent).toBe('two')73 assertHookCalls(one, [1, 1, 2, 2, 0])74 assertHookCalls(two, [1, 1, 2, 1, 0])75 vm.ok = false // teardown76 })77 .then(() => {78 expect(vm.$el.textContent).toBe('')79 assertHookCalls(one, [1, 1, 2, 2, 1])80 assertHookCalls(two, [1, 1, 2, 2, 1])81 })82 .then(done)83 })8485 it('should invoke hooks on the entire sub tree', done => {86 one.template = '<two/>'87 one.components = { two }8889 const vm = new Vue({90 template: `91 <div>92 <keep-alive>93 <one v-if="ok"/>94 </keep-alive>95 </div>96 `,97 data: {98 ok: true99 },100 components101 }).$mount()102103 expect(vm.$el.textContent).toBe('two')104 assertHookCalls(one, [1, 1, 1, 0, 0])105 assertHookCalls(two, [1, 1, 1, 0, 0])106 vm.ok = false107 waitForUpdate(() => {108 expect(vm.$el.textContent).toBe('')109 assertHookCalls(one, [1, 1, 1, 1, 0])110 assertHookCalls(two, [1, 1, 1, 1, 0])111 vm.ok = true112 })113 .then(() => {114 expect(vm.$el.textContent).toBe('two')115 assertHookCalls(one, [1, 1, 2, 1, 0])116 assertHookCalls(two, [1, 1, 2, 1, 0])117 vm.ok = false118 })119 .then(() => {120 expect(vm.$el.textContent).toBe('')121 assertHookCalls(one, [1, 1, 2, 2, 0])122 assertHookCalls(two, [1, 1, 2, 2, 0])123 })124 .then(done)125 })126127 it('should handle nested keep-alive hooks properly', done => {128 one.template = '<keep-alive><two v-if="ok" /></keep-alive>'129 one.data = () => ({ ok: true })130 one.components = { two }131132 const vm = new Vue({133 template: `134 <div>135 <keep-alive>136 <one v-if="ok" ref="one" />137 </keep-alive>138 </div>139 `,140 data: {141 ok: true142 },143 components144 }).$mount()145146 const oneInstance = vm.$refs.one147 expect(vm.$el.textContent).toBe('two')148 assertHookCalls(one, [1, 1, 1, 0, 0])149 assertHookCalls(two, [1, 1, 1, 0, 0])150 vm.ok = false151 waitForUpdate(() => {152 expect(vm.$el.textContent).toBe('')153 assertHookCalls(one, [1, 1, 1, 1, 0])154 assertHookCalls(two, [1, 1, 1, 1, 0])155 })156 .then(() => {157 vm.ok = true158 })159 .then(() => {160 expect(vm.$el.textContent).toBe('two')161 assertHookCalls(one, [1, 1, 2, 1, 0])162 assertHookCalls(two, [1, 1, 2, 1, 0])163 })164 .then(() => {165 // toggle sub component when activated166 oneInstance.ok = false167 })168 .then(() => {169 expect(vm.$el.textContent).toBe('')170 assertHookCalls(one, [1, 1, 2, 1, 0])171 assertHookCalls(two, [1, 1, 2, 2, 0])172 })173 .then(() => {174 oneInstance.ok = true175 })176 .then(() => {177 expect(vm.$el.textContent).toBe('two')178 assertHookCalls(one, [1, 1, 2, 1, 0])179 assertHookCalls(two, [1, 1, 3, 2, 0])180 })181 .then(() => {182 vm.ok = false183 })184 .then(() => {185 expect(vm.$el.textContent).toBe('')186 assertHookCalls(one, [1, 1, 2, 2, 0])187 assertHookCalls(two, [1, 1, 3, 3, 0])188 })189 .then(() => {190 // toggle sub component when parent is deactivated191 oneInstance.ok = false192 })193 .then(() => {194 expect(vm.$el.textContent).toBe('')195 assertHookCalls(one, [1, 1, 2, 2, 0])196 assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected197 })198 .then(() => {199 oneInstance.ok = true200 })201 .then(() => {202 expect(vm.$el.textContent).toBe('')203 assertHookCalls(one, [1, 1, 2, 2, 0])204 assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected205 })206 .then(() => {207 vm.ok = true208 })209 .then(() => {210 expect(vm.$el.textContent).toBe('two')211 assertHookCalls(one, [1, 1, 3, 2, 0])212 assertHookCalls(two, [1, 1, 4, 3, 0])213 })214 .then(() => {215 oneInstance.ok = false216 vm.ok = false217 })218 .then(() => {219 expect(vm.$el.textContent).toBe('')220 assertHookCalls(one, [1, 1, 3, 3, 0])221 assertHookCalls(two, [1, 1, 4, 4, 0])222 })223 .then(() => {224 vm.ok = true225 })226 .then(() => {227 expect(vm.$el.textContent).toBe('')228 assertHookCalls(one, [1, 1, 4, 3, 0])229 assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive230 })231 .then(done)232 })233234 function sharedAssertions(vm, done) {235 expect(vm.$el.textContent).toBe('one')236 assertHookCalls(one, [1, 1, 1, 0, 0])237 assertHookCalls(two, [0, 0, 0, 0, 0])238 vm.view = 'two'239 waitForUpdate(() => {240 expect(vm.$el.textContent).toBe('two')241 assertHookCalls(one, [1, 1, 1, 1, 0])242 assertHookCalls(two, [1, 1, 0, 0, 0])243 vm.view = 'one'244 })245 .then(() => {246 expect(vm.$el.textContent).toBe('one')247 assertHookCalls(one, [1, 1, 2, 1, 0])248 assertHookCalls(two, [1, 1, 0, 0, 1])249 vm.view = 'two'250 })251 .then(() => {252 expect(vm.$el.textContent).toBe('two')253 assertHookCalls(one, [1, 1, 2, 2, 0])254 assertHookCalls(two, [2, 2, 0, 0, 1])255 vm.ok = false // teardown256 })257 .then(() => {258 expect(vm.$el.textContent).toBe('')259 assertHookCalls(one, [1, 1, 2, 2, 1])260 assertHookCalls(two, [2, 2, 0, 0, 2])261 })262 .then(done)263 }264265 it('include (string)', done => {266 const vm = new Vue({267 template: `268 <div v-if="ok">269 <keep-alive include="one">270 <component :is="view"></component>271 </keep-alive>272 </div>273 `,274 data: {275 view: 'one',276 ok: true277 },278 components279 }).$mount()280 sharedAssertions(vm, done)281 })282283 it('include (regex)', done => {284 const vm = new Vue({285 template: `286 <div v-if="ok">287 <keep-alive :include="/^one$/">288 <component :is="view"></component>289 </keep-alive>290 </div>291 `,292 data: {293 view: 'one',294 ok: true295 },296 components297 }).$mount()298 sharedAssertions(vm, done)299 })300301 it('include (array)', done => {302 const vm = new Vue({303 template: `304 <div v-if="ok">305 <keep-alive :include="['one']">306 <component :is="view"></component>307 </keep-alive>308 </div>309 `,310 data: {311 view: 'one',312 ok: true313 },314 components315 }).$mount()316 sharedAssertions(vm, done)317 })318319 it('exclude (string)', done => {320 const vm = new Vue({321 template: `322 <div v-if="ok">323 <keep-alive exclude="two">324 <component :is="view"></component>325 </keep-alive>326 </div>327 `,328 data: {329 view: 'one',330 ok: true331 },332 components333 }).$mount()334 sharedAssertions(vm, done)335 })336337 it('exclude (regex)', done => {338 const vm = new Vue({339 template: `340 <div v-if="ok">341 <keep-alive :exclude="/^two$/">342 <component :is="view"></component>343 </keep-alive>344 </div>345 `,346 data: {347 view: 'one',348 ok: true349 },350 components351 }).$mount()352 sharedAssertions(vm, done)353 })354355 it('exclude (array)', done => {356 const vm = new Vue({357 template: `358 <div v-if="ok">359 <keep-alive :exclude="['two']">360 <component :is="view"></component>361 </keep-alive>362 </div>363 `,364 data: {365 view: 'one',366 ok: true367 },368 components369 }).$mount()370 sharedAssertions(vm, done)371 })372373 it('include + exclude', done => {374 const vm = new Vue({375 template: `376 <div v-if="ok">377 <keep-alive include="one,two" exclude="two">378 <component :is="view"></component>379 </keep-alive>380 </div>381 `,382 data: {383 view: 'one',384 ok: true385 },386 components387 }).$mount()388 sharedAssertions(vm, done)389 })390391 it('prune cache on include/exclude change', done => {392 const vm = new Vue({393 template: `394 <div>395 <keep-alive :include="include">396 <component :is="view"></component>397 </keep-alive>398 </div>399 `,400 data: {401 view: 'one',402 include: 'one,two'403 },404 components405 }).$mount()406407 vm.view = 'two'408 waitForUpdate(() => {409 assertHookCalls(one, [1, 1, 1, 1, 0])410 assertHookCalls(two, [1, 1, 1, 0, 0])411 vm.include = 'two'412 })413 .then(() => {414 assertHookCalls(one, [1, 1, 1, 1, 1])415 assertHookCalls(two, [1, 1, 1, 0, 0])416 vm.view = 'one'417 })418 .then(() => {419 assertHookCalls(one, [2, 2, 1, 1, 1])420 assertHookCalls(two, [1, 1, 1, 1, 0])421 })422 .then(done)423 })424425 it('prune cache on include/exclude change + view switch', done => {426 const vm = new Vue({427 template: `428 <div>429 <keep-alive :include="include">430 <component :is="view"></component>431 </keep-alive>432 </div>433 `,434 data: {435 view: 'one',436 include: 'one,two'437 },438 components439 }).$mount()440441 vm.view = 'two'442 waitForUpdate(() => {443 assertHookCalls(one, [1, 1, 1, 1, 0])444 assertHookCalls(two, [1, 1, 1, 0, 0])445 vm.include = 'one'446 vm.view = 'one'447 })448 .then(() => {449 assertHookCalls(one, [1, 1, 2, 1, 0])450 // two should be pruned451 assertHookCalls(two, [1, 1, 1, 1, 1])452 })453 .then(done)454 })455456 it('should not prune currently active instance', done => {457 const vm = new Vue({458 template: `459 <div>460 <keep-alive :include="include">461 <component :is="view"></component>462 </keep-alive>463 </div>464 `,465 data: {466 view: 'one',467 include: 'one,two'468 },469 components470 }).$mount()471472 vm.include = 'two'473 waitForUpdate(() => {474 assertHookCalls(one, [1, 1, 1, 0, 0])475 assertHookCalls(two, [0, 0, 0, 0, 0])476 vm.view = 'two'477 })478 .then(() => {479 assertHookCalls(one, [1, 1, 1, 0, 1])480 assertHookCalls(two, [1, 1, 1, 0, 0])481 })482 .then(done)483 })484485 // #3882486 it('deeply nested keep-alive should be destroyed properly', done => {487 one.template = `<div><keep-alive><two></two></keep-alive></div>`488 one.components = { two }489 const vm = new Vue({490 template: `<div><parent v-if="ok"></parent></div>`,491 data: { ok: true },492 components: {493 parent: {494 template: `<div><keep-alive><one></one></keep-alive></div>`,495 components: { one }496 }497 }498 }).$mount()499500 assertHookCalls(one, [1, 1, 1, 0, 0])501 assertHookCalls(two, [1, 1, 1, 0, 0])502503 vm.ok = false504 waitForUpdate(() => {505 assertHookCalls(one, [1, 1, 1, 1, 1])506 assertHookCalls(two, [1, 1, 1, 1, 1])507 }).then(done)508 })509510 // #4237511 it('should update latest props/listeners for a re-activated component', done => {512 const one = {513 props: ['prop'],514 template: `<div>one {{ prop }}</div>`515 }516 const two = {517 props: ['prop'],518 template: `<div>two {{ prop }}</div>`519 }520 const vm = new Vue({521 data: { view: 'one', n: 1 },522 template: `523 <div>524 <keep-alive>525 <component :is="view" :prop="n"></component>526 </keep-alive>527 </div>528 `,529 components: { one, two }530 }).$mount()531532 expect(vm.$el.textContent).toBe('one 1')533 vm.n++534 waitForUpdate(() => {535 expect(vm.$el.textContent).toBe('one 2')536 vm.view = 'two'537 })538 .then(() => {539 expect(vm.$el.textContent).toBe('two 2')540 })541 .then(done)542 })543544 it('max', done => {545 const spyA = vi.fn()546 const spyB = vi.fn()547 const spyC = vi.fn()548 const spyAD = vi.fn()549 const spyBD = vi.fn()550 const spyCD = vi.fn()551552 function assertCount(calls) {553 expect([554 spyA.mock.calls.length,555 spyAD.mock.calls.length,556 spyB.mock.calls.length,557 spyBD.mock.calls.length,558 spyC.mock.calls.length,559 spyCD.mock.calls.length560 ]).toEqual(calls)561 }562563 const vm = new Vue({564 template: `565 <keep-alive max="2">566 <component :is="n"></component>567 </keep-alive>568 `,569 data: {570 n: 'aa'571 },572 components: {573 aa: {574 template: '<div>a</div>',575 created: spyA,576 destroyed: spyAD577 },578 bb: {579 template: '<div>bbb</div>',580 created: spyB,581 destroyed: spyBD582 },583 cc: {584 template: '<div>ccc</div>',585 created: spyC,586 destroyed: spyCD587 }588 }589 }).$mount()590591 assertCount([1, 0, 0, 0, 0, 0])592 vm.n = 'bb'593 waitForUpdate(() => {594 assertCount([1, 0, 1, 0, 0, 0])595 vm.n = 'cc'596 })597 .then(() => {598 // should prune A because max cache reached599 assertCount([1, 1, 1, 0, 1, 0])600 vm.n = 'bb'601 })602 .then(() => {603 // B should be reused, and made latest604 assertCount([1, 1, 1, 0, 1, 0])605 vm.n = 'aa'606 })607 .then(() => {608 // C should be pruned because B was used last so C is the oldest cached609 assertCount([2, 1, 1, 0, 1, 1])610 })611 .then(done)612 })613614 it('max=1', done => {615 const spyA = vi.fn()616 const spyB = vi.fn()617 const spyC = vi.fn()618 const spyAD = vi.fn()619 const spyBD = vi.fn()620 const spyCD = vi.fn()621622 function assertCount(calls) {623 expect([624 spyA.mock.calls.length,625 spyAD.mock.calls.length,626 spyB.mock.calls.length,627 spyBD.mock.calls.length,628 spyC.mock.calls.length,629 spyCD.mock.calls.length630 ]).toEqual(calls)631 }632633 const vm = new Vue({634 template: `635 <keep-alive max="1">636 <component :is="n"></component>637 </keep-alive>638 `,639 data: {640 n: 'aa'641 },642 components: {643 aa: {644 template: '<div>a</div>',645 created: spyA,646 destroyed: spyAD647 },648 bb: {649 template: '<div>bbb</div>',650 created: spyB,651 destroyed: spyBD652 },653 cc: {654 template: '<div>ccc</div>',655 created: spyC,656 destroyed: spyCD657 }658 }659 }).$mount()660661 assertCount([1, 0, 0, 0, 0, 0])662 vm.n = 'bb'663 waitForUpdate(() => {664 // should prune A because max cache reached665 assertCount([1, 1, 1, 0, 0, 0])666 vm.n = 'cc'667 })668 .then(() => {669 // should prune B because max cache reached670 assertCount([1, 1, 1, 1, 1, 0])671 vm.n = 'bb'672 })673 .then(() => {674 // B is recreated675 assertCount([1, 1, 2, 1, 1, 1])676 vm.n = 'aa'677 })678 .then(() => {679 // B is destroyed and A recreated680 assertCount([2, 1, 2, 2, 1, 1])681 })682 .then(done)683 })684685 it('should warn unknown component inside', () => {686 new Vue({687 template: `<keep-alive><foo/></keep-alive>`688 }).$mount()689 expect(`Unknown custom element: <foo>`).toHaveBeenWarned()690 })691692 // #6938693 it('should not cache anonymous component when include is specified', done => {694 const Foo = {695 name: 'foo',696 template: `<div>foo</div>`,697 created: vi.fn()698 }699700 const Bar = {701 template: `<div>bar</div>`,702 created: vi.fn()703 }704705 const Child = {706 functional: true,707 render(h, ctx) {708 return h(ctx.props.view ? Foo : Bar)709 }710 }711712 const vm = new Vue({713 template: `714 <keep-alive include="foo">715 <child :view="view"></child>716 </keep-alive>717 `,718 data: {719 view: true720 },721 components: { Child }722 }).$mount()723724 function assert(foo, bar) {725 expect(Foo.created.mock.calls.length).toBe(foo)726 expect(Bar.created.mock.calls.length).toBe(bar)727 }728729 expect(vm.$el.textContent).toBe('foo')730 assert(1, 0)731 vm.view = false732 waitForUpdate(() => {733 expect(vm.$el.textContent).toBe('bar')734 assert(1, 1)735 vm.view = true736 })737 .then(() => {738 expect(vm.$el.textContent).toBe('foo')739 assert(1, 1)740 vm.view = false741 })742 .then(() => {743 expect(vm.$el.textContent).toBe('bar')744 assert(1, 2)745 })746 .then(done)747 })748749 it('should cache anonymous components if include is not specified', done => {750 const Foo = {751 template: `<div>foo</div>`,752 created: vi.fn()753 }754755 const Bar = {756 template: `<div>bar</div>`,757 created: vi.fn()758 }759760 const Child = {761 functional: true,762 render(h, ctx) {763 return h(ctx.props.view ? Foo : Bar)764 }765 }766767 const vm = new Vue({768 template: `769 <keep-alive>770 <child :view="view"></child>771 </keep-alive>772 `,773 data: {774 view: true775 },776 components: { Child }777 }).$mount()778779 function assert(foo, bar) {780 expect(Foo.created.mock.calls.length).toBe(foo)781 expect(Bar.created.mock.calls.length).toBe(bar)782 }783784 expect(vm.$el.textContent).toBe('foo')785 assert(1, 0)786 vm.view = false787 waitForUpdate(() => {788 expect(vm.$el.textContent).toBe('bar')789 assert(1, 1)790 vm.view = true791 })792 .then(() => {793 expect(vm.$el.textContent).toBe('foo')794 assert(1, 1)795 vm.view = false796 })797 .then(() => {798 expect(vm.$el.textContent).toBe('bar')799 assert(1, 1)800 })801 .then(done)802 })803804 // #7105805 it('should not destroy active instance when pruning cache', done => {806 const Foo = {807 template: `<div>foo</div>`,808 destroyed: vi.fn()809 }810 const vm = new Vue({811 template: `812 <div>813 <keep-alive :include="include">814 <foo/>815 </keep-alive>816 </div>817 `,818 data: {819 include: ['foo']820 },821 components: { Foo }822 }).$mount()823 // condition: a render where a previous component is reused824 vm.include = ['foo']825 waitForUpdate(() => {826 vm.include = ['']827 })828 .then(() => {829 expect(Foo.destroyed).not.toHaveBeenCalled()830 })831 .then(done)832 })833})
Findings
✓ No findings reported for this file.