apiAsyncComponent.spec.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import Vue from 'vue'
  2. import { defineAsyncComponent, h, ref, nextTick, defineComponent } from 'v3'
  3. import { Component } from 'types/component'
  4. const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
  5. const loadingComponent = defineComponent({
  6. template: `<div>loading</div>`
  7. })
  8. const resolvedComponent = defineComponent({
  9. template: `<div>resolved</div>`
  10. })
  11. describe('api: defineAsyncComponent', () => {
  12. afterEach(() => {
  13. Vue.config.errorHandler = undefined
  14. })
  15. test('simple usage', async () => {
  16. let resolve: (comp: Component) => void
  17. const Foo = defineAsyncComponent(
  18. () =>
  19. new Promise(r => {
  20. resolve = r as any
  21. })
  22. )
  23. const toggle = ref(true)
  24. const vm = new Vue({
  25. render: () => (toggle.value ? h(Foo) : null)
  26. }).$mount()
  27. expect(vm.$el.nodeType).toBe(8)
  28. resolve!(resolvedComponent)
  29. // first time resolve, wait for macro task since there are multiple
  30. // microtasks / .then() calls
  31. await timeout()
  32. expect(vm.$el.innerHTML).toBe('resolved')
  33. toggle.value = false
  34. await nextTick()
  35. expect(vm.$el.nodeType).toBe(8)
  36. // already resolved component should update on nextTick
  37. toggle.value = true
  38. await nextTick()
  39. expect(vm.$el.innerHTML).toBe('resolved')
  40. })
  41. test('with loading component', async () => {
  42. let resolve: (comp: Component) => void
  43. const Foo = defineAsyncComponent({
  44. loader: () =>
  45. new Promise(r => {
  46. resolve = r as any
  47. }),
  48. loadingComponent,
  49. delay: 1 // defaults to 200
  50. })
  51. const toggle = ref(true)
  52. const vm = new Vue({
  53. render: () => (toggle.value ? h(Foo) : null)
  54. }).$mount()
  55. // due to the delay, initial mount should be empty
  56. expect(vm.$el.nodeType).toBe(8)
  57. // loading show up after delay
  58. await timeout(1)
  59. expect(vm.$el.innerHTML).toBe('loading')
  60. resolve!(resolvedComponent)
  61. await timeout()
  62. expect(vm.$el.innerHTML).toBe('resolved')
  63. toggle.value = false
  64. await nextTick()
  65. expect(vm.$el.nodeType).toBe(8)
  66. // already resolved component should update on nextTick without loading
  67. // state
  68. toggle.value = true
  69. await nextTick()
  70. expect(vm.$el.innerHTML).toBe('resolved')
  71. })
  72. test('error with error component', async () => {
  73. let reject: (e: Error) => void
  74. const Foo = defineAsyncComponent({
  75. loader: () =>
  76. new Promise((_resolve, _reject) => {
  77. reject = _reject
  78. }),
  79. errorComponent: {
  80. template: `<div>errored</div>`
  81. }
  82. })
  83. const toggle = ref(true)
  84. const vm = new Vue({
  85. render: () => (toggle.value ? h(Foo) : null)
  86. }).$mount()
  87. expect(vm.$el.nodeType).toBe(8)
  88. const err = new Error('errored')
  89. reject!(err)
  90. await timeout()
  91. expect('Failed to resolve async').toHaveBeenWarned()
  92. expect(vm.$el.innerHTML).toBe('errored')
  93. toggle.value = false
  94. await nextTick()
  95. expect(vm.$el.nodeType).toBe(8)
  96. })
  97. test('retry (success)', async () => {
  98. let loaderCallCount = 0
  99. let resolve: (comp: Component) => void
  100. let reject: (e: Error) => void
  101. const Foo = defineAsyncComponent({
  102. loader: () => {
  103. loaderCallCount++
  104. return new Promise((_resolve, _reject) => {
  105. resolve = _resolve as any
  106. reject = _reject
  107. })
  108. },
  109. onError(error, retry, fail) {
  110. if (error.message.match(/foo/)) {
  111. retry()
  112. } else {
  113. fail()
  114. }
  115. }
  116. })
  117. const vm = new Vue({
  118. render: () => h(Foo)
  119. }).$mount()
  120. expect(vm.$el.nodeType).toBe(8)
  121. expect(loaderCallCount).toBe(1)
  122. const err = new Error('foo')
  123. reject!(err)
  124. await timeout()
  125. expect(loaderCallCount).toBe(2)
  126. expect(vm.$el.nodeType).toBe(8)
  127. // should render this time
  128. resolve!(resolvedComponent)
  129. await timeout()
  130. expect(vm.$el.innerHTML).toBe('resolved')
  131. })
  132. test('retry (skipped)', async () => {
  133. let loaderCallCount = 0
  134. let reject: (e: Error) => void
  135. const Foo = defineAsyncComponent({
  136. loader: () => {
  137. loaderCallCount++
  138. return new Promise((_resolve, _reject) => {
  139. reject = _reject
  140. })
  141. },
  142. onError(error, retry, fail) {
  143. if (error.message.match(/bar/)) {
  144. retry()
  145. } else {
  146. fail()
  147. }
  148. }
  149. })
  150. const vm = new Vue({
  151. render: () => h(Foo)
  152. }).$mount()
  153. expect(vm.$el.nodeType).toBe(8)
  154. expect(loaderCallCount).toBe(1)
  155. const err = new Error('foo')
  156. reject!(err)
  157. await timeout()
  158. // should fail because retryWhen returns false
  159. expect(loaderCallCount).toBe(1)
  160. expect(vm.$el.nodeType).toBe(8)
  161. expect('Failed to resolve async').toHaveBeenWarned()
  162. })
  163. test('retry (fail w/ max retry attempts)', async () => {
  164. let loaderCallCount = 0
  165. let reject: (e: Error) => void
  166. const Foo = defineAsyncComponent({
  167. loader: () => {
  168. loaderCallCount++
  169. return new Promise((_resolve, _reject) => {
  170. reject = _reject
  171. })
  172. },
  173. onError(error, retry, fail, attempts) {
  174. if (error.message.match(/foo/) && attempts <= 1) {
  175. retry()
  176. } else {
  177. fail()
  178. }
  179. }
  180. })
  181. const vm = new Vue({
  182. render: () => h(Foo)
  183. }).$mount()
  184. expect(vm.$el.nodeType).toBe(8)
  185. expect(loaderCallCount).toBe(1)
  186. // first retry
  187. const err = new Error('foo')
  188. reject!(err)
  189. await timeout()
  190. expect(loaderCallCount).toBe(2)
  191. expect(vm.$el.nodeType).toBe(8)
  192. // 2nd retry, should fail due to reaching maxRetries
  193. reject!(err)
  194. await timeout()
  195. expect(loaderCallCount).toBe(2)
  196. expect(vm.$el.nodeType).toBe(8)
  197. expect('Failed to resolve async').toHaveBeenWarned()
  198. })
  199. })