feat: update Suspense usage (#2099)
See https://github.com/vuejs/vue-next/pull/2099 for details.
This commit is contained in:
@@ -406,7 +406,7 @@ describe('api: defineAsyncComponent', () => {
|
||||
const app = createApp({
|
||||
render: () =>
|
||||
h(Suspense, null, {
|
||||
default: () => [h(Foo), ' & ', h(Foo)],
|
||||
default: () => h('div', [h(Foo), ' & ', h(Foo)]),
|
||||
fallback: () => 'loading'
|
||||
})
|
||||
})
|
||||
@@ -416,7 +416,7 @@ describe('api: defineAsyncComponent', () => {
|
||||
|
||||
resolve!(() => 'resolved')
|
||||
await timeout()
|
||||
expect(serializeInner(root)).toBe('resolved & resolved')
|
||||
expect(serializeInner(root)).toBe('<div>resolved & resolved</div>')
|
||||
})
|
||||
|
||||
test('suspensible: false', async () => {
|
||||
@@ -433,18 +433,18 @@ describe('api: defineAsyncComponent', () => {
|
||||
const app = createApp({
|
||||
render: () =>
|
||||
h(Suspense, null, {
|
||||
default: () => [h(Foo), ' & ', h(Foo)],
|
||||
default: () => h('div', [h(Foo), ' & ', h(Foo)]),
|
||||
fallback: () => 'loading'
|
||||
})
|
||||
})
|
||||
|
||||
app.mount(root)
|
||||
// should not show suspense fallback
|
||||
expect(serializeInner(root)).toBe('<!----> & <!---->')
|
||||
expect(serializeInner(root)).toBe('<div><!----> & <!----></div>')
|
||||
|
||||
resolve!(() => 'resolved')
|
||||
await timeout()
|
||||
expect(serializeInner(root)).toBe('resolved & resolved')
|
||||
expect(serializeInner(root)).toBe('<div>resolved & resolved</div>')
|
||||
})
|
||||
|
||||
test('suspense with error handling', async () => {
|
||||
@@ -460,7 +460,7 @@ describe('api: defineAsyncComponent', () => {
|
||||
const app = createApp({
|
||||
render: () =>
|
||||
h(Suspense, null, {
|
||||
default: () => [h(Foo), ' & ', h(Foo)],
|
||||
default: () => h('div', [h(Foo), ' & ', h(Foo)]),
|
||||
fallback: () => 'loading'
|
||||
})
|
||||
})
|
||||
@@ -472,7 +472,7 @@ describe('api: defineAsyncComponent', () => {
|
||||
reject!(new Error('no'))
|
||||
await timeout()
|
||||
expect(handler).toHaveBeenCalled()
|
||||
expect(serializeInner(root)).toBe('<!----> & <!---->')
|
||||
expect(serializeInner(root)).toBe('<div><!----> & <!----></div>')
|
||||
})
|
||||
|
||||
test('retry (success)', async () => {
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
watch,
|
||||
watchEffect,
|
||||
onUnmounted,
|
||||
onErrorCaptured
|
||||
onErrorCaptured,
|
||||
shallowRef
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
describe('Suspense', () => {
|
||||
@@ -490,7 +491,7 @@ describe('Suspense', () => {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: [h(AsyncOuter), h(Inner)],
|
||||
default: h('div', [h(AsyncOuter), h(Inner)]),
|
||||
fallback: h('div', 'fallback outer')
|
||||
})
|
||||
}
|
||||
@@ -503,14 +504,14 @@ describe('Suspense', () => {
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>async outer</div><div>fallback inner</div>`
|
||||
`<div><div>async outer</div><div>fallback inner</div></div>`
|
||||
)
|
||||
expect(calls).toEqual([`outer mounted`])
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>async outer</div><div>async inner</div>`
|
||||
`<div><div>async outer</div><div>async inner</div></div>`
|
||||
)
|
||||
expect(calls).toEqual([`outer mounted`, `inner mounted`])
|
||||
})
|
||||
@@ -556,7 +557,7 @@ describe('Suspense', () => {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: [h(AsyncOuter), h(Inner)],
|
||||
default: h('div', [h(AsyncOuter), h(Inner)]),
|
||||
fallback: h('div', 'fallback outer')
|
||||
})
|
||||
}
|
||||
@@ -574,7 +575,7 @@ describe('Suspense', () => {
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>async outer</div><div>async inner</div>`
|
||||
`<div><div>async outer</div><div>async inner</div></div>`
|
||||
)
|
||||
expect(calls).toEqual([`inner mounted`, `outer mounted`])
|
||||
})
|
||||
@@ -683,12 +684,12 @@ describe('Suspense', () => {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: [
|
||||
default: h('div', [
|
||||
h(MiddleComponent),
|
||||
h(AsyncChildParent, {
|
||||
msg: 'root async'
|
||||
})
|
||||
],
|
||||
]),
|
||||
fallback: h('div', 'root fallback')
|
||||
})
|
||||
}
|
||||
@@ -722,7 +723,7 @@ describe('Suspense', () => {
|
||||
await deps[3]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>nested fallback</div><div>root async</div>`
|
||||
`<div><div>nested fallback</div><div>root async</div></div>`
|
||||
)
|
||||
expect(calls).toEqual([0, 1, 3])
|
||||
|
||||
@@ -733,7 +734,7 @@ describe('Suspense', () => {
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>nested changed</div><div>root async</div>`
|
||||
`<div><div>nested changed</div><div>root async</div></div>`
|
||||
)
|
||||
expect(calls).toEqual([0, 1, 3, 2])
|
||||
|
||||
@@ -741,51 +742,316 @@ describe('Suspense', () => {
|
||||
msg.value = 'nested changed again'
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>nested changed again</div><div>root async</div>`
|
||||
`<div><div>nested changed again</div><div>root async</div></div>`
|
||||
)
|
||||
})
|
||||
|
||||
test('new async dep after resolve should cause suspense to restart', async () => {
|
||||
const toggle = ref(false)
|
||||
test('switching branches', async () => {
|
||||
const calls: string[] = []
|
||||
const toggle = ref(true)
|
||||
|
||||
const ChildA = defineAsyncComponent({
|
||||
const Foo = defineAsyncComponent({
|
||||
setup() {
|
||||
return () => h('div', 'Child A')
|
||||
onMounted(() => {
|
||||
calls.push('foo mounted')
|
||||
})
|
||||
onUnmounted(() => {
|
||||
calls.push('foo unmounted')
|
||||
})
|
||||
return () => h('div', ['foo', h(FooNested)])
|
||||
}
|
||||
})
|
||||
|
||||
const ChildB = defineAsyncComponent({
|
||||
setup() {
|
||||
return () => h('div', 'Child B')
|
||||
}
|
||||
})
|
||||
const FooNested = defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
calls.push('foo nested mounted')
|
||||
})
|
||||
onUnmounted(() => {
|
||||
calls.push('foo nested unmounted')
|
||||
})
|
||||
return () => h('div', 'foo nested')
|
||||
}
|
||||
},
|
||||
10
|
||||
)
|
||||
|
||||
const Bar = defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
calls.push('bar mounted')
|
||||
})
|
||||
onUnmounted(() => {
|
||||
calls.push('bar unmounted')
|
||||
})
|
||||
return () => h('div', 'bar')
|
||||
}
|
||||
},
|
||||
10
|
||||
)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: [h(ChildA), toggle.value ? h(ChildB) : null],
|
||||
fallback: h('div', 'root fallback')
|
||||
default: toggle.value ? h(Foo) : h(Bar),
|
||||
fallback: h('div', 'fallback')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(`<div>root fallback</div>`)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
expect(calls).toEqual([])
|
||||
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>Child A</div><!---->`)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
expect(calls).toEqual([])
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(calls).toEqual([`foo mounted`, `foo nested mounted`])
|
||||
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
|
||||
|
||||
// toggle
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
expect(deps.length).toBe(3)
|
||||
// should remain on current view
|
||||
expect(calls).toEqual([`foo mounted`, `foo nested mounted`])
|
||||
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
const tempCalls = [
|
||||
`foo mounted`,
|
||||
`foo nested mounted`,
|
||||
`bar mounted`,
|
||||
`foo nested unmounted`,
|
||||
`foo unmounted`
|
||||
]
|
||||
expect(calls).toEqual(tempCalls)
|
||||
expect(serializeInner(root)).toBe(`<div>bar</div>`)
|
||||
|
||||
// toggle back
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>root fallback</div>`)
|
||||
// should remain
|
||||
expect(calls).toEqual(tempCalls)
|
||||
expect(serializeInner(root)).toBe(`<div>bar</div>`)
|
||||
|
||||
await deps[3]
|
||||
await nextTick()
|
||||
// still pending...
|
||||
expect(calls).toEqual(tempCalls)
|
||||
expect(serializeInner(root)).toBe(`<div>bar</div>`)
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
expect(calls).toEqual([
|
||||
...tempCalls,
|
||||
`foo mounted`,
|
||||
`foo nested mounted`,
|
||||
`bar unmounted`
|
||||
])
|
||||
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
|
||||
})
|
||||
|
||||
test('branch switch to 3rd branch before resolve', async () => {
|
||||
const calls: string[] = []
|
||||
|
||||
const makeComp = (name: string, delay = 0) =>
|
||||
defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
calls.push(`${name} mounted`)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
calls.push(`${name} unmounted`)
|
||||
})
|
||||
return () => h('div', [name])
|
||||
}
|
||||
},
|
||||
delay
|
||||
)
|
||||
|
||||
const One = makeComp('one')
|
||||
const Two = makeComp('two', 10)
|
||||
const Three = makeComp('three', 20)
|
||||
|
||||
const view = shallowRef(One)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h(view.value),
|
||||
fallback: h('div', 'fallback')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
expect(calls).toEqual([])
|
||||
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||
expect(calls).toEqual([`one mounted`])
|
||||
|
||||
view.value = Two
|
||||
await nextTick()
|
||||
expect(deps.length).toBe(2)
|
||||
|
||||
// switch before two resovles
|
||||
view.value = Three
|
||||
await nextTick()
|
||||
expect(deps.length).toBe(3)
|
||||
|
||||
// dep for two resolves
|
||||
await deps[1]
|
||||
await nextTick()
|
||||
// should still be on view one
|
||||
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||
expect(calls).toEqual([`one mounted`])
|
||||
|
||||
await deps[2]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>three</div>`)
|
||||
expect(calls).toEqual([`one mounted`, `three mounted`, `one unmounted`])
|
||||
})
|
||||
|
||||
test('branch switch back before resolve', async () => {
|
||||
const calls: string[] = []
|
||||
|
||||
const makeComp = (name: string, delay = 0) =>
|
||||
defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
calls.push(`${name} mounted`)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
calls.push(`${name} unmounted`)
|
||||
})
|
||||
return () => h('div', [name])
|
||||
}
|
||||
},
|
||||
delay
|
||||
)
|
||||
|
||||
const One = makeComp('one')
|
||||
const Two = makeComp('two', 10)
|
||||
|
||||
const view = shallowRef(One)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h(view.value),
|
||||
fallback: h('div', 'fallback')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
expect(calls).toEqual([])
|
||||
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||
expect(calls).toEqual([`one mounted`])
|
||||
|
||||
view.value = Two
|
||||
await nextTick()
|
||||
expect(deps.length).toBe(2)
|
||||
|
||||
// switch back before two resovles
|
||||
view.value = One
|
||||
await nextTick()
|
||||
expect(deps.length).toBe(2)
|
||||
|
||||
// dep for two resolves
|
||||
await deps[1]
|
||||
await nextTick()
|
||||
// should still be on view one
|
||||
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||
expect(calls).toEqual([`one mounted`])
|
||||
})
|
||||
|
||||
test('branch switch timeout + fallback', async () => {
|
||||
const calls: string[] = []
|
||||
|
||||
const makeComp = (name: string, delay = 0) =>
|
||||
defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
calls.push(`${name} mounted`)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
calls.push(`${name} unmounted`)
|
||||
})
|
||||
return () => h('div', [name])
|
||||
}
|
||||
},
|
||||
delay
|
||||
)
|
||||
|
||||
const One = makeComp('one')
|
||||
const Two = makeComp('two', 20)
|
||||
|
||||
const view = shallowRef(One)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
Suspense,
|
||||
{
|
||||
timeout: 10
|
||||
},
|
||||
{
|
||||
default: h(view.value),
|
||||
fallback: h('div', 'fallback')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
expect(calls).toEqual([])
|
||||
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||
expect(calls).toEqual([`one mounted`])
|
||||
|
||||
view.value = Two
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>one</div>`)
|
||||
expect(calls).toEqual([`one mounted`])
|
||||
|
||||
await new Promise(r => setTimeout(r, 10))
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
expect(calls).toEqual([`one mounted`, `one unmounted`])
|
||||
|
||||
await deps[1]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>Child A</div><div>Child B</div>`)
|
||||
expect(serializeInner(root)).toBe(`<div>two</div>`)
|
||||
expect(calls).toEqual([`one mounted`, `one unmounted`, `two mounted`])
|
||||
})
|
||||
|
||||
test.todo('teleport inside suspense')
|
||||
})
|
||||
|
||||
@@ -506,8 +506,10 @@ describe('SSR hydration', () => {
|
||||
const App = {
|
||||
template: `
|
||||
<Suspense @resolve="done">
|
||||
<AsyncChild :n="1" />
|
||||
<AsyncChild :n="2" />
|
||||
<div>
|
||||
<AsyncChild :n="1" />
|
||||
<AsyncChild :n="2" />
|
||||
</div>
|
||||
</Suspense>`,
|
||||
components: {
|
||||
AsyncChild
|
||||
@@ -521,7 +523,7 @@ describe('SSR hydration', () => {
|
||||
// server render
|
||||
container.innerHTML = await renderToString(h(App))
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<!--[--><span>1</span><span>2</span><!--]-->"`
|
||||
`"<div><span>1</span><span>2</span></div>"`
|
||||
)
|
||||
// reset asyncDeps from ssr
|
||||
asyncDeps.length = 0
|
||||
@@ -537,17 +539,23 @@ describe('SSR hydration', () => {
|
||||
|
||||
// should flush buffered effects
|
||||
expect(mountedCalls).toMatchObject([1, 2])
|
||||
expect(container.innerHTML).toMatch(`<span>1</span><span>2</span>`)
|
||||
expect(container.innerHTML).toMatch(
|
||||
`<div><span>1</span><span>2</span></div>`
|
||||
)
|
||||
|
||||
const span1 = container.querySelector('span')!
|
||||
triggerEvent('click', span1)
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toMatch(`<span>2</span><span>2</span>`)
|
||||
expect(container.innerHTML).toMatch(
|
||||
`<div><span>2</span><span>2</span></div>`
|
||||
)
|
||||
|
||||
const span2 = span1.nextSibling as Element
|
||||
triggerEvent('click', span2)
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toMatch(`<span>2</span><span>3</span>`)
|
||||
expect(container.innerHTML).toMatch(
|
||||
`<div><span>2</span><span>3</span></div>`
|
||||
)
|
||||
})
|
||||
|
||||
test('async component', async () => {
|
||||
|
||||
Reference in New Issue
Block a user