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

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
Rizumu Ayaka 2024-02-07 21:03:46 +08:00 committed by GitHub
parent 75c8ff5cb7
commit a15f609044
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 407 additions and 579 deletions

View File

@ -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
}

View File

@ -1,32 +1,19 @@
import { import {
children, children,
ref, ref,
render,
setText, setText,
template, template,
unmountComponent, unmountComponent,
watchEffect, watchEffect,
} from '../src' } from '../src'
import { afterEach, beforeEach, describe, expect } from 'vitest' import { describe, expect } from 'vitest'
import { defineComponent } from '@vue/runtime-core' 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', () => { describe('component', () => {
test('unmountComponent', async () => { test('unmountComponent', async () => {
const Comp = defineComponent({ const { host, instance } = define(() => {
setup() {
const count = ref(0) const count = ref(0)
const t0 = template('<div></div>') const t0 = template('<div></div>')
const n0 = t0() const n0 = t0()
@ -37,9 +24,7 @@ describe('component', () => {
setText(n1, count.value) setText(n1, count.value)
}) })
return n0 return n0
}, }).render()
})
const instance = render(Comp as any, {}, '#host')
expect(host.innerHTML).toBe('<div>0</div>') expect(host.innerHTML).toBe('<div>0</div>')
unmountComponent(instance) unmountComponent(instance)
expect(host.innerHTML).toBe('') expect(host.innerHTML).toBe('')

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { defineComponent } from 'vue'
import { import {
append, append,
children, children,
@ -6,26 +5,14 @@ import {
insert, insert,
nextTick, nextTick,
ref, ref,
render,
renderEffect, renderEffect,
setText, setText,
template, template,
} from '../src' } from '../src'
import type { Mock } from 'vitest' import type { Mock } 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('createIf', () => { describe('createIf', () => {
test('basic', async () => { test('basic', async () => {
@ -44,10 +31,7 @@ describe('createIf', () => {
const t1 = template('<p></p>') const t1 = template('<p></p>')
const t2 = template('<p>zero</p>') const t2 = template('<p>zero</p>')
const component = defineComponent({ const { host } = define(() => {
setup() {
// render
return (() => {
const n0 = t0() const n0 = t0()
const { const {
0: [n1], 0: [n1],
@ -76,10 +60,7 @@ describe('createIf', () => {
n1 as any as ParentNode, n1 as any as ParentNode,
) )
return n0 return n0
})() }).render()
},
})
render(component as any, {}, '#host')
expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>') expect(host.innerHTML).toBe('<div><p>zero</p><!--if--></div>')
expect(spyIfFn!).toHaveBeenCalledTimes(0) expect(spyIfFn!).toHaveBeenCalledTimes(0)
@ -115,11 +96,7 @@ describe('createIf', () => {
const t0 = template('Vapor') const t0 = template('Vapor')
const t1 = template('Hello ') const t1 = template('Hello ')
render( const { host } = define(() => {
defineComponent({
setup() {
// render
return (() => {
const n1 = createIf( const n1 = createIf(
() => ok1.value, () => ok1.value,
() => { () => {
@ -136,12 +113,8 @@ describe('createIf', () => {
}, },
) )
return [n1] return [n1]
})() }).render()
},
}) as any,
{},
'#host',
)
expect(host.innerHTML).toBe('Hello Vapor<!--if--><!--if-->') expect(host.innerHTML).toBe('Hello Vapor<!--if--><!--if-->')
ok1.value = false ok1.value = false

View File

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

View File

@ -1,23 +1,12 @@
import { children, on, render, template, vShow, withDirectives } from '../src' import { children, on, template, vShow, withDirectives } from '../src'
import { defineComponent, nextTick, ref } from 'vue' import { nextTick, ref } from 'vue'
import { afterEach, beforeEach, describe, expect, test } from 'vitest' 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) => const createDemo = (defaultValue: boolean) =>
defineComponent({ define(() => {
setup() {
const visible = ref(defaultValue) const visible = ref(defaultValue)
function handleClick() { function handleClick() {
visible.value = !visible.value visible.value = !visible.value
@ -31,12 +20,10 @@ const createDemo = (defaultValue: boolean) =>
withDirectives(n2, [[vShow, () => visible.value]]) withDirectives(n2, [[vShow, () => visible.value]])
on(n1 as HTMLElement, 'click', () => handleClick) on(n1 as HTMLElement, 'click', () => handleClick)
return n0 return n0
},
}) })
describe('directive: v-show', () => { describe('directive: v-show', () => {
test('basic', async () => { test('basic', async () => {
const demo = createDemo(true) const { host } = createDemo(true).render()
render(demo as any, {}, '#host')
const btn = host.querySelector('button') const btn = host.querySelector('button')
expect(host.innerHTML).toBe('<button>toggle</button><h1>hello world</h1>') expect(host.innerHTML).toBe('<button>toggle</button><h1>hello world</h1>')
btn?.click() btn?.click()
@ -46,8 +33,7 @@ describe('directive: v-show', () => {
) )
}) })
test('should hide content when default value is false', async () => { test('should hide content when default value is false', async () => {
const demo = createDemo(false) const { host } = createDemo(false).render()
render(demo as any, {}, '#host')
const btn = host.querySelector('button') const btn = host.querySelector('button')
const h1 = host.querySelector('h1') const h1 = host.querySelector('h1')
expect(h1?.style.display).toBe('none') expect(h1?.style.display).toBe('none')

View File

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

View File

@ -39,7 +39,14 @@ export {
} from '@vue/reactivity' } from '@vue/reactivity'
export { nextTick } from './scheduler' 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 './render'
export * from './renderWatch' export * from './renderWatch'
export * from './template' export * from './template'