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,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('')

View File

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

View File

@ -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
},
})
}).render(props)
const props = {
get foo() {
return 1
},
get bar() {
return 2
},
}
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)
})

View File

@ -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)
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(component as any, {}, '#host')
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

View File

@ -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()

View File

@ -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')

View File

@ -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 => {

View File

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