apiAsyncComponent.spec.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. import {
  2. defineAsyncComponent,
  3. h,
  4. Component,
  5. ref,
  6. nextTick,
  7. Suspense,
  8. KeepAlive
  9. } from '../src'
  10. import { createApp, nodeOps, serializeInner } from '@vue/runtime-test'
  11. import { onActivated } from '../src/components/KeepAlive'
  12. const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
  13. describe('api: defineAsyncComponent', () => {
  14. test('simple usage', async () => {
  15. let resolve: (comp: Component) => void
  16. const Foo = defineAsyncComponent(
  17. () =>
  18. new Promise(r => {
  19. resolve = r as any
  20. })
  21. )
  22. const toggle = ref(true)
  23. const root = nodeOps.createElement('div')
  24. createApp({
  25. render: () => (toggle.value ? h(Foo) : null)
  26. }).mount(root)
  27. expect(serializeInner(root)).toBe('<!---->')
  28. resolve!(() => 'resolved')
  29. // first time resolve, wait for macro task since there are multiple
  30. // microtasks / .then() calls
  31. await timeout()
  32. expect(serializeInner(root)).toBe('resolved')
  33. toggle.value = false
  34. await nextTick()
  35. expect(serializeInner(root)).toBe('<!---->')
  36. // already resolved component should update on nextTick
  37. toggle.value = true
  38. await nextTick()
  39. expect(serializeInner(root)).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: () => 'loading',
  49. delay: 1 // defaults to 200
  50. })
  51. const toggle = ref(true)
  52. const root = nodeOps.createElement('div')
  53. createApp({
  54. render: () => (toggle.value ? h(Foo) : null)
  55. }).mount(root)
  56. // due to the delay, initial mount should be empty
  57. expect(serializeInner(root)).toBe('<!---->')
  58. // loading show up after delay
  59. await timeout(1)
  60. expect(serializeInner(root)).toBe('loading')
  61. resolve!(() => 'resolved')
  62. await timeout()
  63. expect(serializeInner(root)).toBe('resolved')
  64. toggle.value = false
  65. await nextTick()
  66. expect(serializeInner(root)).toBe('<!---->')
  67. // already resolved component should update on nextTick without loading
  68. // state
  69. toggle.value = true
  70. await nextTick()
  71. expect(serializeInner(root)).toBe('resolved')
  72. })
  73. test('with loading component + explicit delay (0)', async () => {
  74. let resolve: (comp: Component) => void
  75. const Foo = defineAsyncComponent({
  76. loader: () =>
  77. new Promise(r => {
  78. resolve = r as any
  79. }),
  80. loadingComponent: () => 'loading',
  81. delay: 0
  82. })
  83. const toggle = ref(true)
  84. const root = nodeOps.createElement('div')
  85. createApp({
  86. render: () => (toggle.value ? h(Foo) : null)
  87. }).mount(root)
  88. // with delay: 0, should show loading immediately
  89. expect(serializeInner(root)).toBe('loading')
  90. resolve!(() => 'resolved')
  91. await timeout()
  92. expect(serializeInner(root)).toBe('resolved')
  93. toggle.value = false
  94. await nextTick()
  95. expect(serializeInner(root)).toBe('<!---->')
  96. // already resolved component should update on nextTick without loading
  97. // state
  98. toggle.value = true
  99. await nextTick()
  100. expect(serializeInner(root)).toBe('resolved')
  101. })
  102. test('error without error component', async () => {
  103. let resolve: (comp: Component) => void
  104. let reject: (e: Error) => void
  105. const Foo = defineAsyncComponent(
  106. () =>
  107. new Promise((_resolve, _reject) => {
  108. resolve = _resolve as any
  109. reject = _reject
  110. })
  111. )
  112. const toggle = ref(true)
  113. const root = nodeOps.createElement('div')
  114. const app = createApp({
  115. render: () => (toggle.value ? h(Foo) : null)
  116. })
  117. const handler = (app.config.errorHandler = jest.fn())
  118. app.mount(root)
  119. expect(serializeInner(root)).toBe('<!---->')
  120. const err = new Error('foo')
  121. reject!(err)
  122. await timeout()
  123. expect(handler).toHaveBeenCalled()
  124. expect(handler.mock.calls[0][0]).toBe(err)
  125. expect(serializeInner(root)).toBe('<!---->')
  126. toggle.value = false
  127. await nextTick()
  128. expect(serializeInner(root)).toBe('<!---->')
  129. // errored out on previous load, toggle and mock success this time
  130. toggle.value = true
  131. await nextTick()
  132. expect(serializeInner(root)).toBe('<!---->')
  133. // should render this time
  134. resolve!(() => 'resolved')
  135. await timeout()
  136. expect(serializeInner(root)).toBe('resolved')
  137. })
  138. test('error with error component', async () => {
  139. let resolve: (comp: Component) => void
  140. let reject: (e: Error) => void
  141. const Foo = defineAsyncComponent({
  142. loader: () =>
  143. new Promise((_resolve, _reject) => {
  144. resolve = _resolve as any
  145. reject = _reject
  146. }),
  147. errorComponent: (props: { error: Error }) => props.error.message
  148. })
  149. const toggle = ref(true)
  150. const root = nodeOps.createElement('div')
  151. const app = createApp({
  152. render: () => (toggle.value ? h(Foo) : null)
  153. })
  154. const handler = (app.config.errorHandler = jest.fn())
  155. app.mount(root)
  156. expect(serializeInner(root)).toBe('<!---->')
  157. const err = new Error('errored out')
  158. reject!(err)
  159. await timeout()
  160. expect(handler).toHaveBeenCalled()
  161. expect(serializeInner(root)).toBe('errored out')
  162. toggle.value = false
  163. await nextTick()
  164. expect(serializeInner(root)).toBe('<!---->')
  165. // errored out on previous load, toggle and mock success this time
  166. toggle.value = true
  167. await nextTick()
  168. expect(serializeInner(root)).toBe('<!---->')
  169. // should render this time
  170. resolve!(() => 'resolved')
  171. await timeout()
  172. expect(serializeInner(root)).toBe('resolved')
  173. })
  174. // #2129
  175. test('error with error component, without global handler', async () => {
  176. let resolve: (comp: Component) => void
  177. let reject: (e: Error) => void
  178. const Foo = defineAsyncComponent({
  179. loader: () =>
  180. new Promise((_resolve, _reject) => {
  181. resolve = _resolve as any
  182. reject = _reject
  183. }),
  184. errorComponent: (props: { error: Error }) => props.error.message
  185. })
  186. const toggle = ref(true)
  187. const root = nodeOps.createElement('div')
  188. const app = createApp({
  189. render: () => (toggle.value ? h(Foo) : null)
  190. })
  191. app.mount(root)
  192. expect(serializeInner(root)).toBe('<!---->')
  193. const err = new Error('errored out')
  194. reject!(err)
  195. await timeout()
  196. expect(serializeInner(root)).toBe('errored out')
  197. expect(
  198. 'Unhandled error during execution of async component loader'
  199. ).toHaveBeenWarned()
  200. toggle.value = false
  201. await nextTick()
  202. expect(serializeInner(root)).toBe('<!---->')
  203. // errored out on previous load, toggle and mock success this time
  204. toggle.value = true
  205. await nextTick()
  206. expect(serializeInner(root)).toBe('<!---->')
  207. // should render this time
  208. resolve!(() => 'resolved')
  209. await timeout()
  210. expect(serializeInner(root)).toBe('resolved')
  211. })
  212. test('error with error + loading components', async () => {
  213. let resolve: (comp: Component) => void
  214. let reject: (e: Error) => void
  215. const Foo = defineAsyncComponent({
  216. loader: () =>
  217. new Promise((_resolve, _reject) => {
  218. resolve = _resolve as any
  219. reject = _reject
  220. }),
  221. errorComponent: (props: { error: Error }) => props.error.message,
  222. loadingComponent: () => 'loading',
  223. delay: 1
  224. })
  225. const toggle = ref(true)
  226. const root = nodeOps.createElement('div')
  227. const app = createApp({
  228. render: () => (toggle.value ? h(Foo) : null)
  229. })
  230. const handler = (app.config.errorHandler = jest.fn())
  231. app.mount(root)
  232. // due to the delay, initial mount should be empty
  233. expect(serializeInner(root)).toBe('<!---->')
  234. // loading show up after delay
  235. await timeout(1)
  236. expect(serializeInner(root)).toBe('loading')
  237. const err = new Error('errored out')
  238. reject!(err)
  239. await timeout()
  240. expect(handler).toHaveBeenCalled()
  241. expect(serializeInner(root)).toBe('errored out')
  242. toggle.value = false
  243. await nextTick()
  244. expect(serializeInner(root)).toBe('<!---->')
  245. // errored out on previous load, toggle and mock success this time
  246. toggle.value = true
  247. await nextTick()
  248. expect(serializeInner(root)).toBe('<!---->')
  249. // loading show up after delay
  250. await timeout(1)
  251. expect(serializeInner(root)).toBe('loading')
  252. // should render this time
  253. resolve!(() => 'resolved')
  254. await timeout()
  255. expect(serializeInner(root)).toBe('resolved')
  256. })
  257. test('timeout without error component', async () => {
  258. let resolve: (comp: Component) => void
  259. const Foo = defineAsyncComponent({
  260. loader: () =>
  261. new Promise(_resolve => {
  262. resolve = _resolve as any
  263. }),
  264. timeout: 1
  265. })
  266. const root = nodeOps.createElement('div')
  267. const app = createApp({
  268. render: () => h(Foo)
  269. })
  270. const handler = (app.config.errorHandler = jest.fn())
  271. app.mount(root)
  272. expect(serializeInner(root)).toBe('<!---->')
  273. await timeout(1)
  274. expect(handler).toHaveBeenCalled()
  275. expect(handler.mock.calls[0][0].message).toMatch(
  276. `Async component timed out after 1ms.`
  277. )
  278. expect(serializeInner(root)).toBe('<!---->')
  279. // if it resolved after timeout, should still work
  280. resolve!(() => 'resolved')
  281. await timeout()
  282. expect(serializeInner(root)).toBe('resolved')
  283. })
  284. test('timeout with error component', async () => {
  285. let resolve: (comp: Component) => void
  286. const Foo = defineAsyncComponent({
  287. loader: () =>
  288. new Promise(_resolve => {
  289. resolve = _resolve as any
  290. }),
  291. timeout: 1,
  292. errorComponent: () => 'timed out'
  293. })
  294. const root = nodeOps.createElement('div')
  295. const app = createApp({
  296. render: () => h(Foo)
  297. })
  298. const handler = (app.config.errorHandler = jest.fn())
  299. app.mount(root)
  300. expect(serializeInner(root)).toBe('<!---->')
  301. await timeout(1)
  302. expect(handler).toHaveBeenCalled()
  303. expect(serializeInner(root)).toBe('timed out')
  304. // if it resolved after timeout, should still work
  305. resolve!(() => 'resolved')
  306. await timeout()
  307. expect(serializeInner(root)).toBe('resolved')
  308. })
  309. test('timeout with error + loading components', async () => {
  310. let resolve: (comp: Component) => void
  311. const Foo = defineAsyncComponent({
  312. loader: () =>
  313. new Promise(_resolve => {
  314. resolve = _resolve as any
  315. }),
  316. delay: 1,
  317. timeout: 16,
  318. errorComponent: () => 'timed out',
  319. loadingComponent: () => 'loading'
  320. })
  321. const root = nodeOps.createElement('div')
  322. const app = createApp({
  323. render: () => h(Foo)
  324. })
  325. const handler = (app.config.errorHandler = jest.fn())
  326. app.mount(root)
  327. expect(serializeInner(root)).toBe('<!---->')
  328. await timeout(1)
  329. expect(serializeInner(root)).toBe('loading')
  330. await timeout(16)
  331. expect(serializeInner(root)).toBe('timed out')
  332. expect(handler).toHaveBeenCalled()
  333. resolve!(() => 'resolved')
  334. await timeout()
  335. expect(serializeInner(root)).toBe('resolved')
  336. })
  337. test('timeout without error component, but with loading component', async () => {
  338. let resolve: (comp: Component) => void
  339. const Foo = defineAsyncComponent({
  340. loader: () =>
  341. new Promise(_resolve => {
  342. resolve = _resolve as any
  343. }),
  344. delay: 1,
  345. timeout: 16,
  346. loadingComponent: () => 'loading'
  347. })
  348. const root = nodeOps.createElement('div')
  349. const app = createApp({
  350. render: () => h(Foo)
  351. })
  352. const handler = (app.config.errorHandler = jest.fn())
  353. app.mount(root)
  354. expect(serializeInner(root)).toBe('<!---->')
  355. await timeout(1)
  356. expect(serializeInner(root)).toBe('loading')
  357. await timeout(16)
  358. expect(handler).toHaveBeenCalled()
  359. expect(handler.mock.calls[0][0].message).toMatch(
  360. `Async component timed out after 16ms.`
  361. )
  362. // should still display loading
  363. expect(serializeInner(root)).toBe('loading')
  364. resolve!(() => 'resolved')
  365. await timeout()
  366. expect(serializeInner(root)).toBe('resolved')
  367. })
  368. test('with suspense', async () => {
  369. let resolve: (comp: Component) => void
  370. const Foo = defineAsyncComponent(
  371. () =>
  372. new Promise(_resolve => {
  373. resolve = _resolve as any
  374. })
  375. )
  376. const root = nodeOps.createElement('div')
  377. const app = createApp({
  378. render: () =>
  379. h(Suspense, null, {
  380. default: () => h('div', [h(Foo), ' & ', h(Foo)]),
  381. fallback: () => 'loading'
  382. })
  383. })
  384. app.mount(root)
  385. expect(serializeInner(root)).toBe('loading')
  386. resolve!(() => 'resolved')
  387. await timeout()
  388. expect(serializeInner(root)).toBe('<div>resolved & resolved</div>')
  389. })
  390. test('suspensible: false', async () => {
  391. let resolve: (comp: Component) => void
  392. const Foo = defineAsyncComponent({
  393. loader: () =>
  394. new Promise(_resolve => {
  395. resolve = _resolve as any
  396. }),
  397. suspensible: false
  398. })
  399. const root = nodeOps.createElement('div')
  400. const app = createApp({
  401. render: () =>
  402. h(Suspense, null, {
  403. default: () => h('div', [h(Foo), ' & ', h(Foo)]),
  404. fallback: () => 'loading'
  405. })
  406. })
  407. app.mount(root)
  408. // should not show suspense fallback
  409. expect(serializeInner(root)).toBe('<div><!----> & <!----></div>')
  410. resolve!(() => 'resolved')
  411. await timeout()
  412. expect(serializeInner(root)).toBe('<div>resolved & resolved</div>')
  413. })
  414. test('suspense with error handling', async () => {
  415. let reject: (e: Error) => void
  416. const Foo = defineAsyncComponent(
  417. () =>
  418. new Promise((_resolve, _reject) => {
  419. reject = _reject
  420. })
  421. )
  422. const root = nodeOps.createElement('div')
  423. const app = createApp({
  424. render: () =>
  425. h(Suspense, null, {
  426. default: () => h('div', [h(Foo), ' & ', h(Foo)]),
  427. fallback: () => 'loading'
  428. })
  429. })
  430. const handler = (app.config.errorHandler = jest.fn())
  431. app.mount(root)
  432. expect(serializeInner(root)).toBe('loading')
  433. reject!(new Error('no'))
  434. await timeout()
  435. expect(handler).toHaveBeenCalled()
  436. expect(serializeInner(root)).toBe('<div><!----> & <!----></div>')
  437. })
  438. test('retry (success)', async () => {
  439. let loaderCallCount = 0
  440. let resolve: (comp: Component) => void
  441. let reject: (e: Error) => void
  442. const Foo = defineAsyncComponent({
  443. loader: () => {
  444. loaderCallCount++
  445. return new Promise((_resolve, _reject) => {
  446. resolve = _resolve as any
  447. reject = _reject
  448. })
  449. },
  450. onError(error, retry, fail) {
  451. if (error.message.match(/foo/)) {
  452. retry()
  453. } else {
  454. fail()
  455. }
  456. }
  457. })
  458. const root = nodeOps.createElement('div')
  459. const app = createApp({
  460. render: () => h(Foo)
  461. })
  462. const handler = (app.config.errorHandler = jest.fn())
  463. app.mount(root)
  464. expect(serializeInner(root)).toBe('<!---->')
  465. expect(loaderCallCount).toBe(1)
  466. const err = new Error('foo')
  467. reject!(err)
  468. await timeout()
  469. expect(handler).not.toHaveBeenCalled()
  470. expect(loaderCallCount).toBe(2)
  471. expect(serializeInner(root)).toBe('<!---->')
  472. // should render this time
  473. resolve!(() => 'resolved')
  474. await timeout()
  475. expect(handler).not.toHaveBeenCalled()
  476. expect(serializeInner(root)).toBe('resolved')
  477. })
  478. test('retry (skipped)', async () => {
  479. let loaderCallCount = 0
  480. let reject: (e: Error) => void
  481. const Foo = defineAsyncComponent({
  482. loader: () => {
  483. loaderCallCount++
  484. return new Promise((_resolve, _reject) => {
  485. reject = _reject
  486. })
  487. },
  488. onError(error, retry, fail) {
  489. if (error.message.match(/bar/)) {
  490. retry()
  491. } else {
  492. fail()
  493. }
  494. }
  495. })
  496. const root = nodeOps.createElement('div')
  497. const app = createApp({
  498. render: () => h(Foo)
  499. })
  500. const handler = (app.config.errorHandler = jest.fn())
  501. app.mount(root)
  502. expect(serializeInner(root)).toBe('<!---->')
  503. expect(loaderCallCount).toBe(1)
  504. const err = new Error('foo')
  505. reject!(err)
  506. await timeout()
  507. // should fail because retryWhen returns false
  508. expect(handler).toHaveBeenCalled()
  509. expect(handler.mock.calls[0][0]).toBe(err)
  510. expect(loaderCallCount).toBe(1)
  511. expect(serializeInner(root)).toBe('<!---->')
  512. })
  513. test('retry (fail w/ max retry attempts)', async () => {
  514. let loaderCallCount = 0
  515. let reject: (e: Error) => void
  516. const Foo = defineAsyncComponent({
  517. loader: () => {
  518. loaderCallCount++
  519. return new Promise((_resolve, _reject) => {
  520. reject = _reject
  521. })
  522. },
  523. onError(error, retry, fail, attempts) {
  524. if (error.message.match(/foo/) && attempts <= 1) {
  525. retry()
  526. } else {
  527. fail()
  528. }
  529. }
  530. })
  531. const root = nodeOps.createElement('div')
  532. const app = createApp({
  533. render: () => h(Foo)
  534. })
  535. const handler = (app.config.errorHandler = jest.fn())
  536. app.mount(root)
  537. expect(serializeInner(root)).toBe('<!---->')
  538. expect(loaderCallCount).toBe(1)
  539. // first retry
  540. const err = new Error('foo')
  541. reject!(err)
  542. await timeout()
  543. expect(handler).not.toHaveBeenCalled()
  544. expect(loaderCallCount).toBe(2)
  545. expect(serializeInner(root)).toBe('<!---->')
  546. // 2nd retry, should fail due to reaching maxRetries
  547. reject!(err)
  548. await timeout()
  549. expect(handler).toHaveBeenCalled()
  550. expect(handler.mock.calls[0][0]).toBe(err)
  551. expect(loaderCallCount).toBe(2)
  552. expect(serializeInner(root)).toBe('<!---->')
  553. })
  554. test('template ref forwarding', async () => {
  555. let resolve: (comp: Component) => void
  556. const Foo = defineAsyncComponent(
  557. () =>
  558. new Promise(r => {
  559. resolve = r as any
  560. })
  561. )
  562. const fooRef = ref<any>(null)
  563. const toggle = ref(true)
  564. const root = nodeOps.createElement('div')
  565. createApp({
  566. render: () => (toggle.value ? h(Foo, { ref: fooRef }) : null)
  567. }).mount(root)
  568. expect(serializeInner(root)).toBe('<!---->')
  569. expect(fooRef.value).toBe(null)
  570. resolve!({
  571. data() {
  572. return {
  573. id: 'foo'
  574. }
  575. },
  576. render: () => 'resolved'
  577. })
  578. // first time resolve, wait for macro task since there are multiple
  579. // microtasks / .then() calls
  580. await timeout()
  581. expect(serializeInner(root)).toBe('resolved')
  582. expect(fooRef.value.id).toBe('foo')
  583. toggle.value = false
  584. await nextTick()
  585. expect(serializeInner(root)).toBe('<!---->')
  586. expect(fooRef.value).toBe(null)
  587. // already resolved component should update on nextTick
  588. toggle.value = true
  589. await nextTick()
  590. expect(serializeInner(root)).toBe('resolved')
  591. expect(fooRef.value.id).toBe('foo')
  592. })
  593. // #3188
  594. test('the forwarded template ref should always exist when doing multi patching', async () => {
  595. let resolve: (comp: Component) => void
  596. const Foo = defineAsyncComponent(
  597. () =>
  598. new Promise(r => {
  599. resolve = r as any
  600. })
  601. )
  602. const fooRef = ref<any>(null)
  603. const toggle = ref(true)
  604. const updater = ref(0)
  605. const root = nodeOps.createElement('div')
  606. createApp({
  607. render: () =>
  608. toggle.value ? [h(Foo, { ref: fooRef }), updater.value] : null
  609. }).mount(root)
  610. expect(serializeInner(root)).toBe('<!---->0')
  611. expect(fooRef.value).toBe(null)
  612. resolve!({
  613. data() {
  614. return {
  615. id: 'foo'
  616. }
  617. },
  618. render: () => 'resolved'
  619. })
  620. await timeout()
  621. expect(serializeInner(root)).toBe('resolved0')
  622. expect(fooRef.value.id).toBe('foo')
  623. updater.value++
  624. await nextTick()
  625. expect(serializeInner(root)).toBe('resolved1')
  626. expect(fooRef.value.id).toBe('foo')
  627. toggle.value = false
  628. await nextTick()
  629. expect(serializeInner(root)).toBe('<!---->')
  630. expect(fooRef.value).toBe(null)
  631. })
  632. test('vnode hooks on async wrapper', async () => {
  633. let resolve: (comp: Component) => void
  634. const Foo = defineAsyncComponent(
  635. () =>
  636. new Promise(r => {
  637. resolve = r as any
  638. })
  639. )
  640. const updater = ref(0)
  641. const vnodeHooks = {
  642. onVnodeBeforeMount: jest.fn(),
  643. onVnodeMounted: jest.fn(),
  644. onVnodeBeforeUpdate: jest.fn(),
  645. onVnodeUpdated: jest.fn(),
  646. onVnodeBeforeUnmount: jest.fn(),
  647. onVnodeUnmounted: jest.fn()
  648. }
  649. const toggle = ref(true)
  650. const root = nodeOps.createElement('div')
  651. createApp({
  652. render: () => (toggle.value ? [h(Foo, vnodeHooks), updater.value] : null)
  653. }).mount(root)
  654. expect(serializeInner(root)).toBe('<!---->0')
  655. resolve!({
  656. data() {
  657. return {
  658. id: 'foo'
  659. }
  660. },
  661. render: () => 'resolved'
  662. })
  663. await timeout()
  664. expect(serializeInner(root)).toBe('resolved0')
  665. expect(vnodeHooks.onVnodeBeforeMount).toHaveBeenCalledTimes(1)
  666. expect(vnodeHooks.onVnodeMounted).toHaveBeenCalledTimes(1)
  667. updater.value++
  668. await nextTick()
  669. expect(serializeInner(root)).toBe('resolved1')
  670. expect(vnodeHooks.onVnodeBeforeUpdate).toHaveBeenCalledTimes(1)
  671. expect(vnodeHooks.onVnodeUpdated).toHaveBeenCalledTimes(1)
  672. toggle.value = false
  673. await nextTick()
  674. expect(serializeInner(root)).toBe('<!---->')
  675. expect(vnodeHooks.onVnodeBeforeUnmount).toHaveBeenCalledTimes(1)
  676. expect(vnodeHooks.onVnodeUnmounted).toHaveBeenCalledTimes(1)
  677. })
  678. test('with keepalive', async () => {
  679. const spy = jest.fn()
  680. let resolve: (comp: Component) => void
  681. const Foo = defineAsyncComponent(
  682. () =>
  683. new Promise(r => {
  684. resolve = r as any
  685. })
  686. )
  687. const root = nodeOps.createElement('div')
  688. const app = createApp({
  689. render: () => h(KeepAlive, [h(Foo)])
  690. })
  691. app.mount(root)
  692. await nextTick()
  693. resolve!({
  694. setup() {
  695. onActivated(() => {
  696. spy()
  697. })
  698. return () => 'resolved'
  699. }
  700. })
  701. await timeout()
  702. expect(serializeInner(root)).toBe('resolved')
  703. expect(spy).toBeCalledTimes(1)
  704. })
  705. })