test(vapor): block tests

This commit is contained in:
Evan You 2024-12-09 20:57:40 +08:00
parent ec23ab9e3a
commit b7aec139cb
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
8 changed files with 66 additions and 166 deletions

View File

@ -1,12 +1,11 @@
import { fragmentKey, normalizeBlock } from '../../src/block'
import { insert, prepend, remove } from '../../src/dom/node'
import { Fragment, insert, normalizeBlock, prepend, remove } from '../src/block'
const node1 = document.createTextNode('node1')
const node2 = document.createTextNode('node2')
const node3 = document.createTextNode('node3')
const anchor = document.createTextNode('anchor')
describe('element', () => {
describe('node ops', () => {
test('normalizeBlock', () => {
expect(normalizeBlock([node1, node2, node3])).toEqual([node1, node2, node3])
expect(normalizeBlock([node1, [node2, [node3]]])).toEqual([
@ -14,13 +13,14 @@ describe('element', () => {
node2,
node3,
])
expect(
normalizeBlock([
node1,
{ nodes: node2, anchor, [fragmentKey]: true },
[node3],
]),
).toEqual([node1, node2, anchor, node3])
const frag = new Fragment(node2)
frag.anchor = anchor
expect(normalizeBlock([node1, frag, [node3]])).toEqual([
node1,
node2,
anchor,
node3,
])
})
test('insert', () => {
@ -39,15 +39,16 @@ describe('element', () => {
test('prepend', () => {
const container = document.createElement('div')
prepend(container, [node1], node2)
prepend(container, { nodes: node3, [fragmentKey]: true })
prepend(container, new Fragment(node3))
expect(Array.from(container.childNodes)).toEqual([node3, node1, node2])
})
test('remove', () => {
const container = document.createElement('div')
container.append(node1, node2, node3)
const frag = new Fragment(node3)
remove([node1], container)
remove({ nodes: node3, [fragmentKey]: true }, container)
remove(frag, container)
expect(Array.from(container.childNodes)).toEqual([node2])
expect(() => remove(anchor, container)).toThrowError(

View File

@ -1,4 +1,4 @@
import type { NodeRef } from 'packages/runtime-vapor/src/dom/templateRef'
import type { NodeRef } from '../../src/dom/templateRef'
import {
createFor,
createIf,

View File

@ -1,80 +0,0 @@
import {
type Component,
type Directive,
createVaporApp,
resolveComponent,
resolveDirective,
} from 'packages/runtime-vapor/src/_old'
import { makeRender } from '../_utils'
const define = makeRender()
describe('resolveAssets', () => {
test('should work', () => {
const FooBar = () => []
const BarBaz = () => undefined
let component1: Component | string
let component2: Component | string
let component3: Component | string
let component4: Component | string
let directive1: Directive
let directive2: Directive
let directive3: Directive
let directive4: Directive
const Root = define({
render() {
component1 = resolveComponent('FooBar')!
directive1 = resolveDirective('BarBaz')!
// camelize
component2 = resolveComponent('Foo-bar')!
directive2 = resolveDirective('Bar-baz')!
// capitalize
component3 = resolveComponent('fooBar')!
directive3 = resolveDirective('barBaz')!
// camelize and capitalize
component4 = resolveComponent('foo-bar')!
directive4 = resolveDirective('bar-baz')!
return []
},
})
const app = createVaporApp(Root.component)
app.component('FooBar', FooBar)
app.directive('BarBaz', BarBaz)
const root = document.createElement('div')
app.mount(root)
expect(component1!).toBe(FooBar)
expect(component2!).toBe(FooBar)
expect(component3!).toBe(FooBar)
expect(component4!).toBe(FooBar)
expect(directive1!).toBe(BarBaz)
expect(directive2!).toBe(BarBaz)
expect(directive3!).toBe(BarBaz)
expect(directive4!).toBe(BarBaz)
})
describe('warning', () => {
test('used outside render() or setup()', () => {
resolveComponent('foo')
expect(
'[Vue warn]: resolveComponent can only be used in render() or setup().',
).toHaveBeenWarned()
resolveDirective('foo')
expect(
'[Vue warn]: resolveDirective can only be used in render() or setup().',
).toHaveBeenWarned()
})
test('not exist', () => {
const Root = define({
setup() {
resolveComponent('foo')
resolveDirective('bar')
},
})
const app = createVaporApp(Root.component)
const root = document.createElement('div')
app.mount(root)
expect('Failed to resolve component: foo').toHaveBeenWarned()
expect('Failed to resolve directive: bar').toHaveBeenWarned()
})
})
})

View File

@ -1,6 +1,11 @@
import { isArray } from '@vue/shared'
import { type VaporComponentInstance, isVaporComponent } from './component'
import { createComment, insert, remove } from './dom/node'
import {
type VaporComponentInstance,
isVaporComponent,
mountComponent,
unmountComponent,
} from './component'
import { createComment } from './dom/node'
import { EffectScope } from '@vue/reactivity'
export type Block = Node | Fragment | VaporComponentInstance | Block[]
@ -84,28 +89,50 @@ export function normalizeBlock(block: Block): Node[] {
return nodes
}
export function findFirstRootElement(
instance: VaporComponentInstance,
): Element | undefined {
const element = getFirstNode(instance.block)
return element instanceof Element ? element : undefined
}
export function getFirstNode(block: Block | null): Node | undefined {
if (!block || isVaporComponent(block)) return
if (block instanceof Node) return block
if (isArray(block)) {
if (block.length === 1) {
return getFirstNode(block[0])
}
} else {
return getFirstNode(block.nodes)
}
}
// TODO optimize
export function isValidBlock(block: Block): boolean {
return (
normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
)
}
export function insert(
block: Block,
parent: ParentNode,
anchor: Node | null | 0 = null,
): void {
if (block instanceof Node) {
parent.insertBefore(block, anchor === 0 ? parent.firstChild : anchor)
} else if (isVaporComponent(block)) {
mountComponent(block, parent, anchor)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
insert(block[i], parent, anchor)
}
} else {
// fragment
insert(block.nodes, parent, anchor)
if (block.anchor) insert(block.anchor, parent, anchor)
}
}
export function prepend(parent: ParentNode, ...blocks: Block[]): void {
let i = blocks.length
while (i--) insert(blocks[i], parent, 0)
}
export function remove(block: Block, parent: ParentNode): void {
if (block instanceof Node) {
parent.removeChild(block)
} else if (isVaporComponent(block)) {
unmountComponent(block, parent)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
remove(block[i], parent)
}
} else {
// fragment
remove(block.nodes, parent)
if (block.anchor) remove(block.anchor, parent)
}
}

View File

@ -19,7 +19,7 @@ import {
unregisterHMR,
warn,
} from '@vue/runtime-dom'
import { type Block, isBlock } from './block'
import { type Block, insert, isBlock, remove } from './block'
import { pauseTracking, proxyRefs, resetTracking } from '@vue/reactivity'
import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
import {
@ -41,7 +41,6 @@ import {
dynamicSlotsProxyHandlers,
getSlot,
} from './componentSlots'
import { insert, remove } from './dom/node'
import { hmrReload, hmrRerender } from './hmr'
export { currentInstance } from '@vue/runtime-dom'

View File

@ -1,53 +1,6 @@
import { isArray } from '@vue/shared'
import { renderEffect } from '../renderEffect'
import { setText } from './prop'
import type { Block } from '../block'
import {
isVaporComponent,
mountComponent,
unmountComponent,
} from '../component'
export function insert(
block: Block,
parent: ParentNode,
anchor: Node | null | 0 = null,
): void {
if (block instanceof Node) {
parent.insertBefore(block, anchor === 0 ? parent.firstChild : anchor)
} else if (isVaporComponent(block)) {
mountComponent(block, parent, anchor)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
insert(block[i], parent, anchor)
}
} else {
// fragment
insert(block.nodes, parent, anchor)
if (block.anchor) insert(block.anchor, parent, anchor)
}
}
export function prepend(parent: ParentNode, ...blocks: Block[]): void {
for (const b of blocks) insert(b, parent, 0)
}
// TODO invoke unmount recursive
export function remove(block: Block, parent: ParentNode): void {
if (block instanceof Node) {
parent.removeChild(block)
} else if (isVaporComponent(block)) {
unmountComponent(block, parent)
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
remove(block[i], parent)
}
} else {
// fragment
remove(block.nodes, parent)
if (block.anchor) remove(block.anchor, parent)
}
}
export function createTextNode(values?: any[] | (() => any[])): Text {
// eslint-disable-next-line no-restricted-globals

View File

@ -4,7 +4,7 @@ import {
pushWarningContext,
simpleSetCurrentInstance,
} from '@vue/runtime-core'
import { normalizeBlock } from './block'
import { insert, normalizeBlock, remove } from './block'
import {
type VaporComponent,
type VaporComponentInstance,
@ -13,7 +13,6 @@ import {
mountComponent,
unmountComponent,
} from './component'
import { insert, remove } from './dom/node'
export function hmrRerender(instance: VaporComponentInstance): void {
const normalized = normalizeBlock(instance.block)

View File

@ -3,11 +3,12 @@ export { createVaporApp } from './apiCreateApp'
export { defineVaporComponent } from './apiDefineComponent'
// compiler-use only
export { insert, prepend, remove } from './block'
export { createComponent, createComponentWithFallback } from './component'
export { renderEffect } from './renderEffect'
export { createSlot } from './componentSlots'
export { template, children, next } from './dom/template'
export { insert, prepend, remove, createTextNode } from './dom/node'
export { createTextNode } from './dom/node'
export { setStyle } from './dom/style'
export {
setText,