import Vue from 'vue' describe('Component async', () => { const oldSetTimeout = setTimeout const oldClearTimeout = clearTimeout // will contain pending timeouts set during the test iteration // will contain the id of the timeout as the key, and the millisecond timeout as the value // this helps to identify the timeout that is still pending let timeoutsPending = {} beforeAll(function () { // @ts-expect-error global.setTimeout = function (func, delay) { if (delay >= 1000) { // skip vitest internal timeouts return } const id = oldSetTimeout(function () { delete timeoutsPending[id] func() }, delay) as any timeoutsPending[id] = delay return id } global.clearTimeout = function (id) { oldClearTimeout(id) delete timeoutsPending[id] } }) afterAll(function () { global.setTimeout = oldSetTimeout global.clearTimeout = oldClearTimeout }) beforeEach(() => { // reset the timeouts for this iteration timeoutsPending = {} }) afterEach(() => { // after the test is complete no timeouts that have been set up during the test should still be active // compare stringified JSON for better error message containing ID and millisecond timeout expect(JSON.stringify(timeoutsPending)).toEqual(JSON.stringify({})) }) it('normal', done => { const vm = new Vue({ template: '
', components: { test: resolve => { setTimeout(() => { resolve({ template: '
hi
' }) // wait for parent update Vue.nextTick(next) }, 0) } } }).$mount() expect(vm.$el.innerHTML).toBe('') expect(vm.$children.length).toBe(0) function next() { expect(vm.$el.innerHTML).toBe('
hi
') expect(vm.$children.length).toBe(1) done() } }) it('resolve ES module default', done => { const vm = new Vue({ template: '
', components: { test: resolve => { setTimeout(() => { resolve({ __esModule: true, default: { template: '
hi
' } }) // wait for parent update Vue.nextTick(next) }, 0) } } }).$mount() expect(vm.$el.innerHTML).toBe('') expect(vm.$children.length).toBe(0) function next() { expect(vm.$el.innerHTML).toBe('
hi
') expect(vm.$children.length).toBe(1) done() } }) it('as root', done => { const vm = new Vue({ template: '', components: { test: resolve => { setTimeout(() => { resolve({ template: '
hi
' }) // wait for parent update Vue.nextTick(next) }, 0) } } }).$mount() expect(vm.$el.nodeType).toBe(8) expect(vm.$children.length).toBe(0) function next() { expect(vm.$el.nodeType).toBe(1) expect(vm.$el.outerHTML).toBe('
hi
') expect(vm.$children.length).toBe(1) done() } }) it('dynamic', done => { const vm = new Vue({ template: '', data: { view: 'view-a' }, components: { 'view-a': resolve => { setTimeout(() => { resolve({ template: '
A
' }) Vue.nextTick(step1) }, 0) }, 'view-b': resolve => { setTimeout(() => { resolve({ template: '

B

' }) Vue.nextTick(step2) }, 0) } } }).$mount() let aCalled = false function step1() { // ensure A is resolved only once expect(aCalled).toBe(false) aCalled = true expect(vm.$el.tagName).toBe('DIV') expect(vm.$el.textContent).toBe('A') vm.view = 'view-b' } function step2() { expect(vm.$el.tagName).toBe('P') expect(vm.$el.textContent).toBe('B') vm.view = 'view-a' waitForUpdate(() => { expect(vm.$el.tagName).toBe('DIV') expect(vm.$el.textContent).toBe('A') }).then(done) } }) it('warn reject', () => { new Vue({ template: '', components: { test: (resolve, reject) => { reject('nooooo') } } }).$mount() expect('Reason: nooooo').toHaveBeenWarned() }) it('with v-for', done => { const vm = new Vue({ template: '
', data: { list: [1, 2, 3] }, components: { test: resolve => { setTimeout(() => { resolve({ props: ['n'], template: '
{{n}}
' }) Vue.nextTick(next) }, 0) } } }).$mount() function next() { expect(vm.$el.innerHTML).toBe('
1
2
3
') done() } }) it('returning Promise', done => { const vm = new Vue({ template: '
', components: { test: () => { return new Promise(resolve => { setTimeout(() => { resolve({ template: '
hi
' }) // wait for promise resolve and then parent update Promise.resolve().then(() => { Vue.nextTick(next) }) }, 0) }) } } }).$mount() expect(vm.$el.innerHTML).toBe('') expect(vm.$children.length).toBe(0) function next() { expect(vm.$el.innerHTML).toBe('
hi
') expect(vm.$children.length).toBe(1) done() } }) describe('loading/error/timeout', () => { it('with loading component', done => { const vm = new Vue({ template: `
`, components: { test: () => ({ component: new Promise(resolve => { setTimeout(() => { resolve({ template: '
hi
' }) // wait for promise resolve and then parent update Promise.resolve().then(() => { Vue.nextTick(next) }) }, 50) }), loading: { template: `
loading
` }, delay: 1 }) } }).$mount() expect(vm.$el.innerHTML).toBe('') let loadingAsserted = false setTimeout(() => { Vue.nextTick(() => { loadingAsserted = true expect(vm.$el.textContent).toBe('loading') }) }, 1) function next() { expect(loadingAsserted).toBe(true) expect(vm.$el.textContent).toBe('hi') done() } }) it('with loading component (0 delay)', done => { const vm = new Vue({ template: `
`, components: { test: () => ({ component: new Promise(resolve => { setTimeout(() => { resolve({ template: '
hi
' }) // wait for promise resolve and then parent update Promise.resolve().then(() => { Vue.nextTick(next) }) }, 50) }), loading: { template: `
loading
` }, delay: 0 }) } }).$mount() expect(vm.$el.textContent).toBe('loading') function next() { expect(vm.$el.textContent).toBe('hi') done() } }) it('with error component', done => { const vm = new Vue({ template: `
`, components: { test: () => ({ component: new Promise((resolve, reject) => { setTimeout(() => { reject() // wait for promise resolve and then parent update Promise.resolve().then(() => { Vue.nextTick(next) }) }, 50) }), loading: { template: `
loading
` }, error: { template: `
error
` }, delay: 0 }) } }).$mount() expect(vm.$el.textContent).toBe('loading') function next() { expect(`Failed to resolve async component`).toHaveBeenWarned() expect(vm.$el.textContent).toBe('error') done() } }) it('with error component + timeout', done => { const vm = new Vue({ template: `
`, components: { test: () => ({ component: new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: '
hi
' }) // wait for promise resolve and then parent update Promise.resolve().then(() => { Vue.nextTick(next) }) }, 50) }), loading: { template: `
loading
` }, error: { template: `
error
` }, delay: 0, timeout: 1 }) } }).$mount() expect(vm.$el.textContent).toBe('loading') setTimeout(() => { Vue.nextTick(() => { expect(`Failed to resolve async component`).toHaveBeenWarned() expect(vm.$el.textContent).toBe('error') }) }, 1) function next() { expect(vm.$el.textContent).toBe('error') // late resolve ignored done() } }) it('should not trigger timeout if resolved', done => { const vm = new Vue({ template: `
`, components: { test: () => ({ component: new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: '
hi
' }) }, 10) }), error: { template: `
error
` }, timeout: 20 }) } }).$mount() setTimeout(() => { expect(vm.$el.textContent).toBe('hi') expect(`Failed to resolve async component`).not.toHaveBeenWarned() done() }, 50) }) it('should not have running timeout/loading if resolved', done => { const vm = new Vue({ template: `
`, components: { test: () => ({ component: new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: '
hi
' }) Promise.resolve().then(() => { Vue.nextTick(next) }) }, 10) }), loading: { template: `
loading
` }, delay: 30, error: { template: `
error
` }, timeout: 40 }) } }).$mount() function next() { expect(vm.$el.textContent).toBe('hi') // the afterEach() will ensure that the timeouts for delay and timeout have been cleared done() } }) // #7107 it(`should work when resolving sync in sibling component's mounted hook`, done => { let resolveTwo const vm = new Vue({ template: `
`, components: { one: { template: `
one
`, mounted() { resolveTwo() } }, two: resolve => { resolveTwo = () => { resolve({ template: `
two
` }) } } } }).$mount() expect(vm.$el.textContent).toBe('one ') waitForUpdate(() => { expect(vm.$el.textContent).toBe('one two') }).then(done) }) }) })