|
|
@@ -0,0 +1,233 @@
|
|
|
+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<any>(null)
|
|
|
+ const App = {
|
|
|
+ render() {
|
|
|
+ return h(KeepAlive, null, {
|
|
|
+ default: () => h(toggle.value ? one : two, { ref: instanceRef })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ render(h(App), root)
|
|
|
+ expect(serializeInner(root)).toBe(`<div>one</div>`)
|
|
|
+ instanceRef.value.msg = 'changed'
|
|
|
+ await nextTick()
|
|
|
+ expect(serializeInner(root)).toBe(`<div>changed</div>`)
|
|
|
+ toggle.value = false
|
|
|
+ await nextTick()
|
|
|
+ expect(serializeInner(root)).toBe(`<div>two</div>`)
|
|
|
+ toggle.value = true
|
|
|
+ await nextTick()
|
|
|
+ expect(serializeInner(root)).toBe(`<div>changed</div>`)
|
|
|
+ })
|
|
|
+
|
|
|
+ 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(`<div>one</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ assertHookCalls(one, [1, 1, 1, 1, 0])
|
|
|
+ assertHookCalls(two, [1, 1, 1, 0, 0])
|
|
|
+
|
|
|
+ toggle2.value = true
|
|
|
+ await nextTick()
|
|
|
+ expect(serializeInner(root)).toBe(`<div>one</div>`)
|
|
|
+ assertHookCalls(one, [1, 1, 2, 1, 0])
|
|
|
+ assertHookCalls(two, [1, 1, 1, 1, 0])
|
|
|
+
|
|
|
+ toggle2.value = false
|
|
|
+ await nextTick()
|
|
|
+ expect(serializeInner(root)).toBe(`<div>two</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ 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(`<div>two</div>`)
|
|
|
+ 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
|
|
|
+ })
|
|
|
+})
|