소스 검색

test(runtime-vapor): add slot fallback regression coverage

daiwei 6 일 전
부모
커밋
2a6b41a04d

+ 63 - 0
packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts

@@ -305,6 +305,69 @@ describe('VaporKeepAlive', () => {
     expect(html()).toBe(`<div>A</div><!--if--><!--slot-->`)
   })
 
+  test('should preserve active slot fallback across KeepAlive reactivation', async () => {
+    const current = ref<'slot' | 'other'>('slot')
+    const fallbackText = ref('fallback')
+    const fallbackCalls = vi.fn()
+
+    const SlotConsumer = defineVaporComponent({
+      name: 'slot-consumer',
+      setup() {
+        return createSlot('default', null, () => {
+          fallbackCalls()
+          const n0 = template(`<div> </div>`)() as any
+          const x0 = child(n0) as any
+          renderEffect(() => setText(x0, fallbackText.value))
+          return n0
+        })
+      },
+    })
+
+    const Other = defineVaporComponent({
+      name: 'other-view',
+      setup() {
+        return template(`<p>other</p>`)() as any
+      },
+    })
+
+    const { html } = define({
+      setup() {
+        return createComponent(VaporKeepAlive, null, {
+          default: () =>
+            createDynamicComponent(() =>
+              current.value === 'slot' ? SlotConsumer : Other,
+            ),
+        })
+      },
+    }).render()
+
+    expect(html()).toBe(
+      `<div>fallback</div><!--slot--><!--dynamic-component-->`,
+    )
+    expect(fallbackCalls).toHaveBeenCalledTimes(1)
+
+    fallbackText.value = 'updated'
+    await nextTick()
+    expect(html()).toBe(`<div>updated</div><!--slot--><!--dynamic-component-->`)
+    expect(fallbackCalls).toHaveBeenCalledTimes(1)
+
+    current.value = 'other'
+    await nextTick()
+    expect(html()).toBe(`<p>other</p><!--dynamic-component-->`)
+    expect(fallbackCalls).toHaveBeenCalledTimes(1)
+
+    fallbackText.value = 'reactivated'
+    await nextTick()
+    expect(html()).toBe(`<p>other</p><!--dynamic-component-->`)
+
+    current.value = 'slot'
+    await nextTick()
+    expect(html()).toBe(
+      `<div>reactivated</div><!--slot--><!--dynamic-component-->`,
+    )
+    expect(fallbackCalls).toHaveBeenCalledTimes(1)
+  })
+
   test('should call correct lifecycle hooks', async () => {
     const toggle = ref(true)
     const viewRef = ref('one')

+ 44 - 1
packages/runtime-vapor/__tests__/components/Transition.spec.ts

@@ -6,9 +6,10 @@ import {
 } from '../../src'
 import { resolveTransitionBlock } from '../../src/components/Transition'
 import { nextTick, ref } from 'vue'
-import { compile, makeRender } from '../_utils'
+import { compile, makeInteropRender, makeRender } from '../_utils'
 
 const define = makeRender()
+const defineInterop = makeInteropRender()
 
 function createAppearTestState(
   show: boolean,
@@ -366,6 +367,48 @@ describe('Transition', () => {
     expect(getTransitionOwner()?.$transition?.persisted).toBe(true)
   })
 
+  test('interop slot fallback should participate in out-in transition swaps', async () => {
+    const data = ref({
+      show: false,
+      fallback: 'fallback',
+      msg: 'slot',
+    })
+    const Child = compile(
+      `<template>
+        <Transition mode="out-in">
+          <slot>
+            <div>{{ data.fallback }}</div>
+          </slot>
+        </Transition>
+      </template>`,
+      data,
+    )
+    const App = compile(
+      `<template>
+        <components.Child>
+          <template #default v-if="data.show">
+            <span>{{ data.msg }}</span>
+          </template>
+        </components.Child>
+      </template>`,
+      data,
+      { Child },
+      { vapor: false },
+    )
+    const { host } = defineInterop(App as any).render()
+
+    expect(host.innerHTML).toContain('<div>fallback</div>')
+
+    data.value.show = true
+    await nextTick()
+    await nextTick()
+
+    expect(host.innerHTML).toContain(
+      '<span class="v-enter-from v-enter-active">slot</span>',
+    )
+    expect(host.innerHTML).not.toContain('<div>fallback</div>')
+  })
+
   test('dynamic default slot source should trigger enter hooks when toggled on', async () => {
     const onBeforeEnter = vi.fn()
     const onEnter = vi.fn()

+ 66 - 0
packages/runtime-vapor/__tests__/hydration.spec.ts

@@ -10408,6 +10408,72 @@ describe('VDOM interop', () => {
     )
   })
 
+  test('ssr output for forwarded empty named VDOM slot with trailing sibling nodes', async () => {
+    const data = ref({})
+    const ssrComponents: Record<string, any> = {}
+
+    ssrComponents.Page = compile(
+      `<template>
+        <div>
+          <main><span>content</span></main>
+          <slot name="footer-before" />
+          <p>footer</p>
+        </div>
+      </template>`,
+      data,
+      ssrComponents,
+      { vapor: false, ssr: true },
+    )
+    ssrComponents.Layout = compile(
+      `<script setup>
+        const components = _components
+      </script>
+      <template>
+        <div>
+          <slot name="banner" />
+          <components.Page>
+            <template #footer-before>
+              <slot name="footer-before" />
+            </template>
+          </components.Page>
+        </div>
+      </template>`,
+      data,
+      ssrComponents,
+      { vapor: false, ssr: true },
+    )
+
+    const ServerApp = compile(
+      `<script setup>
+        const components = _components
+      </script>
+      <template>
+        <components.Layout>
+          <template #banner>
+            <div>banner</div>
+          </template>
+          <template #footer-before>
+            <slot name="footer-before" />
+          </template>
+        </components.Layout>
+      </template>`,
+      data,
+      ssrComponents,
+      { vapor: true, ssr: true },
+    )
+    const html = await VueServerRenderer.renderToString(
+      runtimeDom.createSSRApp(ServerApp),
+    )
+
+    expect(formatHtml(html)).toMatchInlineSnapshot(`
+    	"<div>
+    	<!--[--><div>banner</div><!--]-->
+    	<div><main><span>content</span></main>
+    	<!--[--><!--]-->
+    	<p>footer</p></div></div>"
+    `)
+  })
+
   test('hydrate prepended multi-root component with trailing empty if should restore outer cursor', async () => {
     const { container } = await testWithVaporApp(
       `<script setup>