]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(vapor): enable more test cases
authorEvan You <evan@vuejs.org>
Thu, 30 Jan 2025 02:12:36 +0000 (10:12 +0800)
committerEvan You <evan@vuejs.org>
Thu, 30 Jan 2025 02:12:36 +0000 (10:12 +0800)
packages/runtime-core/src/apiSetupHelpers.ts
packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts
packages/runtime-vapor/__tests__/apiWatch.spec.ts
packages/runtime-vapor/__tests__/componentSlots.spec.ts
packages/runtime-vapor/__tests__/dom/templateRef.spec.ts
packages/runtime-vapor/__tests__/errorHandling.spec.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/apiTemplateRef.ts
packages/runtime-vapor/src/componentSlots.ts
packages/runtime-vapor/src/index.ts

index 2ddaeb509ad7c2a418e6f852f97924ba7fddec3a..6a5532ad555d097cb25e15c154789a09a11e91d0 100644 (file)
@@ -9,9 +9,10 @@ import {
   isPromise,
 } from '@vue/shared'
 import {
+  type ComponentInternalInstance,
   type SetupContext,
   createSetupContext,
-  getCurrentInstance,
+  getCurrentGenericInstance,
   setCurrentInstance,
   unsetCurrentInstance,
 } from './component'
@@ -381,6 +382,7 @@ export function withDefaults<
   return null as any
 }
 
+// TODO return type for Vapor components
 export function useSlots(): SetupContext['slots'] {
   return getContext().slots
 }
@@ -390,11 +392,16 @@ export function useAttrs(): SetupContext['attrs'] {
 }
 
 function getContext(): SetupContext {
-  const i = getCurrentInstance()!
+  const i = getCurrentGenericInstance()!
   if (__DEV__ && !i) {
     warn(`useContext() called without active instance.`)
   }
-  return i.setupContext || (i.setupContext = createSetupContext(i))
+  if (i.vapor) {
+    return i as any // vapor instance act as its own setup context
+  } else {
+    const ii = i as ComponentInternalInstance
+    return ii.setupContext || (ii.setupContext = createSetupContext(ii))
+  }
 }
 
 /**
@@ -496,7 +503,7 @@ export function createPropsRestProxy(
  * @internal
  */
 export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
-  const ctx = getCurrentInstance()!
+  const ctx = getCurrentGenericInstance()!
   if (__DEV__ && !ctx) {
     warn(
       `withAsyncContext called without active current instance. ` +
index d4f7404487d179e2b957ebf6406b7b05045ffdba..21edc87f0159fbd143f4705f8092164e59e31ab6 100644 (file)
@@ -1,13 +1,18 @@
 import { createComponent, defineVaporComponent, template } from '../src'
-import { ref, useAttrs, useSlots } from '@vue/runtime-dom'
+import {
+  currentInstance,
+  onMounted,
+  ref,
+  useAttrs,
+  useSlots,
+  withAsyncContext,
+} from '@vue/runtime-dom'
 import { makeRender } from './_utils'
 import type { VaporComponentInstance } from '../src/component'
 
 const define = makeRender<any>()
 
-describe.todo('SFC <script setup> helpers', () => {
-  test.todo('should warn runtime usage', () => {})
-
+describe('SFC <script setup> helpers', () => {
   test('useSlots / useAttrs (no args)', () => {
     let slots: VaporComponentInstance['slots'] | undefined
     let attrs: VaporComponentInstance['attrs'] | undefined
@@ -59,23 +64,51 @@ describe.todo('SFC <script setup> helpers', () => {
     expect(attrs).toBe(ctx!.attrs)
   })
 
-  describe.todo('mergeDefaults', () => {
-    test.todo('object syntax', () => {})
-    test.todo('array syntax', () => {})
-    test.todo('merging with skipFactory', () => {})
-    test.todo('should warn missing', () => {})
-  })
+  describe.todo('withAsyncContext', () => {
+    test('basic', async () => {
+      const spy = vi.fn()
 
-  describe('mergeModels', () => {
-    test.todo('array syntax', () => {})
-    test.todo('object syntax', () => {})
-    test.todo('overwrite', () => {})
-  })
+      let beforeInstance: VaporComponentInstance | null = null
+      let afterInstance: VaporComponentInstance | null = null
+      let resolve: (msg: string) => void
 
-  test.todo('createPropsRestProxy', () => {})
+      const Comp = defineVaporComponent({
+        async setup() {
+          let __temp: any, __restore: any
+
+          beforeInstance = currentInstance as VaporComponentInstance
+
+          const msg =
+            (([__temp, __restore] = withAsyncContext(
+              () =>
+                new Promise(r => {
+                  resolve = r
+                }),
+            )),
+            (__temp = await __temp),
+            __restore(),
+            __temp)
+
+          // register the lifecycle after an await statement
+          onMounted(spy)
+          afterInstance = currentInstance as VaporComponentInstance
+          return document.createTextNode(msg)
+        },
+      })
+
+      const { html } = define(Comp).render()
+
+      expect(spy).not.toHaveBeenCalled()
+      resolve!('hello')
+      // wait a macro task tick for all micro ticks to resolve
+      await new Promise(r => setTimeout(r))
+      // mount hook should have been called
+      expect(spy).toHaveBeenCalled()
+      // should retain same instance before/after the await call
+      expect(beforeInstance).toBe(afterInstance)
+      expect(html()).toBe('hello')
+    })
 
-  describe.todo('withAsyncContext', () => {
-    test.todo('basic', async () => {})
     test.todo('error handling', async () => {})
     test.todo('should not leak instance on multiple awaits', async () => {})
     test.todo('should not leak on multiple awaits + error', async () => {})
index c76ce2f2cf22b8b461d59befaf52b059aed7019c..01b83de80a05fb05e458d75a687c24715c9a28f0 100644 (file)
@@ -8,43 +8,50 @@ import {
   watch,
   watchEffect,
 } from '@vue/runtime-dom'
-import { createComponent, defineVaporComponent, renderEffect } from '../src'
+import {
+  createComponent,
+  createIf,
+  createTemplateRefSetter,
+  defineVaporComponent,
+  renderEffect,
+  template,
+} from '../src'
 import { makeRender } from './_utils'
 import type { VaporComponentInstance } from '../src/component'
+import type { RefEl } from '../src/apiTemplateRef'
 
 const define = makeRender()
 
 // only need to port test cases related to in-component usage
 describe('apiWatch', () => {
   // #7030
-  it.todo(
-    // need if support
-    'should not fire on child component unmount w/ flush: pre',
-    async () => {
-      const visible = ref(true)
-      const cb = vi.fn()
-      const Parent = defineVaporComponent({
-        props: ['visible'],
-        setup() {
-          // @ts-expect-error
-          return visible.value ? h(Comp) : null
-        },
-      })
-      const Comp = {
-        setup() {
-          watch(visible, cb, { flush: 'pre' })
-          return []
-        },
-      }
-      define(Parent).render({
-        visible: () => visible.value,
-      })
-      expect(cb).not.toHaveBeenCalled()
-      visible.value = false
-      await nextTick()
-      expect(cb).not.toHaveBeenCalled()
-    },
-  )
+  it(// need if support
+  'should not fire on child component unmount w/ flush: pre', async () => {
+    const visible = ref(true)
+    const cb = vi.fn()
+    const Parent = defineVaporComponent({
+      props: ['visible'],
+      setup() {
+        return createIf(
+          () => visible.value,
+          () => createComponent(Comp),
+        )
+      },
+    })
+    const Comp = {
+      setup() {
+        watch(visible, cb, { flush: 'pre' })
+        return []
+      },
+    }
+    define(Parent).render({
+      visible: () => visible.value,
+    })
+    expect(cb).not.toHaveBeenCalled()
+    visible.value = false
+    await nextTick()
+    expect(cb).not.toHaveBeenCalled()
+  })
 
   // #7030
   it('flush: pre watcher in child component should not fire before parent update', async () => {
@@ -184,41 +191,41 @@ describe('apiWatch', () => {
   })
 
   // #1852
-  it.todo(
-    // need if + templateRef
-    'flush: post watcher should fire after template refs updated',
-    async () => {
-      const toggle = ref(false)
-      let dom: Element | null = null
+  it('flush: post watcher should fire after template refs updated', async () => {
+    const toggle = ref(false)
+    let dom: Element | null = null
+
+    const App = {
+      setup() {
+        const domRef = ref<Element | null>(null)
 
-      const App = {
-        setup() {
-          const domRef = ref<Element | null>(null)
+        watch(
+          toggle,
+          () => {
+            dom = domRef.value
+          },
+          { flush: 'post' },
+        )
 
-          watch(
-            toggle,
-            () => {
-              dom = domRef.value
-            },
-            { flush: 'post' },
-          )
+        const setRef = createTemplateRefSetter()
+        return createIf(
+          () => toggle.value,
+          () => {
+            const n = template('<p>')()
+            setRef(n as RefEl, domRef)
+            return n
+          },
+        )
+      },
+    }
 
-          return () => {
-            // @ts-expect-error
-            return toggle.value ? h('p', { ref: domRef }) : null
-          }
-        },
-      }
-
-      // @ts-expect-error
-      render(h(App), nodeOps.createElement('div'))
-      expect(dom).toBe(null)
-
-      toggle.value = true
-      await nextTick()
-      expect(dom!.tagName).toBe('P')
-    },
-  )
+    define(App).render()
+    expect(dom).toBe(null)
+
+    toggle.value = true
+    await nextTick()
+    expect(dom!.tagName).toBe('P')
+  })
 
   test('should not leak `this.proxy` to setup()', () => {
     const source = vi.fn()
index 452efa9bc330810480adde68ba62e21ddf96c95a..149da8b94e5c7ee2c3f932266c0b25d707769442 100644 (file)
@@ -87,7 +87,7 @@ describe('component: slots', () => {
     expect(instance.slots).toHaveProperty('two')
   })
 
-  test.todo('should work with createFlorSlots', async () => {
+  test('should work with createFlorSlots', async () => {
     const loop = ref([1, 2, 3])
 
     let instance: any
@@ -101,7 +101,6 @@ describe('component: slots', () => {
         return createComponent(Child, null, {
           $: [
             () =>
-              // @ts-expect-error
               createForSlots(loop.value, (item, i) => ({
                 name: item,
                 fn: () => template(item + i)(),
index 608f70a74c34be7f7a27ad2544afe7b046bae260..46aeeeeee585c16916d1b51ccef4b663ac892fca 100644 (file)
@@ -406,7 +406,7 @@ describe('api: template ref', () => {
   })
 
   // compiled output of v-for + template ref
-  test.todo('ref in v-for', async () => {
+  test('ref in v-for', async () => {
     const show = ref(true)
     const list = reactive([1, 2, 3])
     const listRefs = ref([])
@@ -466,7 +466,7 @@ describe('api: template ref', () => {
     expect(mapRefs()).toMatchObject(['2', '3', '4'])
   })
 
-  test.todo('named ref in v-for', async () => {
+  test('named ref in v-for', async () => {
     const show = ref(true)
     const list = reactive([1, 2, 3])
     const listRefs = ref([])
@@ -530,67 +530,64 @@ describe('api: template ref', () => {
   })
 
   // #6697 v-for ref behaves differently under production and development
-  test.todo(
-    'named ref in v-for , should be responsive when rendering',
-    async () => {
-      const list = ref([1, 2, 3])
-      const listRefs = ref([])
-
-      const t0 = template('<div><div></div><ul></ul></div>')
-      const t1 = template('<li></li>')
-      const { render } = define({
-        setup() {
-          return { listRefs }
-        },
-        render() {
-          const n0 = t0()
-          const n1 = n0.firstChild
-          const n2 = n1!.nextSibling!
-          const n3 = createFor(
-            () => list.value,
-            state => {
-              const n4 = t1()
-              createTemplateRefSetter()(
-                n4 as Element,
-                'listRefs',
-                undefined,
-                true,
-              )
-              renderEffect(() => {
-                const [item] = state
-                setText(n4, item)
-              })
-              return n4
-            },
-          )
-          insert(n3, n2 as unknown as ParentNode)
-          renderEffect(() => {
-            setText(n1!, String(listRefs.value))
-          })
-          return n0
-        },
-      })
-
-      const { host } = render()
-
-      await nextTick()
-      expect(String(listRefs.value)).toBe(
-        '[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]',
-      )
-      expect(host.innerHTML).toBe(
-        '<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
-      )
-
-      list.value.splice(0, 1)
-      await nextTick()
-      expect(String(listRefs.value)).toBe(
-        '[object HTMLLIElement],[object HTMLLIElement]',
-      )
-      expect(host.innerHTML).toBe(
-        '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
-      )
-    },
-  )
+  test('named ref in v-for , should be responsive when rendering', async () => {
+    const list = ref([1, 2, 3])
+    const listRefs = ref([])
+
+    const t0 = template('<div><div></div><ul></ul></div>')
+    const t1 = template('<li></li>')
+    const { render } = define({
+      setup() {
+        return { listRefs }
+      },
+      render() {
+        const n0 = t0()
+        const n1 = n0.firstChild
+        const n2 = n1!.nextSibling!
+        const n3 = createFor(
+          () => list.value,
+          state => {
+            const n4 = t1()
+            createTemplateRefSetter()(
+              n4 as Element,
+              'listRefs',
+              undefined,
+              true,
+            )
+            renderEffect(() => {
+              const [item] = state
+              setText(n4, item)
+            })
+            return n4
+          },
+        )
+        insert(n3, n2 as unknown as ParentNode)
+        renderEffect(() => {
+          setText(n1!, String(listRefs.value))
+        })
+        return n0
+      },
+    })
+
+    const { host } = render()
+
+    await nextTick()
+    expect(String(listRefs.value)).toBe(
+      '[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]',
+    )
+    expect(host.innerHTML).toBe(
+      '<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
+    )
+
+    list.value.splice(0, 1)
+    await nextTick()
+    expect(String(listRefs.value)).toBe(
+      '[object HTMLLIElement],[object HTMLLIElement]',
+    )
+    expect(host.innerHTML).toBe(
+      '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
+    )
+  })
 
   test('string ref inside slots', () => {
     const { component: Child } = define({
index 1155fe471a6d7bc43ef120a96cb1cce75b2769d4..87a79614d41beffc777345cccb442937b05619f4 100644 (file)
@@ -209,7 +209,7 @@ describe('error handling', () => {
     expect(fn).toHaveBeenCalledWith(err, 'render function')
   })
 
-  test.todo('in function ref', () => {
+  test('in function ref', () => {
     const err = new Error('foo')
     const ref = () => {
       throw err
index de8767b274a0ddae968390f440888ab8f1f2ad3c..2f27fe6b5e961eff0434e6932df2fefe89446240 100644 (file)
@@ -319,7 +319,7 @@ export const createFor = (
 }
 
 export function createForSlots(
-  source: any[] | Record<any, any> | number | Set<any> | Map<any, any>,
+  source: Source,
   getSlot: (item: any, key: any, index?: number) => DynamicSlot,
 ): DynamicSlot[] {
   const sourceLength = getLength(source)
index 1e4984cf286a3d478ad2073dae36d12df4316e03..c5a6c5fb2b67b8b50e855686f3f6dd61c8a60338 100644 (file)
@@ -6,6 +6,7 @@ import {
   isVaporComponent,
 } from './component'
 import {
+  ErrorCodes,
   type SchedulerJob,
   callWithErrorHandling,
   queuePostFlushCb,
@@ -67,13 +68,10 @@ export function setRef(
 
   if (isFunction(ref)) {
     const invokeRefSetter = (value?: Element | Record<string, any>) => {
-      callWithErrorHandling(
-        ref,
-        currentInstance,
-        // @ts-expect-error
-        null,
-        [value, refs],
-      )
+      callWithErrorHandling(ref, currentInstance, ErrorCodes.FUNCTION_REF, [
+        value,
+        refs,
+      ])
     }
 
     invokeRefSetter(refValue)
index bf3f4ae2de4ef8ed1800493dfb64f775ed4a7fb8..31d94f0726dfe47582a32fd62e16d8d20ed4ba1e 100644 (file)
@@ -35,16 +35,17 @@ export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
     }
   },
   ownKeys(target) {
-    const keys = Object.keys(target)
+    let keys = Object.keys(target)
     const dynamicSources = target.$
     if (dynamicSources) {
+      keys = keys.filter(k => k !== '$')
       for (const source of dynamicSources) {
         if (isFunction(source)) {
           const slot = source()
           if (isArray(slot)) {
-            for (const s of slot) keys.push(s.name)
+            for (const s of slot) keys.push(String(s.name))
           } else {
-            keys.push(slot.name)
+            keys.push(String(slot.name))
           }
         } else {
           keys.push(...Object.keys(source))
@@ -73,9 +74,9 @@ export function getSlot(
         if (slot) {
           if (isArray(slot)) {
             for (const s of slot) {
-              if (s.name === key) return s.fn
+              if (String(s.name) === key) return s.fn
             }
-          } else if (slot.name === key) {
+          } else if (String(slot.name) === key) {
             return slot.fn
           }
         }
@@ -150,6 +151,3 @@ export function createSlot(
 
   return fragment
 }
-
-// TODO
-export function createForSlots(): any {}
index f081ecf2abf358a2e1c9a0b251997b835ba3e7db..4646f4ebec8260ce31d488271130e93260a46db1 100644 (file)
@@ -6,7 +6,7 @@ export { defineVaporComponent } from './apiDefineComponent'
 export { insert, prepend, remove } from './block'
 export { createComponent, createComponentWithFallback } from './component'
 export { renderEffect } from './renderEffect'
-export { createSlot, createForSlots } from './componentSlots'
+export { createSlot } from './componentSlots'
 export { template, children, next } from './dom/template'
 export { createTextNode } from './dom/node'
 export {
@@ -22,5 +22,5 @@ export {
 } from './dom/prop'
 export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
 export { createIf } from './apiCreateIf'
-export { createFor } from './apiCreateFor'
+export { createFor, createForSlots } from './apiCreateFor'
 export { createTemplateRefSetter } from './apiTemplateRef'