diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts new file mode 100644 index 000000000..0565d4377 --- /dev/null +++ b/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( + 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 +} diff --git a/packages/runtime-vapor/__tests__/component.spec.ts b/packages/runtime-vapor/__tests__/component.spec.ts index f7199e2d1..e9ea55d72 100644 --- a/packages/runtime-vapor/__tests__/component.spec.ts +++ b/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('
') - 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('
') + const n0 = t0() + const { + 0: [n1], + } = children(n0) + watchEffect(() => { + setText(n1, count.value) + }) + return n0 + }).render() expect(host.innerHTML).toBe('
0
') unmountComponent(instance) expect(host.innerHTML).toBe('') diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index af301df95..b2e34d572 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/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() 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() }) diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index 52a02d1e2..dc3d3b9f0 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/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() 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) { 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('
1
') // TODO: Fallthrough Attributes expect(host.innerHTML).toBe('
1
') @@ -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('
60000000100000111
') }) @@ -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) }) diff --git a/packages/runtime-vapor/__tests__/if.spec.ts b/packages/runtime-vapor/__tests__/if.spec.ts index df476e54b..e920211bd 100644 --- a/packages/runtime-vapor/__tests__/if.spec.ts +++ b/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('

') const t2 = template('

zero

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

zero

') 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') ok1.value = false diff --git a/packages/runtime-vapor/__tests__/renderWatch.spec.ts b/packages/runtime-vapor/__tests__/renderWatch.spec.ts index c648d184e..bd4a5a039 100644 --- a/packages/runtime-vapor/__tests__/renderWatch.spec.ts +++ b/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() +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('
') + renderFn(ctx) + return t0() + }, }) - demo.render = (ctx: any) => { - const t0 = template('
') - 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() diff --git a/packages/runtime-vapor/__tests__/vShow.spec.ts b/packages/runtime-vapor/__tests__/vShow.spec.ts index 42c5351da..9f498f445 100644 --- a/packages/runtime-vapor/__tests__/vShow.spec.ts +++ b/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('

hello world

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

hello world

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

hello world

') 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') diff --git a/packages/runtime-vapor/src/for.ts b/packages/runtime-vapor/src/for.ts index c486643c8..4eb005ee5 100644 --- a/packages/runtime-vapor/src/for.ts +++ b/packages/runtime-vapor/src/for.ts @@ -16,7 +16,7 @@ interface ForBlock extends Fragment { export const createFor = ( src: () => any[] | Record | Set | Map, renderItem: (block: ForBlock) => Block, - getKey: ((item: any, index: number) => any) | null, + getKey?: (item: any, index: number) => any, getMemo?: (item: any) => any[], hydrationNode?: Node, ): Fragment => { diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 35348f60f..61ccd5d56 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/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'