import Vue from 'vue' import injectStyles from '../transition/inject-styles' import { isIE9 } from 'core/util/env' import { nextFrame } from 'web/runtime/transition-util' describe('Component keep-alive', () => { const { duration, buffer } = injectStyles() let components, one, two, el beforeEach(() => { one = { template: '
one
', created: jasmine.createSpy('one created'), mounted: jasmine.createSpy('one mounted'), activated: jasmine.createSpy('one activated'), deactivated: jasmine.createSpy('one deactivated'), destroyed: jasmine.createSpy('one destroyed') } two = { template: '
two
', created: jasmine.createSpy('two created'), mounted: jasmine.createSpy('two mounted'), activated: jasmine.createSpy('two activated'), deactivated: jasmine.createSpy('two deactivated'), destroyed: jasmine.createSpy('two destroyed') } components = { one, two } el = document.createElement('div') document.body.appendChild(el) }) function assertHookCalls (component, callCounts) { expect([ component.created.calls.count(), component.mounted.calls.count(), component.activated.calls.count(), component.deactivated.calls.count(), component.destroyed.calls.count() ]).toEqual(callCounts) } it('should work', done => { const vm = new Vue({ template: `
`, data: { view: 'one', ok: true }, components }).$mount() expect(vm.$el.textContent).toBe('one') assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [0, 0, 0, 0, 0]) vm.view = 'two' waitForUpdate(() => { expect(vm.$el.textContent).toBe('two') assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) vm.view = 'one' }).then(() => { expect(vm.$el.textContent).toBe('one') assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) vm.view = 'two' }).then(() => { expect(vm.$el.textContent).toBe('two') assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [1, 1, 2, 1, 0]) vm.ok = false // teardown }).then(() => { expect(vm.$el.textContent).toBe('') assertHookCalls(one, [1, 1, 2, 3, 1]) assertHookCalls(two, [1, 1, 2, 2, 1]) }).then(done) }) function sharedAssertions (vm, done) { expect(vm.$el.textContent).toBe('one') assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [0, 0, 0, 0, 0]) vm.view = 'two' waitForUpdate(() => { expect(vm.$el.textContent).toBe('two') assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 0, 0, 0]) vm.view = 'one' }).then(() => { expect(vm.$el.textContent).toBe('one') assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 0, 0, 1]) vm.view = 'two' }).then(() => { expect(vm.$el.textContent).toBe('two') assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [2, 2, 0, 0, 1]) vm.ok = false // teardown }).then(() => { expect(vm.$el.textContent).toBe('') assertHookCalls(one, [1, 1, 2, 3, 1]) assertHookCalls(two, [2, 2, 0, 0, 2]) }).then(done) } it('include (string)', done => { const vm = new Vue({ template: `
`, data: { view: 'one', ok: true }, components }).$mount() sharedAssertions(vm, done) }) it('include (regex)', done => { const vm = new Vue({ template: `
`, data: { view: 'one', ok: true }, components }).$mount() sharedAssertions(vm, done) }) it('exclude (string)', done => { const vm = new Vue({ template: `
`, data: { view: 'one', ok: true }, components }).$mount() sharedAssertions(vm, done) }) it('exclude (regex)', done => { const vm = new Vue({ template: `
`, data: { view: 'one', ok: true }, components }).$mount() sharedAssertions(vm, done) }) it('include + exclude', done => { const vm = new Vue({ template: `
`, data: { view: 'one', ok: true }, components }).$mount() sharedAssertions(vm, done) }) // #3882 it('deeply nested keep-alive should be destroyed properly', done => { one.template = `
` one.components = { two } const vm = new Vue({ template: `
`, data: { ok: true }, components: { parent: { template: `
`, components: { one } } } }).$mount() assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) vm.ok = false waitForUpdate(() => { assertHookCalls(one, [1, 1, 1, 1, 1]) assertHookCalls(two, [1, 1, 1, 1, 1]) }).then(done) }) // #4237 it('should update latest props/listners for a re-activated component', done => { const one = { props: ['prop'], template: `
one {{ prop }}
` } const two = { props: ['prop'], template: `
two {{ prop }}
` } const vm = new Vue({ data: { view: 'one', n: 1 }, template: `
`, components: { one, two } }).$mount() expect(vm.$el.textContent).toBe('one 1') vm.n++ waitForUpdate(() => { expect(vm.$el.textContent).toBe('one 2') vm.view = 'two' }).then(() => { expect(vm.$el.textContent).toBe('two 2') }).then(done) }) if (!isIE9) { it('with transition-mode out-in', done => { let next const vm = new Vue({ template: `
`, data: { view: 'one' }, components, methods: { afterLeave () { next() } } }).$mount(el) expect(vm.$el.textContent).toBe('one') assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [0, 0, 0, 0, 0]) vm.view = 'two' waitForUpdate(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [0, 0, 0, 0, 0]) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) }).thenWaitFor(_next => { next = _next }).then(() => { expect(vm.$el.innerHTML).toBe('') }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' ) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' ) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) }).then(() => { vm.view = 'one' }).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' ) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' ) }).thenWaitFor(_next => { next = _next }).then(() => { expect(vm.$el.innerHTML).toBe('') }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) }).then(done) }) it('with transition-mode in-out', done => { let next const vm = new Vue({ template: `
`, data: { view: 'one' }, components, methods: { afterEnter () { next() } } }).$mount(el) expect(vm.$el.textContent).toBe('one') assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [0, 0, 0, 0, 0]) vm.view = 'two' waitForUpdate(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) }).thenWaitFor(_next => { next = _next }).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) }).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' ) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) }).then(() => { vm.view = 'one' }).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(_next => { next = _next }).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) }).then(done) }) it('dynamic components, in-out with early cancel', done => { let next const vm = new Vue({ template: `
`, data: { view: 'one' }, components, methods: { afterEnter () { next() } } }).$mount(el) expect(vm.$el.textContent).toBe('one') vm.view = 'two' waitForUpdate(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' + '
two
' ) // switch again before enter finishes, // this cancels both enter and leave. vm.view = 'one' }).then(() => { // 1. the pending leaving "one" should be removed instantly. // 2. the entering "two" should be placed into its final state instantly. // 3. a new "one" is created and entering expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(_next => { next = _next }).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
two
' + '
one
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
one
' ) }).then(done).then(done) }) // #4339 it('component with inner transition', done => { const vm = new Vue({ template: `
`, data: { view: 'foo' }, components: { foo: { template: '
foo
' }, bar: { template: '
bar
' } } }).$mount(el) // should not apply transition on initial render by default expect(vm.$el.innerHTML).toBe('
foo
') vm.view = 'bar' waitForUpdate(() => { expect(vm.$el.innerHTML).toBe( '
foo
' + '
bar
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
foo
' + '
bar
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
bar
' ) vm.view = 'foo' }).then(() => { expect(vm.$el.innerHTML).toBe( '
bar
' + '
foo
' ) }).thenWaitFor(nextFrame).then(() => { expect(vm.$el.innerHTML).toBe( '
bar
' + '
foo
' ) }).thenWaitFor(duration + buffer).then(() => { expect(vm.$el.innerHTML).toBe( '
foo
' ) }).then(done) }) } })