component-async.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. import Vue from 'vue'
  2. describe('Component async', () => {
  3. const oldSetTimeout = setTimeout
  4. const oldClearTimeout = clearTimeout
  5. // will contain pending timeouts set during the test iteration
  6. // will contain the id of the timeout as the key, and the millisecond timeout as the value
  7. // this helps to identify the timeout that is still pending
  8. let timeoutsPending = {}
  9. beforeAll(function () {
  10. // @ts-expect-error
  11. global.setTimeout = function (func, delay) {
  12. if (delay >= 1000) {
  13. // skip vitest internal timeouts
  14. return
  15. }
  16. const id = oldSetTimeout(function () {
  17. delete timeoutsPending[id]
  18. func()
  19. }, delay) as any
  20. timeoutsPending[id] = delay
  21. return id
  22. }
  23. global.clearTimeout = function (id) {
  24. oldClearTimeout(id)
  25. delete timeoutsPending[id]
  26. }
  27. })
  28. afterAll(function () {
  29. global.setTimeout = oldSetTimeout
  30. global.clearTimeout = oldClearTimeout
  31. })
  32. beforeEach(() => {
  33. // reset the timeouts for this iteration
  34. timeoutsPending = {}
  35. })
  36. afterEach(() => {
  37. // after the test is complete no timeouts that have been set up during the test should still be active
  38. // compare stringified JSON for better error message containing ID and millisecond timeout
  39. expect(JSON.stringify(timeoutsPending)).toEqual(JSON.stringify({}))
  40. })
  41. it('normal', done => {
  42. const vm = new Vue({
  43. template: '<div><test></test></div>',
  44. components: {
  45. test: resolve => {
  46. setTimeout(() => {
  47. resolve({
  48. template: '<div>hi</div>'
  49. })
  50. // wait for parent update
  51. Vue.nextTick(next)
  52. }, 0)
  53. }
  54. }
  55. }).$mount()
  56. expect(vm.$el.innerHTML).toBe('<!---->')
  57. expect(vm.$children.length).toBe(0)
  58. function next() {
  59. expect(vm.$el.innerHTML).toBe('<div>hi</div>')
  60. expect(vm.$children.length).toBe(1)
  61. done()
  62. }
  63. })
  64. it('resolve ES module default', done => {
  65. const vm = new Vue({
  66. template: '<div><test></test></div>',
  67. components: {
  68. test: resolve => {
  69. setTimeout(() => {
  70. resolve({
  71. __esModule: true,
  72. default: {
  73. template: '<div>hi</div>'
  74. }
  75. })
  76. // wait for parent update
  77. Vue.nextTick(next)
  78. }, 0)
  79. }
  80. }
  81. }).$mount()
  82. expect(vm.$el.innerHTML).toBe('<!---->')
  83. expect(vm.$children.length).toBe(0)
  84. function next() {
  85. expect(vm.$el.innerHTML).toBe('<div>hi</div>')
  86. expect(vm.$children.length).toBe(1)
  87. done()
  88. }
  89. })
  90. it('as root', done => {
  91. const vm = new Vue({
  92. template: '<test></test>',
  93. components: {
  94. test: resolve => {
  95. setTimeout(() => {
  96. resolve({
  97. template: '<div>hi</div>'
  98. })
  99. // wait for parent update
  100. Vue.nextTick(next)
  101. }, 0)
  102. }
  103. }
  104. }).$mount()
  105. expect(vm.$el.nodeType).toBe(8)
  106. expect(vm.$children.length).toBe(0)
  107. function next() {
  108. expect(vm.$el.nodeType).toBe(1)
  109. expect(vm.$el.outerHTML).toBe('<div>hi</div>')
  110. expect(vm.$children.length).toBe(1)
  111. done()
  112. }
  113. })
  114. it('dynamic', done => {
  115. const vm = new Vue({
  116. template: '<component :is="view"></component>',
  117. data: {
  118. view: 'view-a'
  119. },
  120. components: {
  121. 'view-a': resolve => {
  122. setTimeout(() => {
  123. resolve({
  124. template: '<div>A</div>'
  125. })
  126. Vue.nextTick(step1)
  127. }, 0)
  128. },
  129. 'view-b': resolve => {
  130. setTimeout(() => {
  131. resolve({
  132. template: '<p>B</p>'
  133. })
  134. Vue.nextTick(step2)
  135. }, 0)
  136. }
  137. }
  138. }).$mount()
  139. let aCalled = false
  140. function step1() {
  141. // ensure A is resolved only once
  142. expect(aCalled).toBe(false)
  143. aCalled = true
  144. expect(vm.$el.tagName).toBe('DIV')
  145. expect(vm.$el.textContent).toBe('A')
  146. vm.view = 'view-b'
  147. }
  148. function step2() {
  149. expect(vm.$el.tagName).toBe('P')
  150. expect(vm.$el.textContent).toBe('B')
  151. vm.view = 'view-a'
  152. waitForUpdate(() => {
  153. expect(vm.$el.tagName).toBe('DIV')
  154. expect(vm.$el.textContent).toBe('A')
  155. }).then(done)
  156. }
  157. })
  158. it('warn reject', () => {
  159. new Vue({
  160. template: '<test></test>',
  161. components: {
  162. test: (resolve, reject) => {
  163. reject('nooooo')
  164. }
  165. }
  166. }).$mount()
  167. expect('Reason: nooooo').toHaveBeenWarned()
  168. })
  169. it('with v-for', done => {
  170. const vm = new Vue({
  171. template: '<div><test v-for="n in list" :key="n" :n="n"></test></div>',
  172. data: {
  173. list: [1, 2, 3]
  174. },
  175. components: {
  176. test: resolve => {
  177. setTimeout(() => {
  178. resolve({
  179. props: ['n'],
  180. template: '<div>{{n}}</div>'
  181. })
  182. Vue.nextTick(next)
  183. }, 0)
  184. }
  185. }
  186. }).$mount()
  187. function next() {
  188. expect(vm.$el.innerHTML).toBe('<div>1</div><div>2</div><div>3</div>')
  189. done()
  190. }
  191. })
  192. it('returning Promise', done => {
  193. const vm = new Vue({
  194. template: '<div><test></test></div>',
  195. components: {
  196. test: () => {
  197. return new Promise(resolve => {
  198. setTimeout(() => {
  199. resolve({
  200. template: '<div>hi</div>'
  201. })
  202. // wait for promise resolve and then parent update
  203. Promise.resolve().then(() => {
  204. Vue.nextTick(next)
  205. })
  206. }, 0)
  207. })
  208. }
  209. }
  210. }).$mount()
  211. expect(vm.$el.innerHTML).toBe('<!---->')
  212. expect(vm.$children.length).toBe(0)
  213. function next() {
  214. expect(vm.$el.innerHTML).toBe('<div>hi</div>')
  215. expect(vm.$children.length).toBe(1)
  216. done()
  217. }
  218. })
  219. describe('loading/error/timeout', () => {
  220. it('with loading component', done => {
  221. const vm = new Vue({
  222. template: `<div><test/></div>`,
  223. components: {
  224. test: () => ({
  225. component: new Promise(resolve => {
  226. setTimeout(() => {
  227. resolve({ template: '<div>hi</div>' })
  228. // wait for promise resolve and then parent update
  229. Promise.resolve().then(() => {
  230. Vue.nextTick(next)
  231. })
  232. }, 50)
  233. }),
  234. loading: { template: `<div>loading</div>` },
  235. delay: 1
  236. })
  237. }
  238. }).$mount()
  239. expect(vm.$el.innerHTML).toBe('<!---->')
  240. let loadingAsserted = false
  241. setTimeout(() => {
  242. Vue.nextTick(() => {
  243. loadingAsserted = true
  244. expect(vm.$el.textContent).toBe('loading')
  245. })
  246. }, 1)
  247. function next() {
  248. expect(loadingAsserted).toBe(true)
  249. expect(vm.$el.textContent).toBe('hi')
  250. done()
  251. }
  252. })
  253. it('with loading component (0 delay)', done => {
  254. const vm = new Vue({
  255. template: `<div><test/></div>`,
  256. components: {
  257. test: () => ({
  258. component: new Promise(resolve => {
  259. setTimeout(() => {
  260. resolve({ template: '<div>hi</div>' })
  261. // wait for promise resolve and then parent update
  262. Promise.resolve().then(() => {
  263. Vue.nextTick(next)
  264. })
  265. }, 50)
  266. }),
  267. loading: { template: `<div>loading</div>` },
  268. delay: 0
  269. })
  270. }
  271. }).$mount()
  272. expect(vm.$el.textContent).toBe('loading')
  273. function next() {
  274. expect(vm.$el.textContent).toBe('hi')
  275. done()
  276. }
  277. })
  278. it('with error component', done => {
  279. const vm = new Vue({
  280. template: `<div><test/></div>`,
  281. components: {
  282. test: () => ({
  283. component: new Promise((resolve, reject) => {
  284. setTimeout(() => {
  285. reject()
  286. // wait for promise resolve and then parent update
  287. Promise.resolve().then(() => {
  288. Vue.nextTick(next)
  289. })
  290. }, 50)
  291. }),
  292. loading: { template: `<div>loading</div>` },
  293. error: { template: `<div>error</div>` },
  294. delay: 0
  295. })
  296. }
  297. }).$mount()
  298. expect(vm.$el.textContent).toBe('loading')
  299. function next() {
  300. expect(`Failed to resolve async component`).toHaveBeenWarned()
  301. expect(vm.$el.textContent).toBe('error')
  302. done()
  303. }
  304. })
  305. it('with error component + timeout', done => {
  306. const vm = new Vue({
  307. template: `<div><test/></div>`,
  308. components: {
  309. test: () => ({
  310. component: new Promise((resolve, reject) => {
  311. setTimeout(() => {
  312. resolve({ template: '<div>hi</div>' })
  313. // wait for promise resolve and then parent update
  314. Promise.resolve().then(() => {
  315. Vue.nextTick(next)
  316. })
  317. }, 50)
  318. }),
  319. loading: { template: `<div>loading</div>` },
  320. error: { template: `<div>error</div>` },
  321. delay: 0,
  322. timeout: 1
  323. })
  324. }
  325. }).$mount()
  326. expect(vm.$el.textContent).toBe('loading')
  327. setTimeout(() => {
  328. Vue.nextTick(() => {
  329. expect(`Failed to resolve async component`).toHaveBeenWarned()
  330. expect(vm.$el.textContent).toBe('error')
  331. })
  332. }, 1)
  333. function next() {
  334. expect(vm.$el.textContent).toBe('error') // late resolve ignored
  335. done()
  336. }
  337. })
  338. it('should not trigger timeout if resolved', done => {
  339. const vm = new Vue({
  340. template: `<div><test/></div>`,
  341. components: {
  342. test: () => ({
  343. component: new Promise((resolve, reject) => {
  344. setTimeout(() => {
  345. resolve({ template: '<div>hi</div>' })
  346. }, 10)
  347. }),
  348. error: { template: `<div>error</div>` },
  349. timeout: 20
  350. })
  351. }
  352. }).$mount()
  353. setTimeout(() => {
  354. expect(vm.$el.textContent).toBe('hi')
  355. expect(`Failed to resolve async component`).not.toHaveBeenWarned()
  356. done()
  357. }, 50)
  358. })
  359. it('should not have running timeout/loading if resolved', done => {
  360. const vm = new Vue({
  361. template: `<div><test/></div>`,
  362. components: {
  363. test: () => ({
  364. component: new Promise((resolve, reject) => {
  365. setTimeout(() => {
  366. resolve({ template: '<div>hi</div>' })
  367. Promise.resolve().then(() => {
  368. Vue.nextTick(next)
  369. })
  370. }, 10)
  371. }),
  372. loading: { template: `<div>loading</div>` },
  373. delay: 30,
  374. error: { template: `<div>error</div>` },
  375. timeout: 40
  376. })
  377. }
  378. }).$mount()
  379. function next() {
  380. expect(vm.$el.textContent).toBe('hi')
  381. // the afterEach() will ensure that the timeouts for delay and timeout have been cleared
  382. done()
  383. }
  384. })
  385. // #7107
  386. it(`should work when resolving sync in sibling component's mounted hook`, done => {
  387. let resolveTwo
  388. const vm = new Vue({
  389. template: `<div><one/> <two/></div>`,
  390. components: {
  391. one: {
  392. template: `<div>one</div>`,
  393. mounted() {
  394. resolveTwo()
  395. }
  396. },
  397. two: resolve => {
  398. resolveTwo = () => {
  399. resolve({
  400. template: `<div>two</div>`
  401. })
  402. }
  403. }
  404. }
  405. }).$mount()
  406. expect(vm.$el.textContent).toBe('one ')
  407. waitForUpdate(() => {
  408. expect(vm.$el.textContent).toBe('one two')
  409. }).then(done)
  410. })
  411. })
  412. })