hydration.spec.ts 122 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249
  1. import { createVaporSSRApp, delegateEvents } from '../src'
  2. import { nextTick, reactive, 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. import {
  8. DYNAMIC_COMPONENT_ANCHOR_LABEL,
  9. FOR_ANCHOR_LABEL,
  10. IF_ANCHOR_LABEL,
  11. SLOT_ANCHOR_LABEL,
  12. isString,
  13. } from '@vue/shared'
  14. const Vue = { ...runtimeDom, ...runtimeVapor }
  15. function compile(
  16. sfc: string,
  17. data: runtimeDom.Ref<any>,
  18. components: Record<string, any> = {},
  19. { vapor = true, ssr = false } = {},
  20. ) {
  21. if (!sfc.includes(`<script`)) {
  22. sfc =
  23. `<script vapor>const data = _data; const components = _components;</script>` +
  24. sfc
  25. }
  26. const descriptor = parse(sfc).descriptor
  27. const script = compileScript(descriptor, {
  28. id: 'x',
  29. isProd: true,
  30. inlineTemplate: true,
  31. genDefaultAs: '__sfc__',
  32. vapor,
  33. templateOptions: {
  34. ssr,
  35. },
  36. })
  37. const code =
  38. script.content
  39. .replace(/\bimport {/g, 'const {')
  40. .replace(/ as _/g, ': _')
  41. .replace(/} from ['"]vue['"]/g, `} = Vue`)
  42. .replace(/} from "vue\/server-renderer"/g, '} = VueServerRenderer') +
  43. '\nreturn __sfc__'
  44. return new Function('Vue', 'VueServerRenderer', '_data', '_components', code)(
  45. Vue,
  46. VueServerRenderer,
  47. data,
  48. components,
  49. )
  50. }
  51. async function testHydrationInterop(
  52. code: string,
  53. components?: Record<string, string | { code: string; vapor: boolean }>,
  54. data?: any,
  55. ) {
  56. return testHydration(code, components, data, { interop: true, vapor: false })
  57. }
  58. async function testHydration(
  59. code: string,
  60. components: Record<string, string | { code: string; vapor: boolean }> = {},
  61. data: any = ref('foo'),
  62. { interop = false, vapor = true } = {},
  63. ) {
  64. const ssrComponents: any = {}
  65. const clientComponents: any = {}
  66. for (const key in components) {
  67. const comp = components[key]
  68. const code = isString(comp) ? comp : comp.code
  69. const isVaporComp = isString(comp) || !!comp.vapor
  70. clientComponents[key] = compile(code, data, clientComponents, {
  71. vapor: isVaporComp,
  72. ssr: false,
  73. })
  74. ssrComponents[key] = compile(code, data, ssrComponents, {
  75. vapor: isVaporComp,
  76. ssr: true,
  77. })
  78. }
  79. const serverComp = compile(code, data, ssrComponents, { vapor, ssr: true })
  80. const html = await VueServerRenderer.renderToString(
  81. runtimeDom.createSSRApp(serverComp),
  82. )
  83. const container = document.createElement('div')
  84. document.body.appendChild(container)
  85. container.innerHTML = html
  86. const clientComp = compile(code, data, clientComponents, {
  87. vapor,
  88. ssr: false,
  89. })
  90. let app
  91. if (interop) {
  92. app = runtimeDom.createSSRApp(clientComp)
  93. app.use(runtimeVapor.vaporInteropPlugin)
  94. } else {
  95. app = createVaporSSRApp(clientComp)
  96. }
  97. app.mount(container)
  98. return { data, container }
  99. }
  100. const triggerEvent = (type: string, el: Element) => {
  101. const event = new Event(type, { bubbles: true })
  102. el.dispatchEvent(event)
  103. }
  104. delegateEvents('click')
  105. beforeEach(() => {
  106. document.body.innerHTML = ''
  107. })
  108. describe('Vapor Mode hydration', () => {
  109. describe('text', () => {
  110. test('root text', async () => {
  111. const { data, container } = await testHydration(`
  112. <template>{{ data }}</template>
  113. `)
  114. expect(container.innerHTML).toMatchInlineSnapshot(`"foo"`)
  115. data.value = 'bar'
  116. await nextTick()
  117. expect(container.innerHTML).toMatchInlineSnapshot(`"bar"`)
  118. })
  119. test('consecutive text nodes', async () => {
  120. const { data, container } = await testHydration(`
  121. <template>{{ data }}{{ data }}</template>
  122. `)
  123. expect(container.innerHTML).toMatchInlineSnapshot(`"foofoo"`)
  124. data.value = 'bar'
  125. await nextTick()
  126. expect(container.innerHTML).toMatchInlineSnapshot(`"barbar"`)
  127. })
  128. test('consecutive text nodes with anchor insertion', async () => {
  129. const { data, container } = await testHydration(`
  130. <template><span/>{{ data }}{{ data }}<span/></template>
  131. `)
  132. expect(container.innerHTML).toMatchInlineSnapshot(
  133. `"<!--[--><span></span>foofoo<span></span><!--]-->"`,
  134. )
  135. data.value = 'bar'
  136. await nextTick()
  137. expect(container.innerHTML).toMatchInlineSnapshot(
  138. `"<!--[--><span></span>barbar<span></span><!--]-->"`,
  139. )
  140. })
  141. test('mixed text nodes', async () => {
  142. const { data, container } = await testHydration(`
  143. <template>{{ data }}A{{ data }}B{{ data }}</template>
  144. `)
  145. expect(container.innerHTML).toMatchInlineSnapshot(`"fooAfooBfoo"`)
  146. data.value = 'bar'
  147. await nextTick()
  148. expect(container.innerHTML).toMatchInlineSnapshot(`"barAbarBbar"`)
  149. })
  150. test('mixed text nodes with anchor insertion', async () => {
  151. const { data, container } = await testHydration(`
  152. <template><span/>{{ data }}A{{ data }}B{{ data }}<span/></template>
  153. `)
  154. expect(container.innerHTML).toMatchInlineSnapshot(
  155. `"<!--[--><span></span>fooAfooBfoo<span></span><!--]-->"`,
  156. )
  157. data.value = 'bar'
  158. await nextTick()
  159. expect(container.innerHTML).toMatchInlineSnapshot(
  160. `"<!--[--><span></span>barAbarBbar<span></span><!--]-->"`,
  161. )
  162. })
  163. })
  164. describe('element', () => {
  165. test('root comment', async () => {
  166. const { container } = await testHydration(`
  167. <template><!----></template>
  168. `)
  169. expect(container.innerHTML).toBe('<!---->')
  170. expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
  171. })
  172. test('root with mixed element and text', async () => {
  173. const { container, data } = await testHydration(`
  174. <template> A<span>{{ data }}</span>{{ data }}</template>
  175. `)
  176. expect(container.innerHTML).toMatchInlineSnapshot(
  177. `"<!--[--> A<span>foo</span>foo<!--]-->"`,
  178. )
  179. data.value = 'bar'
  180. await nextTick()
  181. expect(container.innerHTML).toMatchInlineSnapshot(
  182. `"<!--[--> A<span>bar</span>bar<!--]-->"`,
  183. )
  184. })
  185. test('empty element', async () => {
  186. const { container } = await testHydration(`
  187. <template><div/></template>
  188. `)
  189. expect(container.innerHTML).toBe('<div></div>')
  190. expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
  191. })
  192. test('element with binding and text children', async () => {
  193. const { container, data } = await testHydration(`
  194. <template><div :class="data">{{ data }}</div></template>
  195. `)
  196. expect(container.innerHTML).toMatchInlineSnapshot(
  197. `"<div class="foo">foo</div>"`,
  198. )
  199. data.value = 'bar'
  200. await nextTick()
  201. expect(container.innerHTML).toMatchInlineSnapshot(
  202. `"<div class="bar">bar</div>"`,
  203. )
  204. })
  205. test('element with elements children', async () => {
  206. const { container } = await testHydration(`
  207. <template>
  208. <div>
  209. <span>{{ data }}</span>
  210. <span :class="data" @click="data = 'bar'"/>
  211. </div>
  212. </template>
  213. `)
  214. expect(container.innerHTML).toMatchInlineSnapshot(
  215. `"<div><span>foo</span><span class="foo"></span></div>"`,
  216. )
  217. // event handler
  218. triggerEvent('click', container.querySelector('.foo')!)
  219. await nextTick()
  220. expect(container.innerHTML).toMatchInlineSnapshot(
  221. `"<div><span>bar</span><span class="bar"></span></div>"`,
  222. )
  223. })
  224. test('element with ref', async () => {
  225. const { data, container } = await testHydration(
  226. `<template>
  227. <div ref="data">hi</div>
  228. </template>
  229. `,
  230. {},
  231. ref(null),
  232. )
  233. expect(data.value).toBe(container.firstChild)
  234. })
  235. })
  236. describe('component', () => {
  237. test('basic component', async () => {
  238. const { container, data } = await testHydration(
  239. `
  240. <template><div><span></span><components.Child/></div></template>
  241. `,
  242. { Child: `<template>{{ data }}</template>` },
  243. )
  244. expect(container.innerHTML).toMatchInlineSnapshot(
  245. `"<div><span></span>foo</div>"`,
  246. )
  247. data.value = 'bar'
  248. await nextTick()
  249. expect(container.innerHTML).toMatchInlineSnapshot(
  250. `"<div><span></span>bar</div>"`,
  251. )
  252. })
  253. test('fragment component', async () => {
  254. const { container, data } = await testHydration(
  255. `
  256. <template><div><span></span><components.Child/></div></template>
  257. `,
  258. { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
  259. )
  260. expect(container.innerHTML).toMatchInlineSnapshot(
  261. `"<div><span></span><!--[--><div>foo</div>-foo-<!--]--></div>"`,
  262. )
  263. data.value = 'bar'
  264. await nextTick()
  265. expect(container.innerHTML).toMatchInlineSnapshot(
  266. `"<div><span></span><!--[--><div>bar</div>-bar-<!--]--></div>"`,
  267. )
  268. })
  269. test('fragment component with prepend', async () => {
  270. const { container, data } = await testHydration(
  271. `
  272. <template><div><components.Child/><span></span></div></template>
  273. `,
  274. { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
  275. )
  276. expect(container.innerHTML).toMatchInlineSnapshot(
  277. `"<div><!--[--><div>foo</div>-foo-<!--]--><span></span></div>"`,
  278. )
  279. data.value = 'bar'
  280. await nextTick()
  281. expect(container.innerHTML).toMatchInlineSnapshot(
  282. `"<div><!--[--><div>bar</div>-bar-<!--]--><span></span></div>"`,
  283. )
  284. })
  285. test('nested fragment components', async () => {
  286. const { container, data } = await testHydration(
  287. `
  288. <template><div><components.Parent/><span></span></div></template>
  289. `,
  290. {
  291. Parent: `<template><div/><components.Child/><div/></template>`,
  292. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  293. },
  294. )
  295. expect(container.innerHTML).toBe(
  296. `<div>` +
  297. `<!--[-->` +
  298. `<div></div>` +
  299. `<!--[--><div>foo</div>-foo-<!--]-->` +
  300. `<div></div>` +
  301. `<!--]-->` +
  302. `<span></span>` +
  303. `</div>`,
  304. )
  305. data.value = 'bar'
  306. await nextTick()
  307. expect(container.innerHTML).toBe(
  308. `<div>` +
  309. `<!--[-->` +
  310. `<div></div>` +
  311. `<!--[--><div>bar</div>-bar-<!--]-->` +
  312. `<div></div>` +
  313. `<!--]-->` +
  314. `<span></span>` +
  315. `</div>`,
  316. )
  317. })
  318. test('component with anchor insertion', async () => {
  319. const { container, data } = await testHydration(
  320. `<template>
  321. <div>
  322. <span/>
  323. <components.Child/>
  324. <span/>
  325. </div>
  326. </template>
  327. `,
  328. {
  329. Child: `<template>{{ data }}</template>`,
  330. },
  331. )
  332. expect(container.innerHTML).toMatchInlineSnapshot(
  333. `"<div><span></span>foo<span></span></div>"`,
  334. )
  335. data.value = 'bar'
  336. await nextTick()
  337. expect(container.innerHTML).toMatchInlineSnapshot(
  338. `"<div><span></span>bar<span></span></div>"`,
  339. )
  340. })
  341. test('nested components with anchor insertion', async () => {
  342. const { container, data } = await testHydration(
  343. `
  344. <template><components.Parent/></template>
  345. `,
  346. {
  347. Parent: `<template><div><span/><components.Child/><span/></div></template>`,
  348. Child: `<template><div>{{ data }}</div></template>`,
  349. },
  350. )
  351. expect(container.innerHTML).toBe(
  352. `<div><span></span><div>foo</div><span></span></div>`,
  353. )
  354. data.value = 'bar'
  355. await nextTick()
  356. expect(container.innerHTML).toBe(
  357. `<div><span></span><div>bar</div><span></span></div>`,
  358. )
  359. })
  360. test('nested components with multi level anchor insertion', async () => {
  361. const { container, data } = await testHydration(
  362. `
  363. <template><div><span></span><components.Parent/><span></span></div></template>
  364. `,
  365. {
  366. Parent: `<template><div><span/><components.Child/><span/></div></template>`,
  367. Child: `<template><div>{{ data }}</div></template>`,
  368. },
  369. )
  370. expect(container.innerHTML).toBe(
  371. `<div>` +
  372. `<span></span>` +
  373. `<div>` +
  374. `<span></span>` +
  375. `<div>foo</div>` +
  376. `<span></span>` +
  377. `</div>` +
  378. `<span></span>` +
  379. `</div>`,
  380. )
  381. data.value = 'bar'
  382. await nextTick()
  383. expect(container.innerHTML).toBe(
  384. `<div>` +
  385. `<span></span>` +
  386. `<div>` +
  387. `<span></span>` +
  388. `<div>bar</div>` +
  389. `<span></span>` +
  390. `</div>` +
  391. `<span></span>` +
  392. `</div>`,
  393. )
  394. })
  395. test('consecutive components with anchor insertion', async () => {
  396. const { container, data } = await testHydration(
  397. `<template>
  398. <div>
  399. <span/>
  400. <components.Child/>
  401. <components.Child/>
  402. <span/>
  403. </div>
  404. </template>
  405. `,
  406. {
  407. Child: `<template>{{ data }}</template>`,
  408. },
  409. )
  410. expect(container.innerHTML).toBe(
  411. `<div>` +
  412. `<span></span>` +
  413. `foo` +
  414. `<!--[[-->foo<!--]]-->` +
  415. `<span></span>` +
  416. `</div>`,
  417. )
  418. data.value = 'bar'
  419. await nextTick()
  420. expect(container.innerHTML).toBe(
  421. `<div>` +
  422. `<span></span>` +
  423. `bar` +
  424. `<!--[[-->bar<!--]]-->` +
  425. `<span></span>` +
  426. `</div>`,
  427. )
  428. })
  429. test('consecutive components with insertion parent', async () => {
  430. const data = reactive({ foo: 'foo', bar: 'bar' })
  431. const { container } = await testHydration(
  432. `<template>
  433. <div>
  434. <components.Child1/>
  435. <components.Child2/>
  436. </div>
  437. </template>
  438. `,
  439. {
  440. Child1: `<template><span>{{ data.foo }}</span></template>`,
  441. Child2: `<template><span>{{ data.bar }}</span></template>`,
  442. },
  443. data,
  444. )
  445. expect(container.innerHTML).toBe(
  446. `<div><span>foo</span><span>bar</span></div>`,
  447. )
  448. data.foo = 'foo1'
  449. data.bar = 'bar1'
  450. await nextTick()
  451. expect(container.innerHTML).toBe(
  452. `<div><span>foo1</span><span>bar1</span></div>`,
  453. )
  454. })
  455. test('nested consecutive components with anchor insertion', async () => {
  456. const { container, data } = await testHydration(
  457. `
  458. <template><components.Parent/></template>
  459. `,
  460. {
  461. Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
  462. Child: `<template><div>{{ data }}</div></template>`,
  463. },
  464. )
  465. expect(container.innerHTML).toBe(
  466. `<div>` +
  467. `<span></span>` +
  468. `<div>foo</div>` +
  469. `<!--[[--><div>foo</div><!--]]-->` +
  470. `<span></span>` +
  471. `</div>`,
  472. )
  473. data.value = 'bar'
  474. await nextTick()
  475. expect(container.innerHTML).toBe(
  476. `<div>` +
  477. `<span></span>` +
  478. `<div>bar</div>` +
  479. `<!--[[--><div>bar</div><!--]]-->` +
  480. `<span></span>` +
  481. `</div>`,
  482. )
  483. })
  484. test('nested consecutive components with multi level anchor insertion', async () => {
  485. const { container, data } = await testHydration(
  486. `
  487. <template><div><span></span><components.Parent/><span></span></div></template>
  488. `,
  489. {
  490. Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
  491. Child: `<template><div>{{ data }}</div></template>`,
  492. },
  493. )
  494. expect(container.innerHTML).toBe(
  495. `<div>` +
  496. `<span></span>` +
  497. `<div>` +
  498. `<span></span>` +
  499. `<div>foo</div>` +
  500. `<!--[[--><div>foo</div><!--]]-->` +
  501. `<span></span>` +
  502. `</div>` +
  503. `<span></span>` +
  504. `</div>`,
  505. )
  506. data.value = 'bar'
  507. await nextTick()
  508. expect(container.innerHTML).toBe(
  509. `<div>` +
  510. `<span></span>` +
  511. `<div>` +
  512. `<span></span>` +
  513. `<div>bar</div>` +
  514. `<!--[[--><div>bar</div><!--]]-->` +
  515. `<span></span>` +
  516. `</div>` +
  517. `<span></span>` +
  518. `</div>`,
  519. )
  520. })
  521. test('mixed component and element with anchor insertion', async () => {
  522. const { container, data } = await testHydration(
  523. `<template>
  524. <div>
  525. <span/>
  526. <components.Child/>
  527. <span/>
  528. <components.Child/>
  529. <span/>
  530. </div>
  531. </template>
  532. `,
  533. {
  534. Child: `<template>{{ data }}</template>`,
  535. },
  536. )
  537. expect(container.innerHTML).toBe(
  538. `<div>` +
  539. `<span></span>` +
  540. `foo` +
  541. `<span></span>` +
  542. `foo` +
  543. `<span></span>` +
  544. `</div>`,
  545. )
  546. data.value = 'bar'
  547. await nextTick()
  548. expect(container.innerHTML).toBe(
  549. `<div>` +
  550. `<span></span>` +
  551. `bar` +
  552. `<span></span>` +
  553. `bar` +
  554. `<span></span>` +
  555. `</div>`,
  556. )
  557. })
  558. test('mixed component and text with anchor insertion', async () => {
  559. const { container, data } = await testHydration(
  560. `<template>
  561. <div>
  562. <span/>
  563. <components.Child/>
  564. {{ data }}
  565. <components.Child/>
  566. <span/>
  567. </div>
  568. </template>
  569. `,
  570. {
  571. Child: `<template>{{ data }}</template>`,
  572. },
  573. )
  574. expect(container.innerHTML).toBe(
  575. `<div>` +
  576. `<span></span>` +
  577. `foo` +
  578. `<!--[[--> foo <!--]]-->` +
  579. `foo` +
  580. `<span></span>` +
  581. `</div>`,
  582. )
  583. data.value = 'bar'
  584. await nextTick()
  585. expect(container.innerHTML).toBe(
  586. `<div>` +
  587. `<span></span>` +
  588. `bar` +
  589. `<!--[[--> bar <!--]]-->` +
  590. `bar` +
  591. `<span></span>` +
  592. `</div>`,
  593. )
  594. })
  595. test('fragment component with anchor insertion', async () => {
  596. const { container, data } = await testHydration(
  597. `<template>
  598. <div>
  599. <span/>
  600. <components.Child/>
  601. <span/>
  602. </div>
  603. </template>
  604. `,
  605. {
  606. Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
  607. },
  608. )
  609. expect(container.innerHTML).toBe(
  610. `<div>` +
  611. `<span></span>` +
  612. `<!--[--><div>foo</div>-foo<!--]-->` +
  613. `<span></span>` +
  614. `</div>`,
  615. )
  616. data.value = 'bar'
  617. await nextTick()
  618. expect(container.innerHTML).toBe(
  619. `<div>` +
  620. `<span></span>` +
  621. `<!--[--><div>bar</div>-bar<!--]-->` +
  622. `<span></span>` +
  623. `</div>`,
  624. )
  625. })
  626. test('nested fragment component with anchor insertion', async () => {
  627. const { container, data } = await testHydration(
  628. `
  629. <template><components.Parent/></template>
  630. `,
  631. {
  632. Parent: `<template><div><span/><components.Child/><span/></div></template>`,
  633. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  634. },
  635. )
  636. expect(container.innerHTML).toBe(
  637. `<div>` +
  638. `<span></span>` +
  639. `<!--[--><div>foo</div>-foo-<!--]-->` +
  640. `<span></span>` +
  641. `</div>`,
  642. )
  643. data.value = 'bar'
  644. await nextTick()
  645. expect(container.innerHTML).toBe(
  646. `<div>` +
  647. `<span></span>` +
  648. `<!--[--><div>bar</div>-bar-<!--]-->` +
  649. `<span></span>` +
  650. `</div>`,
  651. )
  652. })
  653. test('nested fragment component with multi level anchor insertion', async () => {
  654. const { container, data } = await testHydration(
  655. `
  656. <template><div><span/><components.Parent/><span/></div></template>
  657. `,
  658. {
  659. Parent: `<template><div><span/><components.Child/><span/></div></template>`,
  660. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  661. },
  662. )
  663. expect(container.innerHTML).toBe(
  664. `<div>` +
  665. `<span></span>` +
  666. `<div>` +
  667. `<span></span>` +
  668. `<!--[--><div>foo</div>-foo-<!--]-->` +
  669. `<span></span>` +
  670. `</div>` +
  671. `<span></span>` +
  672. `</div>`,
  673. )
  674. data.value = 'bar'
  675. await nextTick()
  676. expect(container.innerHTML).toBe(
  677. `<div>` +
  678. `<span></span>` +
  679. `<div>` +
  680. `<span></span>` +
  681. `<!--[--><div>bar</div>-bar-<!--]-->` +
  682. `<span></span>` +
  683. `</div>` +
  684. `<span></span>` +
  685. `</div>`,
  686. )
  687. })
  688. test('consecutive fragment components with anchor insertion', async () => {
  689. const { container, data } = await testHydration(
  690. `<template>
  691. <div>
  692. <span/>
  693. <components.Child/>
  694. <components.Child/>
  695. <span/>
  696. </div>
  697. </template>
  698. `,
  699. {
  700. Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
  701. },
  702. )
  703. expect(container.innerHTML).toBe(
  704. `<div>` +
  705. `<span></span>` +
  706. `<!--[--><div>foo</div>-foo<!--]-->` +
  707. `<!--[[-->` +
  708. `<!--[--><div>foo</div>-foo<!--]-->` +
  709. `<!--]]-->` +
  710. `<span></span>` +
  711. `</div>`,
  712. )
  713. data.value = 'bar'
  714. await nextTick()
  715. expect(container.innerHTML).toBe(
  716. `<div>` +
  717. `<span></span>` +
  718. `<!--[--><div>bar</div>-bar<!--]-->` +
  719. `<!--[[-->` +
  720. `<!--[--><div>bar</div>-bar<!--]-->` +
  721. `<!--]]-->` +
  722. `<span></span>` +
  723. `</div>`,
  724. )
  725. })
  726. test('nested consecutive fragment components with anchor insertion', async () => {
  727. const { container, data } = await testHydration(
  728. `
  729. <template><components.Parent/></template>
  730. `,
  731. {
  732. Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
  733. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  734. },
  735. )
  736. expect(container.innerHTML).toBe(
  737. `<div>` +
  738. `<span></span>` +
  739. `<!--[--><div>foo</div>-foo-<!--]-->` +
  740. `<!--[[-->` +
  741. `<!--[--><div>foo</div>-foo-<!--]-->` +
  742. `<!--]]-->` +
  743. `<span></span>` +
  744. `</div>`,
  745. )
  746. data.value = 'bar'
  747. await nextTick()
  748. expect(container.innerHTML).toBe(
  749. `<div>` +
  750. `<span></span>` +
  751. `<!--[--><div>bar</div>-bar-<!--]-->` +
  752. `<!--[[-->` +
  753. `<!--[--><div>bar</div>-bar-<!--]-->` +
  754. `<!--]]-->` +
  755. `<span></span>` +
  756. `</div>`,
  757. )
  758. })
  759. test('nested consecutive fragment components with multi level anchor insertion', async () => {
  760. const { container, data } = await testHydration(
  761. `
  762. <template><div><span></span><components.Parent/><span></span></div></template>
  763. `,
  764. {
  765. Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
  766. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  767. },
  768. )
  769. expect(container.innerHTML).toBe(
  770. `<div>` +
  771. `<span></span>` +
  772. `<div>` +
  773. `<span></span>` +
  774. `<!--[--><div>foo</div>-foo-<!--]-->` +
  775. `<!--[[-->` +
  776. `<!--[--><div>foo</div>-foo-<!--]-->` +
  777. `<!--]]-->` +
  778. `<span></span>` +
  779. `</div>` +
  780. `<span></span>` +
  781. `</div>`,
  782. )
  783. data.value = 'bar'
  784. await nextTick()
  785. expect(container.innerHTML).toBe(
  786. `<div>` +
  787. `<span></span>` +
  788. `<div>` +
  789. `<span></span>` +
  790. `<!--[--><div>bar</div>-bar-<!--]-->` +
  791. `<!--[[-->` +
  792. `<!--[--><div>bar</div>-bar-<!--]-->` +
  793. `<!--]]-->` +
  794. `<span></span>` +
  795. `</div>` +
  796. `<span></span>` +
  797. `</div>`,
  798. )
  799. })
  800. test('nested consecutive fragment components with root level anchor insertion', async () => {
  801. const { container, data } = await testHydration(
  802. `
  803. <template><div><span></span><components.Parent/><span></span></div></template>
  804. `,
  805. {
  806. Parent: `<template><components.Child/><components.Child/></template>`,
  807. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  808. },
  809. )
  810. expect(container.innerHTML).toBe(
  811. `<div>` +
  812. `<span></span>` +
  813. `<!--[-->` +
  814. `<!--[--><div>foo</div>-foo-<!--]-->` +
  815. `<!--[--><div>foo</div>-foo-<!--]-->` +
  816. `<!--]-->` +
  817. `<span></span>` +
  818. `</div>`,
  819. )
  820. data.value = 'bar'
  821. await nextTick()
  822. expect(container.innerHTML).toBe(
  823. `<div>` +
  824. `<span></span>` +
  825. `<!--[-->` +
  826. `<!--[--><div>bar</div>-bar-<!--]-->` +
  827. `<!--[--><div>bar</div>-bar-<!--]-->` +
  828. `<!--]-->` +
  829. `<span></span>` +
  830. `</div>`,
  831. )
  832. })
  833. test('mixed fragment component and element with anchor insertion', async () => {
  834. const { container, data } = await testHydration(
  835. `<template>
  836. <div>
  837. <span/>
  838. <components.Child/>
  839. <span/>
  840. <components.Child/>
  841. <span/>
  842. </div>
  843. </template>
  844. `,
  845. {
  846. Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
  847. },
  848. )
  849. expect(container.innerHTML).toBe(
  850. `<div>` +
  851. `<span></span>` +
  852. `<!--[--><div>foo</div>-foo<!--]-->` +
  853. `<span></span>` +
  854. `<!--[--><div>foo</div>-foo<!--]-->` +
  855. `<span></span>` +
  856. `</div>`,
  857. )
  858. data.value = 'bar'
  859. await nextTick()
  860. expect(container.innerHTML).toBe(
  861. `<div>` +
  862. `<span></span>` +
  863. `<!--[--><div>bar</div>-bar<!--]-->` +
  864. `<span></span>` +
  865. `<!--[--><div>bar</div>-bar<!--]-->` +
  866. `<span></span>` +
  867. `</div>`,
  868. )
  869. })
  870. test('mixed fragment component and text with anchor insertion', async () => {
  871. const { container, data } = await testHydration(
  872. `<template>
  873. <div>
  874. <span/>
  875. <components.Child/>
  876. {{ data }}
  877. <components.Child/>
  878. <span/>
  879. </div>
  880. </template>
  881. `,
  882. {
  883. Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
  884. },
  885. )
  886. expect(container.innerHTML).toBe(
  887. `<div>` +
  888. `<span></span>` +
  889. `<!--[--><div>foo</div>-foo<!--]-->` +
  890. ` <!--[[--> foo <!--]]--> ` +
  891. `<!--[--><div>foo</div>-foo<!--]-->` +
  892. `<span></span>` +
  893. `</div>`,
  894. )
  895. data.value = 'bar'
  896. await nextTick()
  897. expect(container.innerHTML).toBe(
  898. `<div>` +
  899. `<span></span>` +
  900. `<!--[--><div>bar</div>-bar<!--]-->` +
  901. ` <!--[[--> bar <!--]]--> ` +
  902. `<!--[--><div>bar</div>-bar<!--]-->` +
  903. `<span></span>` +
  904. `</div>`,
  905. )
  906. })
  907. })
  908. describe('dynamic component', () => {
  909. const anchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL
  910. test('basic dynamic component', async () => {
  911. const { container, data } = await testHydration(
  912. `<template>
  913. <component :is="components[data]"/>
  914. </template>`,
  915. {
  916. foo: `<template><div>foo</div></template>`,
  917. bar: `<template><div>bar</div></template>`,
  918. },
  919. ref('foo'),
  920. )
  921. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  922. data.value = 'bar'
  923. await nextTick()
  924. expect(container.innerHTML).toBe(`<div>bar</div><!--${anchorLabel}-->`)
  925. })
  926. test('dynamic component with anchor insertion', async () => {
  927. const { container, data } = await testHydration(
  928. `<template>
  929. <div>
  930. <span/>
  931. <component :is="components[data]"/>
  932. <span/>
  933. </div>
  934. </template>`,
  935. {
  936. foo: `<template><div>foo</div></template>`,
  937. bar: `<template><div>bar</div></template>`,
  938. },
  939. ref('foo'),
  940. )
  941. expect(container.innerHTML).toBe(
  942. `<div>` +
  943. `<span></span>` +
  944. `<div>foo</div><!--${anchorLabel}-->` +
  945. `<span></span>` +
  946. `</div>`,
  947. )
  948. data.value = 'bar'
  949. await nextTick()
  950. expect(container.innerHTML).toBe(
  951. `<div>` +
  952. `<span></span>` +
  953. `<div>bar</div><!--${anchorLabel}-->` +
  954. `<span></span>` +
  955. `</div>`,
  956. )
  957. })
  958. test('consecutive dynamic components with anchor insertion', async () => {
  959. const { container, data } = await testHydration(
  960. `<template>
  961. <div>
  962. <span/>
  963. <component :is="components[data]"/>
  964. <component :is="components[data]"/>
  965. <span/>
  966. </div>
  967. </template>`,
  968. {
  969. foo: `<template><div>foo</div></template>`,
  970. bar: `<template><div>bar</div></template>`,
  971. },
  972. ref('foo'),
  973. )
  974. expect(container.innerHTML).toBe(
  975. `<div>` +
  976. `<span></span>` +
  977. `<div>foo</div><!--${anchorLabel}-->` +
  978. `<!--[[--><div>foo</div><!--${anchorLabel}--><!--]]-->` +
  979. `<span></span>` +
  980. `</div>`,
  981. )
  982. data.value = 'bar'
  983. await nextTick()
  984. expect(container.innerHTML).toBe(
  985. `<div>` +
  986. `<span></span>` +
  987. `<div>bar</div><!--${anchorLabel}-->` +
  988. `<!--[[--><div>bar</div><!--${anchorLabel}--><!--]]-->` +
  989. `<span></span>` +
  990. `</div>`,
  991. )
  992. })
  993. test('dynamic component fallback', async () => {
  994. const { container, data } = await testHydration(
  995. `<template>
  996. <component :is="'button'">
  997. <span>{{ data }}</span>
  998. </component>
  999. </template>`,
  1000. {},
  1001. ref('foo'),
  1002. )
  1003. expect(container.innerHTML).toBe(
  1004. `<button><span>foo</span></button><!--${anchorLabel}-->`,
  1005. )
  1006. data.value = 'bar'
  1007. await nextTick()
  1008. expect(container.innerHTML).toBe(
  1009. `<button><span>bar</span></button><!--${anchorLabel}-->`,
  1010. )
  1011. })
  1012. })
  1013. describe('if', () => {
  1014. const anchorLabel = IF_ANCHOR_LABEL
  1015. test('basic toggle - true -> false', async () => {
  1016. const data = ref(true)
  1017. const { container } = await testHydration(
  1018. `<template>
  1019. <div v-if="data">foo</div>
  1020. </template>`,
  1021. undefined,
  1022. data,
  1023. )
  1024. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1025. data.value = false
  1026. await nextTick()
  1027. expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
  1028. })
  1029. test('basic toggle - false -> true', async () => {
  1030. const data = ref(false)
  1031. const { container } = await testHydration(
  1032. `<template>
  1033. <div v-if="data">foo</div>
  1034. </template>`,
  1035. undefined,
  1036. data,
  1037. )
  1038. // v-if="false" is rendered as <!----> in the server-rendered HTML
  1039. // it reused as anchor, so the anchor label is empty
  1040. expect(container.innerHTML).toBe(`<!---->`)
  1041. data.value = true
  1042. await nextTick()
  1043. expect(container.innerHTML).toBe(`<div>foo</div><!---->`)
  1044. })
  1045. test('v-if on insertion parent', async () => {
  1046. const data = ref(true)
  1047. const { container } = await testHydration(
  1048. `<template>
  1049. <div v-if="data">
  1050. <components.Child/>
  1051. </div>
  1052. </template>`,
  1053. { Child: `<template>foo</template>` },
  1054. data,
  1055. )
  1056. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1057. data.value = false
  1058. await nextTick()
  1059. expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
  1060. data.value = true
  1061. await nextTick()
  1062. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1063. })
  1064. test('v-if/else-if/else chain - switch branches', async () => {
  1065. const data = ref('a')
  1066. const { container } = await testHydration(
  1067. `<template>
  1068. <div v-if="data === 'a'">foo</div>
  1069. <div v-else-if="data === 'b'">bar</div>
  1070. <div v-else>baz</div>
  1071. </template>`,
  1072. undefined,
  1073. data,
  1074. )
  1075. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1076. data.value = 'b'
  1077. await nextTick()
  1078. expect(container.innerHTML).toBe(`<div>bar</div><!--${anchorLabel}-->`)
  1079. data.value = 'c'
  1080. await nextTick()
  1081. expect(container.innerHTML).toBe(`<div>baz</div><!--${anchorLabel}-->`)
  1082. data.value = 'a'
  1083. await nextTick()
  1084. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1085. })
  1086. test('v-if/else-if/else chain - switch branches (PROD)', async () => {
  1087. try {
  1088. __DEV__ = false
  1089. const data = ref('a')
  1090. const { container } = await testHydration(
  1091. `<template>
  1092. <div v-if="data === 'a'">foo</div>
  1093. <div v-else-if="data === 'b'">bar</div>
  1094. <div v-else>baz</div>
  1095. </template>`,
  1096. undefined,
  1097. data,
  1098. )
  1099. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1100. data.value = 'b'
  1101. await nextTick()
  1102. // In PROD, the anchor of v-else-if (DynamicFragment) is an empty text node,
  1103. // so it won't be rendered
  1104. expect(container.innerHTML).toBe(`<div>bar</div><!--${anchorLabel}-->`)
  1105. data.value = 'c'
  1106. await nextTick()
  1107. // same as above
  1108. expect(container.innerHTML).toBe(`<div>baz</div><!--${anchorLabel}-->`)
  1109. data.value = 'a'
  1110. await nextTick()
  1111. expect(container.innerHTML).toBe(`<div>foo</div><!--${anchorLabel}-->`)
  1112. } finally {
  1113. __DEV__ = true
  1114. }
  1115. })
  1116. test('nested if', async () => {
  1117. const data = reactive({ outer: true, inner: true })
  1118. const { container } = await testHydration(
  1119. `<template>
  1120. <div v-if="data.outer">
  1121. <span>outer</span>
  1122. <div v-if="data.inner">inner</div>
  1123. </div>
  1124. </template>`,
  1125. undefined,
  1126. data,
  1127. )
  1128. expect(container.innerHTML).toBe(
  1129. `<div>` +
  1130. `<span>outer</span>` +
  1131. `<div>inner</div><!--${anchorLabel}-->` +
  1132. `</div><!--${anchorLabel}-->`,
  1133. )
  1134. data.inner = false
  1135. await nextTick()
  1136. expect(container.innerHTML).toBe(
  1137. `<div>` +
  1138. `<span>outer</span>` +
  1139. `<!--${anchorLabel}-->` +
  1140. `</div><!--${anchorLabel}-->`,
  1141. )
  1142. data.outer = false
  1143. await nextTick()
  1144. expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
  1145. })
  1146. test('on component', async () => {
  1147. const data = ref(true)
  1148. const { container } = await testHydration(
  1149. `<template>
  1150. <components.Child v-if="data"/>
  1151. </template>`,
  1152. { Child: `<template>foo</template>` },
  1153. data,
  1154. )
  1155. expect(container.innerHTML).toBe(`foo<!--${anchorLabel}-->`)
  1156. data.value = false
  1157. await nextTick()
  1158. expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
  1159. })
  1160. test('consecutive if node', async () => {
  1161. const data = ref(true)
  1162. const { container } = await testHydration(
  1163. `<template>
  1164. <components.Child v-if="data"/>
  1165. </template>`,
  1166. { Child: `<template><div v-if="data">foo</div></template>` },
  1167. data,
  1168. )
  1169. expect(container.innerHTML).toBe(`<div>foo</div><!--if--><!--if-->`)
  1170. data.value = false
  1171. await nextTick()
  1172. expect(container.innerHTML).toBe(`<!--if-->`)
  1173. data.value = true
  1174. await nextTick()
  1175. expect(container.innerHTML).toBe(`<div>foo</div><!--if--><!--if-->`)
  1176. })
  1177. test('v-if/else-if/else chain on component - switch branches', async () => {
  1178. const data = ref('a')
  1179. const { container } = await testHydration(
  1180. `<template>
  1181. <components.Child1 v-if="data === 'a'"/>
  1182. <components.Child2 v-else-if="data === 'b'"/>
  1183. <components.Child3 v-else/>
  1184. </template>`,
  1185. {
  1186. Child1: `<template><span>{{data}} child1</span></template>`,
  1187. Child2: `<template><span>{{data}} child2</span></template>`,
  1188. Child3: `<template><span>{{data}} child3</span></template>`,
  1189. },
  1190. data,
  1191. )
  1192. expect(container.innerHTML).toBe(
  1193. `<span>a child1</span><!--${anchorLabel}-->`,
  1194. )
  1195. data.value = 'b'
  1196. await nextTick()
  1197. expect(container.innerHTML).toBe(
  1198. `<span>b child2</span><!--${anchorLabel}-->`,
  1199. )
  1200. data.value = 'c'
  1201. await nextTick()
  1202. expect(container.innerHTML).toBe(
  1203. `<span>c child3</span><!--${anchorLabel}-->`,
  1204. )
  1205. data.value = 'a'
  1206. await nextTick()
  1207. expect(container.innerHTML).toBe(
  1208. `<span>a child1</span><!--${anchorLabel}-->`,
  1209. )
  1210. })
  1211. test('v-if/else-if/else chain on component - switch branches (PROD)', async () => {
  1212. try {
  1213. __DEV__ = false
  1214. const data = ref('a')
  1215. const { container } = await testHydration(
  1216. `<template>
  1217. <components.Child1 v-if="data === 'a'"/>
  1218. <components.Child2 v-else-if="data === 'b'"/>
  1219. <components.Child3 v-else/>
  1220. </template>`,
  1221. {
  1222. Child1: `<template><span>{{data}} child1</span></template>`,
  1223. Child2: `<template><span>{{data}} child2</span></template>`,
  1224. Child3: `<template><span>{{data}} child3</span></template>`,
  1225. },
  1226. data,
  1227. )
  1228. expect(container.innerHTML).toBe(
  1229. `<span>a child1</span><!--${anchorLabel}-->`,
  1230. )
  1231. data.value = 'b'
  1232. await nextTick()
  1233. // In PROD, the anchor of v-else-if (DynamicFragment) is an empty text node,
  1234. // so it won't be rendered
  1235. expect(container.innerHTML).toBe(
  1236. `<span>b child2</span><!--${anchorLabel}-->`,
  1237. )
  1238. data.value = 'c'
  1239. await nextTick()
  1240. // same as above
  1241. expect(container.innerHTML).toBe(
  1242. `<span>c child3</span><!--${anchorLabel}-->`,
  1243. )
  1244. data.value = 'a'
  1245. await nextTick()
  1246. expect(container.innerHTML).toBe(
  1247. `<span>a child1</span><!--${anchorLabel}-->`,
  1248. )
  1249. } finally {
  1250. __DEV__ = true
  1251. }
  1252. })
  1253. test('on component with anchor insertion', async () => {
  1254. const data = ref(true)
  1255. const { container } = await testHydration(
  1256. `<template>
  1257. <div>
  1258. <span/>
  1259. <components.Child v-if="data"/>
  1260. <span/>
  1261. </div>
  1262. </template>`,
  1263. { Child: `<template>foo</template>` },
  1264. data,
  1265. )
  1266. expect(container.innerHTML).toBe(
  1267. `<div>` +
  1268. `<span></span>` +
  1269. `foo<!--${anchorLabel}-->` +
  1270. `<span></span>` +
  1271. `</div>`,
  1272. )
  1273. data.value = false
  1274. await nextTick()
  1275. expect(container.innerHTML).toBe(
  1276. `<div>` +
  1277. `<span></span>` +
  1278. `<!--${anchorLabel}-->` +
  1279. `<span></span>` +
  1280. `</div>`,
  1281. )
  1282. })
  1283. test('consecutive component with insertion parent', async () => {
  1284. const data = reactive({
  1285. show: true,
  1286. foo: 'foo',
  1287. bar: 'bar',
  1288. })
  1289. const { container } = await testHydration(
  1290. `<template>
  1291. <div v-if="data.show">
  1292. <components.Child/>
  1293. <components.Child2/>
  1294. </div>
  1295. </template>`,
  1296. {
  1297. Child: `<template><span>{{data.foo}}</span></template>`,
  1298. Child2: `<template><span>{{data.bar}}</span></template>`,
  1299. },
  1300. data,
  1301. )
  1302. expect(container.innerHTML).toBe(
  1303. `<div>` +
  1304. `<span>foo</span>` +
  1305. `<span>bar</span>` +
  1306. `</div>` +
  1307. `<!--${anchorLabel}-->`,
  1308. )
  1309. data.show = false
  1310. await nextTick()
  1311. expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
  1312. })
  1313. test('consecutive v-if on component with anchor insertion', async () => {
  1314. const data = ref(true)
  1315. const { container } = await testHydration(
  1316. `<template>
  1317. <div>
  1318. <span/>
  1319. <components.Child v-if="data"/>
  1320. <components.Child v-if="data"/>
  1321. <span/>
  1322. </div>
  1323. </template>`,
  1324. { Child: `<template>foo</template>` },
  1325. data,
  1326. )
  1327. expect(container.innerHTML).toBe(
  1328. `<div>` +
  1329. `<span></span>` +
  1330. `foo<!--${anchorLabel}-->` +
  1331. `foo<!--${anchorLabel}-->` +
  1332. `<span></span>` +
  1333. `</div>`,
  1334. )
  1335. data.value = false
  1336. await nextTick()
  1337. expect(container.innerHTML).toBe(
  1338. `<div>` +
  1339. `<span></span>` +
  1340. `<!--${anchorLabel}-->` +
  1341. `<!--${anchorLabel}-->` +
  1342. `<span></span>` +
  1343. `</div>`,
  1344. )
  1345. })
  1346. test('on fragment component', async () => {
  1347. const data = ref(true)
  1348. const { container } = await testHydration(
  1349. `<template>
  1350. <div>
  1351. <components.Child v-if="data"/>
  1352. </div>
  1353. </template>`,
  1354. {
  1355. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  1356. },
  1357. data,
  1358. )
  1359. expect(container.innerHTML).toBe(
  1360. `<div>` +
  1361. `<!--[--><div>true</div>-true-<!--]-->` +
  1362. `<!--if-->` +
  1363. `</div>`,
  1364. )
  1365. data.value = false
  1366. await nextTick()
  1367. expect(container.innerHTML).toBe(
  1368. `<div><!--[--><!--]--><!--${anchorLabel}--></div>`,
  1369. )
  1370. })
  1371. test('on fragment component with anchor insertion', async () => {
  1372. const data = ref(true)
  1373. const { container } = await testHydration(
  1374. `<template>
  1375. <div>
  1376. <span/>
  1377. <components.Child v-if="data"/>
  1378. <span/>
  1379. </div>
  1380. </template>`,
  1381. {
  1382. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  1383. },
  1384. data,
  1385. )
  1386. expect(container.innerHTML).toBe(
  1387. `<div>` +
  1388. `<span></span>` +
  1389. `<!--[--><div>true</div>-true-<!--]-->` +
  1390. `<!--if-->` +
  1391. `<span></span>` +
  1392. `</div>`,
  1393. )
  1394. data.value = false
  1395. await nextTick()
  1396. expect(container.innerHTML).toBe(
  1397. `<div>` +
  1398. `<span></span>` +
  1399. `<!--[--><!--]-->` +
  1400. `<!--${anchorLabel}-->` +
  1401. `<span></span>` +
  1402. `</div>`,
  1403. )
  1404. })
  1405. test('consecutive v-if on fragment component with anchor insertion', async () => {
  1406. const data = ref(true)
  1407. const { container } = await testHydration(
  1408. `<template>
  1409. <div>
  1410. <span/>
  1411. <components.Child v-if="data"/>
  1412. <components.Child v-if="data"/>
  1413. <span/>
  1414. </div>
  1415. </template>`,
  1416. {
  1417. Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
  1418. },
  1419. data,
  1420. )
  1421. expect(container.innerHTML).toBe(
  1422. `<div>` +
  1423. `<span></span>` +
  1424. `<!--[--><div>true</div>-true-<!--]--><!--${anchorLabel}-->` +
  1425. `<!--[--><div>true</div>-true-<!--]--><!--${anchorLabel}-->` +
  1426. `<span></span>` +
  1427. `</div>`,
  1428. )
  1429. data.value = false
  1430. await nextTick()
  1431. expect(container.innerHTML).toBe(
  1432. `<div>` +
  1433. `<span></span>` +
  1434. `<!--[--><!--]--><!--${anchorLabel}-->` +
  1435. `<!--[--><!--]--><!--${anchorLabel}-->` +
  1436. `<span></span>` +
  1437. `</div>`,
  1438. )
  1439. })
  1440. test('on dynamic component with anchor insertion', async () => {
  1441. const dynamicComponentAnchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL
  1442. const data = ref(true)
  1443. const { container } = await testHydration(
  1444. `<template>
  1445. <div>
  1446. <span/>
  1447. <component :is="components.Child" v-if="data"/>
  1448. <span/>
  1449. </div>
  1450. </template>`,
  1451. { Child: `<template>foo</template>` },
  1452. data,
  1453. )
  1454. expect(container.innerHTML).toBe(
  1455. `<div>` +
  1456. `<span></span>` +
  1457. `foo<!--${dynamicComponentAnchorLabel}--><!--${anchorLabel}-->` +
  1458. `<span></span>` +
  1459. `</div>`,
  1460. )
  1461. data.value = false
  1462. await nextTick()
  1463. expect(container.innerHTML).toBe(
  1464. `<div>` +
  1465. `<span></span>` +
  1466. `<!--${anchorLabel}-->` +
  1467. `<span></span>` +
  1468. `</div>`,
  1469. )
  1470. })
  1471. })
  1472. describe('for', () => {
  1473. const forAnchorLabel = FOR_ANCHOR_LABEL
  1474. const slotAnchorLabel = SLOT_ANCHOR_LABEL
  1475. test('basic v-for', async () => {
  1476. const { container, data } = await testHydration(
  1477. `<template>
  1478. <div>
  1479. <span v-for="item in data" :key="item">{{ item }}</span>
  1480. </div>
  1481. </template>`,
  1482. undefined,
  1483. ref(['a', 'b', 'c']),
  1484. )
  1485. expect(container.innerHTML).toBe(
  1486. `<div>` +
  1487. `<!--[-->` +
  1488. `<span>a</span>` +
  1489. `<span>b</span>` +
  1490. `<span>c</span>` +
  1491. `<!--]--><!--${forAnchorLabel}-->` +
  1492. `</div>`,
  1493. )
  1494. data.value.push('d')
  1495. await nextTick()
  1496. expect(container.innerHTML).toBe(
  1497. `<div>` +
  1498. `<!--[-->` +
  1499. `<span>a</span>` +
  1500. `<span>b</span>` +
  1501. `<span>c</span>` +
  1502. `<!--]-->` +
  1503. `<span>d</span>` +
  1504. `<!--${forAnchorLabel}-->` +
  1505. `</div>`,
  1506. )
  1507. })
  1508. test('v-for with text node', async () => {
  1509. const { container, data } = await testHydration(
  1510. `<template>
  1511. <div>
  1512. <span v-for="item in data" :key="item">{{ item }}</span>
  1513. </div>
  1514. </template>`,
  1515. undefined,
  1516. ref(['a', 'b', 'c']),
  1517. )
  1518. expect(container.innerHTML).toBe(
  1519. `<div>` +
  1520. `<!--[-->` +
  1521. `<span>a</span><span>b</span><span>c</span>` +
  1522. `<!--]--><!--${forAnchorLabel}-->` +
  1523. `</div>`,
  1524. )
  1525. data.value.push('d')
  1526. await nextTick()
  1527. expect(container.innerHTML).toBe(
  1528. `<div>` +
  1529. `<!--[-->` +
  1530. `<span>a</span><span>b</span><span>c</span>` +
  1531. `<!--]-->` +
  1532. `<span>d</span>` +
  1533. `<!--${forAnchorLabel}-->` +
  1534. `</div>`,
  1535. )
  1536. })
  1537. test('v-for with anchor insertion', async () => {
  1538. const { container, data } = await testHydration(
  1539. `<template>
  1540. <div>
  1541. <span/>
  1542. <span v-for="item in data" :key="item">{{ item }}</span>
  1543. <span/>
  1544. </div>
  1545. </template>`,
  1546. undefined,
  1547. ref(['a', 'b', 'c']),
  1548. )
  1549. expect(container.innerHTML).toBe(
  1550. `<div>` +
  1551. `<span></span>` +
  1552. `<!--[-->` +
  1553. `<span>a</span>` +
  1554. `<span>b</span>` +
  1555. `<span>c</span>` +
  1556. `<!--]--><!--${forAnchorLabel}-->` +
  1557. `<span></span>` +
  1558. `</div>`,
  1559. )
  1560. data.value.push('d')
  1561. await nextTick()
  1562. expect(container.innerHTML).toBe(
  1563. `<div>` +
  1564. `<span></span>` +
  1565. `<!--[-->` +
  1566. `<span>a</span>` +
  1567. `<span>b</span>` +
  1568. `<span>c</span>` +
  1569. `<!--]-->` +
  1570. `<span>d</span>` +
  1571. `<!--${forAnchorLabel}-->` +
  1572. `<span></span>` +
  1573. `</div>`,
  1574. )
  1575. data.value.splice(0, 1)
  1576. await nextTick()
  1577. expect(container.innerHTML).toBe(
  1578. `<div>` +
  1579. `<span></span>` +
  1580. `<!--[-->` +
  1581. `<span>b</span>` +
  1582. `<span>c</span>` +
  1583. `<!--]-->` +
  1584. `<span>d</span>` +
  1585. `<!--${forAnchorLabel}-->` +
  1586. `<span></span>` +
  1587. `</div>`,
  1588. )
  1589. })
  1590. test('consecutive v-for with anchor insertion', async () => {
  1591. const { container, data } = await testHydration(
  1592. `<template>
  1593. <div>
  1594. <span/>
  1595. <span v-for="item in data" :key="item">{{ item }}</span>
  1596. <span v-for="item in data" :key="item">{{ item }}</span>
  1597. <span/>
  1598. </div>
  1599. </template>`,
  1600. undefined,
  1601. ref(['a', 'b', 'c']),
  1602. )
  1603. expect(container.innerHTML).toBe(
  1604. `<div>` +
  1605. `<span></span>` +
  1606. `<!--[-->` +
  1607. `<span>a</span>` +
  1608. `<span>b</span>` +
  1609. `<span>c</span>` +
  1610. `<!--]--><!--${forAnchorLabel}-->` +
  1611. `<!--[-->` +
  1612. `<span>a</span>` +
  1613. `<span>b</span>` +
  1614. `<span>c</span>` +
  1615. `<!--]--><!--${forAnchorLabel}-->` +
  1616. `<span></span>` +
  1617. `</div>`,
  1618. )
  1619. data.value.push('d')
  1620. await nextTick()
  1621. expect(container.innerHTML).toBe(
  1622. `<div>` +
  1623. `<span></span>` +
  1624. `<!--[-->` +
  1625. `<span>a</span>` +
  1626. `<span>b</span>` +
  1627. `<span>c</span>` +
  1628. `<!--]-->` +
  1629. `<span>d</span>` +
  1630. `<!--${forAnchorLabel}-->` +
  1631. `<!--[-->` +
  1632. `<span>a</span>` +
  1633. `<span>b</span>` +
  1634. `<span>c</span>` +
  1635. `<!--]-->` +
  1636. `<span>d</span>` +
  1637. `<!--${forAnchorLabel}-->` +
  1638. `<span></span>` +
  1639. `</div>`,
  1640. )
  1641. data.value.splice(0, 2)
  1642. await nextTick()
  1643. expect(container.innerHTML).toBe(
  1644. `<div>` +
  1645. `<span></span>` +
  1646. `<!--[-->` +
  1647. `<span>c</span>` +
  1648. `<!--]-->` +
  1649. `<span>d</span>` +
  1650. `<!--${forAnchorLabel}-->` +
  1651. `<!--[-->` +
  1652. `<span>c</span>` +
  1653. `<!--]-->` +
  1654. `<span>d</span>` +
  1655. `<!--${forAnchorLabel}-->` +
  1656. `<span></span>` +
  1657. `</div>`,
  1658. )
  1659. })
  1660. test('v-for on component', async () => {
  1661. const { container, data } = await testHydration(
  1662. `<template>
  1663. <div>
  1664. <components.Child v-for="item in data" :key="item"/>
  1665. </div>
  1666. </template>`,
  1667. {
  1668. Child: `<template><div>comp</div></template>`,
  1669. },
  1670. ref(['a', 'b', 'c']),
  1671. )
  1672. expect(container.innerHTML).toBe(
  1673. `<div>` +
  1674. `<!--[-->` +
  1675. `<div>comp</div>` +
  1676. `<div>comp</div>` +
  1677. `<div>comp</div>` +
  1678. `<!--]--><!--${forAnchorLabel}-->` +
  1679. `</div>`,
  1680. )
  1681. data.value.push('d')
  1682. await nextTick()
  1683. expect(container.innerHTML).toBe(
  1684. `<div>` +
  1685. `<!--[-->` +
  1686. `<div>comp</div>` +
  1687. `<div>comp</div>` +
  1688. `<div>comp</div>` +
  1689. `<!--]-->` +
  1690. `<div>comp</div>` +
  1691. `<!--${forAnchorLabel}-->` +
  1692. `</div>`,
  1693. )
  1694. })
  1695. test('v-for on component with slots', async () => {
  1696. const { container, data } = await testHydration(
  1697. `<template>
  1698. <div>
  1699. <components.Child v-for="item in data" :key="item">
  1700. <span>{{ item }}</span>
  1701. </components.Child>
  1702. </div>
  1703. </template>`,
  1704. {
  1705. Child: `<template><slot/></template>`,
  1706. },
  1707. ref(['a', 'b', 'c']),
  1708. )
  1709. expect(container.innerHTML).toBe(
  1710. `<div>` +
  1711. `<!--[-->` +
  1712. `<!--[--><span>a</span><!--]--><!--${slotAnchorLabel}-->` +
  1713. `<!--[--><span>b</span><!--]--><!--${slotAnchorLabel}-->` +
  1714. `<!--[--><span>c</span><!--]--><!--${slotAnchorLabel}-->` +
  1715. `<!--]--><!--${forAnchorLabel}-->` +
  1716. `</div>`,
  1717. )
  1718. data.value.push('d')
  1719. await nextTick()
  1720. expect(container.innerHTML).toBe(
  1721. `<div>` +
  1722. `<!--[-->` +
  1723. `<!--[--><span>a</span><!--]--><!--${slotAnchorLabel}-->` +
  1724. `<!--[--><span>b</span><!--]--><!--${slotAnchorLabel}-->` +
  1725. `<!--[--><span>c</span><!--]--><!--${slotAnchorLabel}-->` +
  1726. `<!--]-->` +
  1727. `<span>d</span><!--${slotAnchorLabel}-->` +
  1728. `<!--${forAnchorLabel}-->` +
  1729. `</div>`,
  1730. )
  1731. })
  1732. test('on fragment component', async () => {
  1733. const { container, data } = await testHydration(
  1734. `<template>
  1735. <div>
  1736. <components.Child v-for="item in data" :key="item"/>
  1737. </div>
  1738. </template>`,
  1739. {
  1740. Child: `<template><div>foo</div>-bar-</template>`,
  1741. },
  1742. ref(['a', 'b', 'c']),
  1743. )
  1744. expect(container.innerHTML).toBe(
  1745. `<div>` +
  1746. `<!--[-->` +
  1747. `<!--[--><div>foo</div>-bar-<!--]-->` +
  1748. `<!--[--><div>foo</div>-bar-<!--]-->` +
  1749. `<!--[--><div>foo</div>-bar-<!--]-->` +
  1750. `<!--]--><!--${forAnchorLabel}-->` +
  1751. `</div>`,
  1752. )
  1753. data.value.push('d')
  1754. await nextTick()
  1755. expect(container.innerHTML).toBe(
  1756. `<div>` +
  1757. `<!--[-->` +
  1758. `<!--[--><div>foo</div>-bar-<!--]-->` +
  1759. `<!--[--><div>foo</div>-bar-<!--]-->` +
  1760. `<!--[--><div>foo</div>-bar-<!--]-->` +
  1761. `<!--]-->` +
  1762. `<div>foo</div>-bar-` +
  1763. `<!--${forAnchorLabel}-->` +
  1764. `</div>`,
  1765. )
  1766. })
  1767. })
  1768. describe('slots', () => {
  1769. const slotAnchorLabel = SLOT_ANCHOR_LABEL
  1770. const forAnchorLabel = FOR_ANCHOR_LABEL
  1771. test('basic slot', async () => {
  1772. const { data, container } = await testHydration(
  1773. `<template>
  1774. <components.Child>
  1775. <span>{{data}}</span>
  1776. </components.Child>
  1777. </template>`,
  1778. {
  1779. Child: `<template><slot/></template>`,
  1780. },
  1781. )
  1782. expect(container.innerHTML).toBe(
  1783. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
  1784. )
  1785. data.value = 'bar'
  1786. await nextTick()
  1787. expect(container.innerHTML).toBe(
  1788. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
  1789. )
  1790. })
  1791. test('named slot', async () => {
  1792. const { data, container } = await testHydration(
  1793. `<template>
  1794. <components.Child>
  1795. <template #foo>
  1796. <span>{{data}}</span>
  1797. </template>
  1798. </components.Child>
  1799. </template>`,
  1800. {
  1801. Child: `<template><slot name="foo"/></template>`,
  1802. },
  1803. )
  1804. expect(container.innerHTML).toBe(
  1805. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
  1806. )
  1807. data.value = 'bar'
  1808. await nextTick()
  1809. expect(container.innerHTML).toBe(
  1810. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
  1811. )
  1812. })
  1813. test('named slot with v-if', async () => {
  1814. const { data, container } = await testHydration(
  1815. `<template>
  1816. <components.Child>
  1817. <template #foo v-if="data">
  1818. <span>{{data}}</span>
  1819. </template>
  1820. </components.Child>
  1821. </template>`,
  1822. {
  1823. Child: `<template><slot name="foo"/></template>`,
  1824. },
  1825. )
  1826. expect(container.innerHTML).toBe(
  1827. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
  1828. )
  1829. data.value = false
  1830. await nextTick()
  1831. expect(container.innerHTML).toBe(
  1832. `<!--[--><!--]--><!--${slotAnchorLabel}-->`,
  1833. )
  1834. })
  1835. test('named slot with v-if and v-for', async () => {
  1836. const data = reactive({
  1837. show: true,
  1838. items: ['a', 'b', 'c'],
  1839. })
  1840. const { container } = await testHydration(
  1841. `<template>
  1842. <components.Child>
  1843. <template #foo v-if="data.show">
  1844. <span v-for="item in data.items" :key="item">{{item}}</span>
  1845. </template>
  1846. </components.Child>
  1847. </template>`,
  1848. {
  1849. Child: `<template><slot name="foo"/></template>`,
  1850. },
  1851. data,
  1852. )
  1853. expect(container.innerHTML).toBe(
  1854. `<!--[-->` +
  1855. `<!--[--><span>a</span><span>b</span><span>c</span><!--]--><!--${forAnchorLabel}-->` +
  1856. `<!--]-->` +
  1857. `<!--${slotAnchorLabel}-->`,
  1858. )
  1859. data.show = false
  1860. await nextTick()
  1861. expect(container.innerHTML).toBe(
  1862. `<!--[--><!--[--><!--]--><!--]--><!--${slotAnchorLabel}-->`,
  1863. )
  1864. })
  1865. test('with anchor insertion', async () => {
  1866. const { data, container } = await testHydration(
  1867. `<template>
  1868. <components.Child>
  1869. <span/>
  1870. <span>{{data}}</span>
  1871. <span/>
  1872. </components.Child>
  1873. </template>`,
  1874. {
  1875. Child: `<template><slot/></template>`,
  1876. },
  1877. )
  1878. expect(container.innerHTML).toBe(
  1879. `<!--[-->` +
  1880. `<span></span>` +
  1881. `<span>foo</span>` +
  1882. `<span></span>` +
  1883. `<!--]-->` +
  1884. `<!--${slotAnchorLabel}-->`,
  1885. )
  1886. data.value = 'bar'
  1887. await nextTick()
  1888. expect(container.innerHTML).toBe(
  1889. `<!--[-->` +
  1890. `<span></span>` +
  1891. `<span>bar</span>` +
  1892. `<span></span>` +
  1893. `<!--]-->` +
  1894. `<!--${slotAnchorLabel}-->`,
  1895. )
  1896. })
  1897. test('with multi level anchor insertion', async () => {
  1898. const { data, container } = await testHydration(
  1899. `<template>
  1900. <components.Child>
  1901. <span/>
  1902. <span>{{data}}</span>
  1903. <span/>
  1904. </components.Child>
  1905. </template>`,
  1906. {
  1907. Child: `
  1908. <template>
  1909. <div/>
  1910. <div/>
  1911. <slot/>
  1912. <div/>
  1913. </div>
  1914. </template>`,
  1915. },
  1916. )
  1917. expect(container.innerHTML).toBe(
  1918. `<!--[-->` +
  1919. `<div></div>` +
  1920. `<div></div>` +
  1921. `<!--[-->` +
  1922. `<span></span>` +
  1923. `<span>foo</span>` +
  1924. `<span></span>` +
  1925. `<!--]-->` +
  1926. `<!--${slotAnchorLabel}-->` +
  1927. `<div></div>` +
  1928. `<!--]-->`,
  1929. )
  1930. data.value = 'bar'
  1931. await nextTick()
  1932. expect(container.innerHTML).toBe(
  1933. `<!--[-->` +
  1934. `<div></div>` +
  1935. `<div></div>` +
  1936. `<!--[-->` +
  1937. `<span></span>` +
  1938. `<span>bar</span>` +
  1939. `<span></span>` +
  1940. `<!--]-->` +
  1941. `<!--${slotAnchorLabel}-->` +
  1942. `<div></div>` +
  1943. `<!--]-->`,
  1944. )
  1945. })
  1946. test('mixed slot and text node', async () => {
  1947. const data = reactive({
  1948. text: 'foo',
  1949. msg: 'hi',
  1950. })
  1951. const { container } = await testHydration(
  1952. `<template>
  1953. <components.Child>
  1954. <span>{{data.text}}</span>
  1955. </components.Child>
  1956. </template>`,
  1957. {
  1958. Child: `<template><div><slot/>{{data.msg}}</div></template>`,
  1959. },
  1960. data,
  1961. )
  1962. expect(container.innerHTML).toBe(
  1963. `<div>` +
  1964. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  1965. `hi` +
  1966. `</div>`,
  1967. )
  1968. data.msg = 'bar'
  1969. await nextTick()
  1970. expect(container.innerHTML).toBe(
  1971. `<div>` +
  1972. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  1973. `bar` +
  1974. `</div>`,
  1975. )
  1976. })
  1977. test('mixed root slot and text node', async () => {
  1978. const data = reactive({
  1979. text: 'foo',
  1980. msg: 'hi',
  1981. })
  1982. const { container } = await testHydration(
  1983. `<template>
  1984. <components.Child>
  1985. <span>{{data.text}}</span>
  1986. </components.Child>
  1987. </template>`,
  1988. {
  1989. Child: `<template>{{data.text}}<slot/>{{data.msg}}</template>`,
  1990. },
  1991. data,
  1992. )
  1993. expect(container.innerHTML).toBe(
  1994. `<!--[-->` +
  1995. `foo` +
  1996. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  1997. `hi` +
  1998. `<!--]-->`,
  1999. )
  2000. data.msg = 'bar'
  2001. await nextTick()
  2002. expect(container.innerHTML).toBe(
  2003. `<!--[-->` +
  2004. `foo` +
  2005. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2006. `bar` +
  2007. `<!--]-->`,
  2008. )
  2009. })
  2010. test('mixed consecutive slot and element', async () => {
  2011. const data = reactive({
  2012. text: 'foo',
  2013. msg: 'hi',
  2014. })
  2015. const { container } = await testHydration(
  2016. `<template>
  2017. <components.Child>
  2018. <template #foo><span>{{data.text}}</span></template>
  2019. <template #bar><span>bar</span></template>
  2020. </components.Child>
  2021. </template>`,
  2022. {
  2023. Child: `<template><div><slot name="foo"/><slot name="bar"/><div>{{data.msg}}</div></div></template>`,
  2024. },
  2025. data,
  2026. )
  2027. expect(container.innerHTML).toBe(
  2028. `<div>` +
  2029. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2030. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
  2031. `<div>hi</div>` +
  2032. `</div>`,
  2033. )
  2034. data.msg = 'bar'
  2035. await nextTick()
  2036. expect(container.innerHTML).toBe(
  2037. `<div>` +
  2038. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2039. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
  2040. `<div>bar</div>` +
  2041. `</div>`,
  2042. )
  2043. })
  2044. test('mixed slot and element', async () => {
  2045. const data = reactive({
  2046. text: 'foo',
  2047. msg: 'hi',
  2048. })
  2049. const { container } = await testHydration(
  2050. `<template>
  2051. <components.Child>
  2052. <span>{{data.text}}</span>
  2053. </components.Child>
  2054. </template>`,
  2055. {
  2056. Child: `<template><div><slot/><div>{{data.msg}}</div></div></template>`,
  2057. },
  2058. data,
  2059. )
  2060. expect(container.innerHTML).toBe(
  2061. `<div>` +
  2062. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2063. `<div>hi</div>` +
  2064. `</div>`,
  2065. )
  2066. data.msg = 'bar'
  2067. await nextTick()
  2068. expect(container.innerHTML).toBe(
  2069. `<div>` +
  2070. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2071. `<div>bar</div>` +
  2072. `</div>`,
  2073. )
  2074. })
  2075. test('mixed slot and component', async () => {
  2076. const data = reactive({
  2077. msg1: 'foo',
  2078. msg2: 'bar',
  2079. })
  2080. const { container } = await testHydration(
  2081. `<template>
  2082. <components.Child>
  2083. <span>{{data.msg1}}</span>
  2084. </components.Child>
  2085. </template>`,
  2086. {
  2087. Child: `
  2088. <template>
  2089. <div>
  2090. <components.Child2/>
  2091. <slot/>
  2092. <components.Child2/>
  2093. </div>
  2094. </template>`,
  2095. Child2: `
  2096. <template>
  2097. <div>{{data.msg2}}</div>
  2098. </template>`,
  2099. },
  2100. data,
  2101. )
  2102. expect(container.innerHTML).toBe(
  2103. `<div>` +
  2104. `<div>bar</div>` +
  2105. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2106. `<div>bar</div>` +
  2107. `</div>`,
  2108. )
  2109. data.msg2 = 'hello'
  2110. await nextTick()
  2111. expect(container.innerHTML).toBe(
  2112. `<div>` +
  2113. `<div>hello</div>` +
  2114. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2115. `<div>hello</div>` +
  2116. `</div>`,
  2117. )
  2118. })
  2119. test('mixed slot and fragment component', async () => {
  2120. const data = reactive({
  2121. msg1: 'foo',
  2122. msg2: 'bar',
  2123. })
  2124. const { container } = await testHydration(
  2125. `<template>
  2126. <components.Child>
  2127. <span>{{data.msg1}}</span>
  2128. </components.Child>
  2129. </template>`,
  2130. {
  2131. Child: `
  2132. <template>
  2133. <div>
  2134. <components.Child2/>
  2135. <slot/>
  2136. <components.Child2/>
  2137. </div>
  2138. </template>`,
  2139. Child2: `
  2140. <template>
  2141. <div>{{data.msg1}}</div> {{data.msg2}}
  2142. </template>`,
  2143. },
  2144. data,
  2145. )
  2146. expect(container.innerHTML).toBe(
  2147. `<div>` +
  2148. `<!--[--><div>foo</div> bar<!--]-->` +
  2149. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2150. `<!--[--><div>foo</div> bar<!--]-->` +
  2151. `</div>`,
  2152. )
  2153. data.msg1 = 'hello'
  2154. data.msg2 = 'vapor'
  2155. await nextTick()
  2156. expect(container.innerHTML).toBe(
  2157. `<div>` +
  2158. `<!--[--><div>hello</div> vapor<!--]-->` +
  2159. `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
  2160. `<!--[--><div>hello</div> vapor<!--]-->` +
  2161. `</div>`,
  2162. )
  2163. })
  2164. test('mixed slot and v-if', async () => {
  2165. const data = reactive({
  2166. show: true,
  2167. msg: 'foo',
  2168. })
  2169. const { container } = await testHydration(
  2170. `<template>
  2171. <components.Child>
  2172. <span>{{data.msg}}</span>
  2173. </components.Child>
  2174. </template>`,
  2175. {
  2176. Child: `
  2177. <template>
  2178. <div v-if="data.show">{{data.msg}}</div>
  2179. <slot/>
  2180. <div v-if="data.show">{{data.msg}}</div>
  2181. </template>`,
  2182. },
  2183. data,
  2184. )
  2185. expect(container.innerHTML).toBe(
  2186. `<!--[-->` +
  2187. `<div>foo</div><!--if-->` +
  2188. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2189. `<div>foo</div><!--if-->` +
  2190. `<!--]-->`,
  2191. )
  2192. data.show = false
  2193. await nextTick()
  2194. expect(container.innerHTML).toBe(
  2195. `<!--[-->` +
  2196. `<!--if-->` +
  2197. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2198. `<!--if-->` +
  2199. `<!--]-->`,
  2200. )
  2201. })
  2202. test('mixed slot and v-for', async () => {
  2203. const data = reactive({
  2204. items: ['a', 'b', 'c'],
  2205. msg: 'foo',
  2206. })
  2207. const { container } = await testHydration(
  2208. `<template>
  2209. <components.Child>
  2210. <span>{{data.msg}}</span>
  2211. </components.Child>
  2212. </template>`,
  2213. {
  2214. Child: `
  2215. <template>
  2216. <div v-for="item in data.items" :key="item">{{item}}</div>
  2217. <slot/>
  2218. <div v-for="item in data.items" :key="item">{{item}}</div>
  2219. </template>`,
  2220. },
  2221. data,
  2222. )
  2223. expect(container.innerHTML).toBe(
  2224. `<!--[-->` +
  2225. `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><!--${forAnchorLabel}-->` +
  2226. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2227. `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><!--${forAnchorLabel}-->` +
  2228. `<!--]-->`,
  2229. )
  2230. data.items.push('d')
  2231. await nextTick()
  2232. expect(container.innerHTML).toBe(
  2233. `<!--[-->` +
  2234. `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><div>d</div><!--${forAnchorLabel}-->` +
  2235. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2236. `<!--[--><div>a</div><div>b</div><div>c</div><!--]--><div>d</div><!--${forAnchorLabel}-->` +
  2237. `<!--]-->`,
  2238. )
  2239. })
  2240. test('consecutive slots', async () => {
  2241. const data = reactive({
  2242. msg1: 'foo',
  2243. msg2: 'bar',
  2244. })
  2245. const { container } = await testHydration(
  2246. `<template>
  2247. <components.Child>
  2248. <span>{{data.msg1}}</span>
  2249. <template #bar>
  2250. <span>{{data.msg2}}</span>
  2251. </template>
  2252. </components.Child>
  2253. </template>`,
  2254. {
  2255. Child: `<template><slot/><slot name="bar"/></template>`,
  2256. },
  2257. data,
  2258. )
  2259. expect(container.innerHTML).toBe(
  2260. `<!--[-->` +
  2261. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2262. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
  2263. `<!--]-->`,
  2264. )
  2265. data.msg1 = 'hello'
  2266. data.msg2 = 'vapor'
  2267. await nextTick()
  2268. expect(container.innerHTML).toBe(
  2269. `<!--[-->` +
  2270. `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
  2271. `<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
  2272. `<!--]-->`,
  2273. )
  2274. })
  2275. test('consecutive slots with anchor insertion', async () => {
  2276. const data = reactive({
  2277. msg1: 'foo',
  2278. msg2: 'bar',
  2279. })
  2280. const { container } = await testHydration(
  2281. `<template>
  2282. <components.Child>
  2283. <span>{{data.msg1}}</span>
  2284. <template #bar>
  2285. <span>{{data.msg2}}</span>
  2286. </template>
  2287. </components.Child>
  2288. </template>`,
  2289. {
  2290. Child: `<template>
  2291. <div>
  2292. <span/>
  2293. <slot/>
  2294. <slot name="bar"/>
  2295. <span/>
  2296. </div>
  2297. </template>`,
  2298. },
  2299. data,
  2300. )
  2301. expect(container.innerHTML).toBe(
  2302. `<div>` +
  2303. `<span></span>` +
  2304. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2305. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
  2306. `<span></span>` +
  2307. `</div>`,
  2308. )
  2309. data.msg1 = 'hello'
  2310. data.msg2 = 'vapor'
  2311. await nextTick()
  2312. expect(container.innerHTML).toBe(
  2313. `<div>` +
  2314. `<span></span>` +
  2315. `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
  2316. `<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
  2317. `<span></span>` +
  2318. `</div>`,
  2319. )
  2320. })
  2321. test('consecutive slots prepend', async () => {
  2322. const data = reactive({
  2323. msg1: 'foo',
  2324. msg2: 'bar',
  2325. msg3: 'baz',
  2326. })
  2327. const { container } = await testHydration(
  2328. `<template>
  2329. <components.Child>
  2330. <template #foo>
  2331. <span>{{data.msg1}}</span>
  2332. </template>
  2333. <template #bar>
  2334. <span>{{data.msg2}}</span>
  2335. </template>
  2336. </components.Child>
  2337. </template>`,
  2338. {
  2339. Child: `<template>
  2340. <div>
  2341. <slot name="foo"/>
  2342. <slot name="bar"/>
  2343. <div>{{data.msg3}}</div>
  2344. </div>
  2345. </template>`,
  2346. },
  2347. data,
  2348. )
  2349. expect(container.innerHTML).toBe(
  2350. `<div>` +
  2351. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
  2352. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
  2353. `<div>baz</div>` +
  2354. `</div>`,
  2355. )
  2356. data.msg1 = 'hello'
  2357. data.msg2 = 'vapor'
  2358. await nextTick()
  2359. expect(container.innerHTML).toBe(
  2360. `<div>` +
  2361. `<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
  2362. `<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
  2363. `<div>baz</div>` +
  2364. `</div>`,
  2365. )
  2366. })
  2367. test('slot fallback', async () => {
  2368. const data = reactive({
  2369. foo: 'foo',
  2370. })
  2371. const { container } = await testHydration(
  2372. `<template>
  2373. <components.Child>
  2374. </components.Child>
  2375. </template>`,
  2376. {
  2377. Child: `<template><slot><span>{{data.foo}}</span></slot></template>`,
  2378. },
  2379. data,
  2380. )
  2381. expect(container.innerHTML).toBe(
  2382. `<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->`,
  2383. )
  2384. data.foo = 'bar'
  2385. await nextTick()
  2386. expect(container.innerHTML).toBe(
  2387. `<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->`,
  2388. )
  2389. })
  2390. })
  2391. describe.todo('transition', async () => {
  2392. test('transition appear', async () => {})
  2393. test('transition appear with v-if', async () => {})
  2394. test('transition appear with v-show', async () => {})
  2395. test('transition appear w/ event listener', async () => {})
  2396. })
  2397. describe.todo('async component')
  2398. describe.todo('data-allow-mismatch')
  2399. // test('with data-allow-mismatch component when using onServerPrefetch', async () => {
  2400. // const Comp = {
  2401. // template: `
  2402. // <div>Comp2</div>
  2403. // `,
  2404. // }
  2405. // let foo: any
  2406. // const App = {
  2407. // setup() {
  2408. // const flag = ref(true)
  2409. // foo = () => {
  2410. // flag.value = false
  2411. // }
  2412. // onServerPrefetch(() => (flag.value = false))
  2413. // return { flag }
  2414. // },
  2415. // components: {
  2416. // Comp,
  2417. // },
  2418. // template: `
  2419. // <span data-allow-mismatch>
  2420. // <Comp v-if="flag"></Comp>
  2421. // </span>
  2422. // `,
  2423. // }
  2424. // // hydrate
  2425. // const container = document.createElement('div')
  2426. // container.innerHTML = await renderToString(h(App))
  2427. // createSSRApp(App).mount(container)
  2428. // expect(container.innerHTML).toBe(
  2429. // '<span data-allow-mismatch=""><div>Comp2</div></span>',
  2430. // )
  2431. // foo()
  2432. // await nextTick()
  2433. // expect(container.innerHTML).toBe(
  2434. // '<span data-allow-mismatch=""><!--v-if--></span>',
  2435. // )
  2436. // })
  2437. // // compile SSR + client render fn from the same template & hydrate
  2438. // test('full compiler integration', async () => {
  2439. // const mounted: string[] = []
  2440. // const log = vi.fn()
  2441. // const toggle = ref(true)
  2442. // const Child = {
  2443. // data() {
  2444. // return {
  2445. // count: 0,
  2446. // text: 'hello',
  2447. // style: {
  2448. // color: 'red',
  2449. // },
  2450. // }
  2451. // },
  2452. // mounted() {
  2453. // mounted.push('child')
  2454. // },
  2455. // template: `
  2456. // <div>
  2457. // <span class="count" :style="style">{{ count }}</span>
  2458. // <button class="inc" @click="count++">inc</button>
  2459. // <button class="change" @click="style.color = 'green'" >change color</button>
  2460. // <button class="emit" @click="$emit('foo')">emit</button>
  2461. // <span class="text">{{ text }}</span>
  2462. // <input v-model="text">
  2463. // </div>
  2464. // `,
  2465. // }
  2466. // const App = {
  2467. // setup() {
  2468. // return { toggle }
  2469. // },
  2470. // mounted() {
  2471. // mounted.push('parent')
  2472. // },
  2473. // template: `
  2474. // <div>
  2475. // <span>hello</span>
  2476. // <template v-if="toggle">
  2477. // <Child @foo="log('child')"/>
  2478. // <template v-if="true">
  2479. // <button class="parent-click" @click="log('click')">click me</button>
  2480. // </template>
  2481. // </template>
  2482. // <span>hello</span>
  2483. // </div>`,
  2484. // components: {
  2485. // Child,
  2486. // },
  2487. // methods: {
  2488. // log,
  2489. // },
  2490. // }
  2491. // const container = document.createElement('div')
  2492. // // server render
  2493. // container.innerHTML = await renderToString(h(App))
  2494. // // hydrate
  2495. // createSSRApp(App).mount(container)
  2496. // // assert interactions
  2497. // // 1. parent button click
  2498. // triggerEvent('click', container.querySelector('.parent-click')!)
  2499. // expect(log).toHaveBeenCalledWith('click')
  2500. // // 2. child inc click + text interpolation
  2501. // const count = container.querySelector('.count') as HTMLElement
  2502. // expect(count.textContent).toBe(`0`)
  2503. // triggerEvent('click', container.querySelector('.inc')!)
  2504. // await nextTick()
  2505. // expect(count.textContent).toBe(`1`)
  2506. // // 3. child color click + style binding
  2507. // expect(count.style.color).toBe('red')
  2508. // triggerEvent('click', container.querySelector('.change')!)
  2509. // await nextTick()
  2510. // expect(count.style.color).toBe('green')
  2511. // // 4. child event emit
  2512. // triggerEvent('click', container.querySelector('.emit')!)
  2513. // expect(log).toHaveBeenCalledWith('child')
  2514. // // 5. child v-model
  2515. // const text = container.querySelector('.text')!
  2516. // const input = container.querySelector('input')!
  2517. // expect(text.textContent).toBe('hello')
  2518. // input.value = 'bye'
  2519. // triggerEvent('input', input)
  2520. // await nextTick()
  2521. // expect(text.textContent).toBe('bye')
  2522. // })
  2523. // test('handle click error in ssr mode', async () => {
  2524. // const App = {
  2525. // setup() {
  2526. // const throwError = () => {
  2527. // throw new Error('Sentry Error')
  2528. // }
  2529. // return { throwError }
  2530. // },
  2531. // template: `
  2532. // <div>
  2533. // <button class="parent-click" @click="throwError">click me</button>
  2534. // </div>`,
  2535. // }
  2536. // const container = document.createElement('div')
  2537. // // server render
  2538. // container.innerHTML = await renderToString(h(App))
  2539. // // hydrate
  2540. // const app = createSSRApp(App)
  2541. // const handler = (app.config.errorHandler = vi.fn())
  2542. // app.mount(container)
  2543. // // assert interactions
  2544. // // parent button click
  2545. // triggerEvent('click', container.querySelector('.parent-click')!)
  2546. // expect(handler).toHaveBeenCalled()
  2547. // })
  2548. // test('handle blur error in ssr mode', async () => {
  2549. // const App = {
  2550. // setup() {
  2551. // const throwError = () => {
  2552. // throw new Error('Sentry Error')
  2553. // }
  2554. // return { throwError }
  2555. // },
  2556. // template: `
  2557. // <div>
  2558. // <input class="parent-click" @blur="throwError"/>
  2559. // </div>`,
  2560. // }
  2561. // const container = document.createElement('div')
  2562. // // server render
  2563. // container.innerHTML = await renderToString(h(App))
  2564. // // hydrate
  2565. // const app = createSSRApp(App)
  2566. // const handler = (app.config.errorHandler = vi.fn())
  2567. // app.mount(container)
  2568. // // assert interactions
  2569. // // parent blur event
  2570. // triggerEvent('blur', container.querySelector('.parent-click')!)
  2571. // expect(handler).toHaveBeenCalled()
  2572. // })
  2573. // test('async component', async () => {
  2574. // const spy = vi.fn()
  2575. // const Comp = () =>
  2576. // h(
  2577. // 'button',
  2578. // {
  2579. // onClick: spy,
  2580. // },
  2581. // 'hello!',
  2582. // )
  2583. // let serverResolve: any
  2584. // let AsyncComp = defineAsyncComponent(
  2585. // () =>
  2586. // new Promise(r => {
  2587. // serverResolve = r
  2588. // }),
  2589. // )
  2590. // const App = {
  2591. // render() {
  2592. // return ['hello', h(AsyncComp), 'world']
  2593. // },
  2594. // }
  2595. // // server render
  2596. // const htmlPromise = renderToString(h(App))
  2597. // serverResolve(Comp)
  2598. // const html = await htmlPromise
  2599. // expect(html).toMatchInlineSnapshot(
  2600. // `"<!--[-->hello<button>hello!</button>world<!--]-->"`,
  2601. // )
  2602. // // hydration
  2603. // let clientResolve: any
  2604. // AsyncComp = defineAsyncComponent(
  2605. // () =>
  2606. // new Promise(r => {
  2607. // clientResolve = r
  2608. // }),
  2609. // )
  2610. // const container = document.createElement('div')
  2611. // container.innerHTML = html
  2612. // createSSRApp(App).mount(container)
  2613. // // hydration not complete yet
  2614. // triggerEvent('click', container.querySelector('button')!)
  2615. // expect(spy).not.toHaveBeenCalled()
  2616. // // resolve
  2617. // clientResolve(Comp)
  2618. // await new Promise(r => setTimeout(r))
  2619. // // should be hydrated now
  2620. // triggerEvent('click', container.querySelector('button')!)
  2621. // expect(spy).toHaveBeenCalled()
  2622. // })
  2623. // test('update async wrapper before resolve', async () => {
  2624. // const Comp = {
  2625. // render() {
  2626. // return h('h1', 'Async component')
  2627. // },
  2628. // }
  2629. // let serverResolve: any
  2630. // let AsyncComp = defineAsyncComponent(
  2631. // () =>
  2632. // new Promise(r => {
  2633. // serverResolve = r
  2634. // }),
  2635. // )
  2636. // const toggle = ref(true)
  2637. // const App = {
  2638. // setup() {
  2639. // onMounted(() => {
  2640. // // change state, this makes updateComponent(AsyncComp) execute before
  2641. // // the async component is resolved
  2642. // toggle.value = false
  2643. // })
  2644. // return () => {
  2645. // return [toggle.value ? 'hello' : 'world', h(AsyncComp)]
  2646. // }
  2647. // },
  2648. // }
  2649. // // server render
  2650. // const htmlPromise = renderToString(h(App))
  2651. // serverResolve(Comp)
  2652. // const html = await htmlPromise
  2653. // expect(html).toMatchInlineSnapshot(
  2654. // `"<!--[-->hello<h1>Async component</h1><!--]-->"`,
  2655. // )
  2656. // // hydration
  2657. // let clientResolve: any
  2658. // AsyncComp = defineAsyncComponent(
  2659. // () =>
  2660. // new Promise(r => {
  2661. // clientResolve = r
  2662. // }),
  2663. // )
  2664. // const container = document.createElement('div')
  2665. // container.innerHTML = html
  2666. // createSSRApp(App).mount(container)
  2667. // // resolve
  2668. // clientResolve(Comp)
  2669. // await new Promise(r => setTimeout(r))
  2670. // // should be hydrated now
  2671. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  2672. // expect(container.innerHTML).toMatchInlineSnapshot(
  2673. // `"<!--[-->world<h1>Async component</h1><!--]-->"`,
  2674. // )
  2675. // })
  2676. // test('hydrate safely when property used by async setup changed before render', async () => {
  2677. // const toggle = ref(true)
  2678. // const AsyncComp = {
  2679. // async setup() {
  2680. // await new Promise<void>(r => setTimeout(r, 10))
  2681. // return () => h('h1', 'Async component')
  2682. // },
  2683. // }
  2684. // const AsyncWrapper = {
  2685. // render() {
  2686. // return h(AsyncComp)
  2687. // },
  2688. // }
  2689. // const SiblingComp = {
  2690. // setup() {
  2691. // toggle.value = false
  2692. // return () => h('span')
  2693. // },
  2694. // }
  2695. // const App = {
  2696. // setup() {
  2697. // return () =>
  2698. // h(
  2699. // Suspense,
  2700. // {},
  2701. // {
  2702. // default: () => [
  2703. // h('main', {}, [
  2704. // h(AsyncWrapper, {
  2705. // prop: toggle.value ? 'hello' : 'world',
  2706. // }),
  2707. // h(SiblingComp),
  2708. // ]),
  2709. // ],
  2710. // },
  2711. // )
  2712. // },
  2713. // }
  2714. // // server render
  2715. // const html = await renderToString(h(App))
  2716. // expect(html).toMatchInlineSnapshot(
  2717. // `"<main><h1 prop="hello">Async component</h1><span></span></main>"`,
  2718. // )
  2719. // expect(toggle.value).toBe(false)
  2720. // // hydration
  2721. // // reset the value
  2722. // toggle.value = true
  2723. // expect(toggle.value).toBe(true)
  2724. // const container = document.createElement('div')
  2725. // container.innerHTML = html
  2726. // createSSRApp(App).mount(container)
  2727. // await new Promise(r => setTimeout(r, 10))
  2728. // expect(toggle.value).toBe(false)
  2729. // // should be hydrated now
  2730. // expect(container.innerHTML).toMatchInlineSnapshot(
  2731. // `"<main><h1 prop="world">Async component</h1><span></span></main>"`,
  2732. // )
  2733. // })
  2734. // test('hydrate safely when property used by deep nested async setup changed before render', async () => {
  2735. // const toggle = ref(true)
  2736. // const AsyncComp = {
  2737. // async setup() {
  2738. // await new Promise<void>(r => setTimeout(r, 10))
  2739. // return () => h('h1', 'Async component')
  2740. // },
  2741. // }
  2742. // const AsyncWrapper = { render: () => h(AsyncComp) }
  2743. // const AsyncWrapperWrapper = { render: () => h(AsyncWrapper) }
  2744. // const SiblingComp = {
  2745. // setup() {
  2746. // toggle.value = false
  2747. // return () => h('span')
  2748. // },
  2749. // }
  2750. // const App = {
  2751. // setup() {
  2752. // return () =>
  2753. // h(
  2754. // Suspense,
  2755. // {},
  2756. // {
  2757. // default: () => [
  2758. // h('main', {}, [
  2759. // h(AsyncWrapperWrapper, {
  2760. // prop: toggle.value ? 'hello' : 'world',
  2761. // }),
  2762. // h(SiblingComp),
  2763. // ]),
  2764. // ],
  2765. // },
  2766. // )
  2767. // },
  2768. // }
  2769. // // server render
  2770. // const html = await renderToString(h(App))
  2771. // expect(html).toMatchInlineSnapshot(
  2772. // `"<main><h1 prop="hello">Async component</h1><span></span></main>"`,
  2773. // )
  2774. // expect(toggle.value).toBe(false)
  2775. // // hydration
  2776. // // reset the value
  2777. // toggle.value = true
  2778. // expect(toggle.value).toBe(true)
  2779. // const container = document.createElement('div')
  2780. // container.innerHTML = html
  2781. // createSSRApp(App).mount(container)
  2782. // await new Promise(r => setTimeout(r, 10))
  2783. // expect(toggle.value).toBe(false)
  2784. // // should be hydrated now
  2785. // expect(container.innerHTML).toMatchInlineSnapshot(
  2786. // `"<main><h1 prop="world">Async component</h1><span></span></main>"`,
  2787. // )
  2788. // })
  2789. // // #3787
  2790. // test('unmount async wrapper before load', async () => {
  2791. // let resolve: any
  2792. // const AsyncComp = defineAsyncComponent(
  2793. // () =>
  2794. // new Promise(r => {
  2795. // resolve = r
  2796. // }),
  2797. // )
  2798. // const show = ref(true)
  2799. // const root = document.createElement('div')
  2800. // root.innerHTML = '<div><div>async</div></div>'
  2801. // createSSRApp({
  2802. // render() {
  2803. // return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
  2804. // },
  2805. // }).mount(root)
  2806. // show.value = false
  2807. // await nextTick()
  2808. // expect(root.innerHTML).toBe('<div><div>hi</div></div>')
  2809. // resolve({})
  2810. // })
  2811. // //#12362
  2812. // test('nested async wrapper', async () => {
  2813. // const Toggle = defineAsyncComponent(
  2814. // () =>
  2815. // new Promise(r => {
  2816. // r(
  2817. // defineComponent({
  2818. // setup(_, { slots }) {
  2819. // const show = ref(false)
  2820. // onMounted(() => {
  2821. // nextTick(() => {
  2822. // show.value = true
  2823. // })
  2824. // })
  2825. // return () =>
  2826. // withDirectives(
  2827. // h('div', null, [renderSlot(slots, 'default')]),
  2828. // [[vShow, show.value]],
  2829. // )
  2830. // },
  2831. // }) as any,
  2832. // )
  2833. // }),
  2834. // )
  2835. // const Wrapper = defineAsyncComponent(() => {
  2836. // return new Promise(r => {
  2837. // r(
  2838. // defineComponent({
  2839. // render(this: any) {
  2840. // return renderSlot(this.$slots, 'default')
  2841. // },
  2842. // }) as any,
  2843. // )
  2844. // })
  2845. // })
  2846. // const count = ref(0)
  2847. // const fn = vi.fn()
  2848. // const Child = {
  2849. // setup() {
  2850. // onMounted(() => {
  2851. // fn()
  2852. // count.value++
  2853. // })
  2854. // return () => h('div', count.value)
  2855. // },
  2856. // }
  2857. // const App = {
  2858. // render() {
  2859. // return h(Toggle, null, {
  2860. // default: () =>
  2861. // h(Wrapper, null, {
  2862. // default: () =>
  2863. // h(Wrapper, null, {
  2864. // default: () => h(Child),
  2865. // }),
  2866. // }),
  2867. // })
  2868. // },
  2869. // }
  2870. // const root = document.createElement('div')
  2871. // root.innerHTML = await renderToString(h(App))
  2872. // expect(root.innerHTML).toMatchInlineSnapshot(
  2873. // `"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
  2874. // )
  2875. // createSSRApp(App).mount(root)
  2876. // await nextTick()
  2877. // await nextTick()
  2878. // expect(root.innerHTML).toMatchInlineSnapshot(
  2879. // `"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
  2880. // )
  2881. // expect(fn).toBeCalledTimes(1)
  2882. // })
  2883. // test('unmount async wrapper before load (fragment)', async () => {
  2884. // let resolve: any
  2885. // const AsyncComp = defineAsyncComponent(
  2886. // () =>
  2887. // new Promise(r => {
  2888. // resolve = r
  2889. // }),
  2890. // )
  2891. // const show = ref(true)
  2892. // const root = document.createElement('div')
  2893. // root.innerHTML = '<div><!--[-->async<!--]--></div>'
  2894. // createSSRApp({
  2895. // render() {
  2896. // return h('div', [show.value ? h(AsyncComp) : h('div', 'hi')])
  2897. // },
  2898. // }).mount(root)
  2899. // show.value = false
  2900. // await nextTick()
  2901. // expect(root.innerHTML).toBe('<div><div>hi</div></div>')
  2902. // resolve({})
  2903. // })
  2904. // test('elements with camel-case in svg ', () => {
  2905. // const { vnode, container } = mountWithHydration(
  2906. // '<animateTransform></animateTransform>',
  2907. // () => h('animateTransform'),
  2908. // )
  2909. // expect(vnode.el).toBe(container.firstChild)
  2910. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  2911. // })
  2912. // test('SVG as a mount container', () => {
  2913. // const svgContainer = document.createElement('svg')
  2914. // svgContainer.innerHTML = '<g></g>'
  2915. // const app = createSSRApp({
  2916. // render: () => h('g'),
  2917. // })
  2918. // expect(
  2919. // (
  2920. // app.mount(svgContainer).$.subTree as VNode<Node, Element> & {
  2921. // el: Element
  2922. // }
  2923. // ).el instanceof SVGElement,
  2924. // )
  2925. // })
  2926. // test('force hydrate prop with `.prop` modifier', () => {
  2927. // const { container } = mountWithHydration('<input type="checkbox">', () =>
  2928. // h('input', {
  2929. // type: 'checkbox',
  2930. // '.indeterminate': true,
  2931. // }),
  2932. // )
  2933. // expect((container.firstChild! as any).indeterminate).toBe(true)
  2934. // })
  2935. // test('force hydrate input v-model with non-string value bindings', () => {
  2936. // const { container } = mountWithHydration(
  2937. // '<input type="checkbox" value="true">',
  2938. // () =>
  2939. // withDirectives(
  2940. // createVNode(
  2941. // 'input',
  2942. // { type: 'checkbox', 'true-value': true },
  2943. // null,
  2944. // PatchFlags.PROPS,
  2945. // ['true-value'],
  2946. // ),
  2947. // [[vModelCheckbox, true]],
  2948. // ),
  2949. // )
  2950. // expect((container.firstChild as any)._trueValue).toBe(true)
  2951. // })
  2952. // test('force hydrate checkbox with indeterminate', () => {
  2953. // const { container } = mountWithHydration(
  2954. // '<input type="checkbox" indeterminate>',
  2955. // () =>
  2956. // createVNode(
  2957. // 'input',
  2958. // { type: 'checkbox', indeterminate: '' },
  2959. // null,
  2960. // PatchFlags.CACHED,
  2961. // ),
  2962. // )
  2963. // expect((container.firstChild as any).indeterminate).toBe(true)
  2964. // })
  2965. // test('force hydrate select option with non-string value bindings', () => {
  2966. // const { container } = mountWithHydration(
  2967. // '<select><option value="true">ok</option></select>',
  2968. // () =>
  2969. // h('select', [
  2970. // // hoisted because bound value is a constant...
  2971. // createVNode('option', { value: true }, null, -1 /* HOISTED */),
  2972. // ]),
  2973. // )
  2974. // expect((container.firstChild!.firstChild as any)._value).toBe(true)
  2975. // })
  2976. // // #7203
  2977. // test('force hydrate custom element with dynamic props', () => {
  2978. // class MyElement extends HTMLElement {
  2979. // foo = ''
  2980. // constructor() {
  2981. // super()
  2982. // }
  2983. // }
  2984. // customElements.define('my-element-7203', MyElement)
  2985. // const msg = ref('bar')
  2986. // const container = document.createElement('div')
  2987. // container.innerHTML = '<my-element-7203></my-element-7203>'
  2988. // const app = createSSRApp({
  2989. // render: () => h('my-element-7203', { foo: msg.value }),
  2990. // })
  2991. // app.mount(container)
  2992. // expect((container.firstChild as any).foo).toBe(msg.value)
  2993. // })
  2994. // // #5728
  2995. // test('empty text node in slot', () => {
  2996. // const Comp = {
  2997. // render(this: any) {
  2998. // return renderSlot(this.$slots, 'default', {}, () => [
  2999. // createTextVNode(''),
  3000. // ])
  3001. // },
  3002. // }
  3003. // const { container, vnode } = mountWithHydration('<!--[--><!--]-->', () =>
  3004. // h(Comp),
  3005. // )
  3006. // expect(container.childNodes.length).toBe(3)
  3007. // const text = container.childNodes[1]
  3008. // expect(text.nodeType).toBe(3)
  3009. // expect(vnode.el).toBe(container.childNodes[0])
  3010. // // component => slot fragment => text node
  3011. // expect((vnode as any).component?.subTree.children[0].el).toBe(text)
  3012. // })
  3013. // // #7215
  3014. // test('empty text node', () => {
  3015. // const Comp = {
  3016. // render(this: any) {
  3017. // return h('p', [''])
  3018. // },
  3019. // }
  3020. // const { container } = mountWithHydration('<p></p>', () => h(Comp))
  3021. // expect(container.childNodes.length).toBe(1)
  3022. // const p = container.childNodes[0]
  3023. // expect(p.childNodes.length).toBe(1)
  3024. // const text = p.childNodes[0]
  3025. // expect(text.nodeType).toBe(3)
  3026. // })
  3027. // // #11372
  3028. // test('object style value tracking in prod', async () => {
  3029. // __DEV__ = false
  3030. // try {
  3031. // const style = reactive({ color: 'red' })
  3032. // const Comp = {
  3033. // render(this: any) {
  3034. // return (
  3035. // openBlock(),
  3036. // createElementBlock(
  3037. // 'div',
  3038. // {
  3039. // style: normalizeStyle(style),
  3040. // },
  3041. // null,
  3042. // 4 /* STYLE */,
  3043. // )
  3044. // )
  3045. // },
  3046. // }
  3047. // const { container } = mountWithHydration(
  3048. // `<div style="color: red;"></div>`,
  3049. // () => h(Comp),
  3050. // )
  3051. // style.color = 'green'
  3052. // await nextTick()
  3053. // expect(container.innerHTML).toBe(`<div style="color: green;"></div>`)
  3054. // } finally {
  3055. // __DEV__ = true
  3056. // }
  3057. // })
  3058. // test('app.unmount()', async () => {
  3059. // const container = document.createElement('DIV')
  3060. // container.innerHTML = '<button></button>'
  3061. // const App = defineComponent({
  3062. // setup(_, { expose }) {
  3063. // const count = ref(0)
  3064. // expose({ count })
  3065. // return () =>
  3066. // h('button', {
  3067. // onClick: () => count.value++,
  3068. // })
  3069. // },
  3070. // })
  3071. // const app = createSSRApp(App)
  3072. // const vm = app.mount(container)
  3073. // await nextTick()
  3074. // expect((container as any)._vnode).toBeDefined()
  3075. // // @ts-expect-error - expose()'d properties are not available on vm type
  3076. // expect(vm.count).toBe(0)
  3077. // app.unmount()
  3078. // expect((container as any)._vnode).toBe(null)
  3079. // })
  3080. // // #6637
  3081. // test('stringified root fragment', () => {
  3082. // mountWithHydration(`<!--[--><div></div><!--]-->`, () =>
  3083. // createStaticVNode(`<div></div>`, 1),
  3084. // )
  3085. // expect(`mismatch`).not.toHaveBeenWarned()
  3086. // })
  3087. // test('transition appear', () => {
  3088. // const { vnode, container } = mountWithHydration(
  3089. // `<template><div>foo</div></template>`,
  3090. // () =>
  3091. // h(
  3092. // Transition,
  3093. // { appear: true },
  3094. // {
  3095. // default: () => h('div', 'foo'),
  3096. // },
  3097. // ),
  3098. // )
  3099. // expect(container.firstChild).toMatchInlineSnapshot(`
  3100. // <div
  3101. // class="v-enter-from v-enter-active"
  3102. // >
  3103. // foo
  3104. // </div>
  3105. // `)
  3106. // expect(vnode.el).toBe(container.firstChild)
  3107. // expect(`mismatch`).not.toHaveBeenWarned()
  3108. // })
  3109. // test('transition appear with v-if', () => {
  3110. // const show = false
  3111. // const { vnode, container } = mountWithHydration(
  3112. // `<template><!----></template>`,
  3113. // () =>
  3114. // h(
  3115. // Transition,
  3116. // { appear: true },
  3117. // {
  3118. // default: () => (show ? h('div', 'foo') : createCommentVNode('')),
  3119. // },
  3120. // ),
  3121. // )
  3122. // expect(container.firstChild).toMatchInlineSnapshot('<!---->')
  3123. // expect(vnode.el).toBe(container.firstChild)
  3124. // expect(`mismatch`).not.toHaveBeenWarned()
  3125. // })
  3126. // test('transition appear with v-show', () => {
  3127. // const show = false
  3128. // const { vnode, container } = mountWithHydration(
  3129. // `<template><div style="display: none;">foo</div></template>`,
  3130. // () =>
  3131. // h(
  3132. // Transition,
  3133. // { appear: true },
  3134. // {
  3135. // default: () =>
  3136. // withDirectives(createVNode('div', null, 'foo'), [[vShow, show]]),
  3137. // },
  3138. // ),
  3139. // )
  3140. // expect(container.firstChild).toMatchInlineSnapshot(`
  3141. // <div
  3142. // class="v-enter-from v-enter-active"
  3143. // style="display: none;"
  3144. // >
  3145. // foo
  3146. // </div>
  3147. // `)
  3148. // expect((container.firstChild as any)[vShowOriginalDisplay]).toBe('')
  3149. // expect(vnode.el).toBe(container.firstChild)
  3150. // expect(`mismatch`).not.toHaveBeenWarned()
  3151. // })
  3152. // test('transition appear w/ event listener', async () => {
  3153. // const container = document.createElement('div')
  3154. // container.innerHTML = `<template><button>0</button></template>`
  3155. // createSSRApp({
  3156. // data() {
  3157. // return {
  3158. // count: 0,
  3159. // }
  3160. // },
  3161. // template: `
  3162. // <Transition appear>
  3163. // <button @click="count++">{{count}}</button>
  3164. // </Transition>
  3165. // `,
  3166. // }).mount(container)
  3167. // expect(container.firstChild).toMatchInlineSnapshot(`
  3168. // <button
  3169. // class="v-enter-from v-enter-active"
  3170. // >
  3171. // 0
  3172. // </button>
  3173. // `)
  3174. // triggerEvent('click', container.querySelector('button')!)
  3175. // await nextTick()
  3176. // expect(container.firstChild).toMatchInlineSnapshot(`
  3177. // <button
  3178. // class="v-enter-from v-enter-active"
  3179. // >
  3180. // 1
  3181. // </button>
  3182. // `)
  3183. // })
  3184. // test('Suspense + transition appear', async () => {
  3185. // const { vnode, container } = mountWithHydration(
  3186. // `<template><div>foo</div></template>`,
  3187. // () =>
  3188. // h(Suspense, {}, () =>
  3189. // h(
  3190. // Transition,
  3191. // { appear: true },
  3192. // {
  3193. // default: () => h('div', 'foo'),
  3194. // },
  3195. // ),
  3196. // ),
  3197. // )
  3198. // expect(vnode.el).toBe(container.firstChild)
  3199. // // wait for hydration to finish
  3200. // await new Promise(r => setTimeout(r))
  3201. // expect(container.firstChild).toMatchInlineSnapshot(`
  3202. // <div
  3203. // class="v-enter-from v-enter-active"
  3204. // >
  3205. // foo
  3206. // </div>
  3207. // `)
  3208. // await nextTick()
  3209. // expect(vnode.el).toBe(container.firstChild)
  3210. // })
  3211. // // #10607
  3212. // test('update component stable slot (prod + optimized mode)', async () => {
  3213. // __DEV__ = false
  3214. // try {
  3215. // const container = document.createElement('div')
  3216. // container.innerHTML = `<template><div show="false"><!--[--><div><div><!----></div></div><div>0</div><!--]--></div></template>`
  3217. // const Comp = {
  3218. // render(this: any) {
  3219. // return (
  3220. // openBlock(),
  3221. // createElementBlock('div', null, [
  3222. // renderSlot(this.$slots, 'default'),
  3223. // ])
  3224. // )
  3225. // },
  3226. // }
  3227. // const show = ref(false)
  3228. // const clicked = ref(false)
  3229. // const Wrapper = {
  3230. // setup() {
  3231. // const items = ref<number[]>([])
  3232. // onMounted(() => {
  3233. // items.value = [1]
  3234. // })
  3235. // return () => {
  3236. // return (
  3237. // openBlock(),
  3238. // createBlock(Comp, null, {
  3239. // default: withCtx(() => [
  3240. // createElementVNode('div', null, [
  3241. // createElementVNode('div', null, [
  3242. // clicked.value
  3243. // ? (openBlock(),
  3244. // createElementBlock('div', { key: 0 }, 'foo'))
  3245. // : createCommentVNode('v-if', true),
  3246. // ]),
  3247. // ]),
  3248. // createElementVNode(
  3249. // 'div',
  3250. // null,
  3251. // items.value.length,
  3252. // 1 /* TEXT */,
  3253. // ),
  3254. // ]),
  3255. // _: 1 /* STABLE */,
  3256. // })
  3257. // )
  3258. // }
  3259. // },
  3260. // }
  3261. // createSSRApp({
  3262. // components: { Wrapper },
  3263. // data() {
  3264. // return { show }
  3265. // },
  3266. // template: `<Wrapper :show="show"/>`,
  3267. // }).mount(container)
  3268. // await nextTick()
  3269. // expect(container.innerHTML).toBe(
  3270. // `<div show="false"><!--[--><div><div><!----></div></div><div>1</div><!--]--></div>`,
  3271. // )
  3272. // show.value = true
  3273. // await nextTick()
  3274. // expect(async () => {
  3275. // clicked.value = true
  3276. // await nextTick()
  3277. // }).not.toThrow("Cannot read properties of null (reading 'insertBefore')")
  3278. // await nextTick()
  3279. // expect(container.innerHTML).toBe(
  3280. // `<div show="true"><!--[--><div><div><div>foo</div></div></div><div>1</div><!--]--></div>`,
  3281. // )
  3282. // } catch (e) {
  3283. // throw e
  3284. // } finally {
  3285. // __DEV__ = true
  3286. // }
  3287. // })
  3288. // describe('mismatch handling', () => {
  3289. // test('text node', () => {
  3290. // const { container } = mountWithHydration(`foo`, () => 'bar')
  3291. // expect(container.textContent).toBe('bar')
  3292. // expect(`Hydration text mismatch`).toHaveBeenWarned()
  3293. // })
  3294. // test('element text content', () => {
  3295. // const { container } = mountWithHydration(`<div>foo</div>`, () =>
  3296. // h('div', 'bar'),
  3297. // )
  3298. // expect(container.innerHTML).toBe('<div>bar</div>')
  3299. // expect(`Hydration text content mismatch`).toHaveBeenWarned()
  3300. // })
  3301. // test('not enough children', () => {
  3302. // const { container } = mountWithHydration(`<div></div>`, () =>
  3303. // h('div', [h('span', 'foo'), h('span', 'bar')]),
  3304. // )
  3305. // expect(container.innerHTML).toBe(
  3306. // '<div><span>foo</span><span>bar</span></div>',
  3307. // )
  3308. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  3309. // })
  3310. // test('too many children', () => {
  3311. // const { container } = mountWithHydration(
  3312. // `<div><span>foo</span><span>bar</span></div>`,
  3313. // () => h('div', [h('span', 'foo')]),
  3314. // )
  3315. // expect(container.innerHTML).toBe('<div><span>foo</span></div>')
  3316. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  3317. // })
  3318. // test('complete mismatch', () => {
  3319. // const { container } = mountWithHydration(
  3320. // `<div><span>foo</span><span>bar</span></div>`,
  3321. // () => h('div', [h('div', 'foo'), h('p', 'bar')]),
  3322. // )
  3323. // expect(container.innerHTML).toBe('<div><div>foo</div><p>bar</p></div>')
  3324. // expect(`Hydration node mismatch`).toHaveBeenWarnedTimes(2)
  3325. // })
  3326. // test('fragment mismatch removal', () => {
  3327. // const { container } = mountWithHydration(
  3328. // `<div><!--[--><div>foo</div><div>bar</div><!--]--></div>`,
  3329. // () => h('div', [h('span', 'replaced')]),
  3330. // )
  3331. // expect(container.innerHTML).toBe('<div><span>replaced</span></div>')
  3332. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  3333. // })
  3334. // test('fragment not enough children', () => {
  3335. // const { container } = mountWithHydration(
  3336. // `<div><!--[--><div>foo</div><!--]--><div>baz</div></div>`,
  3337. // () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]),
  3338. // )
  3339. // expect(container.innerHTML).toBe(
  3340. // '<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>',
  3341. // )
  3342. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  3343. // })
  3344. // test('fragment too many children', () => {
  3345. // const { container } = mountWithHydration(
  3346. // `<div><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>`,
  3347. // () => h('div', [[h('div', 'foo')], h('div', 'baz')]),
  3348. // )
  3349. // expect(container.innerHTML).toBe(
  3350. // '<div><!--[--><div>foo</div><!--]--><div>baz</div></div>',
  3351. // )
  3352. // // fragment ends early and attempts to hydrate the extra <div>bar</div>
  3353. // // as 2nd fragment child.
  3354. // expect(`Hydration text content mismatch`).toHaveBeenWarned()
  3355. // // excessive children removal
  3356. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  3357. // })
  3358. // test('Teleport target has empty children', () => {
  3359. // const teleportContainer = document.createElement('div')
  3360. // teleportContainer.id = 'teleport'
  3361. // document.body.appendChild(teleportContainer)
  3362. // mountWithHydration('<!--teleport start--><!--teleport end-->', () =>
  3363. // h(Teleport, { to: '#teleport' }, [h('span', 'value')]),
  3364. // )
  3365. // expect(teleportContainer.innerHTML).toBe(`<span>value</span>`)
  3366. // expect(`Hydration children mismatch`).toHaveBeenWarned()
  3367. // })
  3368. // test('comment mismatch (element)', () => {
  3369. // const { container } = mountWithHydration(`<div><span></span></div>`, () =>
  3370. // h('div', [createCommentVNode('hi')]),
  3371. // )
  3372. // expect(container.innerHTML).toBe('<div><!--hi--></div>')
  3373. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  3374. // })
  3375. // test('comment mismatch (text)', () => {
  3376. // const { container } = mountWithHydration(`<div>foobar</div>`, () =>
  3377. // h('div', [createCommentVNode('hi')]),
  3378. // )
  3379. // expect(container.innerHTML).toBe('<div><!--hi--></div>')
  3380. // expect(`Hydration node mismatch`).toHaveBeenWarned()
  3381. // })
  3382. // test('class mismatch', () => {
  3383. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  3384. // h('div', { class: ['foo', 'bar'] }),
  3385. // )
  3386. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  3387. // h('div', { class: { foo: true, bar: true } }),
  3388. // )
  3389. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  3390. // h('div', { class: 'foo bar' }),
  3391. // )
  3392. // // SVG classes
  3393. // mountWithHydration(`<svg class="foo bar"></svg>`, () =>
  3394. // h('svg', { class: 'foo bar' }),
  3395. // )
  3396. // // class with different order
  3397. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  3398. // h('div', { class: 'bar foo' }),
  3399. // )
  3400. // expect(`Hydration class mismatch`).not.toHaveBeenWarned()
  3401. // mountWithHydration(`<div class="foo bar"></div>`, () =>
  3402. // h('div', { class: 'foo' }),
  3403. // )
  3404. // expect(`Hydration class mismatch`).toHaveBeenWarned()
  3405. // })
  3406. // test('style mismatch', () => {
  3407. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  3408. // h('div', { style: { color: 'red' } }),
  3409. // )
  3410. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  3411. // h('div', { style: `color:red;` }),
  3412. // )
  3413. // mountWithHydration(
  3414. // `<div style="color:red; font-size: 12px;"></div>`,
  3415. // () => h('div', { style: `font-size: 12px; color:red;` }),
  3416. // )
  3417. // mountWithHydration(`<div style="color:red;display:none;"></div>`, () =>
  3418. // withDirectives(createVNode('div', { style: 'color: red' }, ''), [
  3419. // [vShow, false],
  3420. // ]),
  3421. // )
  3422. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3423. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  3424. // h('div', { style: { color: 'green' } }),
  3425. // )
  3426. // expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
  3427. // })
  3428. // test('style mismatch when no style attribute is present', () => {
  3429. // mountWithHydration(`<div></div>`, () =>
  3430. // h('div', { style: { color: 'red' } }),
  3431. // )
  3432. // expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
  3433. // })
  3434. // test('style mismatch w/ v-show', () => {
  3435. // mountWithHydration(`<div style="color:red;display:none"></div>`, () =>
  3436. // withDirectives(createVNode('div', { style: 'color: red' }, ''), [
  3437. // [vShow, false],
  3438. // ]),
  3439. // )
  3440. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3441. // mountWithHydration(`<div style="color:red;"></div>`, () =>
  3442. // withDirectives(createVNode('div', { style: 'color: red' }, ''), [
  3443. // [vShow, false],
  3444. // ]),
  3445. // )
  3446. // expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
  3447. // })
  3448. // test('attr mismatch', () => {
  3449. // mountWithHydration(`<div id="foo"></div>`, () => h('div', { id: 'foo' }))
  3450. // mountWithHydration(`<div spellcheck></div>`, () =>
  3451. // h('div', { spellcheck: '' }),
  3452. // )
  3453. // mountWithHydration(`<div></div>`, () => h('div', { id: undefined }))
  3454. // // boolean
  3455. // mountWithHydration(`<select multiple></div>`, () =>
  3456. // h('select', { multiple: true }),
  3457. // )
  3458. // mountWithHydration(`<select multiple></div>`, () =>
  3459. // h('select', { multiple: 'multiple' }),
  3460. // )
  3461. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3462. // mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
  3463. // expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(1)
  3464. // mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
  3465. // expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
  3466. // })
  3467. // test('attr special case: textarea value', () => {
  3468. // mountWithHydration(`<textarea>foo</textarea>`, () =>
  3469. // h('textarea', { value: 'foo' }),
  3470. // )
  3471. // mountWithHydration(`<textarea></textarea>`, () =>
  3472. // h('textarea', { value: '' }),
  3473. // )
  3474. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3475. // mountWithHydration(`<textarea>foo</textarea>`, () =>
  3476. // h('textarea', { value: 'bar' }),
  3477. // )
  3478. // expect(`Hydration attribute mismatch`).toHaveBeenWarned()
  3479. // })
  3480. // // #11873
  3481. // test('<textarea> with newlines at the beginning', async () => {
  3482. // const render = () => h('textarea', null, '\nhello')
  3483. // const html = await renderToString(createSSRApp({ render }))
  3484. // mountWithHydration(html, render)
  3485. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  3486. // })
  3487. // test('<pre> with newlines at the beginning', async () => {
  3488. // const render = () => h('pre', null, '\n')
  3489. // const html = await renderToString(createSSRApp({ render }))
  3490. // mountWithHydration(html, render)
  3491. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  3492. // })
  3493. // test('boolean attr handling', () => {
  3494. // mountWithHydration(`<input />`, () => h('input', { readonly: false }))
  3495. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3496. // mountWithHydration(`<input readonly />`, () =>
  3497. // h('input', { readonly: true }),
  3498. // )
  3499. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3500. // mountWithHydration(`<input readonly="readonly" />`, () =>
  3501. // h('input', { readonly: true }),
  3502. // )
  3503. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3504. // })
  3505. // test('client value is null or undefined', () => {
  3506. // mountWithHydration(`<div></div>`, () =>
  3507. // h('div', { draggable: undefined }),
  3508. // )
  3509. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3510. // mountWithHydration(`<input />`, () => h('input', { type: null }))
  3511. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3512. // })
  3513. // test('should not warn against object values', () => {
  3514. // mountWithHydration(`<input />`, () => h('input', { from: {} }))
  3515. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3516. // })
  3517. // test('should not warn on falsy bindings of non-property keys', () => {
  3518. // mountWithHydration(`<button />`, () => h('button', { href: undefined }))
  3519. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3520. // })
  3521. // test('should not warn on non-renderable option values', () => {
  3522. // mountWithHydration(`<select><option>hello</option></select>`, () =>
  3523. // h('select', [h('option', { value: ['foo'] }, 'hello')]),
  3524. // )
  3525. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3526. // })
  3527. // test('should not warn css v-bind', () => {
  3528. // const container = document.createElement('div')
  3529. // container.innerHTML = `<div style="--foo:red;color:var(--foo);" />`
  3530. // const app = createSSRApp({
  3531. // setup() {
  3532. // useCssVars(() => ({
  3533. // foo: 'red',
  3534. // }))
  3535. // return () => h('div', { style: { color: 'var(--foo)' } })
  3536. // },
  3537. // })
  3538. // app.mount(container)
  3539. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3540. // })
  3541. // // #10317 - test case from #10325
  3542. // test('css vars should only be added to expected on component root dom', () => {
  3543. // const container = document.createElement('div')
  3544. // container.innerHTML = `<div style="--foo:red;"><div style="color:var(--foo);" /></div>`
  3545. // const app = createSSRApp({
  3546. // setup() {
  3547. // useCssVars(() => ({
  3548. // foo: 'red',
  3549. // }))
  3550. // return () =>
  3551. // h('div', null, [h('div', { style: { color: 'var(--foo)' } })])
  3552. // },
  3553. // })
  3554. // app.mount(container)
  3555. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3556. // })
  3557. // // #11188
  3558. // test('css vars support fallthrough', () => {
  3559. // const container = document.createElement('div')
  3560. // container.innerHTML = `<div style="padding: 4px;--foo:red;"></div>`
  3561. // const app = createSSRApp({
  3562. // setup() {
  3563. // useCssVars(() => ({
  3564. // foo: 'red',
  3565. // }))
  3566. // return () => h(Child)
  3567. // },
  3568. // })
  3569. // const Child = {
  3570. // setup() {
  3571. // return () => h('div', { style: 'padding: 4px' })
  3572. // },
  3573. // }
  3574. // app.mount(container)
  3575. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3576. // })
  3577. // // #11189
  3578. // test('should not warn for directives that mutate DOM in created', () => {
  3579. // const container = document.createElement('div')
  3580. // container.innerHTML = `<div class="test red"></div>`
  3581. // const vColor: ObjectDirective = {
  3582. // created(el, binding) {
  3583. // el.classList.add(binding.value)
  3584. // },
  3585. // }
  3586. // const app = createSSRApp({
  3587. // setup() {
  3588. // return () =>
  3589. // withDirectives(h('div', { class: 'test' }), [[vColor, 'red']])
  3590. // },
  3591. // })
  3592. // app.mount(container)
  3593. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3594. // })
  3595. // test('escape css var name', () => {
  3596. // const container = document.createElement('div')
  3597. // container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
  3598. // const app = createSSRApp({
  3599. // setup() {
  3600. // useCssVars(() => ({
  3601. // 'foo.bar': 'red',
  3602. // }))
  3603. // return () => h(Child)
  3604. // },
  3605. // })
  3606. // const Child = {
  3607. // setup() {
  3608. // return () => h('div', { style: 'padding: 4px' })
  3609. // },
  3610. // }
  3611. // app.mount(container)
  3612. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3613. // })
  3614. // })
  3615. // describe('data-allow-mismatch', () => {
  3616. // test('element text content', () => {
  3617. // const { container } = mountWithHydration(
  3618. // `<div data-allow-mismatch="text">foo</div>`,
  3619. // () => h('div', 'bar'),
  3620. // )
  3621. // expect(container.innerHTML).toBe(
  3622. // '<div data-allow-mismatch="text">bar</div>',
  3623. // )
  3624. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  3625. // })
  3626. // test('not enough children', () => {
  3627. // const { container } = mountWithHydration(
  3628. // `<div data-allow-mismatch="children"></div>`,
  3629. // () => h('div', [h('span', 'foo'), h('span', 'bar')]),
  3630. // )
  3631. // expect(container.innerHTML).toBe(
  3632. // '<div data-allow-mismatch="children"><span>foo</span><span>bar</span></div>',
  3633. // )
  3634. // expect(`Hydration children mismatch`).not.toHaveBeenWarned()
  3635. // })
  3636. // test('too many children', () => {
  3637. // const { container } = mountWithHydration(
  3638. // `<div data-allow-mismatch="children"><span>foo</span><span>bar</span></div>`,
  3639. // () => h('div', [h('span', 'foo')]),
  3640. // )
  3641. // expect(container.innerHTML).toBe(
  3642. // '<div data-allow-mismatch="children"><span>foo</span></div>',
  3643. // )
  3644. // expect(`Hydration children mismatch`).not.toHaveBeenWarned()
  3645. // })
  3646. // test('complete mismatch', () => {
  3647. // const { container } = mountWithHydration(
  3648. // `<div data-allow-mismatch="children"><span>foo</span><span>bar</span></div>`,
  3649. // () => h('div', [h('div', 'foo'), h('p', 'bar')]),
  3650. // )
  3651. // expect(container.innerHTML).toBe(
  3652. // '<div data-allow-mismatch="children"><div>foo</div><p>bar</p></div>',
  3653. // )
  3654. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  3655. // })
  3656. // test('fragment mismatch removal', () => {
  3657. // const { container } = mountWithHydration(
  3658. // `<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--></div>`,
  3659. // () => h('div', [h('span', 'replaced')]),
  3660. // )
  3661. // expect(container.innerHTML).toBe(
  3662. // '<div data-allow-mismatch="children"><span>replaced</span></div>',
  3663. // )
  3664. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  3665. // })
  3666. // test('fragment not enough children', () => {
  3667. // const { container } = mountWithHydration(
  3668. // `<div data-allow-mismatch="children"><!--[--><div>foo</div><!--]--><div>baz</div></div>`,
  3669. // () => h('div', [[h('div', 'foo'), h('div', 'bar')], h('div', 'baz')]),
  3670. // )
  3671. // expect(container.innerHTML).toBe(
  3672. // '<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>',
  3673. // )
  3674. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  3675. // })
  3676. // test('fragment too many children', () => {
  3677. // const { container } = mountWithHydration(
  3678. // `<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>`,
  3679. // () => h('div', [[h('div', 'foo')], h('div', 'baz')]),
  3680. // )
  3681. // expect(container.innerHTML).toBe(
  3682. // '<div data-allow-mismatch="children"><!--[--><div>foo</div><!--]--><div>baz</div></div>',
  3683. // )
  3684. // // fragment ends early and attempts to hydrate the extra <div>bar</div>
  3685. // // as 2nd fragment child.
  3686. // expect(`Hydration text content mismatch`).not.toHaveBeenWarned()
  3687. // // excessive children removal
  3688. // expect(`Hydration children mismatch`).not.toHaveBeenWarned()
  3689. // })
  3690. // test('comment mismatch (element)', () => {
  3691. // const { container } = mountWithHydration(
  3692. // `<div data-allow-mismatch="children"><span></span></div>`,
  3693. // () => h('div', [createCommentVNode('hi')]),
  3694. // )
  3695. // expect(container.innerHTML).toBe(
  3696. // '<div data-allow-mismatch="children"><!--hi--></div>',
  3697. // )
  3698. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  3699. // })
  3700. // test('comment mismatch (text)', () => {
  3701. // const { container } = mountWithHydration(
  3702. // `<div data-allow-mismatch="children">foobar</div>`,
  3703. // () => h('div', [createCommentVNode('hi')]),
  3704. // )
  3705. // expect(container.innerHTML).toBe(
  3706. // '<div data-allow-mismatch="children"><!--hi--></div>',
  3707. // )
  3708. // expect(`Hydration node mismatch`).not.toHaveBeenWarned()
  3709. // })
  3710. // test('class mismatch', () => {
  3711. // mountWithHydration(
  3712. // `<div class="foo bar" data-allow-mismatch="class"></div>`,
  3713. // () => h('div', { class: 'foo' }),
  3714. // )
  3715. // expect(`Hydration class mismatch`).not.toHaveBeenWarned()
  3716. // })
  3717. // test('style mismatch', () => {
  3718. // mountWithHydration(
  3719. // `<div style="color:red;" data-allow-mismatch="style"></div>`,
  3720. // () => h('div', { style: { color: 'green' } }),
  3721. // )
  3722. // expect(`Hydration style mismatch`).not.toHaveBeenWarned()
  3723. // })
  3724. // test('attr mismatch', () => {
  3725. // mountWithHydration(`<div data-allow-mismatch="attribute"></div>`, () =>
  3726. // h('div', { id: 'foo' }),
  3727. // )
  3728. // mountWithHydration(
  3729. // `<div id="bar" data-allow-mismatch="attribute"></div>`,
  3730. // () => h('div', { id: 'foo' }),
  3731. // )
  3732. // expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
  3733. // })
  3734. // })
  3735. test.todo('Teleport')
  3736. test.todo('Suspense')
  3737. })
  3738. describe('VDOM hydration interop', () => {
  3739. test('basic vapor component', async () => {
  3740. const data = ref(true)
  3741. const { container } = await testHydrationInterop(
  3742. `<script setup>const data = _data; const components = _components;</script>
  3743. <template>
  3744. <components.VaporChild/>
  3745. </template>`,
  3746. {
  3747. VaporChild: {
  3748. code: `<template>{{ data }}</template>`,
  3749. vapor: true,
  3750. },
  3751. },
  3752. data,
  3753. )
  3754. expect(container.innerHTML).toMatchInlineSnapshot(`"true"`)
  3755. data.value = false
  3756. await nextTick()
  3757. expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
  3758. })
  3759. test('nested components (VDOM -> Vapor -> VDOM)', async () => {
  3760. const data = ref(true)
  3761. const { container } = await testHydrationInterop(
  3762. `<script setup>const data = _data; const components = _components;</script>
  3763. <template>
  3764. <components.VaporChild/>
  3765. </template>`,
  3766. {
  3767. VaporChild: {
  3768. code: `<template><components.VdomChild/></template>`,
  3769. vapor: true,
  3770. },
  3771. VdomChild: {
  3772. code: `<script setup>const data = _data;</script>
  3773. <template>{{ data }}</template>`,
  3774. vapor: false,
  3775. },
  3776. },
  3777. data,
  3778. )
  3779. expect(container.innerHTML).toMatchInlineSnapshot(`"true"`)
  3780. data.value = false
  3781. await nextTick()
  3782. expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
  3783. })
  3784. test('nested components (VDOM -> Vapor -> VDOM (with slot fallback))', async () => {
  3785. const data = ref(true)
  3786. const { container } = await testHydrationInterop(
  3787. `<script setup>const data = _data; const components = _components;</script>
  3788. <template>
  3789. <components.VaporChild/>
  3790. </template>`,
  3791. {
  3792. VaporChild: {
  3793. code: `<template><components.VdomChild/></template>`,
  3794. vapor: true,
  3795. },
  3796. VdomChild: {
  3797. code: `<script setup>const data = _data;</script>
  3798. <template><slot><span>{{data}}</span></slot></template>`,
  3799. vapor: false,
  3800. },
  3801. },
  3802. data,
  3803. )
  3804. expect(container.innerHTML).toMatchInlineSnapshot(
  3805. `"<!--[--><span>true</span><!--]--><!--slot-->"`,
  3806. )
  3807. data.value = false
  3808. await nextTick()
  3809. expect(container.innerHTML).toMatchInlineSnapshot(
  3810. `"<!--[--><span>false</span><!--]--><!--slot-->"`,
  3811. )
  3812. })
  3813. test('nested components (VDOM -> Vapor(with slot fallback) -> VDOM)', async () => {
  3814. const data = ref(true)
  3815. const { container } = await testHydrationInterop(
  3816. `<script setup>const data = _data; const components = _components;</script>
  3817. <template>
  3818. <components.VaporChild/>
  3819. </template>`,
  3820. {
  3821. VaporChild: {
  3822. code: `<template>
  3823. <components.VdomChild>
  3824. <template #default>
  3825. <span>{{data}} vapor fallback</span>
  3826. </template>
  3827. </components.VdomChild>
  3828. </template>`,
  3829. vapor: true,
  3830. },
  3831. VdomChild: {
  3832. code: `<script setup>const data = _data;</script>
  3833. <template><slot><span>vdom fallback</span></slot></template>`,
  3834. vapor: false,
  3835. },
  3836. },
  3837. data,
  3838. )
  3839. expect(container.innerHTML).toMatchInlineSnapshot(
  3840. `"<!--[--><span>true vapor fallback</span><!--]--><!--slot-->"`,
  3841. )
  3842. data.value = false
  3843. await nextTick()
  3844. expect(container.innerHTML).toMatchInlineSnapshot(
  3845. `"<!--[--><span>false vapor fallback</span><!--]--><!--slot-->"`,
  3846. )
  3847. })
  3848. test('vapor slot render vdom component', async () => {
  3849. const data = ref(true)
  3850. const { container } = await testHydrationInterop(
  3851. `<script setup>const data = _data; const components = _components;</script>
  3852. <template>
  3853. <components.VaporChild>
  3854. <components.VdomChild/>
  3855. </components.VaporChild>
  3856. </template>`,
  3857. {
  3858. VaporChild: {
  3859. code: `<template><div><slot/></div></template>`,
  3860. vapor: true,
  3861. },
  3862. VdomChild: {
  3863. code: `<script setup>const data = _data;</script>
  3864. <template>{{ data }}</template>`,
  3865. vapor: false,
  3866. },
  3867. },
  3868. data,
  3869. )
  3870. expect(container.innerHTML).toMatchInlineSnapshot(
  3871. `"<div><!--[-->true<!--]--><!--slot--></div>"`,
  3872. )
  3873. data.value = false
  3874. await nextTick()
  3875. expect(container.innerHTML).toMatchInlineSnapshot(
  3876. `"<div><!--[-->false<!--]--><!--slot--></div>"`,
  3877. )
  3878. })
  3879. })