Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit bbd1944

Browse files
committed
test(runtime-vapor): finish createVaporApp unit tests
1 parent 8ccfce5 commit bbd1944

File tree

4 files changed

+339
-18
lines changed

4 files changed

+339
-18
lines changed

packages/runtime-vapor/__tests__/_utils.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,50 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
1717
},
1818
) {
1919
let host: HTMLElement
20+
function resetHost() {
21+
return (host = initHost())
22+
}
23+
2024
beforeEach(() => {
21-
host = initHost()
25+
resetHost()
2226
})
2327
afterEach(() => {
2428
host.remove()
2529
})
2630

27-
const define = (comp: Component) => {
31+
function define(comp: Component) {
2832
const component = defineComponent(comp as any)
29-
let instance: ComponentInternalInstance
33+
let instance: ComponentInternalInstance | undefined
3034
let app: App
31-
const render = (
35+
36+
function render(
3237
props: RawProps = {},
33-
container: string | ParentNode = '#host',
34-
) => {
38+
container: string | ParentNode = host,
39+
) {
40+
create(props)
41+
return mount(container)
42+
}
43+
44+
function create(props: RawProps = {}) {
45+
app?.unmount()
3546
app = createVaporApp(component, props)
47+
return res()
48+
}
49+
50+
function mount(container: string | ParentNode = host) {
3651
instance = app.mount(container)
3752
return res()
3853
}
54+
3955
const res = () => ({
4056
component,
4157
host,
4258
instance,
4359
app,
60+
create,
61+
mount,
4462
render,
63+
resetHost,
4564
})
4665

4766
return res()

packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts

Lines changed: 277 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,193 @@
1-
import { type Component, type Plugin, createVaporApp, inject } from '../src'
2-
;``
3-
describe('api: createApp', () => {
1+
import {
2+
type ComponentInternalInstance,
3+
type Plugin,
4+
createComponent,
5+
createTextNode,
6+
createVaporApp,
7+
defineComponent,
8+
getCurrentInstance,
9+
inject,
10+
provide,
11+
resolveComponent,
12+
resolveDirective,
13+
withDirectives,
14+
} from '../src'
15+
import { warn } from '../src/warning'
16+
import { makeRender } from './_utils'
17+
18+
const define = makeRender()
19+
20+
describe('api: createVaporApp', () => {
21+
test('mount', () => {
22+
const Comp = defineComponent({
23+
props: {
24+
count: { default: 0 },
25+
},
26+
setup(props) {
27+
return createTextNode(() => [props.count])
28+
},
29+
})
30+
31+
const root1 = document.createElement('div')
32+
createVaporApp(Comp).mount(root1)
33+
expect(root1.innerHTML).toBe(`0`)
34+
//#5571 mount multiple apps to the same host element
35+
createVaporApp(Comp).mount(root1)
36+
expect(
37+
`There is already an app instance mounted on the host container`,
38+
).toHaveBeenWarned()
39+
40+
// mount with props
41+
const root2 = document.createElement('div')
42+
const app2 = createVaporApp(Comp, { count: () => 1 })
43+
app2.mount(root2)
44+
expect(root2.innerHTML).toBe(`1`)
45+
46+
// remount warning
47+
const root3 = document.createElement('div')
48+
app2.mount(root3)
49+
expect(root3.innerHTML).toBe(``)
50+
expect(`already been mounted`).toHaveBeenWarned()
51+
})
52+
53+
test('unmount', () => {
54+
const Comp = defineComponent({
55+
props: {
56+
count: { default: 0 },
57+
},
58+
setup(props) {
59+
return createTextNode(() => [props.count])
60+
},
61+
})
62+
63+
const root = document.createElement('div')
64+
const app = createVaporApp(Comp)
65+
66+
// warning
67+
app.unmount()
68+
expect(`that is not mounted`).toHaveBeenWarned()
69+
70+
app.mount(root)
71+
72+
app.unmount()
73+
expect(root.innerHTML).toBe(``)
74+
})
75+
76+
test('provide', () => {
77+
const Root = define({
78+
setup() {
79+
// test override
80+
provide('foo', 3)
81+
return createComponent(Child)
82+
},
83+
})
84+
85+
const Child = defineComponent({
86+
setup() {
87+
const foo = inject('foo')
88+
const bar = inject('bar')
89+
try {
90+
inject('__proto__')
91+
} catch (e: any) {}
92+
return createTextNode(() => [`${foo},${bar}`])
93+
},
94+
})
95+
96+
const { app, mount, create, host } = Root.create(null)
97+
app.provide('foo', 1)
98+
app.provide('bar', 2)
99+
mount()
100+
expect(host.innerHTML).toBe(`3,2`)
101+
expect('[Vue warn]: injection "__proto__" not found.').toHaveBeenWarned()
102+
103+
const { app: app2 } = create()
104+
app2.provide('bar', 1)
105+
app2.provide('bar', 2)
106+
expect(`App already provides property with key "bar".`).toHaveBeenWarned()
107+
})
108+
109+
test('runWithContext', () => {
110+
const { app } = define({
111+
setup() {
112+
provide('foo', 'should not be seen')
113+
return document.createElement('div')
114+
},
115+
}).create()
116+
app.provide('foo', 1)
117+
118+
expect(app.runWithContext(() => inject('foo'))).toBe(1)
119+
120+
expect(
121+
app.runWithContext(() => {
122+
app.runWithContext(() => {})
123+
return inject('foo')
124+
}),
125+
).toBe(1)
126+
127+
// ensure the context is restored
128+
inject('foo')
129+
expect('inject() can only be used inside setup').toHaveBeenWarned()
130+
})
131+
132+
test('component', () => {
133+
const { app, mount, host } = define({
134+
setup() {
135+
const FooBar = resolveComponent('foo-bar')
136+
const BarBaz = resolveComponent('bar-baz')
137+
// @ts-expect-error TODO support string
138+
return [createComponent(FooBar), createComponent(BarBaz)]
139+
},
140+
}).create()
141+
142+
const FooBar = () => createTextNode(['foobar!'])
143+
app.component('FooBar', FooBar)
144+
expect(app.component('FooBar')).toBe(FooBar)
145+
146+
app.component('BarBaz', () => createTextNode(['barbaz!']))
147+
app.component('BarBaz', () => createTextNode(['barbaz!']))
148+
expect(
149+
'Component "BarBaz" has already been registered in target app.',
150+
).toHaveBeenWarnedTimes(1)
151+
152+
mount()
153+
expect(host.innerHTML).toBe(`foobar!barbaz!`)
154+
})
155+
156+
test('directive', () => {
157+
const spy1 = vi.fn()
158+
const spy2 = vi.fn()
159+
160+
const { app, mount } = define({
161+
setup() {
162+
const FooBar = resolveDirective('foo-bar')
163+
const BarBaz = resolveDirective('bar-baz')
164+
return withDirectives(document.createElement('div'), [
165+
[FooBar],
166+
[BarBaz],
167+
])
168+
},
169+
}).create()
170+
171+
const FooBar = { mounted: spy1 }
172+
app.directive('FooBar', FooBar)
173+
expect(app.directive('FooBar')).toBe(FooBar)
174+
175+
app.directive('BarBaz', { mounted: spy2 })
176+
app.directive('BarBaz', { mounted: spy2 })
177+
expect(
178+
'Directive "BarBaz" has already been registered in target app.',
179+
).toHaveBeenWarnedTimes(1)
180+
181+
mount()
182+
expect(spy1).toHaveBeenCalled()
183+
expect(spy2).toHaveBeenCalled()
184+
185+
app.directive('bind', FooBar)
186+
expect(
187+
`Do not use built-in directive ids as custom directive id: bind`,
188+
).toHaveBeenWarned()
189+
})
190+
4191
test('use', () => {
5192
const PluginA: Plugin = app => app.provide('foo', 1)
6193
const PluginB: Plugin = {
@@ -14,22 +201,20 @@ describe('api: createApp', () => {
14201
}
15202
const PluginD: any = undefined
16203

17-
const Root: Component = {
204+
const { app, host, mount } = define({
18205
setup() {
19206
const foo = inject('foo')
20207
const bar = inject('bar')
21208
return document.createTextNode(`${foo},${bar}`)
22209
},
23-
}
210+
}).create()
24211

25-
const app = createVaporApp(Root)
26212
app.use(PluginA)
27213
app.use(PluginB, 1, 1)
28214
app.use(PluginC)
29215

30-
const root = document.createElement('div')
31-
app.mount(root)
32-
expect(root.innerHTML).toBe(`1,2`)
216+
mount()
217+
expect(host.innerHTML).toBe(`1,2`)
33218

34219
app.use(PluginA)
35220
expect(
@@ -42,4 +227,87 @@ describe('api: createApp', () => {
42227
`function.`,
43228
).toHaveBeenWarnedTimes(1)
44229
})
230+
231+
test('config.errorHandler', () => {
232+
const error = new Error()
233+
let instance: ComponentInternalInstance
234+
235+
const handler = vi.fn((err, _instance, info) => {
236+
expect(err).toBe(error)
237+
expect(_instance).toBe(instance)
238+
expect(info).toBe(`render function`)
239+
})
240+
241+
const { app, mount } = define({
242+
setup() {
243+
instance = getCurrentInstance()!
244+
},
245+
render() {
246+
throw error
247+
},
248+
}).create()
249+
app.config.errorHandler = handler
250+
mount()
251+
expect(handler).toHaveBeenCalled()
252+
})
253+
254+
test('config.warnHandler', () => {
255+
let instance: ComponentInternalInstance
256+
257+
const handler = vi.fn((msg, _instance, trace) => {
258+
expect(msg).toMatch(`warn message`)
259+
expect(_instance).toBe(instance)
260+
expect(trace).toMatch(`Hello`)
261+
})
262+
263+
const { app, mount } = define({
264+
name: 'Hello',
265+
setup() {
266+
instance = getCurrentInstance()!
267+
warn('warn message')
268+
},
269+
}).create()
270+
271+
app.config.warnHandler = handler
272+
mount()
273+
expect(handler).toHaveBeenCalledTimes(1)
274+
})
275+
276+
describe('config.isNativeTag', () => {
277+
const isNativeTag = vi.fn(tag => tag === 'div')
278+
279+
test('Component.name', () => {
280+
const { app, mount } = define({
281+
name: 'div',
282+
render(): any {},
283+
}).create()
284+
285+
Object.defineProperty(app.config, 'isNativeTag', {
286+
value: isNativeTag,
287+
writable: false,
288+
})
289+
290+
mount()
291+
expect(
292+
`Do not use built-in or reserved HTML elements as component id: div`,
293+
).toHaveBeenWarned()
294+
})
295+
296+
test('register using app.component', () => {
297+
const { app, mount } = define({
298+
render(): any {},
299+
}).create()
300+
301+
Object.defineProperty(app.config, 'isNativeTag', {
302+
value: isNativeTag,
303+
writable: false,
304+
})
305+
306+
app.component('div', () => createTextNode(['div']))
307+
mount()
308+
expect(
309+
`Do not use built-in or reserved HTML elements as component id: div`,
310+
).toHaveBeenWarned()
311+
})
312+
})
45313
})

0 commit comments

Comments
 (0)