hydration.spec.ts 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793
  1. import { createVaporSSRApp, delegateEvents } from '../src'
  2. import { nextTick, ref } from '@vue/runtime-dom'
  3. import { compileScript, parse } from '@vue/compiler-sfc'
  4. import * as runtimeVapor from '../src'
  5. import * as runtimeDom from '@vue/runtime-dom'
  6. import * as VueServerRenderer from '@vue/server-renderer'
  7. const Vue = { ...runtimeDom, ...runtimeVapor }
  8. function compile(
  9. sfc: string,
  10. data: runtimeDom.Ref<any>,
  11. components: Record<string, any> = {},
  12. ssr = false,
  13. ) {
  14. if (!sfc.includes(`<script`)) {
  15. sfc =
  16. `<script vapor>const data = _data; const components = _components;</script>` +
  17. sfc
  18. }
  19. const descriptor = parse(sfc).descriptor
  20. const script = compileScript(descriptor, {
  21. id: 'x',
  22. isProd: true,
  23. inlineTemplate: true,
  24. genDefaultAs: '__sfc__',
  25. vapor: true,
  26. templateOptions: {
  27. ssr,
  28. },
  29. })
  30. const code =
  31. script.content
  32. .replace(/\bimport {/g, 'const {')
  33. .replace(/ as _/g, ': _')
  34. .replace(/} from ['"]vue['"]/g, `} = Vue`)
  35. .replace(/} from "vue\/server-renderer"/g, '} = VueServerRenderer') +
  36. '\nreturn __sfc__'
  37. return new Function('Vue', 'VueServerRenderer', '_data', '_components', code)(
  38. Vue,
  39. VueServerRenderer,
  40. data,
  41. components,
  42. )
  43. }
  44. async function testHydration(
  45. code: string,
  46. components: Record<string, string> = {},
  47. ) {
  48. const data = ref('foo')
  49. const ssrComponents: any = {}
  50. const clientComponents: any = {}
  51. for (const key in components) {
  52. clientComponents[key] = compile(components[key], data, clientComponents)
  53. ssrComponents[key] = compile(components[key], data, ssrComponents, true)
  54. }
  55. const serverComp = compile(code, data, ssrComponents, true)
  56. const html = await VueServerRenderer.renderToString(
  57. runtimeDom.createSSRApp(serverComp),
  58. )
  59. const container = document.createElement('div')
  60. document.body.appendChild(container)
  61. container.innerHTML = html
  62. const clientComp = compile(code, data, clientComponents)
  63. const app = createVaporSSRApp(clientComp)
  64. app.mount(container)
  65. return { data, container }
  66. }
  67. const triggerEvent = (type: string, el: Element) => {
  68. const event = new Event(type, { bubbles: true })
  69. el.dispatchEvent(event)
  70. }
  71. describe('Vapor Mode hydration', () => {
  72. delegateEvents('click')
  73. beforeEach(() => {
  74. document.body.innerHTML = ''
  75. })
  76. test('root text', async () => {
  77. const { data, container } = await testHydration(`
  78. <template>{{ data }}</template>
  79. `)
  80. expect(container.innerHTML).toMatchInlineSnapshot(`"foo"`)
  81. data.value = 'bar'
  82. await nextTick()
  83. expect(container.innerHTML).toMatchInlineSnapshot(`"bar"`)
  84. })
  85. test('root comment', async () => {
  86. const { container } = await testHydration(`
  87. <template><!----></template>
  88. `)
  89. expect(container.innerHTML).toBe('<!---->')
  90. expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
  91. })
  92. test('root with mixed element and text', async () => {
  93. const { container, data } = await testHydration(`
  94. <template> A<span>{{ data }}</span>{{ data }}</template>
  95. `)
  96. expect(container.innerHTML).toMatchInlineSnapshot(
  97. `"<!--[--> A<span>foo</span>foo<!--]-->"`,
  98. )
  99. data.value = 'bar'
  100. await nextTick()
  101. expect(container.innerHTML).toMatchInlineSnapshot(
  102. `"<!--[--> A<span>bar</span>bar<!--]-->"`,
  103. )
  104. })
  105. test('empty element', async () => {
  106. const { container } = await testHydration(`
  107. <template><div/></template>
  108. `)
  109. expect(container.innerHTML).toBe('<div></div>')
  110. expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
  111. })
  112. test('element with binding and text children', async () => {
  113. const { container, data } = await testHydration(`
  114. <template><div :class="data">{{ data }}</div></template>
  115. `)
  116. expect(container.innerHTML).toMatchInlineSnapshot(
  117. `"<div class="foo">foo</div>"`,
  118. )
  119. data.value = 'bar'
  120. await nextTick()
  121. expect(container.innerHTML).toMatchInlineSnapshot(
  122. `"<div class="bar">bar</div>"`,
  123. )
  124. })
  125. test('element with elements children', async () => {
  126. const { container } = await testHydration(`
  127. <template>
  128. <div>
  129. <span>{{ data }}</span>
  130. <span :class="data" @click="data = 'bar'"/>
  131. </div>
  132. </template>
  133. `)
  134. expect(container.innerHTML).toMatchInlineSnapshot(
  135. `"<div><span>foo</span><span class="foo"></span></div>"`,
  136. )
  137. // event handler
  138. triggerEvent('click', container.querySelector('.foo')!)
  139. await nextTick()
  140. expect(container.innerHTML).toMatchInlineSnapshot(
  141. `"<div><span>bar</span><span class="bar"></span></div>"`,
  142. )
  143. })
  144. test('basic component', async () => {
  145. const { container, data } = await testHydration(
  146. `
  147. <template><div><span></span><components.Child/></div></template>
  148. `,
  149. { Child: `<template>{{ data }}</template>` },
  150. )
  151. expect(container.innerHTML).toMatchInlineSnapshot(
  152. `"<div><span></span>foo</div>"`,
  153. )
  154. data.value = 'bar'
  155. await nextTick()
  156. expect(container.innerHTML).toMatchInlineSnapshot(
  157. `"<div><span></span>bar</div>"`,
  158. )
  159. })
  160. test('fragment component', async () => {
  161. const { container, data } = await testHydration(
  162. `
  163. <template><div><span></span><components.Child/></div></template>
  164. `,
  165. { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
  166. )
  167. expect(container.innerHTML).toMatchInlineSnapshot(
  168. `"<div><span></span><!--[--><div>foo</div>-foo-<!--]--></div>"`,
  169. )
  170. data.value = 'bar'
  171. await nextTick()
  172. expect(container.innerHTML).toMatchInlineSnapshot(
  173. `"<div><span></span><!--[--><div>bar</div>-bar-<!--]--></div>"`,
  174. )
  175. })
  176. test('fragment component with prepend', async () => {
  177. const { container, data } = await testHydration(
  178. `
  179. <template><div><components.Child/><span></span></div></template>
  180. `,
  181. { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
  182. )
  183. expect(container.innerHTML).toMatchInlineSnapshot(
  184. `"<div><!--[--><div>foo</div>-foo-<!--]--><span></span></div>"`,
  185. )
  186. data.value = 'bar'
  187. await nextTick()
  188. expect(container.innerHTML).toMatchInlineSnapshot(
  189. `"<div><!--[--><div>bar</div>-bar-<!--]--><span></span></div>"`,
  190. )
  191. })
  192. test('nested fragment components', async () => {
  193. const { container, data } = await testHydration(
  194. `
  195. <template><div><components.Parent/><span></span></div></template>
  196. `,
  197. {
  198. Parent: `<template><div/><components.Child/><div/></template>`,
  199. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  200. },
  201. )
  202. expect(container.innerHTML).toMatchInlineSnapshot(
  203. `"<div><!--[--><div></div><!--[--><div>foo</div>-foo-<!--]--><div></div><!--]--><span></span></div>"`,
  204. )
  205. data.value = 'bar'
  206. await nextTick()
  207. expect(container.innerHTML).toMatchInlineSnapshot(
  208. `"<div><!--[--><div></div><!--[--><div>bar</div>-bar-<!--]--><div></div><!--]--><span></span></div>"`,
  209. )
  210. })
  211. // problem is the <!> placeholder does not exist in SSR output
  212. test.todo('component with anchor insertion', async () => {
  213. const { container, data } = await testHydration(
  214. `
  215. <template>
  216. <div>
  217. <span/>
  218. <components.Child/>
  219. <span/>
  220. </div>
  221. </template>
  222. `,
  223. {
  224. Child: `<template>{{ data }}</template>`,
  225. },
  226. )
  227. expect(container.innerHTML).toMatchInlineSnapshot()
  228. data.value = 'bar'
  229. await nextTick()
  230. expect(container.innerHTML).toMatchInlineSnapshot()
  231. })
  232. test.todo('consecutive component with anchor insertion', async () => {
  233. const { container, data } = await testHydration(
  234. `<template>
  235. <div>
  236. <span/>
  237. <components.Child/>
  238. <components.Child/>
  239. <span/>
  240. </div>
  241. </template>
  242. `,
  243. {
  244. Child: `<template>{{ data }}</template>`,
  245. },
  246. )
  247. expect(container.innerHTML).toMatchInlineSnapshot()
  248. data.value = 'bar'
  249. await nextTick()
  250. expect(container.innerHTML).toMatchInlineSnapshot()
  251. })
  252. test.todo('if')
  253. test.todo('for')
  254. test.todo('slots')
  255. // test('element with ref', () => {
  256. // const el = ref()
  257. // const { vnode, container } = mountWithHydration('<div></div>', () =>
  258. // h('div', { ref: el }),
  259. // )
  260. // expect(vnode.el).toBe(container.firstChild)
  261. // expect(el.value).toBe(vnode.el)
  262. // })
  263. // test('with data-allow-mismatch component when using onServerPrefetch', async () => {
  264. // const Comp = {
  265. // template: `
  266. // <div>Comp2</div>
  267. // `,
  268. // }
  269. // let foo: any
  270. // const App = {
  271. // setup() {
  272. // const flag = ref(true)
  273. // foo = () => {
  274. // flag.value = false
  275. // }
  276. // onServerPrefetch(() => (flag.value = false))
  277. // return { flag }
  278. // },
  279. // components: {
  280. // Comp,
  281. // },
  282. // template: `
  283. // <span data-allow-mismatch>
  284. // <Comp v-if="flag"></Comp>
  285. // </span>
  286. // `,
  287. // }
  288. // // hydrate
  289. // const container = document.createElement('div')
  290. // container.innerHTML = await renderToString(h(App))
  291. // createSSRApp(App).mount(container)
  292. // expect(container.innerHTML).toBe(
  293. // '<span data-allow-mismatch=""><div>Comp2</div></span>',
  294. // )
  295. // foo()
  296. // await nextTick()
  297. // expect(container.innerHTML).toBe(
  298. // '<span data-allow-mismatch=""><!--v-if--></span>',
  299. // )
  300. // })
  301. // // compile SSR + client render fn from the same template & hydrate
  302. // test('full compiler integration', async () => {
  303. // const mounted: string[] = []
  304. // const log = vi.fn()
  305. // const toggle = ref(true)
  306. // const Child = {
  307. // data() {
  308. // return {
  309. // count: 0,
  310. // text: 'hello',
  311. // style: {
  312. // color: 'red',
  313. // },
  314. // }
  315. // },
  316. // mounted() {
  317. // mounted.push('child')
  318. // },
  319. // template: `
  320. // <div>
  321. // <span class="count" :style="style">{{ count }}</span>
  322. // <button class="inc" @click="count++">inc</button>
  323. // <button class="change" @click="style.color = 'green'" >change color</button>
  324. // <button class="emit" @click="$emit('foo')">emit</button>
  325. // <span class="text">{{ text }}</span>
  326. // <input v-model="text">
  327. // </div>
  328. // `,
  329. // }
  330. // const App = {
  331. // setup() {
  332. // return { toggle }
  333. // },
  334. // mounted() {
  335. // mounted.push('parent')
  336. // },
  337. // template: `
  338. // <div>
  339. // <span>hello</span>
  340. // <template v-if="toggle">
  341. // <Child @foo="log('child')"/>
  342. // <template v-if="true">
  343. // <button class="parent-click" @click="log('click')">click me</button>
  344. // </template>
  345. // </template>
  346. // <span>hello</span>
  347. // </div>`,
  348. // components: {
  349. // Child,
  350. // },
  351. // methods: {
  352. // log,
  353. // },
  354. // }
  355. // const container = document.createElement('div')
  356. // // server render
  357. // container.innerHTML = await renderToString(h(App))
  358. // // hydrate
  359. // createSSRApp(App).mount(container)
  360. // // assert interactions
  361. // // 1. parent button click
  362. // triggerEvent('click', container.querySelector('.parent-click')!)
  363. // expect(log).toHaveBeenCalledWith('click')
  364. // // 2. child inc click + text interpolation
  365. // const count = container.querySelector('.count') as HTMLElement
  366. // expect(count.textContent).toBe(`0`)
  367. // triggerEvent('click', container.querySelector('.inc')!)
  368. // await nextTick()
  369. // expect(count.textContent).toBe(`1`)
  370. // // 3. child color click + style binding
  371. // expect(count.style.color).toBe('red')
  372. // triggerEvent('click', container.querySelector('.change')!)
  373. // await nextTick()
  374. // expect(count.style.color).toBe('green')
  375. // // 4. child event emit
  376. // triggerEvent('click', container.querySelector('.emit')!)
  377. // expect(log).toHaveBeenCalledWith('child')
  378. // // 5. child v-model
  379. // const text = container.querySelector('.text')!
  380. // const input = container.querySelector('input')!
  381. // expect(text.textContent).toBe('hello')
  382. // input.value = 'bye'
  383. // triggerEvent('input', input)
  384. // await nextTick()
  385. // expect(text.textContent).toBe('bye')
  386. // })
  387. // test('handle click error in ssr mode', async () => {
  388. // const App = {
  389. // setup() {
  390. // const throwError = () => {
  391. // throw new Error('Sentry Error')
  392. // }
  393. // return { throwError }
  394. // },
  395. // template: `
  396. // <div>
  397. // <button class="parent-click" @click="throwError">click me</button>
  398. // </div>`,
  399. // }
  400. // const container = document.createElement('div')
  401. // // server render
  402. // container.innerHTML = await renderToString(h(App))
  403. // // hydrate
  404. // const app = createSSRApp(App)
  405. // const handler = (app.config.errorHandler = vi.fn())
  406. // app.mount(container)
  407. // // assert interactions
  408. // // parent button click
  409. // triggerEvent('click', container.querySelector('.parent-click')!)
  410. // expect(handler).toHaveBeenCalled()
  411. // })
  412. // test('handle blur error in ssr mode', async () => {
  413. // const App = {
  414. // setup() {
  415. // const throwError = () => {
  416. // throw new Error('Sentry Error')
  417. // }
  418. // return { throwError }
  419. // },
  420. // template: `
  421. // <div>
  422. // <input class="parent-click" @blur="throwError"/>
  423. // </div>`,
  424. // }
  425. // const container = document.createElement('div')
  426. // // server render
  427. // container.innerHTML = await renderToString(h(App))
  428. // // hydrate
  429. // const app = createSSRApp(App)
  430. // const handler = (app.config.errorHandler = vi.fn())
  431. // app.mount(container)
  432. // // assert interactions
  433. // // parent blur event
  434. // triggerEvent('blur', container.querySelector('.parent-click')!)
  435. // expect(handler).toHaveBeenCalled()
  436. // })
  437. // test('async component', async () => {
  438. // const spy = vi.fn()
  439. // const Comp = () =>
  440. // h(
  441. // 'button',
  442. // {
  443. // onClick: spy,
  444. // },
  445. // 'hello!',
  446. // )
  447. // let serverResolve: any
  448. // let AsyncComp = defineAsyncComponent(
  449. // () =>
  450. // new Promise(r => {
  451. // serverResolve = r
  452. // }),
  453. // )
  454. // const App = {
  455. // render() {
  456. // return ['hello', h(AsyncComp), 'world']
  457. // },
  458. // }
  459. // // server render
  460. // const htmlPromise = renderToString(h(App))
  461. // serverResolve(Comp)
  462. // const html = await htmlPromise
  463. // expect(html).toMatchInlineSnapshot(
  464. // `"<!--[-->hello<button>hello!</button>world<!--]-->"`,
  465. // )
  466. // // hydration
  467. // let clientResolve: any
  468. // AsyncComp = defineAsyncComponent(
  469. // () =>
  470. // new Promise(r => {
  471. // clientResolve = r
  472. // }),
  473. // )
  474. // const container = document.createElement('div')
  475. // container.innerHTML = html
  476. // createSSRApp(App).mount(container)
  477. // // hydration not complete yet
  478. // triggerEvent('click', container.querySelector('button')!)
  479. // expect(spy).not.toHaveBeenCalled()
  480. // // resolve
  481. // clientResolve(Comp)
  482. // await new Promise(r => setTimeout(r))
  483. // // should be hydrated now
  484. // triggerEvent('click', container.querySelector('button')!)
  485. // expect(spy).toHaveBeenCalled()
  486. // })
  487. // test('update async wrapper before resolve', async () => {
  488. // const Comp = {
  489. // render() {
  490. // return h('h1', 'Async component')
  491. // },
  492. // }
  493. // let serverResolve: any
  494. // let AsyncComp = defineAsyncComponent(
  495. // () =>
  496. // new Promise(r => {
  497. // serverResolve = r
  498. // }),
  499. // )
  500. // const toggle = ref(true)
  501. // const App = {
  502. // setup() {
  503. // onMounted(() => {
  504. // // change state, this makes updateComponent(AsyncComp) execute before
  505. // // the async component is resolved
  506. // toggle.value = false
  507. // })
  508. // return () => {
  509. // return [toggle.value ? 'hello' : 'world', h(AsyncComp)]
  510. // }
  511. // },
  512. // }
  513. // // server render
  514. // const htmlPromise = renderToString(h(App))
  515. // serverResolve(Comp)
  516. // const html = await htmlPromise
  517. // expect(html).toMatchInlineSnapshot(
  518. // `"<!--[-->hello<h1>Async component</h1><!--]-->"`,
  519. // )
  520. // // hydration
  521. // let clientResolve: any
  522. // AsyncComp = defineAsyncComponent(
  523. // () =>
  524. // new Promise(r => {
  525. // clientResolve = r
  526. // }),
  527. // )
  528. // const container = document.createElement('div')
  529. // container.innerHTML = html
  530. // createSSRApp(App).mount(container)
  531. // // resolve
  532. // clientResolve(Comp)
  533. // await new Promise(r => setTimeout(r))
  534. // // should be hydrated now
  535. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  536. // expect(container.innerHTML).toMatchInlineSnapshot(
  537. // `"<!--[-->world<h1>Async component</h1><!--]-->"`,
  538. // )
  539. // })
  540. // test('hydrate safely when property used by async setup changed before render', async () => {
  541. // const toggle = ref(true)
  542. // const AsyncComp = {
  543. // async setup() {
  544. // await new Promise<void>(r => setTimeout(r, 10))
  545. // return () => h('h1', 'Async component')
  546. // },
  547. // }
  548. // const AsyncWrapper = {
  549. // render() {
  550. // return h(AsyncComp)
  551. // },
  552. // }
  553. // const SiblingComp = {
  554. // setup() {
  555. // toggle.value = false
  556. // return () => h('span')
  557. // },
  558. // }
  559. // const App = {
  560. // setup() {
  561. // return () =>
  562. // h(
  563. // Suspense,
  564. // {},
  565. // {
  566. // default: () => [
  567. // h('main', {}, [
  568. // h(AsyncWrapper, {
  569. // prop: toggle.value ? 'hello' : 'world',
  570. // }),
  571. // h(SiblingComp),
  572. // ]),
  573. // ],
  574. // },
  575. // )
  576. // },
  577. // }
  578. // // server render
  579. // const html = await renderToString(h(App))
  580. // expect(html).toMatchInlineSnapshot(
  581. // `"<main><h1 prop="hello">Async component</h1><span></span></main>"`,
  582. // )
  583. // expect(toggle.value).toBe(false)
  584. // // hydration
  585. // // reset the value
  586. // toggle.value = true
  587. // expect(toggle.value).toBe(true)
  588. // const container = document.createElement('div')
  589. // container.innerHTML = html
  590. // createSSRApp(App).mount(container)
  591. // await new Promise(r => setTimeout(r, 10))
  592. // expect(toggle.value).toBe(false)
  593. // // should be hydrated now
  594. // expect(container.innerHTML).toMatchInlineSnapshot(
  595. // `"<main><h1 prop="world">Async component</h1><span></span></main>"`,
  596. // )
  597. // })
  598. // test('hydrate safely when property used by deep nested async setup changed before render', async () => {
  599. // const toggle = ref(true)
  600. // const AsyncComp = {
  601. // async setup() {
  602. // await new Promise<void>(r => setTimeout(r, 10))
  603. // return () => h('h1', 'Async component')
  604. // },
  605. // }
  606. // const AsyncWrapper = { render: () => h(AsyncComp) }
  607. // const AsyncWrapperWrapper = { render: () => h(AsyncWrapper) }
  608. // const SiblingComp = {
  609. // setup() {
  610. // toggle.value = false
  611. // return () => h('span')
  612. // },
  613. // }
  614. // const App = {
  615. // setup() {
  616. // return () =>
  617. // h(
  618. // Suspense,
  619. // {},
  620. // {
  621. // default: () => [
  622. // h('main', {}, [
  623. // h(AsyncWrapperWrapper, {
  624. // prop: toggle.value ? 'hello' : 'world',
  625. // }),
  626. // h(SiblingComp),
  627. // ]),
  628. // ],
  629. // },
  630. // )
  631. // },
  632. // }
  633. // // server render
  634. // const html = await renderToString(h(App))
  635. // expect(html).toMatchInlineSnapshot(
  636. // `"<main><h1 prop="hello">Async component</h1><span></span></main>"`,
  637. // )
  638. // expect(toggle.value).toBe(false)
  639. // // hydration
  640. // // reset the value
  641. // toggle.value = true
  642. // expect(toggle.value).toBe(true)
  643. // const container = document.createElement('div')
  644. // container.innerHTML = html
  645. // createSSRApp(App).mount(container)
  646. // await new Promise(r => setTimeout(r, 10))
  647. // expect(toggle.value).toBe(false)
  648. // // should be hydrated now
  649. // expect(container.innerHTML).toMatchInlineSnapshot(
  650. // `"<main><h1 prop="world">Async component</h1><span></span></main>"`,
  651. // )
  652. // })
  653. // // #3787
  654. // test('unmount async wrapper before load', async () => {
  655. // let resolve: any
  656. // const AsyncComp = defineAsyncComponent(
  657. // () =>
  658. // new Promise(r => {
  659. // resolve = r
  660. // }),
  661. // )
  662. // const show = ref(true)
  663. // const root = document.createElement('div')
  664. // root.innerHTML = '<div><div>async</div></div>'
  665. // createSSRApp({
  666. // render() {
  667. // return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
  668. // },
  669. // }).mount(root)
  670. // show.value = false
  671. // await nextTick()
  672. // expect(root.innerHTML).toBe('<div><div>hi</div></div>')
  673. // resolve({})
  674. // })
  675. // //#12362
  676. // test('nested async wrapper', async () => {
  677. // const Toggle = defineAsyncComponent(
  678. // () =>
  679. // new Promise(r => {
  680. // r(
  681. // defineComponent({
  682. // setup(_, { slots }) {
  683. // const show = ref(false)
  684. // onMounted(() => {
  685. // nextTick(() => {
  686. // show.value = true
  687. // })
  688. // })
  689. // return () =>
  690. // withDirectives(
  691. // h('div', null, [renderSlot(slots, 'default')]),
  692. // [[vShow, show.value]],
  693. // )
  694. // },
  695. // }) as any,
  696. // )
  697. // }),
  698. // )
  699. // const Wrapper = defineAsyncComponent(() => {
  700. // return new Promise(r => {
  701. // r(
  702. // defineComponent({
  703. // render(this: any) {
  704. // return renderSlot(this.$slots, 'default')
  705. // },
  706. // }) as any,
  707. // )
  708. // })
  709. // })
  710. // const count = ref(0)
  711. // const fn = vi.fn()
  712. // const Child = {
  713. // setup() {
  714. // onMounted(() => {
  715. // fn()
  716. // count.value++
  717. // })
  718. // return () => h('div', count.value)
  719. // },
  720. // }
  721. // const App = {
  722. // render() {
  723. // return h(Toggle, null, {
  724. // default: () =>
  725. // h(Wrapper, null, {
  726. // default: () =>
  727. // h(Wrapper, null, {
  728. // default: () => h(Child),
  729. // }),
  730. // }),
  731. // })
  732. // },
  733. // }
  734. // const root = document.createElement('div')
  735. // root.innerHTML = await renderToString(h(App))
  736. // expect(root.innerHTML).toMatchInlineSnapshot(
  737. // `"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
  738. // )
  739. // createSSRApp(App).mount(root)
  740. // await nextTick()
  741. // await nextTick()
  742. // expect(root.innerHTML).toMatchInlineSnapshot(
  743. // `"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
  744. // )
  745. // expect(fn).toBeCalledTimes(1)
  746. // })
  747. // test('unmount async wrapper before load (fragment)', async () => {
  748. // let resolve: any
  749. // const AsyncComp = defineAsyncComponent(
  750. // () =>
  751. // new Promise(r => {
  752. // resolve = r
  753. // }),
  754. // )
  755. // const show = ref(true)
  756. // const root = document.createElement('div')
  757. // root.innerHTML = '<div><!--[-->async<!--]--></div>'
  758. // createSSRApp({
  759. // render() {
  760. // return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
  761. // },
  762. // }).mount(root)
  763. // show.value = false
  764. // await nextTick()
  765. // expect(root.innerHTML).toBe('<div><div>hi</div></div>')
  766. // resolve({})
  767. // })
  768. // test('elements with camel-case in svg ', () => {
  769. // const { vnode, container } = mountWithHydration(
  770. // '<animateTransform></animateTransform>',
  771. // () => h('animateTransform'),
  772. // )
  773. // expect(vnode.el).toBe(container.firstChild)
  774. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  775. // })
  776. // test('SVG as a mount container', () => {
  777. // const svgContainer = document.createElement('svg')
  778. // svgContainer.innerHTML = '<g></g>'
  779. // const app = createSSRApp({
  780. // render: () => h('g'),
  781. // })
  782. // expect(
  783. // (
  784. // app.mount(svgContainer).$.subTree as VNode<Node, Element> & {
  785. // el: Element
  786. // }
  787. // ).el instanceof SVGElement,
  788. // )
  789. // })
  790. // test('force hydrate prop with `.prop` modifier', () => {
  791. // const { container } = mountWithHydration('<input type="checkbox">', () =>
  792. // h('input', {
  793. // type: 'checkbox',
  794. // '.indeterminate': true,
  795. // }),
  796. // )
  797. // expect((container.firstChild! as any).indeterminate).toBe(true)
  798. // })
  799. // test('force hydrate input v-model with non-string value bindings', () => {
  800. // const { container } = mountWithHydration(
  801. // '<input type="checkbox" value="true">',
  802. // () =>
  803. // withDirectives(
  804. // createVNode(
  805. // 'input',
  806. // { type: 'checkbox', 'true-value': true },
  807. // null,
  808. // PatchFlags.PROPS,
  809. // ['true-value'],
  810. // ),
  811. // [[vModelCheckbox, true]],
  812. // ),
  813. // )
  814. // expect((container.firstChild as any)._trueValue).toBe(true)
  815. // })
  816. // test('force hydrate checkbox with indeterminate', () => {
  817. // const { container } = mountWithHydration(
  818. // '<input type="checkbox" indeterminate>',
  819. // () =>
  820. // createVNode(
  821. // 'input',
  822. // { type: 'checkbox', indeterminate: '' },
  823. // null,
  824. // PatchFlags.CACHED,
  825. // ),
  826. // )
  827. // expect((container.firstChild as any).indeterminate).toBe(true)
  828. // })
  829. // test('force hydrate select option with non-string value bindings', () => {
  830. // const { container } = mountWithHydration(
  831. // '<select><option value="true">ok</option></select>',
  832. // () =>
  833. // h('select', [
  834. // // hoisted because bound value is a constant...
  835. // createVNode('option', { value: true }, null, -1 /* HOISTED */),
  836. // ]),
  837. // )
  838. // expect((container.firstChild!.firstChild as any)._value).toBe(true)
  839. // })
  840. // // #7203
  841. // test('force hydrate custom element with dynamic props', () => {
  842. // class MyElement extends HTMLElement {
  843. // foo = ''
  844. // constructor() {
  845. // super()
  846. // }
  847. // }
  848. // customElements.define('my-element-7203', MyElement)
  849. // const msg = ref('bar')
  850. // const container = document.createElement('div')
  851. // container.innerHTML = '<my-element-7203></my-element-7203>'
  852. // const app = createSSRApp({
  853. // render: () => h('my-element-7203', { foo: msg.value }),
  854. // })
  855. // app.mount(container)
  856. // expect((container.firstChild as any).foo).toBe(msg.value)
  857. // })
  858. // // #5728
  859. // test('empty text node in slot', () => {
  860. // const Comp = {
  861. // render(this: any) {
  862. // return renderSlot(this.$slots, 'default', {}, () => [
  863. // createTextVNode(''),
  864. // ])
  865. // },
  866. // }
  867. // const { container, vnode } = mountWithHydration('<!--[--><!--]-->', () =>
  868. // h(Comp),
  869. // )
  870. // expect(container.childNodes.length).toBe(3)
  871. // const text = container.childNodes[1]
  872. // expect(text.nodeType).toBe(3)
  873. // expect(vnode.el).toBe(container.childNodes[0])
  874. // // component => slot fragment => text node
  875. // expect((vnode as any).component?.subTree.children[0].el).toBe(text)
  876. // })
  877. // // #7215
  878. // test('empty text node', () => {
  879. // const Comp = {
  880. // render(this: any) {
  881. // return h('p', [''])
  882. // },
  883. // }
  884. // const { container } = mountWithHydration('<p></p>', () => h(Comp))
  885. // expect(container.childNodes.length).toBe(1)
  886. // const p = container.childNodes[0]
  887. // expect(p.childNodes.length).toBe(1)
  888. // const text = p.childNodes[0]
  889. // expect(text.nodeType).toBe(3)
  890. // })
  891. // // #11372
  892. // test('object style value tracking in prod', async () => {
  893. // __DEV__ = false
  894. // try {
  895. // const style = reactive({ color: 'red' })
  896. // const Comp = {
  897. // render(this: any) {
  898. // return (
  899. // openBlock(),
  900. // createElementBlock(
  901. // 'div',
  902. // {
  903. // style: normalizeStyle(style),
  904. // },
  905. // null,
  906. // 4 /* STYLE */,
  907. // )
  908. // )
  909. // },
  910. // }
  911. // const { container } = mountWithHydration(
  912. // `<div style="color: red;"></div>`,
  913. // () => h(Comp),
  914. // )
  915. // style.color = 'green'
  916. // await nextTick()
  917. // expect(container.innerHTML).toBe(`<div style="color: green;"></div>`)
  918. // } finally {
  919. // __DEV__ = true
  920. // }
  921. // })
  922. // test('app.unmount()', async () => {
  923. // const container = document.createElement('DIV')
  924. // container.innerHTML = '<button></button>'
  925. // const App = defineComponent({
  926. // setup(_, { expose }) {
  927. // const count = ref(0)
  928. // expose({ count })
  929. // return () =>
  930. // h('button', {
  931. // onClick: () => count.value++,
  932. // })
  933. // },
  934. // })
  935. // const app = createSSRApp(App)
  936. // const vm = app.mount(container)
  937. // await nextTick()
  938. // expect((container as any)._vnode).toBeDefined()
  939. // // @ts-expect-error - expose()'d properties are not available on vm type
  940. // expect(vm.count).toBe(0)
  941. // app.unmount()
  942. // expect((container as any)._vnode).toBe(null)
  943. // })
  944. // // #6637
  945. // test('stringified root fragment', () => {
  946. // mountWithHydration(`<!--[--><div></div><!--]-->`, () =>
  947. // createStaticVNode(`<div></div>`, 1),
  948. // )
  949. // expect(`mismatch`).not.toHaveBeenWarned()
  950. // })
  951. // test('transition appear', () => {
  952. // const { vnode, container } = mountWithHydration(
  953. // `<template><div>foo</div></template>`,
  954. // () =>
  955. // h(
  956. // Transition,
  957. // { appear: true },
  958. // {
  959. // default: () => h('div', 'foo'),
  960. // },
  961. // ),
  962. // )
  963. // expect(container.firstChild).toMatchInlineSnapshot(`
  964. // <div
  965. // class="v-enter-from v-enter-active"
  966. // >
  967. // foo
  968. // </div>
  969. // `)
  970. // expect(vnode.el).toBe(container.firstChild)
  971. // expect(`mismatch`).not.toHaveBeenWarned()
  972. // })
  973. // test('transition appear with v-if', () => {
  974. // const show = false
  975. // const { vnode, container } = mountWithHydration(
  976. // `<template><!----></template>`,
  977. // () =>
  978. // h(
  979. // Transition,
  980. // { appear: true },
  981. // {
  982. // default: () => (show ? h('div', 'foo') : createCommentVNode('')),
  983. // },
  984. // ),
  985. // )
  986. // expect(container.firstChild).toMatchInlineSnapshot('<!---->')
  987. // expect(vnode.el).toBe(container.firstChild)
  988. // expect(`mismatch`).not.toHaveBeenWarned()
  989. // })
  990. // test('transition appear with v-show', () => {
  991. // const show = false
  992. // const { vnode, container } = mountWithHydration(
  993. // `<template><div style="display: none;">foo</div></template>`,
  994. // () =>
  995. // h(
  996. // Transition,
  997. // { appear: true },
  998. // {
  999. // default: () =>
  1000. // withDirectives(createVNode('div', null, 'foo'), [[vShow, show]]),
  1001. // },
  1002. // ),
  1003. // )
  1004. // expect(container.firstChild).toMatchInlineSnapshot(`
  1005. // <div
  1006. // class="v-enter-from v-enter-active"
  1007. // style="display: none;"
  1008. // >
  1009. // foo
  1010. // </div>
  1011. // `)
  1012. // expect((container.firstChild as any)[vShowOriginalDisplay]).toBe('')
  1013. // expect(vnode.el).toBe(container.firstChild)
  1014. // expect(`mismatch`).not.toHaveBeenWarned()
  1015. // })
  1016. // test('transition appear w/ event listener', async () => {
  1017. // const container = document.createElement('div')
  1018. // container.innerHTML = `<template><button>0</button></template>`
  1019. // createSSRApp({
  1020. // data() {
  1021. // return {
  1022. // count: 0,
  1023. // }
  1024. // },
  1025. // template: `
  1026. // <Transition appear>
  1027. // <button @click="count++">{{count}}</button>
  1028. // </Transition>
  1029. // `,
  1030. // }).mount(container)
  1031. // expect(container.firstChild).toMatchInlineSnapshot(`
  1032. // <button
  1033. // class="v-enter-from v-enter-active"
  1034. // >
  1035. // 0
  1036. // </button>
  1037. // `)
  1038. // triggerEvent('click', container.querySelector('button')!)
  1039. // await nextTick()
  1040. // expect(container.firstChild).toMatchInlineSnapshot(`
  1041. // <button
  1042. // class="v-enter-from v-enter-active"
  1043. // >
  1044. // 1
  1045. // </button>
  1046. // `)
  1047. // })
  1048. // test('Suspense + transition appear', async () => {
  1049. // const { vnode, container } = mountWithHydration(
  1050. // `<template><div>foo</div></template>`,
  1051. // () =>
  1052. // h(Suspense, {}, () =>
  1053. // h(
  1054. // Transition,
  1055. // { appear: true },
  1056. // {
  1057. // default: () => h('div', 'foo'),
  1058. // },
  1059. // ),
  1060. // ),
  1061. // )
  1062. // expect(vnode.el).toBe(container.firstChild)
  1063. // // wait for hydration to finish
  1064. // await new Promise(r => setTimeout(r))
  1065. // expect(container.firstChild).toMatchInlineSnapshot(`
  1066. // <div
  1067. // class="v-enter-from v-enter-active"
  1068. // >
  1069. // foo
  1070. // </div>
  1071. // `)
  1072. // await nextTick()
  1073. // expect(vnode.el).toBe(container.firstChild)
  1074. // })
  1075. // // #10607
  1076. // test('update component stable slot (prod + optimized mode)', async () => {
  1077. // __DEV__ = false
  1078. // try {
  1079. // const container = document.createElement('div')
  1080. // container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
  1081. // const Comp = {
  1082. // render(this: any) {
  1083. // return (
  1084. // openBlock(),
  1085. // createElementBlock('div', null, [
  1086. // renderSlot(this.$slots, 'default'),
  1087. // ])
  1088. // )
  1089. // },
  1090. // }
  1091. // const show = ref(false)
  1092. // const clicked = ref(false)
  1093. // const Wrapper = {
  1094. // setup() {
  1095. // const items = ref<number[]>([])
  1096. // onMounted(() => {
  1097. // items.value = [1]
  1098. // })
  1099. // return () => {
  1100. // return (
  1101. // openBlock(),
  1102. // createBlock(Comp, null, {
  1103. // default: withCtx(() => [
  1104. // createElementVNode('div', null, [
  1105. // createElementVNode('div', null, [
  1106. // clicked.value
  1107. // ? (openBlock(),
  1108. // createElementBlock('div', { key: 0 }, 'foo'))
  1109. // : createCommentVNode('v-if', true),
  1110. // ]),
  1111. // ]),
  1112. // createElementVNode(
  1113. // 'div',
  1114. // null,
  1115. // items.value.length,
  1116. // 1 /* TEXT */,
  1117. // ),
  1118. // ]),
  1119. // _: 1 /* STABLE */,
  1120. // })
  1121. // )
  1122. // }
  1123. // },
  1124. // }
  1125. // createSSRApp({
  1126. // components: { Wrapper },
  1127. // data() {
  1128. // return { show }
  1129. // },
  1130. // template: `<Wrapper :show="show"/>`,
  1131. // }).mount(container)
  1132. // await nextTick()
  1133. // expect(container.innerHTML).toBe(
  1134. // `<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
  1135. // )
  1136. // show.value = true
  1137. // await nextTick()
  1138. // expect(async () => {
  1139. // clicked.value = true
  1140. // await nextTick()
  1141. // }).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
  1142. // await nextTick()
  1143. // expect(container.innerHTML).toBe(
  1144. // `<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
  1145. // )
  1146. // } catch (e) {
  1147. // throw e
  1148. // } finally {
  1149. // __DEV__ = true
  1150. // }
  1151. // })
  1152. // describe('mismatch handling', () => {
  1153. // test('text node', () => {
  1154. // const { container } = mountWithHydration(`foo`, () => 'bar')
  1155. // expect(container.textContent).toBe('bar')
  1156. // expect(`Hydration text mismatch`).toHaveBeenWarned()
  1157. // })
  1158. // test('element text content', () => {
  1159. // const { container } = mountWithHydration(`<div>foo</div>`, () =>
  1160. // h('div', 'bar'),
  1161. // )
  1162. // expect(container.innerHTML).toBe('<div>bar</div>')
  1163. // expect(`Hydration text content mismatch`).toHaveBeenWarned()
  1164. // })
  1165. // test('not enough children', () => {
  1166. // const { container } = mountWithHydration(`<div></div>`, () =>
  1167. // h('div', [h('span', 'foo'), h('span', 'bar')]),
  1168. // )
  1169. // expect(container.innerHTML).toBe(
  1170. // '<div><span>foo</span><span>bar</span></div>',
  1171. // )
  1172. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  1173. // })
  1174. // test('too many children', () => {
  1175. // const { container } = mountWithHydration(
  1176. // `<div><span>foo</span><span>bar</span></div>`,
  1177. // () => h('div', [h('span', 'foo')]),
  1178. // )
  1179. // expect(container.innerHTML).toBe('<div><span>foo</span></div>')
  1180. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  1181. // })
  1182. // test('complete mismatch', () => {
  1183. // const { container } = mountWithHydration(
  1184. // `<div><span>foo</span><span>bar</span></div>`,
  1185. // () => h('div', [h('div', 'foo'), h('p', 'bar')]),
  1186. // )
  1187. // expect(container.innerHTML).toBe('<div><div>foo</div><p>bar</p></div>')
  1188. // expect(`Hydration node mismatch`).toHaveBeenWarnedTimes(2)
  1189. // })
  1190. // test('fragment mismatch removal', () => {
  1191. // const { container } = mountWithHydration(
  1192. // `<div><!--[--><div>foo</div><div>bar</div><!--]--></div>`,
  1193. // () => h('div', [h('span', 'replaced')]),
  1194. // )
  1195. // expect(container.innerHTML).toBe('<div><span>replaced</span></div>')
  1196. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  1197. // })
  1198. // test('fragment not enough children', () => {
  1199. // const { container } = mountWithHydration(
  1200. // `<div><!--[--><div>foo</div><!--]--><div>baz</div></div>`,
  1201. // () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]),
  1202. // )
  1203. // expect(container.innerHTML).toBe(
  1204. // '<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>',
  1205. // )
  1206. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  1207. // })
  1208. // test('fragment too many children', () => {
  1209. // const { container } = mountWithHydration(
  1210. // `<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>`,
  1211. // () => h('div', [[h('div', 'foo')], h('div', 'baz')]),
  1212. // )
  1213. // expect(container.innerHTML).toBe(
  1214. // '<div><!--[--><div>foo</div><!--]--><div>baz</div></div>',
  1215. // )
  1216. // // fragment ends early and attempts to hydrate the extra <div>bar</div>
  1217. // // as 2nd fragment child.
  1218. // expect(`Hydration text content mismatch`).toHaveBeenWarned()
  1219. // // excessive children removal
  1220. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  1221. // })
  1222. // test('Teleport target has empty children', () => {
  1223. // const teleportContainer = document.createElement('div')
  1224. // teleportContainer.id = 'teleport'
  1225. // document.body.appendChild(teleportContainer)
  1226. // mountWithHydration('<!--teleport start--><!--teleport end-->', () =>
  1227. // h(Teleport, { to: '#teleport' }, [h('span', 'value')]),
  1228. // )
  1229. // expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
  1230. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  1231. // })
  1232. // test('comment mismatch (element)', () => {
  1233. // const { container } = mountWithHydration(`<div><span></span></div>`, () =>
  1234. // h('div', [createCommentVNode('hi')]),
  1235. // )
  1236. // expect(container.innerHTML).toBe('<div><!--hi--></div>')
  1237. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  1238. // })
  1239. // test('comment mismatch (text)', () => {
  1240. // const { container } = mountWithHydration(`<div>foobar</div>`, () =>
  1241. // h('div', [createCommentVNode('hi')]),
  1242. // )
  1243. // expect(container.innerHTML).toBe('<div><!--hi--></div>')
  1244. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  1245. // })
  1246. // test('class mismatch', () => {
  1247. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  1248. // h('div', { class: ['foo', 'bar'] }),
  1249. // )
  1250. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  1251. // h('div', { class: { foo: true, bar: true } }),
  1252. // )
  1253. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  1254. // h('div', { class: 'foo bar' }),
  1255. // )
  1256. // // SVG classes
  1257. // mountWithHydration(`<svg class="foo bar"></svg>`, () =>
  1258. // h('svg', { class: 'foo bar' }),
  1259. // )
  1260. // // class with different order
  1261. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  1262. // h('div', { class: 'bar foo' }),
  1263. // )
  1264. // expect(`Hydration class mismatch`).not.toHaveBeenWarned()
  1265. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  1266. // h('div', { class: 'foo' }),
  1267. // )
  1268. // expect(`Hydration class mismatch`).toHaveBeenWarned()
  1269. // })
  1270. // test('style mismatch', () => {
  1271. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  1272. // h('div', { style: { color: 'red' } }),
  1273. // )
  1274. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  1275. // h('div', { style: `color:red;` }),
  1276. // )
  1277. // mountWithHydration(
  1278. // `<div style="color:red; font-size: 12px;"></div>`,
  1279. // () => h('div', { style: `font-size: 12px; color:red;` }),
  1280. // )
  1281. // mountWithHydration(`<div style="color:red;display:none;"></div>`, () =>
  1282. // withDirectives(createVNode('div', { style: 'color: red' }, ''), [
  1283. // [vShow, false],
  1284. // ]),
  1285. // )
  1286. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1287. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  1288. // h('div', { style: { color: 'green' } }),
  1289. // )
  1290. // expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
  1291. // })
  1292. // test('style mismatch when no style attribute is present', () => {
  1293. // mountWithHydration(`<div></div>`, () =>
  1294. // h('div', { style: { color: 'red' } }),
  1295. // )
  1296. // expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
  1297. // })
  1298. // test('style mismatch w/ v-show', () => {
  1299. // mountWithHydration(`<div style="color:red;display:none"></div>`, () =>
  1300. // withDirectives(createVNode('div', { style: 'color: red' }, ''), [
  1301. // [vShow, false],
  1302. // ]),
  1303. // )
  1304. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1305. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  1306. // withDirectives(createVNode('div', { style: 'color: red' }, ''), [
  1307. // [vShow, false],
  1308. // ]),
  1309. // )
  1310. // expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
  1311. // })
  1312. // test('attr mismatch', () => {
  1313. // mountWithHydration(`<div id="foo"></div>`, () => h('div', { id: 'foo' }))
  1314. // mountWithHydration(`<div spellcheck></div>`, () =>
  1315. // h('div', { spellcheck: '' }),
  1316. // )
  1317. // mountWithHydration(`<div></div>`, () => h('div', { id: undefined }))
  1318. // // boolean
  1319. // mountWithHydration(`<select multiple></div>`, () =>
  1320. // h('select', { multiple: true }),
  1321. // )
  1322. // mountWithHydration(`<select multiple></div>`, () =>
  1323. // h('select', { multiple: 'multiple' }),
  1324. // )
  1325. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1326. // mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
  1327. // expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(1)
  1328. // mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
  1329. // expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
  1330. // })
  1331. // test('attr special case: textarea value', () => {
  1332. // mountWithHydration(`<textarea>foo</textarea>`, () =>
  1333. // h('textarea', { value: 'foo' }),
  1334. // )
  1335. // mountWithHydration(`<textarea></textarea>`, () =>
  1336. // h('textarea', { value: '' }),
  1337. // )
  1338. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1339. // mountWithHydration(`<textarea>foo</textarea>`, () =>
  1340. // h('textarea', { value: 'bar' }),
  1341. // )
  1342. // expect(`Hydration attribute mismatch`).toHaveBeenWarned()
  1343. // })
  1344. // // #11873
  1345. // test('<textarea> with newlines at the beginning', async () => {
  1346. // const render = () => h('textarea', null, '\nhello')
  1347. // const html = await renderToString(createSSRApp({ render }))
  1348. // mountWithHydration(html, render)
  1349. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  1350. // })
  1351. // test('<pre> with newlines at the beginning', async () => {
  1352. // const render = () => h('pre', null, '\n')
  1353. // const html = await renderToString(createSSRApp({ render }))
  1354. // mountWithHydration(html, render)
  1355. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  1356. // })
  1357. // test('boolean attr handling', () => {
  1358. // mountWithHydration(`<input />`, () => h('input', { readonly: false }))
  1359. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1360. // mountWithHydration(`<input readonly />`, () =>
  1361. // h('input', { readonly: true }),
  1362. // )
  1363. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1364. // mountWithHydration(`<input readonly="readonly" />`, () =>
  1365. // h('input', { readonly: true }),
  1366. // )
  1367. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1368. // })
  1369. // test('client value is null or undefined', () => {
  1370. // mountWithHydration(`<div></div>`, () =>
  1371. // h('div', { draggable: undefined }),
  1372. // )
  1373. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1374. // mountWithHydration(`<input />`, () => h('input', { type: null }))
  1375. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1376. // })
  1377. // test('should not warn against object values', () => {
  1378. // mountWithHydration(`<input />`, () => h('input', { from: {} }))
  1379. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1380. // })
  1381. // test('should not warn on falsy bindings of non-property keys', () => {
  1382. // mountWithHydration(`<button />`, () => h('button', { href: undefined }))
  1383. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1384. // })
  1385. // test('should not warn on non-renderable option values', () => {
  1386. // mountWithHydration(`<select><option>hello</option></select>`, () =>
  1387. // h('select', [h('option', { value: ['foo'] }, 'hello')]),
  1388. // )
  1389. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1390. // })
  1391. // test('should not warn css v-bind', () => {
  1392. // const container = document.createElement('div')
  1393. // container.innerHTML = `<div style="--foo:red;color:var(--foo);" />`
  1394. // const app = createSSRApp({
  1395. // setup() {
  1396. // useCssVars(() => ({
  1397. // foo: 'red',
  1398. // }))
  1399. // return () => h('div', { style: { color: 'var(--foo)' } })
  1400. // },
  1401. // })
  1402. // app.mount(container)
  1403. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1404. // })
  1405. // // #10317 - test case from #10325
  1406. // test('css vars should only be added to expected on component root dom', () => {
  1407. // const container = document.createElement('div')
  1408. // container.innerHTML = `<div style="--foo:red;"><div style="color:var(--foo);" /></div>`
  1409. // const app = createSSRApp({
  1410. // setup() {
  1411. // useCssVars(() => ({
  1412. // foo: 'red',
  1413. // }))
  1414. // return () =>
  1415. // h('div', null, [h('div', { style: { color: 'var(--foo)' } })])
  1416. // },
  1417. // })
  1418. // app.mount(container)
  1419. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1420. // })
  1421. // // #11188
  1422. // test('css vars support fallthrough', () => {
  1423. // const container = document.createElement('div')
  1424. // container.innerHTML = `<div style="padding: 4px;--foo:red;"></div>`
  1425. // const app = createSSRApp({
  1426. // setup() {
  1427. // useCssVars(() => ({
  1428. // foo: 'red',
  1429. // }))
  1430. // return () => h(Child)
  1431. // },
  1432. // })
  1433. // const Child = {
  1434. // setup() {
  1435. // return () => h('div', { style: 'padding: 4px' })
  1436. // },
  1437. // }
  1438. // app.mount(container)
  1439. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1440. // })
  1441. // // #11189
  1442. // test('should not warn for directives that mutate DOM in created', () => {
  1443. // const container = document.createElement('div')
  1444. // container.innerHTML = `<div class="test red"></div>`
  1445. // const vColor: ObjectDirective = {
  1446. // created(el, binding) {
  1447. // el.classList.add(binding.value)
  1448. // },
  1449. // }
  1450. // const app = createSSRApp({
  1451. // setup() {
  1452. // return () =>
  1453. // withDirectives(h('div', { class: 'test' }), [[vColor, 'red']])
  1454. // },
  1455. // })
  1456. // app.mount(container)
  1457. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1458. // })
  1459. // test('escape css var name', () => {
  1460. // const container = document.createElement('div')
  1461. // container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
  1462. // const app = createSSRApp({
  1463. // setup() {
  1464. // useCssVars(() => ({
  1465. // 'foo.bar': 'red',
  1466. // }))
  1467. // return () => h(Child)
  1468. // },
  1469. // })
  1470. // const Child = {
  1471. // setup() {
  1472. // return () => h('div', { style: 'padding: 4px' })
  1473. // },
  1474. // }
  1475. // app.mount(container)
  1476. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1477. // })
  1478. // })
  1479. // describe('data-allow-mismatch', () => {
  1480. // test('element text content', () => {
  1481. // const { container } = mountWithHydration(
  1482. // `<div data-allow-mismatch="text">foo</div>`,
  1483. // () => h('div', 'bar'),
  1484. // )
  1485. // expect(container.innerHTML).toBe(
  1486. // '<div data-allow-mismatch="text">bar</div>',
  1487. // )
  1488. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  1489. // })
  1490. // test('not enough children', () => {
  1491. // const { container } = mountWithHydration(
  1492. // `<div data-allow-mismatch="children"></div>`,
  1493. // () => h('div', [h('span', 'foo'), h('span', 'bar')]),
  1494. // )
  1495. // expect(container.innerHTML).toBe(
  1496. // '<div data-allow-mismatch="children"><span>foo</span><span>bar</span></div>',
  1497. // )
  1498. // expect(`Hydration children mismatch`).not.toHaveBeenWarned()
  1499. // })
  1500. // test('too many children', () => {
  1501. // const { container } = mountWithHydration(
  1502. // `<div data-allow-mismatch="children"><span>foo</span><span>bar</span></div>`,
  1503. // () => h('div', [h('span', 'foo')]),
  1504. // )
  1505. // expect(container.innerHTML).toBe(
  1506. // '<div data-allow-mismatch="children"><span>foo</span></div>',
  1507. // )
  1508. // expect(`Hydration children mismatch`).not.toHaveBeenWarned()
  1509. // })
  1510. // test('complete mismatch', () => {
  1511. // const { container } = mountWithHydration(
  1512. // `<div data-allow-mismatch="children"><span>foo</span><span>bar</span></div>`,
  1513. // () => h('div', [h('div', 'foo'), h('p', 'bar')]),
  1514. // )
  1515. // expect(container.innerHTML).toBe(
  1516. // '<div data-allow-mismatch="children"><div>foo</div><p>bar</p></div>',
  1517. // )
  1518. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  1519. // })
  1520. // test('fragment mismatch removal', () => {
  1521. // const { container } = mountWithHydration(
  1522. // `<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--></div>`,
  1523. // () => h('div', [h('span', 'replaced')]),
  1524. // )
  1525. // expect(container.innerHTML).toBe(
  1526. // '<div data-allow-mismatch="children"><span>replaced</span></div>',
  1527. // )
  1528. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  1529. // })
  1530. // test('fragment not enough children', () => {
  1531. // const { container } = mountWithHydration(
  1532. // `<div data-allow-mismatch="children"><!--[--><div>foo</div><!--]--><div>baz</div></div>`,
  1533. // () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]),
  1534. // )
  1535. // expect(container.innerHTML).toBe(
  1536. // '<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>',
  1537. // )
  1538. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  1539. // })
  1540. // test('fragment too many children', () => {
  1541. // const { container } = mountWithHydration(
  1542. // `<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>`,
  1543. // () => h('div', [[h('div', 'foo')], h('div', 'baz')]),
  1544. // )
  1545. // expect(container.innerHTML).toBe(
  1546. // '<div data-allow-mismatch="children"><!--[--><div>foo</div><!--]--><div>baz</div></div>',
  1547. // )
  1548. // // fragment ends early and attempts to hydrate the extra <div>bar</div>
  1549. // // as 2nd fragment child.
  1550. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  1551. // // excessive children removal
  1552. // expect(`Hydration children mismatch`).not.toHaveBeenWarned()
  1553. // })
  1554. // test('comment mismatch (element)', () => {
  1555. // const { container } = mountWithHydration(
  1556. // `<div data-allow-mismatch="children"><span></span></div>`,
  1557. // () => h('div', [createCommentVNode('hi')]),
  1558. // )
  1559. // expect(container.innerHTML).toBe(
  1560. // '<div data-allow-mismatch="children"><!--hi--></div>',
  1561. // )
  1562. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  1563. // })
  1564. // test('comment mismatch (text)', () => {
  1565. // const { container } = mountWithHydration(
  1566. // `<div data-allow-mismatch="children">foobar</div>`,
  1567. // () => h('div', [createCommentVNode('hi')]),
  1568. // )
  1569. // expect(container.innerHTML).toBe(
  1570. // '<div data-allow-mismatch="children"><!--hi--></div>',
  1571. // )
  1572. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  1573. // })
  1574. // test('class mismatch', () => {
  1575. // mountWithHydration(
  1576. // `<div class="foo bar" data-allow-mismatch="class"></div>`,
  1577. // () => h('div', { class: 'foo' }),
  1578. // )
  1579. // expect(`Hydration class mismatch`).not.toHaveBeenWarned()
  1580. // })
  1581. // test('style mismatch', () => {
  1582. // mountWithHydration(
  1583. // `<div style="color:red;" data-allow-mismatch="style"></div>`,
  1584. // () => h('div', { style: { color: 'green' } }),
  1585. // )
  1586. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  1587. // })
  1588. // test('attr mismatch', () => {
  1589. // mountWithHydration(`<div data-allow-mismatch="attribute"></div>`, () =>
  1590. // h('div', { id: 'foo' }),
  1591. // )
  1592. // mountWithHydration(
  1593. // `<div id="bar" data-allow-mismatch="attribute"></div>`,
  1594. // () => h('div', { id: 'foo' }),
  1595. // )
  1596. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  1597. // })
  1598. // })
  1599. test.todo('Teleport')
  1600. test.todo('Suspense')
  1601. })