]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: port tests
authordaiwei <daiwei521@126.com>
Thu, 10 Apr 2025 06:10:37 +0000 (14:10 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 10 Apr 2025 06:10:37 +0000 (14:10 +0800)
packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
packages/runtime-vapor/src/components/KeepAlive.ts
packages/runtime-vapor/src/index.ts

index 829421ca21c6c9459ce39f958684ff0d9ad39907..754d83ac68075b2287e0746df6efab6e7473c4a9 100644 (file)
@@ -5,7 +5,9 @@ import {
   onDeactivated,
   onMounted,
   onUnmounted,
+  reactive,
   ref,
+  shallowRef,
 } from 'vue'
 import type { LooseRawProps, VaporComponent } from '../../src/component'
 import { makeRender } from '../_utils'
@@ -1037,4 +1039,151 @@ describe('VaporKeepAlive', () => {
       expect(html()).toBe(`<div>1</div><!--if-->`)
     })
   })
+
+  test.todo('should work with async component', async () => {})
+
+  test('handle error in async onActivated', async () => {
+    const err = new Error('foo')
+    const handler = vi.fn()
+    const Child = defineVaporComponent({
+      setup() {
+        onActivated(async () => {
+          throw err
+        })
+
+        return template(`<span></span`)()
+      },
+    })
+
+    const { app } = define({
+      setup() {
+        return createComponent(VaporKeepAlive, null, {
+          default: () => createComponent(Child),
+        })
+      },
+    }).create()
+
+    app.config.errorHandler = handler
+    app.mount(document.createElement('div'))
+
+    await nextTick()
+    expect(handler).toHaveBeenCalledTimes(1)
+  })
+
+  test('should avoid unmount later included components', async () => {
+    const unmountedA = vi.fn()
+    const mountedA = vi.fn()
+    const activatedA = vi.fn()
+    const deactivatedA = vi.fn()
+    const unmountedB = vi.fn()
+    const mountedB = vi.fn()
+
+    const A = defineVaporComponent({
+      name: 'A',
+      setup() {
+        onMounted(mountedA)
+        onUnmounted(unmountedA)
+        onActivated(activatedA)
+        onDeactivated(deactivatedA)
+        return template(`<div>A</div>`)()
+      },
+    })
+
+    const B = defineVaporComponent({
+      name: 'B',
+      setup() {
+        onMounted(mountedB)
+        onUnmounted(unmountedB)
+        return template(`<div>B</div>`)()
+      },
+    })
+
+    const include = reactive<string[]>([])
+    const current = shallowRef(A)
+    const { html } = define({
+      setup() {
+        return createComponent(
+          VaporKeepAlive,
+          { include: () => include },
+          {
+            default: () => createDynamicComponent(() => current.value),
+          },
+        )
+      },
+    }).render()
+
+    expect(html()).toBe(`<div>A</div><!--dynamic-component-->`)
+    expect(mountedA).toHaveBeenCalledTimes(1)
+    expect(unmountedA).toHaveBeenCalledTimes(0)
+    expect(activatedA).toHaveBeenCalledTimes(0)
+    expect(deactivatedA).toHaveBeenCalledTimes(0)
+    expect(mountedB).toHaveBeenCalledTimes(0)
+    expect(unmountedB).toHaveBeenCalledTimes(0)
+
+    include.push('A') // cache A
+    await nextTick()
+    current.value = B // toggle to B
+    await nextTick()
+    expect(html()).toBe(`<div>B</div><!--dynamic-component-->`)
+    expect(mountedA).toHaveBeenCalledTimes(1)
+    expect(unmountedA).toHaveBeenCalledTimes(0)
+    expect(activatedA).toHaveBeenCalledTimes(0)
+    expect(deactivatedA).toHaveBeenCalledTimes(1)
+    expect(mountedB).toHaveBeenCalledTimes(1)
+    expect(unmountedB).toHaveBeenCalledTimes(0)
+  })
+
+  test('remove component from include then switching child', async () => {
+    const About = defineVaporComponent({
+      name: 'About',
+      setup() {
+        return template(`<h1>About</h1>`)()
+      },
+    })
+    const mountedHome = vi.fn()
+    const unmountedHome = vi.fn()
+    const activatedHome = vi.fn()
+    const deactivatedHome = vi.fn()
+
+    const Home = defineVaporComponent({
+      name: 'Home',
+      setup() {
+        onMounted(mountedHome)
+        onUnmounted(unmountedHome)
+        onDeactivated(deactivatedHome)
+        onActivated(activatedHome)
+        return template(`<h1>Home</h1>`)()
+      },
+    })
+
+    const activeViewName = ref('Home')
+    const cacheList = reactive(['Home'])
+
+    define({
+      setup() {
+        return createComponent(
+          VaporKeepAlive,
+          { include: () => cacheList },
+          {
+            default: () => {
+              return createIf(
+                () => activeViewName.value === 'Home',
+                () => createComponent(Home),
+                () => createComponent(About),
+              )
+            },
+          },
+        )
+      },
+    }).render()
+
+    expect(mountedHome).toHaveBeenCalledTimes(1)
+    expect(activatedHome).toHaveBeenCalledTimes(1)
+    cacheList.splice(0, 1)
+    await nextTick()
+    activeViewName.value = 'About'
+    await nextTick()
+    expect(deactivatedHome).toHaveBeenCalledTimes(0)
+    expect(unmountedHome).toHaveBeenCalledTimes(1)
+  })
 })
index ec7cce0be6cc4da804836ae067b47035a26fe0a6..127f0c94f90907374c83f4a2fab30cf3d81e3ed2 100644 (file)
@@ -12,7 +12,13 @@ import {
   warn,
   watch,
 } from '@vue/runtime-dom'
-import { type Block, insert, isFragment, isValidBlock } from '../block'
+import {
+  type Block,
+  DynamicFragment,
+  insert,
+  isFragment,
+  isValidBlock,
+} from '../block'
 import {
   type ObjectVaporComponent,
   type VaporComponent,
@@ -153,7 +159,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
       }
     }
 
-    const children = slots.default()
+    let children = slots.default()
     if (isArray(children) && children.length > 1) {
       if (__DEV__) {
         warn(`KeepAlive should contain exactly one component child.`)
@@ -161,6 +167,13 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
       return children
     }
 
+    // wrap children in dynamic fragment
+    if (!isFragment(children)) {
+      const frag = new DynamicFragment()
+      frag.update(() => children)
+      children = frag
+    }
+
     function pruneCache(filter: (name: string) => boolean) {
       cache.forEach((instance, key) => {
         const name = getComponentName(instance.type)
@@ -197,16 +210,6 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
   },
 })
 
-export const VaporKeepAlive = VaporKeepAliveImpl as any as {
-  __isKeepAlive: true
-  new (): {
-    $props: KeepAliveProps
-    $slots: {
-      default(): Block
-    }
-  }
-}
-
 function getInnerBlock(block: Block): VaporComponentInstance | undefined {
   if (isVaporComponent(block)) {
     return block
index 6cc06a72e870ff1ab313c28c976b09778bf7a052..f656520e33e21ff64cd502f241c0774346246e5c 100644 (file)
@@ -3,7 +3,7 @@ export { createVaporApp, createVaporSSRApp } from './apiCreateApp'
 export { defineVaporComponent } from './apiDefineComponent'
 export { vaporInteropPlugin } from './vdomInterop'
 export type { VaporDirective } from './directives/custom'
-export { VaporKeepAlive } from './components/KeepAlive'
+export { VaporKeepAliveImpl as VaporKeepAlive } from './components/KeepAlive'
 
 // compiler-use only
 export { insert, prepend, remove, isFragment, VaporFragment } from './block'