test: more tests for suspense

This commit is contained in:
Evan You 2019-09-11 11:09:16 -04:00
parent dff4e7cd44
commit ccfcdb8746

View File

@ -6,47 +6,50 @@ import {
render, render,
nodeOps, nodeOps,
serializeInner, serializeInner,
nextTick nextTick,
onMounted,
watch,
onUnmounted
} from '@vue/runtime-test' } from '@vue/runtime-test'
describe('renderer: suspense', () => { describe('renderer: suspense', () => {
it('basic usage (nested + multiple deps)', async () => {
const msg = ref('hello')
const deps: Promise<any>[] = [] const deps: Promise<any>[] = []
const createAsyncComponent = (loader: () => Promise<ComponentOptions>) => ({ beforeEach(() => {
deps.length = 0
})
// a simple async factory for testing purposes only.
function createAsyncComponent<T extends ComponentOptions>(
comp: T,
delay: number = 0
) {
return {
async setup(props: any, { slots }: any) { async setup(props: any, { slots }: any) {
const p = loader() const p: Promise<T> = new Promise(r => setTimeout(() => r(comp), delay))
deps.push(p) deps.push(p)
const Inner = await p const Inner = await p
return () => h(Inner, props, slots) return () => h(Inner, props, slots)
} }
}) }
}
const AsyncChild = createAsyncComponent( it('basic usage (nested + multiple deps)', async () => {
() => const msg = ref('hello')
new Promise(resolve => {
setTimeout(() => { const AsyncChild = createAsyncComponent({
resolve({
setup(props: { msg: string }) { setup(props: { msg: string }) {
return () => h('div', props.msg) return () => h('div', props.msg)
} }
}) })
}, 0)
})
)
const AsyncChild2 = createAsyncComponent( const AsyncChild2 = createAsyncComponent(
() => {
new Promise(resolve => {
setTimeout(() => {
resolve({
setup(props: { msg: string }) { setup(props: { msg: string }) {
return () => h('div', props.msg) return () => h('div', props.msg)
} }
}) },
}, 10) 10
})
) )
const Mid = { const Mid = {
@ -77,22 +80,11 @@ describe('renderer: suspense', () => {
}) })
test('fallback content', async () => { test('fallback content', async () => {
const deps: Promise<any>[] = [] const Async = createAsyncComponent({
render() {
const Async = { return h('div', 'async')
async setup() {
const p = new Promise(r => setTimeout(r, 1))
deps.push(p)
await p
// test resume for returning bindings
return {
msg: 'async'
}
},
render(this: any) {
return h('div', this.msg)
}
} }
})
const Comp = { const Comp = {
setup() { setup() {
@ -113,16 +105,112 @@ describe('renderer: suspense', () => {
expect(serializeInner(root)).toBe(`<div>async</div>`) expect(serializeInner(root)).toBe(`<div>async</div>`)
}) })
test.todo('buffer mounted/updated hooks & watch callbacks') test('onResolve', async () => {
const Async = createAsyncComponent({
render() {
return h('div', 'async')
}
})
test.todo('onResolve') const onResolve = jest.fn()
const Comp = {
setup() {
return () =>
h(
Suspense,
{
onResolve
},
{
default: h(Async),
fallback: h('div', 'fallback')
}
)
}
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
expect(onResolve).not.toHaveBeenCalled()
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>async</div>`)
expect(onResolve).toHaveBeenCalled()
})
test('buffer mounted/updated hooks & watch callbacks', async () => {
const deps: Promise<any>[] = []
const calls: string[] = []
const toggle = ref(true)
const Async = {
async setup() {
const p = new Promise(r => setTimeout(r, 1))
deps.push(p)
watch(() => {
calls.push('watch callback')
})
onMounted(() => {
calls.push('mounted')
})
onUnmounted(() => {
calls.push('unmounted')
})
await p
// test resume for returning bindings
return {
msg: 'async'
}
},
render(this: any) {
return h('div', this.msg)
}
}
const Comp = {
setup() {
return () =>
h(Suspense, null, {
default: toggle.value ? h(Async) : null,
fallback: h('div', 'fallback')
})
}
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
expect(calls).toEqual([])
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>async</div>`)
expect(calls).toEqual([`watch callback`, `mounted`])
// effects inside an already resolved suspense should happen at normal timing
toggle.value = false
await nextTick()
expect(serializeInner(root)).toBe(`<!---->`)
expect(calls).toEqual([`watch callback`, `mounted`, 'unmounted'])
})
// should receive updated props/slots when resolved
test.todo('content update before suspense resolve') test.todo('content update before suspense resolve')
// mount/unmount hooks should not even fire
test.todo('unmount before suspense resolve') test.todo('unmount before suspense resolve')
test.todo('nested suspense') test.todo('nested suspense')
test.todo('new async dep after resolve should cause suspense to restart')
test.todo('error handling') test.todo('error handling')
test.todo('portal inside suspense') test.todo('portal inside suspense')