test(vapor): enable more test cases

This commit is contained in:
Evan You 2025-01-30 10:12:36 +08:00
parent 6979952613
commit e49c5a17da
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
10 changed files with 202 additions and 163 deletions

View File

@ -9,9 +9,10 @@ import {
isPromise, isPromise,
} from '@vue/shared' } from '@vue/shared'
import { import {
type ComponentInternalInstance,
type SetupContext, type SetupContext,
createSetupContext, createSetupContext,
getCurrentInstance, getCurrentGenericInstance,
setCurrentInstance, setCurrentInstance,
unsetCurrentInstance, unsetCurrentInstance,
} from './component' } from './component'
@ -381,6 +382,7 @@ export function withDefaults<
return null as any return null as any
} }
// TODO return type for Vapor components
export function useSlots(): SetupContext['slots'] { export function useSlots(): SetupContext['slots'] {
return getContext().slots return getContext().slots
} }
@ -390,11 +392,16 @@ export function useAttrs(): SetupContext['attrs'] {
} }
function getContext(): SetupContext { function getContext(): SetupContext {
const i = getCurrentInstance()! const i = getCurrentGenericInstance()!
if (__DEV__ && !i) { if (__DEV__ && !i) {
warn(`useContext() called without active instance.`) warn(`useContext() called without active instance.`)
} }
return i.setupContext || (i.setupContext = createSetupContext(i)) if (i.vapor) {
return i as any // vapor instance act as its own setup context
} else {
const ii = i as ComponentInternalInstance
return ii.setupContext || (ii.setupContext = createSetupContext(ii))
}
} }
/** /**
@ -496,7 +503,7 @@ export function createPropsRestProxy(
* @internal * @internal
*/ */
export function withAsyncContext(getAwaitable: () => any): [any, () => void] { export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
const ctx = getCurrentInstance()! const ctx = getCurrentGenericInstance()!
if (__DEV__ && !ctx) { if (__DEV__ && !ctx) {
warn( warn(
`withAsyncContext called without active current instance. ` + `withAsyncContext called without active current instance. ` +

View File

@ -1,13 +1,18 @@
import { createComponent, defineVaporComponent, template } from '../src' import { createComponent, defineVaporComponent, template } from '../src'
import { ref, useAttrs, useSlots } from '@vue/runtime-dom' import {
currentInstance,
onMounted,
ref,
useAttrs,
useSlots,
withAsyncContext,
} from '@vue/runtime-dom'
import { makeRender } from './_utils' import { makeRender } from './_utils'
import type { VaporComponentInstance } from '../src/component' import type { VaporComponentInstance } from '../src/component'
const define = makeRender<any>() const define = makeRender<any>()
describe.todo('SFC <script setup> helpers', () => { describe('SFC <script setup> helpers', () => {
test.todo('should warn runtime usage', () => {})
test('useSlots / useAttrs (no args)', () => { test('useSlots / useAttrs (no args)', () => {
let slots: VaporComponentInstance['slots'] | undefined let slots: VaporComponentInstance['slots'] | undefined
let attrs: VaporComponentInstance['attrs'] | undefined let attrs: VaporComponentInstance['attrs'] | undefined
@ -59,23 +64,51 @@ describe.todo('SFC <script setup> helpers', () => {
expect(attrs).toBe(ctx!.attrs) expect(attrs).toBe(ctx!.attrs)
}) })
describe.todo('mergeDefaults', () => {
test.todo('object syntax', () => {})
test.todo('array syntax', () => {})
test.todo('merging with skipFactory', () => {})
test.todo('should warn missing', () => {})
})
describe('mergeModels', () => {
test.todo('array syntax', () => {})
test.todo('object syntax', () => {})
test.todo('overwrite', () => {})
})
test.todo('createPropsRestProxy', () => {})
describe.todo('withAsyncContext', () => { describe.todo('withAsyncContext', () => {
test.todo('basic', async () => {}) test('basic', async () => {
const spy = vi.fn()
let beforeInstance: VaporComponentInstance | null = null
let afterInstance: VaporComponentInstance | null = null
let resolve: (msg: string) => void
const Comp = defineVaporComponent({
async setup() {
let __temp: any, __restore: any
beforeInstance = currentInstance as VaporComponentInstance
const msg =
(([__temp, __restore] = withAsyncContext(
() =>
new Promise(r => {
resolve = r
}),
)),
(__temp = await __temp),
__restore(),
__temp)
// register the lifecycle after an await statement
onMounted(spy)
afterInstance = currentInstance as VaporComponentInstance
return document.createTextNode(msg)
},
})
const { html } = define(Comp).render()
expect(spy).not.toHaveBeenCalled()
resolve!('hello')
// wait a macro task tick for all micro ticks to resolve
await new Promise(r => setTimeout(r))
// mount hook should have been called
expect(spy).toHaveBeenCalled()
// should retain same instance before/after the await call
expect(beforeInstance).toBe(afterInstance)
expect(html()).toBe('hello')
})
test.todo('error handling', async () => {}) test.todo('error handling', async () => {})
test.todo('should not leak instance on multiple awaits', async () => {}) test.todo('should not leak instance on multiple awaits', async () => {})
test.todo('should not leak on multiple awaits + error', async () => {}) test.todo('should not leak on multiple awaits + error', async () => {})

View File

@ -8,26 +8,34 @@ import {
watch, watch,
watchEffect, watchEffect,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { createComponent, defineVaporComponent, renderEffect } from '../src' import {
createComponent,
createIf,
createTemplateRefSetter,
defineVaporComponent,
renderEffect,
template,
} from '../src'
import { makeRender } from './_utils' import { makeRender } from './_utils'
import type { VaporComponentInstance } from '../src/component' import type { VaporComponentInstance } from '../src/component'
import type { RefEl } from '../src/apiTemplateRef'
const define = makeRender() const define = makeRender()
// only need to port test cases related to in-component usage // only need to port test cases related to in-component usage
describe('apiWatch', () => { describe('apiWatch', () => {
// #7030 // #7030
it.todo( it(// need if support
// need if support 'should not fire on child component unmount w/ flush: pre', async () => {
'should not fire on child component unmount w/ flush: pre',
async () => {
const visible = ref(true) const visible = ref(true)
const cb = vi.fn() const cb = vi.fn()
const Parent = defineVaporComponent({ const Parent = defineVaporComponent({
props: ['visible'], props: ['visible'],
setup() { setup() {
// @ts-expect-error return createIf(
return visible.value ? h(Comp) : null () => visible.value,
() => createComponent(Comp),
)
}, },
}) })
const Comp = { const Comp = {
@ -43,8 +51,7 @@ describe('apiWatch', () => {
visible.value = false visible.value = false
await nextTick() await nextTick()
expect(cb).not.toHaveBeenCalled() expect(cb).not.toHaveBeenCalled()
}, })
)
// #7030 // #7030
it('flush: pre watcher in child component should not fire before parent update', async () => { it('flush: pre watcher in child component should not fire before parent update', async () => {
@ -184,10 +191,7 @@ describe('apiWatch', () => {
}) })
// #1852 // #1852
it.todo( it('flush: post watcher should fire after template refs updated', async () => {
// need if + templateRef
'flush: post watcher should fire after template refs updated',
async () => {
const toggle = ref(false) const toggle = ref(false)
let dom: Element | null = null let dom: Element | null = null
@ -203,22 +207,25 @@ describe('apiWatch', () => {
{ flush: 'post' }, { flush: 'post' },
) )
return () => { const setRef = createTemplateRefSetter()
// @ts-expect-error return createIf(
return toggle.value ? h('p', { ref: domRef }) : null () => toggle.value,
} () => {
const n = template('<p>')()
setRef(n as RefEl, domRef)
return n
},
)
}, },
} }
// @ts-expect-error define(App).render()
render(h(App), nodeOps.createElement('div'))
expect(dom).toBe(null) expect(dom).toBe(null)
toggle.value = true toggle.value = true
await nextTick() await nextTick()
expect(dom!.tagName).toBe('P') expect(dom!.tagName).toBe('P')
}, })
)
test('should not leak `this.proxy` to setup()', () => { test('should not leak `this.proxy` to setup()', () => {
const source = vi.fn() const source = vi.fn()

View File

@ -87,7 +87,7 @@ describe('component: slots', () => {
expect(instance.slots).toHaveProperty('two') expect(instance.slots).toHaveProperty('two')
}) })
test.todo('should work with createFlorSlots', async () => { test('should work with createFlorSlots', async () => {
const loop = ref([1, 2, 3]) const loop = ref([1, 2, 3])
let instance: any let instance: any
@ -101,7 +101,6 @@ describe('component: slots', () => {
return createComponent(Child, null, { return createComponent(Child, null, {
$: [ $: [
() => () =>
// @ts-expect-error
createForSlots(loop.value, (item, i) => ({ createForSlots(loop.value, (item, i) => ({
name: item, name: item,
fn: () => template(item + i)(), fn: () => template(item + i)(),

View File

@ -406,7 +406,7 @@ describe('api: template ref', () => {
}) })
// compiled output of v-for + template ref // compiled output of v-for + template ref
test.todo('ref in v-for', async () => { test('ref in v-for', async () => {
const show = ref(true) const show = ref(true)
const list = reactive([1, 2, 3]) const list = reactive([1, 2, 3])
const listRefs = ref([]) const listRefs = ref([])
@ -466,7 +466,7 @@ describe('api: template ref', () => {
expect(mapRefs()).toMatchObject(['2', '3', '4']) expect(mapRefs()).toMatchObject(['2', '3', '4'])
}) })
test.todo('named ref in v-for', async () => { test('named ref in v-for', async () => {
const show = ref(true) const show = ref(true)
const list = reactive([1, 2, 3]) const list = reactive([1, 2, 3])
const listRefs = ref([]) const listRefs = ref([])
@ -530,9 +530,7 @@ describe('api: template ref', () => {
}) })
// #6697 v-for ref behaves differently under production and development // #6697 v-for ref behaves differently under production and development
test.todo( test('named ref in v-for , should be responsive when rendering', async () => {
'named ref in v-for , should be responsive when rendering',
async () => {
const list = ref([1, 2, 3]) const list = ref([1, 2, 3])
const listRefs = ref([]) const listRefs = ref([])
@ -589,8 +587,7 @@ describe('api: template ref', () => {
expect(host.innerHTML).toBe( expect(host.innerHTML).toBe(
'<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>', '<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
) )
}, })
)
test('string ref inside slots', () => { test('string ref inside slots', () => {
const { component: Child } = define({ const { component: Child } = define({

View File

@ -209,7 +209,7 @@ describe('error handling', () => {
expect(fn).toHaveBeenCalledWith(err, 'render function') expect(fn).toHaveBeenCalledWith(err, 'render function')
}) })
test.todo('in function ref', () => { test('in function ref', () => {
const err = new Error('foo') const err = new Error('foo')
const ref = () => { const ref = () => {
throw err throw err

View File

@ -319,7 +319,7 @@ export const createFor = (
} }
export function createForSlots( export function createForSlots(
source: any[] | Record<any, any> | number | Set<any> | Map<any, any>, source: Source,
getSlot: (item: any, key: any, index?: number) => DynamicSlot, getSlot: (item: any, key: any, index?: number) => DynamicSlot,
): DynamicSlot[] { ): DynamicSlot[] {
const sourceLength = getLength(source) const sourceLength = getLength(source)

View File

@ -6,6 +6,7 @@ import {
isVaporComponent, isVaporComponent,
} from './component' } from './component'
import { import {
ErrorCodes,
type SchedulerJob, type SchedulerJob,
callWithErrorHandling, callWithErrorHandling,
queuePostFlushCb, queuePostFlushCb,
@ -67,13 +68,10 @@ export function setRef(
if (isFunction(ref)) { if (isFunction(ref)) {
const invokeRefSetter = (value?: Element | Record<string, any>) => { const invokeRefSetter = (value?: Element | Record<string, any>) => {
callWithErrorHandling( callWithErrorHandling(ref, currentInstance, ErrorCodes.FUNCTION_REF, [
ref, value,
currentInstance, refs,
// @ts-expect-error ])
null,
[value, refs],
)
} }
invokeRefSetter(refValue) invokeRefSetter(refValue)

View File

@ -35,16 +35,17 @@ export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
} }
}, },
ownKeys(target) { ownKeys(target) {
const keys = Object.keys(target) let keys = Object.keys(target)
const dynamicSources = target.$ const dynamicSources = target.$
if (dynamicSources) { if (dynamicSources) {
keys = keys.filter(k => k !== '$')
for (const source of dynamicSources) { for (const source of dynamicSources) {
if (isFunction(source)) { if (isFunction(source)) {
const slot = source() const slot = source()
if (isArray(slot)) { if (isArray(slot)) {
for (const s of slot) keys.push(s.name) for (const s of slot) keys.push(String(s.name))
} else { } else {
keys.push(slot.name) keys.push(String(slot.name))
} }
} else { } else {
keys.push(...Object.keys(source)) keys.push(...Object.keys(source))
@ -73,9 +74,9 @@ export function getSlot(
if (slot) { if (slot) {
if (isArray(slot)) { if (isArray(slot)) {
for (const s of slot) { for (const s of slot) {
if (s.name === key) return s.fn if (String(s.name) === key) return s.fn
} }
} else if (slot.name === key) { } else if (String(slot.name) === key) {
return slot.fn return slot.fn
} }
} }
@ -150,6 +151,3 @@ export function createSlot(
return fragment return fragment
} }
// TODO
export function createForSlots(): any {}

View File

@ -6,7 +6,7 @@ export { defineVaporComponent } from './apiDefineComponent'
export { insert, prepend, remove } from './block' export { insert, prepend, remove } from './block'
export { createComponent, createComponentWithFallback } from './component' export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect' export { renderEffect } from './renderEffect'
export { createSlot, createForSlots } from './componentSlots' export { createSlot } from './componentSlots'
export { template, children, next } from './dom/template' export { template, children, next } from './dom/template'
export { createTextNode } from './dom/node' export { createTextNode } from './dom/node'
export { export {
@ -22,5 +22,5 @@ export {
} from './dom/prop' } from './dom/prop'
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event' export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
export { createIf } from './apiCreateIf' export { createIf } from './apiCreateIf'
export { createFor } from './apiCreateFor' export { createFor, createForSlots } from './apiCreateFor'
export { createTemplateRefSetter } from './apiTemplateRef' export { createTemplateRefSetter } from './apiTemplateRef'