component-async.spec.js 12 KB

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