Prechádzať zdrojové kódy

test(runtime-vapor): refactor duplicate compoent test code (#120)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
Rizumu Ayaka 2 rokov pred
rodič
commit
a15f609044

+ 53 - 0
packages/runtime-vapor/__tests__/_utils.ts

@@ -0,0 +1,53 @@
+import { type Data, isFunction } from '@vue/shared'
+import {
+  type ComponentInternalInstance,
+  type ObjectComponent,
+  type SetupFn,
+  render as _render,
+  defineComponent,
+} from '../src'
+
+export function makeRender<Component = ObjectComponent | SetupFn>(
+  initHost = () => {
+    const host = document.createElement('div')
+    host.setAttribute('id', 'host')
+    document.body.appendChild(host)
+    return host
+  },
+) {
+  let host: HTMLElement
+  beforeEach(() => {
+    host = initHost()
+  })
+  afterEach(() => {
+    host.remove()
+  })
+
+  const define = (comp: Component) => {
+    const component = defineComponent(
+      isFunction(comp)
+        ? {
+            setup: comp,
+          }
+        : comp,
+    )
+    let instance: ComponentInternalInstance
+    const render = (
+      props: Data = {},
+      container: string | ParentNode = '#host',
+    ) => {
+      instance = _render(component, props, container)
+      return res()
+    }
+    const res = () => ({
+      component,
+      host,
+      instance,
+      render,
+    })
+
+    return res()
+  }
+
+  return define
+}

+ 15 - 30
packages/runtime-vapor/__tests__/component.spec.ts

@@ -1,45 +1,30 @@
 import {
   children,
   ref,
-  render,
   setText,
   template,
   unmountComponent,
   watchEffect,
 } from '../src'
-import { afterEach, beforeEach, describe, expect } from 'vitest'
-import { defineComponent } from '@vue/runtime-core'
+import { describe, expect } from 'vitest'
+import { makeRender } from './_utils'
 
-let host: HTMLElement
+const define = makeRender()
 
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => {
-  initHost()
-})
-afterEach(() => {
-  host.remove()
-})
 describe('component', () => {
   test('unmountComponent', async () => {
-    const Comp = defineComponent({
-      setup() {
-        const count = ref(0)
-        const t0 = template('<div></div>')
-        const n0 = t0()
-        const {
-          0: [n1],
-        } = children(n0)
-        watchEffect(() => {
-          setText(n1, count.value)
-        })
-        return n0
-      },
-    })
-    const instance = render(Comp as any, {}, '#host')
+    const { host, instance } = define(() => {
+      const count = ref(0)
+      const t0 = template('<div></div>')
+      const n0 = t0()
+      const {
+        0: [n1],
+      } = children(n0)
+      watchEffect(() => {
+        setText(n1, count.value)
+      })
+      return n0
+    }).render()
     expect(host.innerHTML).toBe('<div>0</div>')
     unmountComponent(instance)
     expect(host.innerHTML).toBe('')

+ 133 - 198
packages/runtime-vapor/__tests__/componentEmits.spec.ts

@@ -3,28 +3,15 @@
 // Note: emits and listener fallthrough is tested in
 // ./rendererAttrsFallthrough.spec.ts.
 
-import {
-  defineComponent,
-  nextTick,
-  onBeforeUnmount,
-  render,
-  unmountComponent,
-} from '../src'
+import { nextTick, onBeforeUnmount, unmountComponent } from '../src'
 import { isEmitListener } from '../src/componentEmits'
+import { makeRender } from './_utils'
 
-let host: HTMLElement
-
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => initHost())
-afterEach(() => host.remove())
+const define = makeRender<any>()
 
 describe('component: emit', () => {
   test('trigger handlers', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('foo')
@@ -35,21 +22,17 @@ describe('component: emit', () => {
     const onfoo = vi.fn()
     const onBar = vi.fn()
     const onBaz = vi.fn()
-    render(
-      Foo,
-      {
-        get onfoo() {
-          return onfoo
-        },
-        get onBar() {
-          return onBar
-        },
-        get ['on!baz']() {
-          return onBaz
-        },
+    render({
+      get onfoo() {
+        return onfoo
       },
-      '#host',
-    )
+      get onBar() {
+        return onBar
+      },
+      get ['on!baz']() {
+        return onBaz
+      },
+    })
 
     expect(onfoo).not.toHaveBeenCalled()
     expect(onBar).toHaveBeenCalled()
@@ -57,7 +40,7 @@ describe('component: emit', () => {
   })
 
   test('trigger camelCase handler', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('test-event')
@@ -65,20 +48,16 @@ describe('component: emit', () => {
     })
 
     const fooSpy = vi.fn()
-    render(
-      Foo,
-      {
-        get onTestEvent() {
-          return fooSpy
-        },
+    render({
+      get onTestEvent() {
+        return fooSpy
       },
-      '#host',
-    )
+    })
     expect(fooSpy).toHaveBeenCalled()
   })
 
   test('trigger kebab-case handler', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('test-event')
@@ -86,21 +65,17 @@ describe('component: emit', () => {
     })
 
     const fooSpy = vi.fn()
-    render(
-      Foo,
-      {
-        get ['onTest-event']() {
-          return fooSpy
-        },
+    render({
+      get ['onTest-event']() {
+        return fooSpy
       },
-      '#host',
-    )
+    })
     expect(fooSpy).toHaveBeenCalledTimes(1)
   })
 
   // #3527
   test.todo('trigger mixed case handlers', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('test-event')
@@ -111,7 +86,6 @@ describe('component: emit', () => {
     const fooSpy = vi.fn()
     const barSpy = vi.fn()
     render(
-      Foo,
       // TODO: impl `toHandlers`
       {
         get ['onTest-Event']() {
@@ -121,7 +95,6 @@ describe('component: emit', () => {
           return barSpy
         },
       },
-      '#host',
     )
     expect(fooSpy).toHaveBeenCalledTimes(1)
     expect(barSpy).toHaveBeenCalledTimes(1)
@@ -129,7 +102,7 @@ describe('component: emit', () => {
 
   // for v-model:foo-bar usage in DOM templates
   test('trigger hyphenated events for update:xxx events', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('update:fooProp')
@@ -139,25 +112,21 @@ describe('component: emit', () => {
 
     const fooSpy = vi.fn()
     const barSpy = vi.fn()
-    render(
-      Foo,
-      {
-        get ['onUpdate:fooProp']() {
-          return fooSpy
-        },
-        get ['onUpdate:bar-prop']() {
-          return barSpy
-        },
+    render({
+      get ['onUpdate:fooProp']() {
+        return fooSpy
       },
-      '#host',
-    )
+      get ['onUpdate:bar-prop']() {
+        return barSpy
+      },
+    })
 
     expect(fooSpy).toHaveBeenCalled()
     expect(barSpy).toHaveBeenCalled()
   })
 
   test('should trigger array of listeners', async () => {
-    const App = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('foo', 1)
@@ -167,15 +136,11 @@ describe('component: emit', () => {
     const fn1 = vi.fn()
     const fn2 = vi.fn()
 
-    render(
-      App,
-      {
-        get onFoo() {
-          return [fn1, fn2]
-        },
+    render({
+      get onFoo() {
+        return [fn1, fn2]
       },
-      '#host',
-    )
+    })
     expect(fn1).toHaveBeenCalledTimes(1)
     expect(fn1).toHaveBeenCalledWith(1)
     expect(fn2).toHaveBeenCalledTimes(1)
@@ -191,15 +156,14 @@ describe('component: emit', () => {
   })
 
   test('should not warn if has equivalent onXXX prop', () => {
-    const Foo = defineComponent({
+    define({
       props: ['onFoo'],
       emits: [],
       render() {},
       setup(_: any, { emit }: any) {
         emit('foo')
       },
-    })
-    render(Foo, {}, '#host')
+    }).render()
     expect(
       `Component emitted event "foo" but it is neither declared`,
     ).not.toHaveBeenWarned()
@@ -219,7 +183,7 @@ describe('component: emit', () => {
   // )
 
   test('.once', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       emits: {
         foo: null,
@@ -234,24 +198,20 @@ describe('component: emit', () => {
     })
     const fn = vi.fn()
     const barFn = vi.fn()
-    render(
-      Foo,
-      {
-        get onFooOnce() {
-          return fn
-        },
-        get onBarOnce() {
-          return barFn
-        },
+    render({
+      get onFooOnce() {
+        return fn
       },
-      '#host',
-    )
+      get onBarOnce() {
+        return barFn
+      },
+    })
     expect(fn).toHaveBeenCalledTimes(1)
     expect(barFn).toHaveBeenCalledTimes(1)
   })
 
   test('.once with normal listener of the same name', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       emits: {
         foo: null,
@@ -263,24 +223,20 @@ describe('component: emit', () => {
     })
     const onFoo = vi.fn()
     const onFooOnce = vi.fn()
-    render(
-      Foo,
-      {
-        get onFoo() {
-          return onFoo
-        },
-        get onFooOnce() {
-          return onFooOnce
-        },
+    render({
+      get onFoo() {
+        return onFoo
       },
-      '#host',
-    )
+      get onFooOnce() {
+        return onFooOnce
+      },
+    })
     expect(onFoo).toHaveBeenCalledTimes(2)
     expect(onFooOnce).toHaveBeenCalledTimes(1)
   })
 
   test('.number modifier should work with v-model on component', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('update:modelValue', '1')
@@ -289,30 +245,26 @@ describe('component: emit', () => {
     })
     const fn1 = vi.fn()
     const fn2 = vi.fn()
-    render(
-      Foo,
-      {
-        get modelValue() {
-          return null
-        },
-        get modelModifiers() {
-          return { number: true }
-        },
-        get ['onUpdate:modelValue']() {
-          return fn1
-        },
-        get foo() {
-          return null
-        },
-        get fooModifiers() {
-          return { number: true }
-        },
-        get ['onUpdate:foo']() {
-          return fn2
-        },
+    render({
+      get modelValue() {
+        return null
       },
-      '#host',
-    )
+      get modelModifiers() {
+        return { number: true }
+      },
+      get ['onUpdate:modelValue']() {
+        return fn1
+      },
+      get foo() {
+        return null
+      },
+      get fooModifiers() {
+        return { number: true }
+      },
+      get ['onUpdate:foo']() {
+        return fn2
+      },
+    })
     expect(fn1).toHaveBeenCalledTimes(1)
     expect(fn1).toHaveBeenCalledWith(1)
     expect(fn2).toHaveBeenCalledTimes(1)
@@ -320,7 +272,7 @@ describe('component: emit', () => {
   })
 
   test('.trim modifier should work with v-model on component', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('update:modelValue', ' one ')
@@ -329,30 +281,26 @@ describe('component: emit', () => {
     })
     const fn1 = vi.fn()
     const fn2 = vi.fn()
-    render(
-      Foo,
-      {
-        get modelValue() {
-          return null
-        },
-        get modelModifiers() {
-          return { trim: true }
-        },
-        get ['onUpdate:modelValue']() {
-          return fn1
-        },
-        get foo() {
-          return null
-        },
-        get fooModifiers() {
-          return { trim: true }
-        },
-        get 'onUpdate:foo'() {
-          return fn2
-        },
+    render({
+      get modelValue() {
+        return null
       },
-      '#host',
-    )
+      get modelModifiers() {
+        return { trim: true }
+      },
+      get ['onUpdate:modelValue']() {
+        return fn1
+      },
+      get foo() {
+        return null
+      },
+      get fooModifiers() {
+        return { trim: true }
+      },
+      get 'onUpdate:foo'() {
+        return fn2
+      },
+    })
     expect(fn1).toHaveBeenCalledTimes(1)
     expect(fn1).toHaveBeenCalledWith('one')
     expect(fn2).toHaveBeenCalledTimes(1)
@@ -360,7 +308,7 @@ describe('component: emit', () => {
   })
 
   test('.trim and .number modifiers should work with v-model on component', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('update:modelValue', '    +01.2    ')
@@ -369,30 +317,26 @@ describe('component: emit', () => {
     })
     const fn1 = vi.fn()
     const fn2 = vi.fn()
-    render(
-      Foo,
-      {
-        get modelValue() {
-          return null
-        },
-        get modelModifiers() {
-          return { trim: true, number: true }
-        },
-        get ['onUpdate:modelValue']() {
-          return fn1
-        },
-        get foo() {
-          return null
-        },
-        get fooModifiers() {
-          return { trim: true, number: true }
-        },
-        get ['onUpdate:foo']() {
-          return fn2
-        },
+    render({
+      get modelValue() {
+        return null
       },
-      '#host',
-    )
+      get modelModifiers() {
+        return { trim: true, number: true }
+      },
+      get ['onUpdate:modelValue']() {
+        return fn1
+      },
+      get foo() {
+        return null
+      },
+      get fooModifiers() {
+        return { trim: true, number: true }
+      },
+      get ['onUpdate:foo']() {
+        return fn2
+      },
+    })
     expect(fn1).toHaveBeenCalledTimes(1)
     expect(fn1).toHaveBeenCalledWith(1.2)
     expect(fn2).toHaveBeenCalledTimes(1)
@@ -400,28 +344,24 @@ describe('component: emit', () => {
   })
 
   test('only trim string parameter when work with v-model on component', () => {
-    const Foo = defineComponent({
+    const { render } = define({
       render() {},
       setup(_: any, { emit }: any) {
         emit('update:modelValue', ' foo ', { bar: ' bar ' })
       },
     })
     const fn = vi.fn()
-    render(
-      Foo,
-      {
-        get modelValue() {
-          return null
-        },
-        get modelModifiers() {
-          return { trim: true }
-        },
-        get ['onUpdate:modelValue']() {
-          return fn
-        },
+    render({
+      get modelValue() {
+        return null
       },
-      '#host',
-    )
+      get modelModifiers() {
+        return { trim: true }
+      },
+      get ['onUpdate:modelValue']() {
+        return fn
+      },
+    })
     expect(fn).toHaveBeenCalledTimes(1)
     expect(fn).toHaveBeenCalledWith('foo', { bar: ' bar ' })
   })
@@ -457,7 +397,7 @@ describe('component: emit', () => {
 
   test('does not emit after unmount', async () => {
     const fn = vi.fn()
-    const Foo = defineComponent({
+    const { instance } = define({
       emits: ['closing'],
       setup(_: any, { emit }: any) {
         onBeforeUnmount(async () => {
@@ -466,18 +406,13 @@ describe('component: emit', () => {
         })
       },
       render() {},
-    })
-    const i = render(
-      Foo,
-      {
-        get onClosing() {
-          return fn
-        },
+    }).render({
+      get onClosing() {
+        return fn
       },
-      '#host',
-    )
+    })
     await nextTick()
-    unmountComponent(i)
+    unmountComponent(instance)
     await nextTick()
     expect(fn).not.toHaveBeenCalled()
   })

+ 110 - 197
packages/runtime-vapor/__tests__/componentProps.spec.ts

@@ -16,22 +16,16 @@ import {
   template,
   watchEffect,
 } from '../src'
+import { makeRender } from './_utils'
 
-let host: HTMLElement
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => initHost())
-afterEach(() => host.remove())
+const define = makeRender<any>()
 
 describe('component props (vapor)', () => {
   test('stateful', () => {
     let props: any
     // TODO: attrs
 
-    const Comp = defineComponent({
+    const { render } = define({
       props: ['fooBar', 'barBaz'],
       render() {
         const instance = getCurrentInstance()!
@@ -39,46 +33,34 @@ describe('component props (vapor)', () => {
       },
     })
 
-    render(
-      Comp,
-      {
-        get fooBar() {
-          return 1
-        },
+    render({
+      get fooBar() {
+        return 1
       },
-      host,
-    )
+    })
     expect(props.fooBar).toEqual(1)
 
     // test passing kebab-case and resolving to camelCase
-    render(
-      Comp,
-      {
-        get ['foo-bar']() {
-          return 2
-        },
+    render({
+      get ['foo-bar']() {
+        return 2
       },
-      host,
-    )
+    })
     expect(props.fooBar).toEqual(2)
 
     // test updating kebab-case should not delete it (#955)
-    render(
-      Comp,
-      {
-        get ['foo-bar']() {
-          return 3
-        },
-        get barBaz() {
-          return 5
-        },
+    render({
+      get ['foo-bar']() {
+        return 3
       },
-      host,
-    )
+      get barBaz() {
+        return 5
+      },
+    })
     expect(props.fooBar).toEqual(3)
     expect(props.barBaz).toEqual(5)
 
-    render(Comp, {}, host)
+    render({})
     expect(props.fooBar).toBeUndefined()
     expect(props.barBaz).toBeUndefined()
     // expect(props.qux).toEqual(5) // TODO: attrs
@@ -92,7 +74,7 @@ describe('component props (vapor)', () => {
     let props: any
     // TODO: attrs
 
-    const Comp: FunctionalComponent = defineComponent((_props: any) => {
+    const { component: Comp, render } = define((_props: any) => {
       const instance = getCurrentInstance()!
       props = instance.props
       return {}
@@ -100,29 +82,21 @@ describe('component props (vapor)', () => {
     Comp.props = ['foo']
     Comp.render = (() => {}) as any
 
-    render(
-      Comp,
-      {
-        get foo() {
-          return 1
-        },
+    render({
+      get foo() {
+        return 1
       },
-      host,
-    )
+    })
     expect(props.foo).toEqual(1)
 
-    render(
-      Comp,
-      {
-        get foo() {
-          return 2
-        },
+    render({
+      get foo() {
+        return 2
       },
-      host,
-    )
+    })
     expect(props.foo).toEqual(2)
 
-    render(Comp, {}, host)
+    render({})
     expect(props.foo).toBeUndefined()
   })
 
@@ -130,7 +104,7 @@ describe('component props (vapor)', () => {
     let props: any
     // TODO: attrs
 
-    const Comp: FunctionalComponent = defineComponent((_props: any) => {
+    const { component: Comp, render } = define((_props: any) => {
       const instance = getCurrentInstance()!
       props = instance.props
       return {}
@@ -138,32 +112,24 @@ describe('component props (vapor)', () => {
     Comp.props = undefined as any
     Comp.render = (() => {}) as any
 
-    render(
-      Comp,
-      {
-        get foo() {
-          return 1
-        },
+    render({
+      get foo() {
+        return 1
       },
-      host,
-    )
+    })
     expect(props.foo).toBeUndefined()
 
-    render(
-      Comp,
-      {
-        get foo() {
-          return 2
-        },
+    render({
+      get foo() {
+        return 2
       },
-      host,
-    )
+    })
     expect(props.foo).toBeUndefined()
   })
 
   test('boolean casting', () => {
     let props: any
-    const Comp = defineComponent({
+    const { render } = define({
       props: {
         foo: Boolean,
         bar: Boolean,
@@ -176,16 +142,12 @@ describe('component props (vapor)', () => {
       },
     })
 
-    render(
-      Comp,
-      {
-        // absent should cast to false
-        bar: '', // empty string should cast to true
-        baz: 'baz', // same string should cast to true
-        qux: 'ok', // other values should be left in-tact (but raise warning)
-      },
-      host,
-    )
+    render({
+      // absent should cast to false
+      bar: '', // empty string should cast to true
+      baz: 'baz', // same string should cast to true
+      qux: 'ok', // other values should be left in-tact (but raise warning)
+    })
 
     expect(props.foo).toBe(false)
     expect(props.bar).toBe(true)
@@ -198,7 +160,7 @@ describe('component props (vapor)', () => {
     const defaultFn = vi.fn(() => ({ a: 1 }))
     const defaultBaz = vi.fn(() => ({ b: 1 }))
 
-    const Comp = defineComponent({
+    const { render } = define({
       props: {
         foo: {
           default: 1,
@@ -217,15 +179,11 @@ describe('component props (vapor)', () => {
       },
     })
 
-    render(
-      Comp,
-      {
-        get foo() {
-          return 2
-        },
+    render({
+      get foo() {
+        return 2
       },
-      host,
-    )
+    })
     expect(props.foo).toBe(2)
     // const prevBar = props.bar
     props.bar
@@ -237,58 +195,42 @@ describe('component props (vapor)', () => {
 
     // #999: updates should not cause default factory of unchanged prop to be
     // called again
-    render(
-      Comp,
-      {
-        get foo() {
-          return 3
-        },
+    render({
+      get foo() {
+        return 3
       },
-      host,
-    )
+    })
     expect(props.foo).toBe(3)
     expect(props.bar).toEqual({ a: 1 })
     // expect(props.bar).toBe(prevBar) // failed: (caching is not supported)
     // expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
 
-    render(
-      Comp,
-      {
-        get bar() {
-          return { b: 2 }
-        },
+    render({
+      get bar() {
+        return { b: 2 }
       },
-      host,
-    )
+    })
     expect(props.foo).toBe(1)
     expect(props.bar).toEqual({ b: 2 })
     // expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
 
-    render(
-      Comp,
-      {
-        get foo() {
-          return 3
-        },
-        get bar() {
-          return { b: 3 }
-        },
+    render({
+      get foo() {
+        return 3
       },
-      host,
-    )
+      get bar() {
+        return { b: 3 }
+      },
+    })
     expect(props.foo).toBe(3)
     expect(props.bar).toEqual({ b: 3 })
     // expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
 
-    render(
-      Comp,
-      {
-        get bar() {
-          return { b: 4 }
-        },
+    render({
+      get bar() {
+        return { b: 4 }
       },
-      host,
-    )
+    })
     expect(props.foo).toBe(1)
     expect(props.bar).toEqual({ b: 4 })
     // expect(defaultFn).toHaveBeenCalledTimes(1) // failed: caching is not supported (called 3 times)
@@ -301,7 +243,7 @@ describe('component props (vapor)', () => {
   // NOTE: maybe it's unnecessary
   // https://github.com/vuejs/core-vapor/pull/99#discussion_r1472647377
   test('optimized props updates', async () => {
-    const Child = defineComponent({
+    const renderChild = define({
       props: ['foo'],
       render() {
         const instance = getCurrentInstance()!
@@ -315,19 +257,18 @@ describe('component props (vapor)', () => {
         })
         return n0
       },
-    })
+    }).render
 
     const foo = ref(1)
     const id = ref('a')
-    const Comp = defineComponent({
+    const { instance, host } = define({
       setup() {
         return { foo, id }
       },
       render(_ctx: Record<string, any>) {
         const t0 = template('')
         const n0 = t0()
-        render(
-          Child,
+        renderChild(
           {
             get foo() {
               return _ctx.foo
@@ -340,10 +281,8 @@ describe('component props (vapor)', () => {
         )
         return n0
       },
-    })
-
-    const instace = render(Comp, {}, host)
-    const reset = setCurrentInstance(instace)
+    }).render()
+    const reset = setCurrentInstance(instance)
     // expect(host.innerHTML).toBe('<div id="a">1</div>') // TODO: Fallthrough Attributes
     expect(host.innerHTML).toBe('<div>1</div>')
 
@@ -366,7 +305,16 @@ describe('component props (vapor)', () => {
         return true
       })
 
-      const Comp = defineComponent({
+      const props = {
+        get foo() {
+          return 1
+        },
+        get bar() {
+          return 2
+        },
+      }
+
+      define({
         props: {
           foo: {
             type: Number,
@@ -381,18 +329,8 @@ describe('component props (vapor)', () => {
           const n0 = t0()
           return n0
         },
-      })
-
-      const props = {
-        get foo() {
-          return 1
-        },
-        get bar() {
-          return 2
-        },
-      }
+      }).render(props)
 
-      render(Comp, props, host)
       expect(mockFn).toHaveBeenCalled()
       // NOTE: Vapor Component props defined by getter. So, `props` not Equal to `{ foo: 1, bar: 2 }`
       // expect(mockFn).toHaveBeenCalledWith(1, { foo: 1, bar: 2 })
@@ -407,7 +345,7 @@ describe('component props (vapor)', () => {
       'validator should not be able to mutate other props',
       async () => {
         const mockFn = vi.fn((...args: any[]) => true)
-        const Comp = defineComponent({
+        defineComponent({
           props: {
             foo: {
               type: Number,
@@ -423,20 +361,15 @@ describe('component props (vapor)', () => {
             const n0 = t0()
             return n0
           },
+        }).render({
+          get foo() {
+            return 1
+          },
+          get bar() {
+            return 2
+          },
         })
 
-        render(
-          Comp,
-          {
-            get foo() {
-              return 1
-            },
-            get bar() {
-              return 2
-            },
-          },
-          host,
-        )
         expect(
           `Set operation on key "bar" failed: target is readonly.`,
         ).toHaveBeenWarnedLast()
@@ -450,7 +383,7 @@ describe('component props (vapor)', () => {
   })
 
   test('warn absent required props', () => {
-    const Comp = defineComponent({
+    define({
       props: {
         bool: { type: Boolean, required: true },
         str: { type: String, required: true },
@@ -459,8 +392,7 @@ describe('component props (vapor)', () => {
       setup() {
         return () => null
       },
-    })
-    render(Comp, {}, host)
+    }).render()
     expect(`Missing required prop: "bool"`).toHaveBeenWarned()
     expect(`Missing required prop: "str"`).toHaveBeenWarned()
     expect(`Missing required prop: "num"`).toHaveBeenWarned()
@@ -471,29 +403,23 @@ describe('component props (vapor)', () => {
 
   // #3495
   test('should not warn required props using kebab-case', async () => {
-    const Comp = defineComponent({
+    define({
       props: {
         fooBar: { type: String, required: true },
       },
       setup() {
         return () => null
       },
-    })
-
-    render(
-      Comp,
-      {
-        get ['foo-bar']() {
-          return 'hello'
-        },
+    }).render({
+      get ['foo-bar']() {
+        return 'hello'
       },
-      host,
-    )
+    })
     expect(`Missing required prop: "fooBar"`).not.toHaveBeenWarned()
   })
 
   test('props type support BigInt', () => {
-    const Comp = defineComponent({
+    const { host } = define({
       props: {
         foo: BigInt,
       },
@@ -509,19 +435,11 @@ describe('component props (vapor)', () => {
         })
         return n0
       },
-    })
-
-    render(
-      Comp,
-      {
-        get foo() {
-          return (
-            BigInt(BigInt(100000111)) + BigInt(2000000000) * BigInt(30000000)
-          )
-        },
+    }).render({
+      get foo() {
+        return BigInt(BigInt(100000111)) + BigInt(2000000000) * BigInt(30000000)
       },
-      '#host',
-    )
+    })
     expect(host.innerHTML).toBe('<div>60000000100000111</div>')
   })
 
@@ -560,7 +478,7 @@ describe('component props (vapor)', () => {
   })
 
   test('support null in required + multiple-type declarations', () => {
-    const Comp = defineComponent({
+    const { render } = define({
       props: {
         foo: { type: [Function, null], required: true },
       },
@@ -568,11 +486,11 @@ describe('component props (vapor)', () => {
     })
 
     expect(() => {
-      render(Comp, { foo: () => {} }, host)
+      render({ foo: () => {} })
     }).not.toThrow()
 
     expect(() => {
-      render(Comp, { foo: null }, host)
+      render({ foo: null })
     }).not.toThrow()
   })
 
@@ -588,12 +506,7 @@ describe('component props (vapor)', () => {
         type: String,
       },
     }
-    const Comp = defineComponent({
-      props,
-      render() {},
-    })
-
-    render(Comp, { msg: 'test' }, host)
+    define({ props, render() {} }).render({ msg: 'test' })
 
     expect(Object.keys(props.msg).length).toBe(1)
   })

+ 50 - 77
packages/runtime-vapor/__tests__/if.spec.ts

@@ -1,4 +1,3 @@
-import { defineComponent } from 'vue'
 import {
   append,
   children,
@@ -6,26 +5,14 @@ import {
   insert,
   nextTick,
   ref,
-  render,
   renderEffect,
   setText,
   template,
 } from '../src'
 import type { Mock } from 'vitest'
+import { makeRender } from './_utils'
 
-let host: HTMLElement
-
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => {
-  initHost()
-})
-afterEach(() => {
-  host.remove()
-})
+const define = makeRender()
 
 describe('createIf', () => {
   test('basic', async () => {
@@ -44,42 +31,36 @@ describe('createIf', () => {
     const t1 = template('<p></p>')
     const t2 = template('<p>zero</p>')
 
-    const component = defineComponent({
-      setup() {
-        // render
-        return (() => {
-          const n0 = t0()
-          const {
-            0: [n1],
-          } = children(n0)
-
-          insert(
-            createIf(
-              () => count.value,
-              // v-if
-              (spyIfFn ||= vi.fn(() => {
-                const n2 = t1()
-                const {
-                  0: [n3],
-                } = children(n2)
-                renderEffect(() => {
-                  setText(n3, count.value)
-                })
-                return n2
-              })),
-              // v-else
-              (spyElseFn ||= vi.fn(() => {
-                const n4 = t2()
-                return n4
-              })),
-            ),
-            n1 as any as ParentNode,
-          )
-          return n0
-        })()
-      },
-    })
-    render(component as any, {}, '#host')
+    const { host } = define(() => {
+      const n0 = t0()
+      const {
+        0: [n1],
+      } = children(n0)
+
+      insert(
+        createIf(
+          () => count.value,
+          // v-if
+          (spyIfFn ||= vi.fn(() => {
+            const n2 = t1()
+            const {
+              0: [n3],
+            } = children(n2)
+            renderEffect(() => {
+              setText(n3, count.value)
+            })
+            return n2
+          })),
+          // v-else
+          (spyElseFn ||= vi.fn(() => {
+            const n4 = t2()
+            return n4
+          })),
+        ),
+        n1 as any as ParentNode,
+      )
+      return n0
+    }).render()
 
     expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>')
     expect(spyIfFn!).toHaveBeenCalledTimes(0)
@@ -115,33 +96,25 @@ describe('createIf', () => {
 
     const t0 = template('Vapor')
     const t1 = template('Hello ')
-    render(
-      defineComponent({
-        setup() {
-          // render
-          return (() => {
-            const n1 = createIf(
-              () => ok1.value,
-              () => {
-                const n2 = t1()
-                const n3 = createIf(
-                  () => ok2.value,
-                  () => {
-                    const n4 = t0()
-                    return n4
-                  },
-                )
-                append(n2, n3)
-                return n2
-              },
-            )
-            return [n1]
-          })()
+    const { host } = define(() => {
+      const n1 = createIf(
+        () => ok1.value,
+        () => {
+          const n2 = t1()
+          const n3 = createIf(
+            () => ok2.value,
+            () => {
+              const n4 = t0()
+              return n4
+            },
+          )
+          append(n2, n3)
+          return n2
         },
-      }) as any,
-      {},
-      '#host',
-    )
+      )
+      return [n1]
+    }).render()
+
     expect(host.innerHTML).toBe('Hello Vapor<!--if--><!--if-->')
 
     ok1.value = false

+ 17 - 41
packages/runtime-vapor/__tests__/renderWatch.spec.ts

@@ -1,11 +1,9 @@
-import { defineComponent } from 'vue'
 import {
   nextTick,
   onBeforeUpdate,
   onEffectCleanup,
   onUpdated,
   ref,
-  render,
   renderEffect,
   renderWatch,
   template,
@@ -13,41 +11,25 @@ import {
   watchPostEffect,
   watchSyncEffect,
 } from '../src'
+import { makeRender } from './_utils'
 
-let host: HTMLElement
-
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => {
-  initHost()
-})
-afterEach(() => {
-  host.remove()
-})
-const createDemo = (
-  setupFn: (porps: any, ctx: any) => any,
-  renderFn: (ctx: any) => any,
-) => {
-  const demo = defineComponent({
-    setup(...args) {
-      const returned = setupFn(...args)
+const define = makeRender<any>()
+const createDemo = (setupFn: () => any, renderFn: (ctx: any) => any) =>
+  define({
+    setup: () => {
+      const returned = setupFn()
       Object.defineProperty(returned, '__isScriptSetup', {
         enumerable: false,
         value: true,
       })
       return returned
     },
+    render: (ctx: any) => {
+      const t0 = template('<div></div>')
+      renderFn(ctx)
+      return t0()
+    },
   })
-  demo.render = (ctx: any) => {
-    const t0 = template('<div></div>')
-    renderFn(ctx)
-    return t0()
-  }
-  return () => render(demo as any, {}, '#host')
-}
 
 describe('renderWatch', () => {
   test('effect', async () => {
@@ -79,7 +61,7 @@ describe('renderWatch', () => {
   test('should run with the scheduling order', async () => {
     const calls: string[] = []
 
-    const mount = createDemo(
+    const { instance } = createDemo(
       () => {
         // setup
         const source = ref(0)
@@ -129,10 +111,7 @@ describe('renderWatch', () => {
           },
         )
       },
-    )
-
-    // Mount
-    const instance = mount()
+    ).render()
     const { change, changeRender } = instance.setupState as any
 
     expect(calls).toEqual(['pre 0', 'sync 0', 'renderEffect 0'])
@@ -164,7 +143,7 @@ describe('renderWatch', () => {
   })
 
   test('errors should include the execution location with beforeUpdate hook', async () => {
-    const mount = createDemo(
+    const { instance } = createDemo(
       // setup
       () => {
         const source = ref()
@@ -180,9 +159,7 @@ describe('renderWatch', () => {
           ctx.source
         })
       },
-    )
-
-    const instance = mount()
+    ).render()
     const { update } = instance.setupState as any
     await expect(async () => {
       update()
@@ -195,7 +172,7 @@ describe('renderWatch', () => {
   })
 
   test('errors should include the execution location with updated hook', async () => {
-    const mount = createDemo(
+    const { instance } = createDemo(
       // setup
       () => {
         const source = ref(0)
@@ -211,9 +188,8 @@ describe('renderWatch', () => {
           ctx.source
         })
       },
-    )
+    ).render()
 
-    const instance = mount()
     const { update } = instance.setupState as any
     await expect(async () => {
       update()

+ 21 - 35
packages/runtime-vapor/__tests__/vShow.spec.ts

@@ -1,42 +1,29 @@
-import { children, on, render, template, vShow, withDirectives } from '../src'
-import { defineComponent, nextTick, ref } from 'vue'
-import { afterEach, beforeEach, describe, expect, test } from 'vitest'
+import { children, on, template, vShow, withDirectives } from '../src'
+import { nextTick, ref } from 'vue'
+import { describe, expect, test } from 'vitest'
+import { makeRender } from './_utils'
 
-let host: HTMLElement
+const define = makeRender()
 
-const initHost = () => {
-  host = document.createElement('div')
-  host.setAttribute('id', 'host')
-  document.body.appendChild(host)
-}
-beforeEach(() => {
-  initHost()
-})
-afterEach(() => {
-  host.remove()
-})
 const createDemo = (defaultValue: boolean) =>
-  defineComponent({
-    setup() {
-      const visible = ref(defaultValue)
-      function handleClick() {
-        visible.value = !visible.value
-      }
-      const t0 = template('<button>toggle</button><h1>hello world</h1>')
-      const n0 = t0()
-      const {
-        0: [n1],
-        1: [n2],
-      } = children(n0)
-      withDirectives(n2, [[vShow, () => visible.value]])
-      on(n1 as HTMLElement, 'click', () => handleClick)
-      return n0
-    },
+  define(() => {
+    const visible = ref(defaultValue)
+    function handleClick() {
+      visible.value = !visible.value
+    }
+    const t0 = template('<button>toggle</button><h1>hello world</h1>')
+    const n0 = t0()
+    const {
+      0: [n1],
+      1: [n2],
+    } = children(n0)
+    withDirectives(n2, [[vShow, () => visible.value]])
+    on(n1 as HTMLElement, 'click', () => handleClick)
+    return n0
   })
 describe('directive: v-show', () => {
   test('basic', async () => {
-    const demo = createDemo(true)
-    render(demo as any, {}, '#host')
+    const { host } = createDemo(true).render()
     const btn = host.querySelector('button')
     expect(host.innerHTML).toBe('<button>toggle</button><h1>hello world</h1>')
     btn?.click()
@@ -46,8 +33,7 @@ describe('directive: v-show', () => {
     )
   })
   test('should hide content when default value is false', async () => {
-    const demo = createDemo(false)
-    render(demo as any, {}, '#host')
+    const { host } = createDemo(false).render()
     const btn = host.querySelector('button')
     const h1 = host.querySelector('h1')
     expect(h1?.style.display).toBe('none')

+ 1 - 1
packages/runtime-vapor/src/for.ts

@@ -16,7 +16,7 @@ interface ForBlock extends Fragment {
 export const createFor = (
   src: () => any[] | Record<string, string> | Set<any> | Map<any, any>,
   renderItem: (block: ForBlock) => Block,
-  getKey: ((item: any, index: number) => any) | null,
+  getKey?: (item: any, index: number) => any,
   getMemo?: (item: any) => any[],
   hydrationNode?: Node,
 ): Fragment => {

+ 8 - 1
packages/runtime-vapor/src/index.ts

@@ -39,7 +39,14 @@ export {
 } from '@vue/reactivity'
 
 export { nextTick } from './scheduler'
-export { getCurrentInstance, type ComponentInternalInstance } from './component'
+export {
+  getCurrentInstance,
+  type ComponentInternalInstance,
+  type Component,
+  type ObjectComponent,
+  type FunctionalComponent,
+  type SetupFn,
+} from './component'
 export * from './render'
 export * from './renderWatch'
 export * from './template'