test(vapor): renderEffect

This commit is contained in:
Evan You 2024-12-09 18:35:41 +08:00
parent 2bbb6d2fc5
commit ec23ab9e3a
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
4 changed files with 43 additions and 34 deletions

View File

@ -1,18 +1,17 @@
import {
type App,
type Component,
type ComponentInternalInstance,
type ObjectComponent,
type SetupFn,
createVaporApp,
defineComponent,
} from '../src/_old'
import type { RawProps } from '../src/_old/componentProps'
import { createVaporApp, defineVaporComponent } from '../src'
import type { App } from '@vue/runtime-dom'
import type {
ObjectVaporComponent,
VaporComponent,
VaporComponentInstance,
VaporSetupFn,
} from '../src/component'
import type { RawProps } from '../src/componentProps'
export interface RenderContext {
component: Component
component: VaporComponent
host: HTMLElement
instance: ComponentInternalInstance | undefined
instance: VaporComponentInstance | undefined
app: App
create: (props?: RawProps) => RenderContext
mount: (container?: string | ParentNode) => RenderContext
@ -21,7 +20,7 @@ export interface RenderContext {
html: () => string
}
export function makeRender<C = ObjectComponent | SetupFn>(
export function makeRender<C = ObjectVaporComponent | VaporSetupFn>(
initHost = (): HTMLDivElement => {
const host = document.createElement('div')
host.setAttribute('id', 'host')
@ -42,8 +41,8 @@ export function makeRender<C = ObjectComponent | SetupFn>(
})
function define(comp: C) {
const component = defineComponent(comp as any)
let instance: ComponentInternalInstance | undefined
const component = defineVaporComponent(comp as any)
let instance: VaporComponentInstance | undefined
let app: App
function render(
@ -61,7 +60,7 @@ export function makeRender<C = ObjectComponent | SetupFn>(
}
function mount(container: string | ParentNode = host) {
instance = app.mount(container)
instance = app.mount(container) as any as VaporComponentInstance
return res()
}

View File

@ -1,21 +1,18 @@
import {
EffectScope,
type GenericComponentInstance,
currentInstance,
getCurrentScope,
nextTick,
onBeforeUpdate,
onEffectCleanup,
onUpdated,
ref,
renderEffect,
template,
watchEffect,
watchPostEffect,
watchSyncEffect,
} from '../src/_old'
import {
type ComponentInternalInstance,
currentInstance,
} from '../src/_old/component'
} from '@vue/runtime-dom'
import { renderEffect, template } from '../src'
import { onEffectCleanup } from '@vue/reactivity'
import { makeRender } from './_utils'
const define = makeRender<any>()
@ -110,8 +107,10 @@ describe('renderEffect', () => {
})
},
).render()
const { change, changeRender } = instance?.setupState as any
await nextTick()
expect(calls).toEqual(['pre 0', 'sync 0', 'renderEffect 0', 'post 0'])
calls.length = 0
@ -126,8 +125,8 @@ describe('renderEffect', () => {
expect(calls).toEqual([
'pre cleanup 0',
'pre 1',
'beforeUpdate 1',
'renderEffect cleanup 0',
'beforeUpdate 1',
'renderEffect 1',
'post cleanup 0',
'post 1',
@ -146,8 +145,8 @@ describe('renderEffect', () => {
expect(calls).toEqual([
'pre cleanup 1',
'pre 2',
'beforeUpdate 2',
'renderEffect cleanup 1',
'beforeUpdate 2',
'renderEffect 2',
'post cleanup 1',
'post 2',
@ -174,6 +173,7 @@ describe('renderEffect', () => {
},
).render()
const { update } = instance?.setupState as any
await expect(async () => {
update()
await nextTick()
@ -182,6 +182,9 @@ describe('renderEffect', () => {
expect(
'[Vue warn]: Unhandled error during execution of beforeUpdate hook',
).toHaveBeenWarned()
expect(
'[Vue warn]: Unhandled error during execution of component update',
).toHaveBeenWarned()
})
test('errors should include the execution location with updated hook', async () => {
@ -204,6 +207,7 @@ describe('renderEffect', () => {
).render()
const { update } = instance?.setupState as any
await expect(async () => {
update()
await nextTick()
@ -217,15 +221,17 @@ describe('renderEffect', () => {
test('should be called with the current instance and current scope', async () => {
const source = ref(0)
const scope = new EffectScope()
let instanceSnap: ComponentInternalInstance | null = null
let instanceSnap: GenericComponentInstance | null = null
let scopeSnap: EffectScope | undefined = undefined
const { instance } = define(() => {
scope.run(() => {
renderEffect(() => {
source.value
instanceSnap = currentInstance
scopeSnap = getCurrentScope()
})
})
return []
}).render()
expect(instanceSnap).toBe(instance)

View File

@ -34,8 +34,7 @@ export const createVaporApp: CreateAppFunction<
ParentNode,
VaporComponent
> = comp => {
if (!_createApp)
_createApp = createAppAPI(mountApp, unmountApp, i => i.exposed)
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i)
const app = _createApp(comp)
const mount = app.mount
app.mount = (container, ...args: any[]) => {

View File

@ -1,4 +1,4 @@
import { ReactiveEffect } from '@vue/reactivity'
import { ReactiveEffect, getCurrentScope } from '@vue/reactivity'
import {
type SchedulerJob,
currentInstance,
@ -12,7 +12,8 @@ import { invokeArrayFns } from '@vue/shared'
export function renderEffect(fn: () => void, noLifecycle = false): void {
const instance = currentInstance as VaporComponentInstance
if (__DEV__ && !isVaporComponent(instance)) {
const scope = getCurrentScope()
if (__DEV__ && !__TEST__ && !isVaporComponent(instance)) {
warn('renderEffect called without active vapor instance.')
}
@ -21,7 +22,9 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
: () => {
const prev = currentInstance
simpleSetCurrentInstance(instance)
if (scope) scope.on()
if (
instance &&
instance.isMounted &&
!instance.isUpdating &&
(instance.bu || instance.u)
@ -36,17 +39,19 @@ export function renderEffect(fn: () => void, noLifecycle = false): void {
} else {
fn()
}
if (scope) scope.off()
simpleSetCurrentInstance(prev, instance)
}
const effect = new ReactiveEffect(renderEffectFn)
const job: SchedulerJob = effect.runIfDirty.bind(effect)
job.i = instance
job.id = instance.uid
if (instance) {
job.i = instance
job.id = instance.uid
}
effect.scheduler = () => queueJob(job)
effect.run()
// TODO lifecycle
// TODO recurse handling
// TODO measure
}