vue2/test/unit/features/v3/apiAsyncComponent.spec.ts

242 lines
5.7 KiB
TypeScript

import Vue from 'vue'
import { defineAsyncComponent, h, ref, nextTick, defineComponent } from 'v3'
import { Component } from 'types/component'
const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
const loadingComponent = defineComponent({
template: `<div>loading</div>`
})
const resolvedComponent = defineComponent({
template: `<div>resolved</div>`
})
describe('api: defineAsyncComponent', () => {
afterEach(() => {
Vue.config.errorHandler = undefined
})
test('simple usage', async () => {
let resolve: (comp: Component) => void
const Foo = defineAsyncComponent(
() =>
new Promise(r => {
resolve = r as any
})
)
const toggle = ref(true)
const vm = new Vue({
render: () => (toggle.value ? h(Foo) : null)
}).$mount()
expect(vm.$el.nodeType).toBe(8)
resolve!(resolvedComponent)
// first time resolve, wait for macro task since there are multiple
// microtasks / .then() calls
await timeout()
expect(vm.$el.innerHTML).toBe('resolved')
toggle.value = false
await nextTick()
expect(vm.$el.nodeType).toBe(8)
// already resolved component should update on nextTick
toggle.value = true
await nextTick()
expect(vm.$el.innerHTML).toBe('resolved')
})
test('with loading component', async () => {
let resolve: (comp: Component) => void
const Foo = defineAsyncComponent({
loader: () =>
new Promise(r => {
resolve = r as any
}),
loadingComponent,
delay: 1 // defaults to 200
})
const toggle = ref(true)
const vm = new Vue({
render: () => (toggle.value ? h(Foo) : null)
}).$mount()
// due to the delay, initial mount should be empty
expect(vm.$el.nodeType).toBe(8)
// loading show up after delay
await timeout(1)
expect(vm.$el.innerHTML).toBe('loading')
resolve!(resolvedComponent)
await timeout()
expect(vm.$el.innerHTML).toBe('resolved')
toggle.value = false
await nextTick()
expect(vm.$el.nodeType).toBe(8)
// already resolved component should update on nextTick without loading
// state
toggle.value = true
await nextTick()
expect(vm.$el.innerHTML).toBe('resolved')
})
test('error with error component', async () => {
let reject: (e: Error) => void
const Foo = defineAsyncComponent({
loader: () =>
new Promise((_resolve, _reject) => {
reject = _reject
}),
errorComponent: {
template: `<div>errored</div>`
}
})
const toggle = ref(true)
const vm = new Vue({
render: () => (toggle.value ? h(Foo) : null)
}).$mount()
expect(vm.$el.nodeType).toBe(8)
const err = new Error('errored')
reject!(err)
await timeout()
expect('Failed to resolve async').toHaveBeenWarned()
expect(vm.$el.innerHTML).toBe('errored')
toggle.value = false
await nextTick()
expect(vm.$el.nodeType).toBe(8)
})
test('retry (success)', async () => {
let loaderCallCount = 0
let resolve: (comp: Component) => void
let reject: (e: Error) => void
const Foo = defineAsyncComponent({
loader: () => {
loaderCallCount++
return new Promise((_resolve, _reject) => {
resolve = _resolve as any
reject = _reject
})
},
onError(error, retry, fail) {
if (error.message.match(/foo/)) {
retry()
} else {
fail()
}
}
})
const vm = new Vue({
render: () => h(Foo)
}).$mount()
expect(vm.$el.nodeType).toBe(8)
expect(loaderCallCount).toBe(1)
const err = new Error('foo')
reject!(err)
await timeout()
expect(loaderCallCount).toBe(2)
expect(vm.$el.nodeType).toBe(8)
// should render this time
resolve!(resolvedComponent)
await timeout()
expect(vm.$el.innerHTML).toBe('resolved')
})
test('retry (skipped)', async () => {
let loaderCallCount = 0
let reject: (e: Error) => void
const Foo = defineAsyncComponent({
loader: () => {
loaderCallCount++
return new Promise((_resolve, _reject) => {
reject = _reject
})
},
onError(error, retry, fail) {
if (error.message.match(/bar/)) {
retry()
} else {
fail()
}
}
})
const vm = new Vue({
render: () => h(Foo)
}).$mount()
expect(vm.$el.nodeType).toBe(8)
expect(loaderCallCount).toBe(1)
const err = new Error('foo')
reject!(err)
await timeout()
// should fail because retryWhen returns false
expect(loaderCallCount).toBe(1)
expect(vm.$el.nodeType).toBe(8)
expect('Failed to resolve async').toHaveBeenWarned()
})
test('retry (fail w/ max retry attempts)', async () => {
let loaderCallCount = 0
let reject: (e: Error) => void
const Foo = defineAsyncComponent({
loader: () => {
loaderCallCount++
return new Promise((_resolve, _reject) => {
reject = _reject
})
},
onError(error, retry, fail, attempts) {
if (error.message.match(/foo/) && attempts <= 1) {
retry()
} else {
fail()
}
}
})
const vm = new Vue({
render: () => h(Foo)
}).$mount()
expect(vm.$el.nodeType).toBe(8)
expect(loaderCallCount).toBe(1)
// first retry
const err = new Error('foo')
reject!(err)
await timeout()
expect(loaderCallCount).toBe(2)
expect(vm.$el.nodeType).toBe(8)
// 2nd retry, should fail due to reaching maxRetries
reject!(err)
await timeout()
expect(loaderCallCount).toBe(2)
expect(vm.$el.nodeType).toBe(8)
expect('Failed to resolve async').toHaveBeenWarned()
})
})