hydrationStrategies.spec.ts 6.1 KB

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