| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- 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()
- })
- })
|