]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: add interop tests
authordaiwei <daiwei521@126.com>
Fri, 7 Mar 2025 06:28:42 +0000 (14:28 +0800)
committerdaiwei <daiwei521@126.com>
Fri, 7 Mar 2025 06:29:11 +0000 (14:29 +0800)
packages-private/vapor-e2e-test/__tests__/transition.spec.ts
packages-private/vapor-e2e-test/__tests__/vdomInterop.spec.ts
packages-private/vapor-e2e-test/interop/App.vue
packages-private/vapor-e2e-test/interop/main.ts
packages-private/vapor-e2e-test/transition/components/VaporCompA.vue
packages-private/vapor-e2e-test/transition/components/VaporCompB.vue
packages-private/vapor-e2e-test/transition/components/VdomComp.vue
packages/runtime-vapor/src/components/Transition.ts
packages/runtime-vapor/src/vdomInterop.ts
packages/vue/__tests__/e2e/e2eUtils.ts

index 6afc39a364455c57b4ac3b830fe690bbd2f71747..19770e7b9bbe354917a9f0bf9a21035669ef6a51 100644 (file)
@@ -5,8 +5,18 @@ import {
 } from '../../../packages/vue/__tests__/e2e/e2eUtils'
 import connect from 'connect'
 import sirv from 'sirv'
-const { page, classList, text, nextFrame, timeout, isVisible, count, html } =
-  setupPuppeteer()
+const {
+  page,
+  classList,
+  text,
+  nextFrame,
+  timeout,
+  isVisible,
+  count,
+  html,
+  transitionStart,
+  waitForElement,
+} = setupPuppeteer()
 
 const duration = process.env.CI ? 200 : 50
 const buffer = process.env.CI ? 50 : 20
@@ -32,43 +42,6 @@ describe('vapor transition', () => {
     await page().waitForSelector('#app')
   })
 
-  const transitionStart = (btnSelector: string, containerSelector: string) =>
-    page().evaluate(
-      ([btnSel, containerSel]) => {
-        ;(document.querySelector(btnSel) as HTMLElement)!.click()
-        return Promise.resolve().then(() => {
-          const container = document.querySelector(containerSel)!
-          return {
-            classNames: container.className.split(/\s+/g),
-            innerHTML: container.innerHTML,
-          }
-        })
-      },
-      [btnSelector, containerSelector],
-    )
-
-  const waitForElement = (
-    selector: string,
-    text: string,
-    classNames: string[], // if empty, check for no classes
-    timeout = 2000,
-  ) =>
-    page().waitForFunction(
-      (sel, expectedText, expectedClasses) => {
-        const el = document.querySelector(sel)
-        const hasClasses =
-          expectedClasses.length === 0
-            ? el?.classList.length === 0
-            : expectedClasses.every(c => el?.classList.contains(c))
-        const hasText = el?.textContent?.includes(expectedText)
-        return !!el && hasClasses && hasText
-      },
-      { timeout },
-      selector,
-      text,
-      classNames,
-    )
-
   test(
     'should work with v-show',
     async () => {
index 360f48085a14e7ae4f587d9a89ffeae216f582fc..a6eb410fbb4188e191629e4e64925a4b4c533086 100644 (file)
@@ -5,10 +5,23 @@ import {
 } from '../../../packages/vue/__tests__/e2e/e2eUtils'
 import connect from 'connect'
 import sirv from 'sirv'
+const {
+  page,
+  click,
+  text,
+  enterValue,
+  html,
+  transitionStart,
+  waitForElement,
+  nextFrame,
+  timeout,
+} = setupPuppeteer()
+
+const duration = process.env.CI ? 200 : 50
+const buffer = process.env.CI ? 50 : 20
+const transitionFinish = (time = duration) => timeout(time + buffer)
 
 describe('vdom / vapor interop', () => {
-  const { page, click, text, enterValue } = setupPuppeteer()
-
   let server: any
   const port = '8193'
   beforeAll(() => {
@@ -22,12 +35,15 @@ describe('vdom / vapor interop', () => {
     server.close()
   })
 
+  beforeEach(async () => {
+    const baseUrl = `http://localhost:${port}/interop/`
+    await page().goto(baseUrl)
+    await page().waitForSelector('#app')
+  })
+
   test(
     'should work',
     async () => {
-      const baseUrl = `http://localhost:${port}/interop/`
-      await page().goto(baseUrl)
-
       expect(await text('.vapor > h2')).toContain('Vapor component in VDOM')
 
       expect(await text('.vapor-prop')).toContain('hello')
@@ -81,4 +97,121 @@ describe('vdom / vapor interop', () => {
     },
     E2E_TIMEOUT,
   )
+
+  describe('vdom transition', () => {
+    test(
+      'render vapor component',
+      async () => {
+        const btnSelector = '.trans-vapor > button'
+        const containerSelector = '.trans-vapor > div'
+
+        expect(await html(containerSelector)).toBe(
+          `<div key="0">vapor compA</div>`,
+        )
+
+        // comp leave
+        expect(
+          (await transitionStart(btnSelector, containerSelector)).innerHTML,
+        ).toBe(
+          `<div key="0" class="v-leave-from v-leave-active">vapor compA</div><!---->`,
+        )
+
+        await nextFrame()
+        expect(await html(containerSelector)).toBe(
+          `<div key="0" class="v-leave-active v-leave-to">vapor compA</div><!---->`,
+        )
+
+        await transitionFinish()
+        expect(await html(containerSelector)).toBe(`<!---->`)
+
+        // comp enter
+        expect(
+          (await transitionStart(btnSelector, containerSelector)).innerHTML,
+        ).toBe(
+          `<div key="0" class="v-enter-from v-enter-active">vapor compA</div>`,
+        )
+
+        await nextFrame()
+        expect(await html(containerSelector)).toBe(
+          `<div key="0" class="v-enter-active v-enter-to">vapor compA</div>`,
+        )
+
+        await transitionFinish()
+        expect(await html(containerSelector)).toBe(
+          `<div key="0" class="">vapor compA</div>`,
+        )
+      },
+      E2E_TIMEOUT,
+    )
+
+    test(
+      'switch between vdom/vapor component (out-in mode)',
+      async () => {
+        const btnSelector = '.trans-vdom-vapor-out-in > button'
+        const containerSelector = '.trans-vdom-vapor-out-in > div'
+        const childSelector = `${containerSelector} > div`
+
+        expect(await html(containerSelector)).toBe(`<div>vdom comp</div>`)
+
+        // switch to vapor comp
+        // vdom comp leave
+        expect(
+          (await transitionStart(btnSelector, containerSelector)).innerHTML,
+        ).toBe(
+          `<div class="fade-leave-from fade-leave-active">vdom comp</div><!---->`,
+        )
+
+        await nextFrame()
+        expect(await html(containerSelector)).toBe(
+          `<div class="fade-leave-active fade-leave-to">vdom comp</div><!---->`,
+        )
+
+        // vapor comp enter
+        await waitForElement(childSelector, 'vapor compA', [
+          'fade-enter-from',
+          'fade-enter-active',
+        ])
+
+        await nextFrame()
+        expect(await html(containerSelector)).toBe(
+          `<div class="fade-enter-active fade-enter-to">vapor compA</div>`,
+        )
+
+        await transitionFinish()
+        expect(await html(containerSelector)).toBe(
+          `<div class="">vapor compA</div>`,
+        )
+
+        // switch to vdom comp
+        // vapor comp leave
+        expect(
+          (await transitionStart(btnSelector, containerSelector)).innerHTML,
+        ).toBe(
+          `<div class="fade-leave-from fade-leave-active">vapor compA</div><!---->`,
+        )
+
+        await nextFrame()
+        expect(await html(containerSelector)).toBe(
+          `<div class="fade-leave-active fade-leave-to">vapor compA</div><!---->`,
+        )
+
+        // vdom comp enter
+        await waitForElement(childSelector, 'vdom comp', [
+          'fade-enter-from',
+          'fade-enter-active',
+        ])
+
+        await nextFrame()
+        expect(await html(containerSelector)).toBe(
+          `<div class="fade-enter-active fade-enter-to">vdom comp</div>`,
+        )
+
+        await transitionFinish()
+        expect(await html(containerSelector)).toBe(
+          `<div class="">vdom comp</div>`,
+        )
+      },
+      E2E_TIMEOUT,
+    )
+  })
 })
index 772a6989dd74c90f2e1cb10fbfed734996cc44fe..f29df3c80cd2ac7255a043e71d1802b916fbbf4b 100644 (file)
@@ -1,9 +1,18 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, shallowRef } from 'vue'
 import VaporComp from './VaporComp.vue'
+import VaporCompA from '../transition/components/VaporCompA.vue'
+import VdomComp from '../transition/components/VdomComp.vue'
 
 const msg = ref('hello')
 const passSlot = ref(true)
+
+const toggleVapor = ref(true)
+const interopComponent = shallowRef(VdomComp)
+function toggleInteropComponent() {
+  interopComponent.value =
+    interopComponent.value === VaporCompA ? VdomComp : VaporCompA
+}
 </script>
 
 <template>
@@ -19,4 +28,28 @@ const passSlot = ref(true)
 
     <template #test v-if="passSlot">A test slot</template>
   </VaporComp>
+
+  <!-- transition interop -->
+  <div>
+    <div class="trans-vapor">
+      <button @click="toggleVapor = !toggleVapor">
+        toggle vapor component
+      </button>
+      <div>
+        <Transition>
+          <VaporCompA v-if="toggleVapor" />
+        </Transition>
+      </div>
+    </div>
+    <div class="trans-vdom-vapor-out-in">
+      <button @click="toggleInteropComponent">
+        switch between vdom/vapor component out-in mode
+      </button>
+      <div>
+        <Transition name="fade" mode="out-in">
+          <component :is="interopComponent"></component>
+        </Transition>
+      </div>
+    </div>
+  </div>
 </template>
index d5d6d7dcf8ca81835a59c39a7523229a80243f36..41155dc5cb2064900b734fc6240eb5184486c3e8 100644 (file)
@@ -1,4 +1,5 @@
 import { createApp, vaporInteropPlugin } from 'vue'
 import App from './App.vue'
+import '../transition/style.css'
 
 createApp(App).use(vaporInteropPlugin).mount('#app')
index 40b09eaf45b4e0a19bc88acc9ced2d463cdba4e4..f6902d8cf780259c681a04e6f6ff7e166ecfa14a 100644 (file)
@@ -1,4 +1,4 @@
-<script vapor>
+<script setup vapor lang="ts">
 const msg = 'vapor compA'
 </script>
 <template>
index c22f3b6bbc5ee10d440165cd8fe736be971ddd40..db90f993f12933f851fa5d691d51e6a1a37c9a2b 100644 (file)
@@ -1,4 +1,4 @@
-<script vapor>
+<script setup vapor lang="ts">
 const msg = 'vapor compB'
 </script>
 <template>
index 009ca229a5ae709f35fb8e194f9bf3deb9f44f16..cb6ec7ccad1368d40c2fd8d18b6024b3cedd1774 100644 (file)
@@ -1,4 +1,4 @@
-<script setup>
+<script setup lang="ts">
 const msg = 'vdom comp'
 </script>
 <template>
index 8ff58bf73cb6a60e86ee4976bb070265aec94302..e3094ed14b60ded973439958f8c9fff3db18f3e7 100644 (file)
@@ -20,7 +20,7 @@ import {
   type VaporTransitionHooks,
   isFragment,
 } from '../block'
-import { isVaporComponent } from '../component'
+import { type VaporComponentInstance, isVaporComponent } from '../component'
 
 const decorate = (t: typeof VaporTransition) => {
   t.displayName = 'VaporTransition'
@@ -244,3 +244,13 @@ export function findTransitionBlock(block: Block): TransitionBlock | undefined {
 
   return child
 }
+
+export function setTransitionToInstance(
+  block: VaporComponentInstance,
+  hooks: VaporTransitionHooks,
+): void {
+  const child = findTransitionBlock(block.block)
+  if (!child) return
+
+  setTransitionHooks(child, hooks)
+}
index 44ec5105e9bed3ce7e2ccf68fdc184dabf6e87d8..4724b302ce12b706c4564a082c6acb10646fdb57 100644 (file)
@@ -15,7 +15,7 @@ import {
   ensureRenderer,
   onScopeDispose,
   renderSlot,
-  setTransitionHooks,
+  setTransitionHooks as setVNodeTransitionHooks,
   shallowRef,
   simpleSetCurrentInstance,
 } from '@vue/runtime-dom'
@@ -28,13 +28,20 @@ import {
   mountComponent,
   unmountComponent,
 } from './component'
-import { type Block, VaporFragment, insert, remove } from './block'
+import {
+  type Block,
+  VaporFragment,
+  type VaporTransitionHooks,
+  insert,
+  remove,
+} from './block'
 import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
 import { type RawProps, rawPropsProxyHandlers } from './componentProps'
 import type { RawSlots, VaporSlot } from './componentSlots'
 import { renderEffect } from './renderEffect'
 import { createTextNode } from './dom/node'
 import { optimizePropertyLookup } from './dom/prop'
+import { setTransitionToInstance } from './components/Transition'
 
 // mounting vapor components and slots in vdom
 const vaporInteropImpl: Omit<
@@ -62,6 +69,12 @@ const vaporInteropImpl: Omit<
     ))
     instance.rawPropsRef = propsRef
     instance.rawSlotsRef = slotsRef
+    if (vnode.transition) {
+      setTransitionToInstance(
+        instance,
+        vnode.transition as VaporTransitionHooks,
+      )
+    }
     mountComponent(instance, container, selfAnchor)
     simpleSetCurrentInstance(prev)
     return instance
@@ -174,7 +187,7 @@ function createVDOMComponent(
   let isMounted = false
   const parentInstance = currentInstance as VaporComponentInstance
   const unmount = (parentNode?: ParentNode, transition?: TransitionHooks) => {
-    if (transition) setTransitionHooks(vnode, transition)
+    if (transition) setVNodeTransitionHooks(vnode, transition)
     internals.umt(vnode.component!, null, !!parentNode)
   }
 
@@ -182,7 +195,7 @@ function createVDOMComponent(
     const prev = currentInstance
     simpleSetCurrentInstance(parentInstance)
     if (!isMounted) {
-      if (transition) setTransitionHooks(vnode, transition)
+      if (transition) setVNodeTransitionHooks(vnode, transition)
       internals.mt(
         vnode,
         parentNode,
index 2ffebeb59508ff2d62c02b75ae99d33e8c976243..ac05a47e7e0e6f3addc996b4a3450eba328358b0 100644 (file)
@@ -50,6 +50,16 @@ interface PuppeteerUtils {
   clearValue(selector: string): Promise<any>
   timeout(time: number): Promise<any>
   nextFrame(): Promise<any>
+  transitionStart(
+    btnSelector: string,
+    containerSelector: string,
+  ): Promise<{ classNames: string[]; innerHTML: string }>
+  waitForElement(
+    selector: string,
+    text: string,
+    classNames: string[],
+    timeout?: number,
+  ): Promise<any>
 }
 
 export function setupPuppeteer(args?: string[]): PuppeteerUtils {
@@ -200,6 +210,43 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
     })
   }
 
+  const transitionStart = (btnSelector: string, containerSelector: string) =>
+    page.evaluate(
+      ([btnSel, containerSel]) => {
+        ;(document.querySelector(btnSel) as HTMLElement)!.click()
+        return Promise.resolve().then(() => {
+          const container = document.querySelector(containerSel)!
+          return {
+            classNames: container.className.split(/\s+/g),
+            innerHTML: container.innerHTML,
+          }
+        })
+      },
+      [btnSelector, containerSelector],
+    )
+
+  const waitForElement = (
+    selector: string,
+    text: string,
+    classNames: string[], // if empty, check for no classes
+    timeout = 2000,
+  ) =>
+    page.waitForFunction(
+      (sel, expectedText, expectedClasses) => {
+        const el = document.querySelector(sel)
+        const hasClasses =
+          expectedClasses.length === 0
+            ? el?.classList.length === 0
+            : expectedClasses.every(c => el?.classList.contains(c))
+        const hasText = el?.textContent?.includes(expectedText)
+        return !!el && hasClasses && hasText
+      },
+      { timeout },
+      selector,
+      text,
+      classNames,
+    )
+
   return {
     page: () => page,
     click,
@@ -219,5 +266,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
     clearValue,
     timeout,
     nextFrame,
+    transitionStart,
+    waitForElement,
   }
 }