hydrationStrategies.spec.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import path from 'node:path'
  2. import { setupPuppeteer } from './e2eUtils'
  3. import type { Ref } from '../../src/runtime'
  4. declare const window: Window & {
  5. isHydrated: boolean
  6. isRootMounted: boolean
  7. teardownCalled?: boolean
  8. show: Ref<boolean>
  9. resolveLoader: () => void
  10. }
  11. describe('async component hydration strategies', () => {
  12. const { page, click, text, count } = setupPuppeteer(['--window-size=800,600'])
  13. async function goToCase(name: string, query = '') {
  14. const file = `file://${path.resolve(__dirname, `./hydration-strat-${name}.html${query}`)}`
  15. await page().goto(file)
  16. }
  17. async function assertHydrationSuccess(n = '1') {
  18. await click('button')
  19. expect(await text('button')).toBe(n)
  20. }
  21. test('idle', async () => {
  22. const messages: string[] = []
  23. page().on('console', e => messages.push(e.text()))
  24. await goToCase('idle')
  25. // not hydrated yet
  26. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  27. // trigger loader
  28. await page().evaluate(() => window.resolveLoader())
  29. // wait for hydration
  30. await page().waitForFunction(() => window.isHydrated)
  31. // assert message order: hydration should happen after already queued main thread work
  32. expect(messages.slice(1)).toMatchObject(['resolve', 'busy', 'hydrated'])
  33. await assertHydrationSuccess()
  34. })
  35. test('visible', async () => {
  36. await goToCase('visible')
  37. await page().waitForFunction(() => window.isRootMounted)
  38. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  39. // scroll down
  40. await page().evaluate(() => window.scrollTo({ top: 1000 }))
  41. await page().waitForFunction(() => window.isHydrated)
  42. await assertHydrationSuccess()
  43. })
  44. test('visible (with rootMargin)', async () => {
  45. await goToCase('visible', '?rootMargin=1000')
  46. await page().waitForFunction(() => window.isRootMounted)
  47. // should hydrate without needing to scroll
  48. await page().waitForFunction(() => window.isHydrated)
  49. await assertHydrationSuccess()
  50. })
  51. test('visible (fragment)', async () => {
  52. await goToCase('visible', '?fragment')
  53. await page().waitForFunction(() => window.isRootMounted)
  54. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  55. expect(await count('span')).toBe(2)
  56. // scroll down
  57. await page().evaluate(() => window.scrollTo({ top: 1000 }))
  58. await page().waitForFunction(() => window.isHydrated)
  59. await assertHydrationSuccess()
  60. })
  61. test('visible (root v-if) should not throw error', async () => {
  62. const spy = vi.fn()
  63. const currentPage = page()
  64. currentPage.on('pageerror', spy)
  65. await goToCase('visible', '?v-if')
  66. await page().waitForFunction(() => window.isRootMounted)
  67. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  68. expect(spy).toBeCalledTimes(0)
  69. currentPage.off('pageerror', spy)
  70. })
  71. test('media query', async () => {
  72. await goToCase('media')
  73. await page().waitForFunction(() => window.isRootMounted)
  74. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  75. // resize
  76. await page().setViewport({ width: 400, height: 600 })
  77. await page().waitForFunction(() => window.isHydrated)
  78. await assertHydrationSuccess()
  79. })
  80. // #13255
  81. test('media query (patched before hydration)', async () => {
  82. const spy = vi.fn()
  83. const currentPage = page()
  84. currentPage.on('pageerror', spy)
  85. const warn: any[] = []
  86. currentPage.on('console', e => warn.push(e.text()))
  87. await goToCase('media')
  88. await page().waitForFunction(() => window.isRootMounted)
  89. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  90. // patch
  91. await page().evaluate(() => (window.show.value = false))
  92. await click('button')
  93. expect(await text('button')).toBe('1')
  94. // resize
  95. await page().setViewport({ width: 400, height: 600 })
  96. await page().waitForFunction(() => window.isHydrated)
  97. await assertHydrationSuccess('2')
  98. expect(spy).toBeCalledTimes(0)
  99. currentPage.off('pageerror', spy)
  100. expect(
  101. warn.some(w => w.includes('Skipping lazy hydration for component')),
  102. ).toBe(true)
  103. })
  104. test('interaction', async () => {
  105. await goToCase('interaction')
  106. await page().waitForFunction(() => window.isRootMounted)
  107. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  108. await click('button')
  109. await page().waitForFunction(() => window.isHydrated)
  110. // should replay event
  111. expect(await text('button')).toBe('1')
  112. await assertHydrationSuccess('2')
  113. })
  114. test('interaction (fragment)', async () => {
  115. await goToCase('interaction', '?fragment')
  116. await page().waitForFunction(() => window.isRootMounted)
  117. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  118. await click('button')
  119. await page().waitForFunction(() => window.isHydrated)
  120. // should replay event
  121. expect(await text('button')).toBe('1')
  122. await assertHydrationSuccess('2')
  123. })
  124. test('custom', async () => {
  125. await goToCase('custom')
  126. await page().waitForFunction(() => window.isRootMounted)
  127. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  128. await click('#custom-trigger')
  129. await page().waitForFunction(() => window.isHydrated)
  130. await assertHydrationSuccess()
  131. })
  132. test('custom teardown', async () => {
  133. await goToCase('custom')
  134. await page().waitForFunction(() => window.isRootMounted)
  135. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  136. await page().evaluate(() => (window.show.value = false))
  137. expect(await text('#app')).toBe('off')
  138. expect(await page().evaluate(() => window.isHydrated)).toBe(false)
  139. expect(await page().evaluate(() => window.teardownCalled)).toBe(true)
  140. })
  141. })