import { ComponentOptions } from '../src/component' import { h, TestElement, nodeOps, render, ref, KeepAlive, serializeInner, nextTick } from '@vue/runtime-test' describe('keep-alive', () => { let one: ComponentOptions let two: ComponentOptions let root: TestElement beforeEach(() => { root = nodeOps.createElement('div') one = { data: () => ({ msg: 'one' }), render() { return h('div', this.msg) }, created: jest.fn(), mounted: jest.fn(), activated: jest.fn(), deactivated: jest.fn(), unmounted: jest.fn() } two = { data: () => ({ msg: 'two' }), render() { return h('div', this.msg) }, created: jest.fn(), mounted: jest.fn(), activated: jest.fn(), deactivated: jest.fn(), unmounted: jest.fn() } }) function assertHookCalls(component: any, callCounts: number[]) { expect([ component.created.mock.calls.length, component.mounted.mock.calls.length, component.activated.mock.calls.length, component.deactivated.mock.calls.length, component.unmounted.mock.calls.length ]).toEqual(callCounts) } test('should preserve state', async () => { const toggle = ref(true) const instanceRef = ref(null) const App = { render() { return h(KeepAlive, null, { default: () => h(toggle.value ? one : two, { ref: instanceRef }) }) } } render(h(App), root) expect(serializeInner(root)).toBe(`
one
`) instanceRef.value.msg = 'changed' await nextTick() expect(serializeInner(root)).toBe(`
changed
`) toggle.value = false await nextTick() expect(serializeInner(root)).toBe(`
two
`) toggle.value = true await nextTick() expect(serializeInner(root)).toBe(`
changed
`) }) test('should call correct lifecycle hooks', async () => { const toggle1 = ref(true) const toggle2 = ref(true) const App = { render() { return toggle1.value ? h(KeepAlive, () => h(toggle2.value ? one : two)) : null } } render(h(App), root) expect(serializeInner(root)).toBe(`
one
`) assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [0, 0, 0, 0, 0]) // toggle kept-alive component toggle2.value = false await nextTick() expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) toggle2.value = true await nextTick() expect(serializeInner(root)).toBe(`
one
`) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) toggle2.value = false await nextTick() expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [1, 1, 2, 1, 0]) // teardown keep-alive, should unmount all components including cached toggle1.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 2, 2, 1]) assertHookCalls(two, [1, 1, 2, 2, 1]) }) test('should call lifecycle hooks on nested components', async () => { one.render = () => h(two) const toggle = ref(true) const App = { render() { return h(KeepAlive, () => (toggle.value ? h(one) : null)) } } render(h(App), root) expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) toggle.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) toggle.value = true await nextTick() expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 2, 1, 0]) toggle.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [1, 1, 2, 2, 0]) }) test('should call correct hooks for nested keep-alive', async () => { const toggle2 = ref(true) one.render = () => h(KeepAlive, () => (toggle2.value ? h(two) : null)) const toggle1 = ref(true) const App = { render() { return h(KeepAlive, () => (toggle1.value ? h(one) : null)) } } render(h(App), root) expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 1, 0, 0]) assertHookCalls(two, [1, 1, 1, 0, 0]) toggle1.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 1, 1, 0]) assertHookCalls(two, [1, 1, 1, 1, 0]) toggle1.value = true await nextTick() expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 2, 1, 0]) // toggle nested instance toggle2.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 2, 2, 0]) toggle2.value = true await nextTick() expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 2, 1, 0]) assertHookCalls(two, [1, 1, 3, 2, 0]) toggle1.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [1, 1, 3, 3, 0]) // toggle nested instance when parent is deactivated toggle2.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected toggle2.value = true await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 2, 2, 0]) assertHookCalls(two, [1, 1, 3, 3, 0]) // should not be affected toggle1.value = true await nextTick() expect(serializeInner(root)).toBe(`
two
`) assertHookCalls(one, [1, 1, 3, 2, 0]) assertHookCalls(two, [1, 1, 4, 3, 0]) toggle1.value = false toggle2.value = false await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 3, 3, 0]) assertHookCalls(two, [1, 1, 4, 4, 0]) toggle1.value = true await nextTick() expect(serializeInner(root)).toBe(``) assertHookCalls(one, [1, 1, 4, 3, 0]) assertHookCalls(two, [1, 1, 4, 4, 0]) // should remain inactive }) })