Transition.spec.ts 110 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533
  1. import type { ElementHandle } from 'puppeteer'
  2. import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
  3. import path from 'node:path'
  4. import { Transition, createApp, h, nextTick, ref } from 'vue'
  5. describe('e2e: Transition', () => {
  6. const { page, html, classList, style, isVisible, timeout, nextFrame, click } =
  7. setupPuppeteer()
  8. const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
  9. const duration = process.env.CI ? 200 : 50
  10. const buffer = process.env.CI ? 50 : 20
  11. const transitionFinish = (time = duration) => timeout(time + buffer)
  12. const classWhenTransitionStart = () =>
  13. page().evaluate(() => {
  14. ;(document.querySelector('#toggleBtn') as any)!.click()
  15. return Promise.resolve().then(() => {
  16. return document.querySelector('#container div')!.className.split(/\s+/g)
  17. })
  18. })
  19. beforeEach(async () => {
  20. await page().goto(baseUrl)
  21. await page().waitForSelector('#app')
  22. })
  23. describe('transition with v-if', () => {
  24. test(
  25. 'basic transition',
  26. async () => {
  27. await page().evaluate(() => {
  28. const { createApp, ref } = (window as any).Vue
  29. createApp({
  30. template: `
  31. <div id="container">
  32. <transition>
  33. <div v-if="toggle" class="test">content</div>
  34. </transition>
  35. </div>
  36. <button id="toggleBtn" @click="click">button</button>
  37. `,
  38. setup: () => {
  39. const toggle = ref(true)
  40. const click = () => (toggle.value = !toggle.value)
  41. return { toggle, click }
  42. },
  43. }).mount('#app')
  44. })
  45. expect(await html('#container')).toBe('<div class="test">content</div>')
  46. // leave
  47. expect(await classWhenTransitionStart()).toStrictEqual([
  48. 'test',
  49. 'v-leave-from',
  50. 'v-leave-active',
  51. ])
  52. await nextFrame()
  53. expect(await classList('.test')).toStrictEqual([
  54. 'test',
  55. 'v-leave-active',
  56. 'v-leave-to',
  57. ])
  58. await transitionFinish()
  59. expect(await html('#container')).toBe('<!--v-if-->')
  60. // enter
  61. expect(await classWhenTransitionStart()).toStrictEqual([
  62. 'test',
  63. 'v-enter-from',
  64. 'v-enter-active',
  65. ])
  66. await nextFrame()
  67. expect(await classList('.test')).toStrictEqual([
  68. 'test',
  69. 'v-enter-active',
  70. 'v-enter-to',
  71. ])
  72. await transitionFinish()
  73. expect(await html('#container')).toBe('<div class="test">content</div>')
  74. },
  75. E2E_TIMEOUT,
  76. )
  77. test(
  78. 'named transition',
  79. async () => {
  80. await page().evaluate(() => {
  81. const { createApp, ref } = (window as any).Vue
  82. createApp({
  83. template: `
  84. <div id="container">
  85. <transition name="test">
  86. <div v-if="toggle" class="test">content</div>
  87. </transition>
  88. </div>
  89. <button id="toggleBtn" @click="click">button</button>
  90. `,
  91. setup: () => {
  92. const toggle = ref(true)
  93. const click = () => (toggle.value = !toggle.value)
  94. return { toggle, click }
  95. },
  96. }).mount('#app')
  97. })
  98. expect(await html('#container')).toBe('<div class="test">content</div>')
  99. // leave
  100. expect(await classWhenTransitionStart()).toStrictEqual([
  101. 'test',
  102. 'test-leave-from',
  103. 'test-leave-active',
  104. ])
  105. await nextFrame()
  106. expect(await classList('.test')).toStrictEqual([
  107. 'test',
  108. 'test-leave-active',
  109. 'test-leave-to',
  110. ])
  111. await transitionFinish()
  112. expect(await html('#container')).toBe('<!--v-if-->')
  113. // enter
  114. expect(await classWhenTransitionStart()).toStrictEqual([
  115. 'test',
  116. 'test-enter-from',
  117. 'test-enter-active',
  118. ])
  119. await nextFrame()
  120. expect(await classList('.test')).toStrictEqual([
  121. 'test',
  122. 'test-enter-active',
  123. 'test-enter-to',
  124. ])
  125. await transitionFinish()
  126. expect(await html('#container')).toBe('<div class="test">content</div>')
  127. },
  128. E2E_TIMEOUT,
  129. )
  130. test(
  131. 'custom transition classes',
  132. async () => {
  133. await page().evaluate(() => {
  134. const { createApp, ref } = (window as any).Vue
  135. createApp({
  136. template: `
  137. <div id="container">
  138. <transition enter-from-class="hello-from"
  139. enter-active-class="hello-active"
  140. enter-to-class="hello-to"
  141. leave-from-class="bye-from"
  142. leave-active-class="bye-active"
  143. leave-to-class="bye-to">
  144. <div v-if="toggle" class="test">content</div>
  145. </transition>
  146. </div>
  147. <button id="toggleBtn" @click="click">button</button>
  148. `,
  149. setup: () => {
  150. const toggle = ref(true)
  151. const click = () => (toggle.value = !toggle.value)
  152. return { toggle, click }
  153. },
  154. }).mount('#app')
  155. })
  156. expect(await html('#container')).toBe('<div class="test">content</div>')
  157. // leave
  158. expect(await classWhenTransitionStart()).toStrictEqual([
  159. 'test',
  160. 'bye-from',
  161. 'bye-active',
  162. ])
  163. await nextFrame()
  164. expect(await classList('.test')).toStrictEqual([
  165. 'test',
  166. 'bye-active',
  167. 'bye-to',
  168. ])
  169. await transitionFinish()
  170. expect(await html('#container')).toBe('<!--v-if-->')
  171. // enter
  172. expect(await classWhenTransitionStart()).toStrictEqual([
  173. 'test',
  174. 'hello-from',
  175. 'hello-active',
  176. ])
  177. await nextFrame()
  178. expect(await classList('.test')).toStrictEqual([
  179. 'test',
  180. 'hello-active',
  181. 'hello-to',
  182. ])
  183. await transitionFinish()
  184. expect(await html('#container')).toBe('<div class="test">content</div>')
  185. },
  186. E2E_TIMEOUT,
  187. )
  188. test(
  189. 'transition with dynamic name',
  190. async () => {
  191. await page().evaluate(() => {
  192. const { createApp, ref } = (window as any).Vue
  193. createApp({
  194. template: `
  195. <div id="container">
  196. <transition :name="name">
  197. <div v-if="toggle" class="test">content</div>
  198. </transition>
  199. </div>
  200. <button id="toggleBtn" @click="click">button</button>
  201. <button id="changeNameBtn" @click="changeName">button</button>
  202. `,
  203. setup: () => {
  204. const name = ref('test')
  205. const toggle = ref(true)
  206. const click = () => (toggle.value = !toggle.value)
  207. const changeName = () => (name.value = 'changed')
  208. return { toggle, click, name, changeName }
  209. },
  210. }).mount('#app')
  211. })
  212. expect(await html('#container')).toBe('<div class="test">content</div>')
  213. // leave
  214. expect(await classWhenTransitionStart()).toStrictEqual([
  215. 'test',
  216. 'test-leave-from',
  217. 'test-leave-active',
  218. ])
  219. await nextFrame()
  220. expect(await classList('.test')).toStrictEqual([
  221. 'test',
  222. 'test-leave-active',
  223. 'test-leave-to',
  224. ])
  225. await transitionFinish()
  226. expect(await html('#container')).toBe('<!--v-if-->')
  227. // enter
  228. await page().evaluate(() => {
  229. ;(document.querySelector('#changeNameBtn') as any).click()
  230. })
  231. expect(await classWhenTransitionStart()).toStrictEqual([
  232. 'test',
  233. 'changed-enter-from',
  234. 'changed-enter-active',
  235. ])
  236. await nextFrame()
  237. expect(await classList('.test')).toStrictEqual([
  238. 'test',
  239. 'changed-enter-active',
  240. 'changed-enter-to',
  241. ])
  242. await transitionFinish()
  243. expect(await html('#container')).toBe('<div class="test">content</div>')
  244. },
  245. E2E_TIMEOUT,
  246. )
  247. test(
  248. 'transition events without appear',
  249. async () => {
  250. const beforeLeaveSpy = vi.fn()
  251. const onLeaveSpy = vi.fn()
  252. const afterLeaveSpy = vi.fn()
  253. const beforeEnterSpy = vi.fn()
  254. const onEnterSpy = vi.fn()
  255. const afterEnterSpy = vi.fn()
  256. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  257. await page().exposeFunction('onEnterSpy', onEnterSpy)
  258. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  259. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  260. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  261. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  262. await page().evaluate(() => {
  263. const {
  264. beforeEnterSpy,
  265. onEnterSpy,
  266. afterEnterSpy,
  267. beforeLeaveSpy,
  268. onLeaveSpy,
  269. afterLeaveSpy,
  270. } = window as any
  271. const { createApp, ref } = (window as any).Vue
  272. createApp({
  273. template: `
  274. <div id="container">
  275. <transition
  276. name="test"
  277. @before-enter="beforeEnterSpy()"
  278. @enter="onEnterSpy()"
  279. @after-enter="afterEnterSpy()"
  280. @before-leave="beforeLeaveSpy()"
  281. @leave="onLeaveSpy()"
  282. @after-leave="afterLeaveSpy()">
  283. <div v-if="toggle" class="test">content</div>
  284. </transition>
  285. </div>
  286. <button id="toggleBtn" @click="click">button</button>
  287. `,
  288. setup: () => {
  289. const toggle = ref(true)
  290. const click = () => (toggle.value = !toggle.value)
  291. return {
  292. toggle,
  293. click,
  294. beforeEnterSpy,
  295. onEnterSpy,
  296. afterEnterSpy,
  297. beforeLeaveSpy,
  298. onLeaveSpy,
  299. afterLeaveSpy,
  300. }
  301. },
  302. }).mount('#app')
  303. })
  304. expect(await html('#container')).toBe('<div class="test">content</div>')
  305. // leave
  306. expect(await classWhenTransitionStart()).toStrictEqual([
  307. 'test',
  308. 'test-leave-from',
  309. 'test-leave-active',
  310. ])
  311. expect(beforeLeaveSpy).toBeCalled()
  312. expect(onLeaveSpy).toBeCalled()
  313. expect(afterLeaveSpy).not.toBeCalled()
  314. await nextFrame()
  315. expect(await classList('.test')).toStrictEqual([
  316. 'test',
  317. 'test-leave-active',
  318. 'test-leave-to',
  319. ])
  320. expect(afterLeaveSpy).not.toBeCalled()
  321. await transitionFinish()
  322. expect(await html('#container')).toBe('<!--v-if-->')
  323. expect(afterLeaveSpy).toBeCalled()
  324. // enter
  325. expect(await classWhenTransitionStart()).toStrictEqual([
  326. 'test',
  327. 'test-enter-from',
  328. 'test-enter-active',
  329. ])
  330. expect(beforeEnterSpy).toBeCalled()
  331. expect(onEnterSpy).toBeCalled()
  332. expect(afterEnterSpy).not.toBeCalled()
  333. await nextFrame()
  334. expect(await classList('.test')).toStrictEqual([
  335. 'test',
  336. 'test-enter-active',
  337. 'test-enter-to',
  338. ])
  339. expect(afterEnterSpy).not.toBeCalled()
  340. await transitionFinish()
  341. expect(await html('#container')).toBe('<div class="test">content</div>')
  342. expect(afterEnterSpy).toBeCalled()
  343. },
  344. E2E_TIMEOUT,
  345. )
  346. test(
  347. 'events with arguments',
  348. async () => {
  349. const beforeLeaveSpy = vi.fn()
  350. const onLeaveSpy = vi.fn()
  351. const afterLeaveSpy = vi.fn()
  352. const beforeEnterSpy = vi.fn()
  353. const onEnterSpy = vi.fn()
  354. const afterEnterSpy = vi.fn()
  355. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  356. await page().exposeFunction('onEnterSpy', onEnterSpy)
  357. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  358. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  359. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  360. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  361. await page().evaluate(() => {
  362. const {
  363. beforeEnterSpy,
  364. onEnterSpy,
  365. afterEnterSpy,
  366. beforeLeaveSpy,
  367. onLeaveSpy,
  368. afterLeaveSpy,
  369. } = window as any
  370. const { createApp, ref } = (window as any).Vue
  371. createApp({
  372. template: `
  373. <div id="container">
  374. <transition
  375. :css="false"
  376. name="test"
  377. @before-enter="beforeEnterSpy"
  378. @enter="onEnterSpy"
  379. @after-enter="afterEnterSpy"
  380. @before-leave="beforeLeaveSpy"
  381. @leave="onLeaveSpy"
  382. @after-leave="afterLeaveSpy">
  383. <div v-if="toggle" class="test">content</div>
  384. </transition>
  385. </div>
  386. <button id="toggleBtn" @click="click">button</button>
  387. `,
  388. setup: () => {
  389. const toggle = ref(true)
  390. const click = () => (toggle.value = !toggle.value)
  391. return {
  392. toggle,
  393. click,
  394. beforeEnterSpy(el: Element) {
  395. beforeEnterSpy()
  396. el.classList.add('before-enter')
  397. },
  398. onEnterSpy(el: Element, done: () => void) {
  399. onEnterSpy()
  400. el.classList.add('enter')
  401. setTimeout(done, 200)
  402. },
  403. afterEnterSpy(el: Element) {
  404. afterEnterSpy()
  405. el.classList.add('after-enter')
  406. },
  407. beforeLeaveSpy(el: HTMLDivElement) {
  408. beforeLeaveSpy()
  409. el.classList.add('before-leave')
  410. },
  411. onLeaveSpy(el: HTMLDivElement, done: () => void) {
  412. onLeaveSpy()
  413. el.classList.add('leave')
  414. setTimeout(done, 200)
  415. },
  416. afterLeaveSpy: (el: Element) => {
  417. afterLeaveSpy()
  418. },
  419. }
  420. },
  421. }).mount('#app')
  422. })
  423. expect(await html('#container')).toBe('<div class="test">content</div>')
  424. // leave
  425. await click('#toggleBtn')
  426. expect(beforeLeaveSpy).toBeCalled()
  427. expect(onLeaveSpy).toBeCalled()
  428. expect(afterLeaveSpy).not.toBeCalled()
  429. expect(await classList('.test')).toStrictEqual([
  430. 'test',
  431. 'before-leave',
  432. 'leave',
  433. ])
  434. await timeout(200 + buffer)
  435. expect(afterLeaveSpy).toBeCalled()
  436. expect(await html('#container')).toBe('<!--v-if-->')
  437. // enter
  438. await click('#toggleBtn')
  439. expect(beforeEnterSpy).toBeCalled()
  440. expect(onEnterSpy).toBeCalled()
  441. expect(afterEnterSpy).not.toBeCalled()
  442. expect(await classList('.test')).toStrictEqual([
  443. 'test',
  444. 'before-enter',
  445. 'enter',
  446. ])
  447. await timeout(200 + buffer)
  448. expect(afterEnterSpy).toBeCalled()
  449. expect(await html('#container')).toBe(
  450. '<div class="test before-enter enter after-enter">content</div>',
  451. )
  452. },
  453. E2E_TIMEOUT,
  454. )
  455. test('onEnterCancelled', async () => {
  456. const enterCancelledSpy = vi.fn()
  457. await page().exposeFunction('enterCancelledSpy', enterCancelledSpy)
  458. await page().evaluate(() => {
  459. const { enterCancelledSpy } = window as any
  460. const { createApp, ref } = (window as any).Vue
  461. createApp({
  462. template: `
  463. <div id="container">
  464. <transition
  465. name="test"
  466. @enter-cancelled="enterCancelledSpy()">
  467. <div v-if="toggle" class="test">content</div>
  468. </transition>
  469. </div>
  470. <button id="toggleBtn" @click="click">button</button>
  471. `,
  472. setup: () => {
  473. const toggle = ref(false)
  474. const click = () => (toggle.value = !toggle.value)
  475. return {
  476. toggle,
  477. click,
  478. enterCancelledSpy,
  479. }
  480. },
  481. }).mount('#app')
  482. })
  483. expect(await html('#container')).toBe('<!--v-if-->')
  484. // enter
  485. expect(await classWhenTransitionStart()).toStrictEqual([
  486. 'test',
  487. 'test-enter-from',
  488. 'test-enter-active',
  489. ])
  490. await nextFrame()
  491. expect(await classList('.test')).toStrictEqual([
  492. 'test',
  493. 'test-enter-active',
  494. 'test-enter-to',
  495. ])
  496. // cancel (leave)
  497. expect(await classWhenTransitionStart()).toStrictEqual([
  498. 'test',
  499. 'test-leave-from',
  500. 'test-leave-active',
  501. ])
  502. expect(enterCancelledSpy).toBeCalled()
  503. await nextFrame()
  504. expect(await classList('.test')).toStrictEqual([
  505. 'test',
  506. 'test-leave-active',
  507. 'test-leave-to',
  508. ])
  509. await transitionFinish()
  510. expect(await html('#container')).toBe('<!--v-if-->')
  511. })
  512. test(
  513. 'transition on appear',
  514. async () => {
  515. const appearClass = await page().evaluate(async () => {
  516. const { createApp, ref } = (window as any).Vue
  517. createApp({
  518. template: `
  519. <div id="container">
  520. <transition name="test"
  521. appear
  522. appear-from-class="test-appear-from"
  523. appear-to-class="test-appear-to"
  524. appear-active-class="test-appear-active">
  525. <div v-if="toggle" class="test">content</div>
  526. </transition>
  527. </div>
  528. <button id="toggleBtn" @click="click">button</button>
  529. `,
  530. setup: () => {
  531. const toggle = ref(true)
  532. const click = () => (toggle.value = !toggle.value)
  533. return { toggle, click }
  534. },
  535. }).mount('#app')
  536. return Promise.resolve().then(() => {
  537. return document.querySelector('.test')!.className.split(/\s+/g)
  538. })
  539. })
  540. // appear
  541. expect(appearClass).toStrictEqual([
  542. 'test',
  543. 'test-appear-from',
  544. 'test-appear-active',
  545. ])
  546. await nextFrame()
  547. expect(await classList('.test')).toStrictEqual([
  548. 'test',
  549. 'test-appear-active',
  550. 'test-appear-to',
  551. ])
  552. await transitionFinish()
  553. expect(await html('#container')).toBe('<div class="test">content</div>')
  554. // leave
  555. expect(await classWhenTransitionStart()).toStrictEqual([
  556. 'test',
  557. 'test-leave-from',
  558. 'test-leave-active',
  559. ])
  560. await nextFrame()
  561. expect(await classList('.test')).toStrictEqual([
  562. 'test',
  563. 'test-leave-active',
  564. 'test-leave-to',
  565. ])
  566. await transitionFinish()
  567. expect(await html('#container')).toBe('<!--v-if-->')
  568. // enter
  569. expect(await classWhenTransitionStart()).toStrictEqual([
  570. 'test',
  571. 'test-enter-from',
  572. 'test-enter-active',
  573. ])
  574. await nextFrame()
  575. expect(await classList('.test')).toStrictEqual([
  576. 'test',
  577. 'test-enter-active',
  578. 'test-enter-to',
  579. ])
  580. await transitionFinish()
  581. expect(await html('#container')).toBe('<div class="test">content</div>')
  582. },
  583. E2E_TIMEOUT,
  584. )
  585. test(
  586. 'transition events with appear',
  587. async () => {
  588. const onLeaveSpy = vi.fn()
  589. const onEnterSpy = vi.fn()
  590. const onAppearSpy = vi.fn()
  591. const beforeLeaveSpy = vi.fn()
  592. const beforeEnterSpy = vi.fn()
  593. const beforeAppearSpy = vi.fn()
  594. const afterLeaveSpy = vi.fn()
  595. const afterEnterSpy = vi.fn()
  596. const afterAppearSpy = vi.fn()
  597. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  598. await page().exposeFunction('onEnterSpy', onEnterSpy)
  599. await page().exposeFunction('onAppearSpy', onAppearSpy)
  600. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  601. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  602. await page().exposeFunction('beforeAppearSpy', beforeAppearSpy)
  603. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  604. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  605. await page().exposeFunction('afterAppearSpy', afterAppearSpy)
  606. const appearClass = await page().evaluate(async () => {
  607. const {
  608. beforeAppearSpy,
  609. onAppearSpy,
  610. afterAppearSpy,
  611. beforeEnterSpy,
  612. onEnterSpy,
  613. afterEnterSpy,
  614. beforeLeaveSpy,
  615. onLeaveSpy,
  616. afterLeaveSpy,
  617. } = window as any
  618. const { createApp, ref } = (window as any).Vue
  619. createApp({
  620. template: `
  621. <div id="container">
  622. <transition
  623. name="test"
  624. appear
  625. appear-from-class="test-appear-from"
  626. appear-to-class="test-appear-to"
  627. appear-active-class="test-appear-active"
  628. @before-enter="beforeEnterSpy()"
  629. @enter="onEnterSpy()"
  630. @after-enter="afterEnterSpy()"
  631. @before-leave="beforeLeaveSpy()"
  632. @leave="onLeaveSpy()"
  633. @after-leave="afterLeaveSpy()"
  634. @before-appear="beforeAppearSpy()"
  635. @appear="onAppearSpy()"
  636. @after-appear="afterAppearSpy()">
  637. <div v-if="toggle" class="test">content</div>
  638. </transition>
  639. </div>
  640. <button id="toggleBtn" @click="click">button</button>
  641. `,
  642. setup: () => {
  643. const toggle = ref(true)
  644. const click = () => (toggle.value = !toggle.value)
  645. return {
  646. toggle,
  647. click,
  648. beforeAppearSpy,
  649. onAppearSpy,
  650. afterAppearSpy,
  651. beforeEnterSpy,
  652. onEnterSpy,
  653. afterEnterSpy,
  654. beforeLeaveSpy,
  655. onLeaveSpy,
  656. afterLeaveSpy,
  657. }
  658. },
  659. }).mount('#app')
  660. return Promise.resolve().then(() => {
  661. return document.querySelector('.test')!.className.split(/\s+/g)
  662. })
  663. })
  664. // appear
  665. expect(appearClass).toStrictEqual([
  666. 'test',
  667. 'test-appear-from',
  668. 'test-appear-active',
  669. ])
  670. expect(beforeAppearSpy).toBeCalled()
  671. expect(onAppearSpy).toBeCalled()
  672. expect(afterAppearSpy).not.toBeCalled()
  673. await nextFrame()
  674. expect(await classList('.test')).toStrictEqual([
  675. 'test',
  676. 'test-appear-active',
  677. 'test-appear-to',
  678. ])
  679. expect(afterAppearSpy).not.toBeCalled()
  680. await transitionFinish()
  681. expect(await html('#container')).toBe('<div class="test">content</div>')
  682. expect(afterAppearSpy).toBeCalled()
  683. expect(beforeEnterSpy).not.toBeCalled()
  684. expect(onEnterSpy).not.toBeCalled()
  685. expect(afterEnterSpy).not.toBeCalled()
  686. // leave
  687. expect(await classWhenTransitionStart()).toStrictEqual([
  688. 'test',
  689. 'test-leave-from',
  690. 'test-leave-active',
  691. ])
  692. expect(beforeLeaveSpy).toBeCalled()
  693. expect(onLeaveSpy).toBeCalled()
  694. expect(afterLeaveSpy).not.toBeCalled()
  695. await nextFrame()
  696. expect(await classList('.test')).toStrictEqual([
  697. 'test',
  698. 'test-leave-active',
  699. 'test-leave-to',
  700. ])
  701. expect(afterLeaveSpy).not.toBeCalled()
  702. await transitionFinish()
  703. expect(await html('#container')).toBe('<!--v-if-->')
  704. expect(afterLeaveSpy).toBeCalled()
  705. // enter
  706. expect(await classWhenTransitionStart()).toStrictEqual([
  707. 'test',
  708. 'test-enter-from',
  709. 'test-enter-active',
  710. ])
  711. expect(beforeEnterSpy).toBeCalled()
  712. expect(onEnterSpy).toBeCalled()
  713. expect(afterEnterSpy).not.toBeCalled()
  714. await nextFrame()
  715. expect(await classList('.test')).toStrictEqual([
  716. 'test',
  717. 'test-enter-active',
  718. 'test-enter-to',
  719. ])
  720. expect(afterEnterSpy).not.toBeCalled()
  721. await transitionFinish()
  722. expect(await html('#container')).toBe('<div class="test">content</div>')
  723. expect(afterEnterSpy).toBeCalled()
  724. },
  725. E2E_TIMEOUT,
  726. )
  727. test(
  728. 'css: false',
  729. async () => {
  730. const onBeforeEnterSpy = vi.fn()
  731. const onEnterSpy = vi.fn()
  732. const onAfterEnterSpy = vi.fn()
  733. const onBeforeLeaveSpy = vi.fn()
  734. const onLeaveSpy = vi.fn()
  735. const onAfterLeaveSpy = vi.fn()
  736. await page().exposeFunction('onBeforeEnterSpy', onBeforeEnterSpy)
  737. await page().exposeFunction('onEnterSpy', onEnterSpy)
  738. await page().exposeFunction('onAfterEnterSpy', onAfterEnterSpy)
  739. await page().exposeFunction('onBeforeLeaveSpy', onBeforeLeaveSpy)
  740. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  741. await page().exposeFunction('onAfterLeaveSpy', onAfterLeaveSpy)
  742. await page().evaluate(() => {
  743. const {
  744. onBeforeEnterSpy,
  745. onEnterSpy,
  746. onAfterEnterSpy,
  747. onBeforeLeaveSpy,
  748. onLeaveSpy,
  749. onAfterLeaveSpy,
  750. } = window as any
  751. const { createApp, ref } = (window as any).Vue
  752. createApp({
  753. template: `
  754. <div id="container">
  755. <transition
  756. :css="false"
  757. name="test"
  758. @before-enter="onBeforeEnterSpy()"
  759. @enter="onEnterSpy()"
  760. @after-enter="onAfterEnterSpy()"
  761. @before-leave="onBeforeLeaveSpy()"
  762. @leave="onLeaveSpy()"
  763. @after-leave="onAfterLeaveSpy()">
  764. <div v-if="toggle" class="test">content</div>
  765. </transition>
  766. </div>
  767. <button id="toggleBtn" @click="click"></button>
  768. `,
  769. setup: () => {
  770. const toggle = ref(true)
  771. const click = () => (toggle.value = !toggle.value)
  772. return {
  773. toggle,
  774. click,
  775. onBeforeEnterSpy,
  776. onEnterSpy,
  777. onAfterEnterSpy,
  778. onBeforeLeaveSpy,
  779. onLeaveSpy,
  780. onAfterLeaveSpy,
  781. }
  782. },
  783. }).mount('#app')
  784. })
  785. expect(await html('#container')).toBe('<div class="test">content</div>')
  786. // leave
  787. await click('#toggleBtn')
  788. expect(onBeforeLeaveSpy).toBeCalled()
  789. expect(onLeaveSpy).toBeCalled()
  790. expect(onAfterLeaveSpy).toBeCalled()
  791. expect(await html('#container')).toBe('<!--v-if-->')
  792. // enter
  793. await classWhenTransitionStart()
  794. expect(onBeforeEnterSpy).toBeCalled()
  795. expect(onEnterSpy).toBeCalled()
  796. expect(onAfterEnterSpy).toBeCalled()
  797. expect(await html('#container')).toBe('<div class="test">content</div>')
  798. },
  799. E2E_TIMEOUT,
  800. )
  801. test(
  802. 'no transition detected',
  803. async () => {
  804. await page().evaluate(() => {
  805. const { createApp, ref } = (window as any).Vue
  806. createApp({
  807. template: `
  808. <div id="container">
  809. <transition name="noop">
  810. <div v-if="toggle">content</div>
  811. </transition>
  812. </div>
  813. <button id="toggleBtn" @click="click">button</button>
  814. `,
  815. setup: () => {
  816. const toggle = ref(true)
  817. const click = () => (toggle.value = !toggle.value)
  818. return { toggle, click }
  819. },
  820. }).mount('#app')
  821. })
  822. expect(await html('#container')).toBe('<div>content</div>')
  823. // leave
  824. expect(await classWhenTransitionStart()).toStrictEqual([
  825. 'noop-leave-from',
  826. 'noop-leave-active',
  827. ])
  828. await nextFrame()
  829. expect(await html('#container')).toBe('<!--v-if-->')
  830. // enter
  831. expect(await classWhenTransitionStart()).toStrictEqual([
  832. 'noop-enter-from',
  833. 'noop-enter-active',
  834. ])
  835. await nextFrame()
  836. expect(await html('#container')).toBe('<div class="">content</div>')
  837. },
  838. E2E_TIMEOUT,
  839. )
  840. test(
  841. 'animations',
  842. async () => {
  843. await page().evaluate(() => {
  844. const { createApp, ref } = (window as any).Vue
  845. createApp({
  846. template: `
  847. <div id="container">
  848. <transition name="test-anim">
  849. <div v-if="toggle">content</div>
  850. </transition>
  851. </div>
  852. <button id="toggleBtn" @click="click">button</button>
  853. `,
  854. setup: () => {
  855. const toggle = ref(true)
  856. const click = () => (toggle.value = !toggle.value)
  857. return { toggle, click }
  858. },
  859. }).mount('#app')
  860. })
  861. expect(await html('#container')).toBe('<div>content</div>')
  862. // leave
  863. expect(await classWhenTransitionStart()).toStrictEqual([
  864. 'test-anim-leave-from',
  865. 'test-anim-leave-active',
  866. ])
  867. await nextFrame()
  868. expect(await classList('#container div')).toStrictEqual([
  869. 'test-anim-leave-active',
  870. 'test-anim-leave-to',
  871. ])
  872. await transitionFinish(duration * 2)
  873. expect(await html('#container')).toBe('<!--v-if-->')
  874. // enter
  875. expect(await classWhenTransitionStart()).toStrictEqual([
  876. 'test-anim-enter-from',
  877. 'test-anim-enter-active',
  878. ])
  879. await nextFrame()
  880. expect(await classList('#container div')).toStrictEqual([
  881. 'test-anim-enter-active',
  882. 'test-anim-enter-to',
  883. ])
  884. await transitionFinish()
  885. expect(await html('#container')).toBe('<div class="">content</div>')
  886. },
  887. E2E_TIMEOUT,
  888. )
  889. test(
  890. 'explicit transition type',
  891. async () => {
  892. await page().evaluate(() => {
  893. const { createApp, ref } = (window as any).Vue
  894. createApp({
  895. template: `
  896. <div id="container"><transition name="test-anim-long" type="animation"><div v-if="toggle">content</div></transition></div>
  897. <button id="toggleBtn" @click="click">button</button>
  898. `,
  899. setup: () => {
  900. const toggle = ref(true)
  901. const click = () => (toggle.value = !toggle.value)
  902. return { toggle, click }
  903. },
  904. }).mount('#app')
  905. })
  906. expect(await html('#container')).toBe('<div>content</div>')
  907. // leave
  908. expect(await classWhenTransitionStart()).toStrictEqual([
  909. 'test-anim-long-leave-from',
  910. 'test-anim-long-leave-active',
  911. ])
  912. await nextFrame()
  913. expect(await classList('#container div')).toStrictEqual([
  914. 'test-anim-long-leave-active',
  915. 'test-anim-long-leave-to',
  916. ])
  917. if (!process.env.CI) {
  918. await new Promise(r => {
  919. setTimeout(r, duration - buffer)
  920. })
  921. expect(await classList('#container div')).toStrictEqual([
  922. 'test-anim-long-leave-active',
  923. 'test-anim-long-leave-to',
  924. ])
  925. }
  926. await transitionFinish(duration * 2)
  927. expect(await html('#container')).toBe('<!--v-if-->')
  928. // enter
  929. expect(await classWhenTransitionStart()).toStrictEqual([
  930. 'test-anim-long-enter-from',
  931. 'test-anim-long-enter-active',
  932. ])
  933. await nextFrame()
  934. expect(await classList('#container div')).toStrictEqual([
  935. 'test-anim-long-enter-active',
  936. 'test-anim-long-enter-to',
  937. ])
  938. if (!process.env.CI) {
  939. await new Promise(r => {
  940. setTimeout(r, duration - buffer)
  941. })
  942. expect(await classList('#container div')).toStrictEqual([
  943. 'test-anim-long-enter-active',
  944. 'test-anim-long-enter-to',
  945. ])
  946. }
  947. await transitionFinish(duration * 2)
  948. expect(await html('#container')).toBe('<div class="">content</div>')
  949. },
  950. E2E_TIMEOUT,
  951. )
  952. test(
  953. 'transition on SVG elements',
  954. async () => {
  955. await page().evaluate(() => {
  956. const { createApp, ref } = (window as any).Vue
  957. createApp({
  958. template: `
  959. <svg id="container">
  960. <transition name="test">
  961. <circle v-if="toggle" cx="0" cy="0" r="10" class="test"></circle>
  962. </transition>
  963. </svg>
  964. <button id="toggleBtn" @click="click">button</button>
  965. `,
  966. setup: () => {
  967. const toggle = ref(true)
  968. const click = () => (toggle.value = !toggle.value)
  969. return { toggle, click }
  970. },
  971. }).mount('#app')
  972. })
  973. expect(await html('#container')).toBe(
  974. '<circle cx="0" cy="0" r="10" class="test"></circle>',
  975. )
  976. const svgTransitionStart = () =>
  977. page().evaluate(() => {
  978. document.querySelector('button')!.click()
  979. return Promise.resolve().then(() => {
  980. return document
  981. .querySelector('.test')!
  982. .getAttribute('class')!
  983. .split(/\s+/g)
  984. })
  985. })
  986. // leave
  987. expect(await svgTransitionStart()).toStrictEqual([
  988. 'test',
  989. 'test-leave-from',
  990. 'test-leave-active',
  991. ])
  992. await nextFrame()
  993. expect(await classList('.test')).toStrictEqual([
  994. 'test',
  995. 'test-leave-active',
  996. 'test-leave-to',
  997. ])
  998. await transitionFinish()
  999. expect(await html('#container')).toBe('<!--v-if-->')
  1000. // enter
  1001. expect(await svgTransitionStart()).toStrictEqual([
  1002. 'test',
  1003. 'test-enter-from',
  1004. 'test-enter-active',
  1005. ])
  1006. await nextFrame()
  1007. expect(await classList('.test')).toStrictEqual([
  1008. 'test',
  1009. 'test-enter-active',
  1010. 'test-enter-to',
  1011. ])
  1012. await transitionFinish()
  1013. expect(await html('#container')).toBe(
  1014. '<circle cx="0" cy="0" r="10" class="test"></circle>',
  1015. )
  1016. },
  1017. E2E_TIMEOUT,
  1018. )
  1019. test(
  1020. 'custom transition higher-order component',
  1021. async () => {
  1022. await page().evaluate(() => {
  1023. const { createApp, ref, h, Transition } = (window as any).Vue
  1024. createApp({
  1025. template: `
  1026. <div id="container"><my-transition><div v-if="toggle" class="test">content</div></my-transition></div>
  1027. <button id="toggleBtn" @click="click">button</button>
  1028. `,
  1029. components: {
  1030. 'my-transition': (props: any, { slots }: any) => {
  1031. return h(Transition, { name: 'test' }, slots)
  1032. },
  1033. },
  1034. setup: () => {
  1035. const toggle = ref(true)
  1036. const click = () => (toggle.value = !toggle.value)
  1037. return { toggle, click }
  1038. },
  1039. }).mount('#app')
  1040. })
  1041. expect(await html('#container')).toBe('<div class="test">content</div>')
  1042. // leave
  1043. expect(await classWhenTransitionStart()).toStrictEqual([
  1044. 'test',
  1045. 'test-leave-from',
  1046. 'test-leave-active',
  1047. ])
  1048. await nextFrame()
  1049. expect(await classList('.test')).toStrictEqual([
  1050. 'test',
  1051. 'test-leave-active',
  1052. 'test-leave-to',
  1053. ])
  1054. await transitionFinish()
  1055. expect(await html('#container')).toBe('<!--v-if-->')
  1056. // enter
  1057. expect(await classWhenTransitionStart()).toStrictEqual([
  1058. 'test',
  1059. 'test-enter-from',
  1060. 'test-enter-active',
  1061. ])
  1062. await nextFrame()
  1063. expect(await classList('.test')).toStrictEqual([
  1064. 'test',
  1065. 'test-enter-active',
  1066. 'test-enter-to',
  1067. ])
  1068. await transitionFinish()
  1069. expect(await html('#container')).toBe('<div class="test">content</div>')
  1070. },
  1071. E2E_TIMEOUT,
  1072. )
  1073. test(
  1074. 'transition on child components with empty root node',
  1075. async () => {
  1076. await page().evaluate(() => {
  1077. const { createApp, ref } = (window as any).Vue
  1078. createApp({
  1079. template: `
  1080. <div id="container">
  1081. <transition name="test">
  1082. <component class="test" :is="view"></component>
  1083. </transition>
  1084. </div>
  1085. <button id="toggleBtn" @click="click">button</button>
  1086. <button id="changeViewBtn" @click="change">button</button>
  1087. `,
  1088. components: {
  1089. one: {
  1090. template: '<div v-if="false">one</div>',
  1091. },
  1092. two: {
  1093. template: '<div>two</div>',
  1094. },
  1095. },
  1096. setup: () => {
  1097. const toggle = ref(true)
  1098. const view = ref('one')
  1099. const click = () => (toggle.value = !toggle.value)
  1100. const change = () =>
  1101. (view.value = view.value === 'one' ? 'two' : 'one')
  1102. return { toggle, click, change, view }
  1103. },
  1104. }).mount('#app')
  1105. })
  1106. expect(await html('#container')).toBe('<!--v-if-->')
  1107. // change view -> 'two'
  1108. await page().evaluate(() => {
  1109. ;(document.querySelector('#changeViewBtn') as any)!.click()
  1110. })
  1111. // enter
  1112. expect(await classWhenTransitionStart()).toStrictEqual([
  1113. 'test',
  1114. 'test-enter-from',
  1115. 'test-enter-active',
  1116. ])
  1117. await nextFrame()
  1118. expect(await classList('.test')).toStrictEqual([
  1119. 'test',
  1120. 'test-enter-active',
  1121. 'test-enter-to',
  1122. ])
  1123. await transitionFinish()
  1124. expect(await html('#container')).toBe('<div class="test">two</div>')
  1125. // change view -> 'one'
  1126. await page().evaluate(() => {
  1127. ;(document.querySelector('#changeViewBtn') as any)!.click()
  1128. })
  1129. // leave
  1130. expect(await classWhenTransitionStart()).toStrictEqual([
  1131. 'test',
  1132. 'test-leave-from',
  1133. 'test-leave-active',
  1134. ])
  1135. await nextFrame()
  1136. expect(await classList('.test')).toStrictEqual([
  1137. 'test',
  1138. 'test-leave-active',
  1139. 'test-leave-to',
  1140. ])
  1141. await transitionFinish()
  1142. expect(await html('#container')).toBe('<!--v-if-->')
  1143. },
  1144. E2E_TIMEOUT,
  1145. )
  1146. // issue https://github.com/vuejs/core/issues/7649
  1147. test(
  1148. 'transition with v-if at component root-level',
  1149. async () => {
  1150. await page().evaluate(() => {
  1151. const { createApp, ref } = (window as any).Vue
  1152. createApp({
  1153. template: `
  1154. <div id="container">
  1155. <transition name="test" mode="out-in">
  1156. <component class="test" :is="view"></component>
  1157. </transition>
  1158. </div>
  1159. <button id="toggleBtn" @click="click">button</button>
  1160. <button id="changeViewBtn" @click="change">button</button>
  1161. `,
  1162. components: {
  1163. one: {
  1164. template: '<div v-if="false">one</div>',
  1165. },
  1166. two: {
  1167. template: '<div>two</div>',
  1168. },
  1169. },
  1170. setup: () => {
  1171. const toggle = ref(true)
  1172. const view = ref('one')
  1173. const click = () => (toggle.value = !toggle.value)
  1174. const change = () =>
  1175. (view.value = view.value === 'one' ? 'two' : 'one')
  1176. return { toggle, click, change, view }
  1177. },
  1178. }).mount('#app')
  1179. })
  1180. expect(await html('#container')).toBe('<!--v-if-->')
  1181. // change view -> 'two'
  1182. await page().evaluate(() => {
  1183. ;(document.querySelector('#changeViewBtn') as any)!.click()
  1184. })
  1185. // enter
  1186. expect(await classWhenTransitionStart()).toStrictEqual([
  1187. 'test',
  1188. 'test-enter-from',
  1189. 'test-enter-active',
  1190. ])
  1191. await nextFrame()
  1192. expect(await classList('.test')).toStrictEqual([
  1193. 'test',
  1194. 'test-enter-active',
  1195. 'test-enter-to',
  1196. ])
  1197. await transitionFinish()
  1198. expect(await html('#container')).toBe('<div class="test">two</div>')
  1199. // change view -> 'one'
  1200. await page().evaluate(() => {
  1201. ;(document.querySelector('#changeViewBtn') as any)!.click()
  1202. })
  1203. // leave
  1204. expect(await classWhenTransitionStart()).toStrictEqual([
  1205. 'test',
  1206. 'test-leave-from',
  1207. 'test-leave-active',
  1208. ])
  1209. await nextFrame()
  1210. expect(await classList('.test')).toStrictEqual([
  1211. 'test',
  1212. 'test-leave-active',
  1213. 'test-leave-to',
  1214. ])
  1215. await transitionFinish()
  1216. expect(await html('#container')).toBe('<!--v-if-->')
  1217. },
  1218. E2E_TIMEOUT,
  1219. )
  1220. // #3716
  1221. test(
  1222. 'wrapping transition + fallthrough attrs',
  1223. async () => {
  1224. await page().evaluate(() => {
  1225. const { createApp, ref } = (window as any).Vue
  1226. createApp({
  1227. components: {
  1228. 'my-transition': {
  1229. template: `
  1230. <transition foo="1" name="test">
  1231. <slot></slot>
  1232. </transition>
  1233. `,
  1234. },
  1235. },
  1236. template: `
  1237. <div id="container">
  1238. <my-transition>
  1239. <div v-if="toggle">content</div>
  1240. </my-transition>
  1241. </div>
  1242. <button id="toggleBtn" @click="click">button</button>
  1243. `,
  1244. setup: () => {
  1245. const toggle = ref(true)
  1246. const click = () => (toggle.value = !toggle.value)
  1247. return { toggle, click }
  1248. },
  1249. }).mount('#app')
  1250. })
  1251. expect(await html('#container')).toBe('<div foo="1">content</div>')
  1252. await click('#toggleBtn')
  1253. // toggle again before leave finishes
  1254. await nextTick()
  1255. await click('#toggleBtn')
  1256. await transitionFinish()
  1257. expect(await html('#container')).toBe(
  1258. '<div foo="1" class="">content</div>',
  1259. )
  1260. },
  1261. E2E_TIMEOUT,
  1262. )
  1263. // #11061
  1264. test(
  1265. 'transition + fallthrough attrs (in-out mode)',
  1266. async () => {
  1267. const beforeLeaveSpy = vi.fn()
  1268. const onLeaveSpy = vi.fn()
  1269. const afterLeaveSpy = vi.fn()
  1270. const beforeEnterSpy = vi.fn()
  1271. const onEnterSpy = vi.fn()
  1272. const afterEnterSpy = vi.fn()
  1273. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1274. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1275. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  1276. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  1277. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  1278. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  1279. await page().evaluate(() => {
  1280. const { onEnterSpy, onLeaveSpy } = window as any
  1281. const { createApp, ref } = (window as any).Vue
  1282. createApp({
  1283. components: {
  1284. one: {
  1285. template: '<div>one</div>',
  1286. },
  1287. two: {
  1288. template: '<div>two</div>',
  1289. },
  1290. },
  1291. template: `
  1292. <div id="container">
  1293. <transition foo="1" name="test" mode="in-out"
  1294. @before-enter="beforeEnterSpy()"
  1295. @enter="onEnterSpy()"
  1296. @after-enter="afterEnterSpy()"
  1297. @before-leave="beforeLeaveSpy()"
  1298. @leave="onLeaveSpy()"
  1299. @after-leave="afterLeaveSpy()">
  1300. <component :is="view"></component>
  1301. </transition>
  1302. </div>
  1303. <button id="toggleBtn" @click="click">button</button>
  1304. `,
  1305. setup: () => {
  1306. const view = ref('one')
  1307. const click = () =>
  1308. (view.value = view.value === 'one' ? 'two' : 'one')
  1309. return {
  1310. view,
  1311. click,
  1312. beforeEnterSpy,
  1313. onEnterSpy,
  1314. afterEnterSpy,
  1315. beforeLeaveSpy,
  1316. onLeaveSpy,
  1317. afterLeaveSpy,
  1318. }
  1319. },
  1320. }).mount('#app')
  1321. })
  1322. expect(await html('#container')).toBe('<div foo="1">one</div>')
  1323. // toggle
  1324. await click('#toggleBtn')
  1325. await nextTick()
  1326. await transitionFinish()
  1327. expect(beforeEnterSpy).toBeCalledTimes(1)
  1328. expect(onEnterSpy).toBeCalledTimes(1)
  1329. expect(afterEnterSpy).toBeCalledTimes(1)
  1330. expect(beforeLeaveSpy).toBeCalledTimes(1)
  1331. expect(onLeaveSpy).toBeCalledTimes(1)
  1332. expect(afterLeaveSpy).toBeCalledTimes(1)
  1333. expect(await html('#container')).toBe('<div foo="1" class="">two</div>')
  1334. // toggle back
  1335. await click('#toggleBtn')
  1336. await nextTick()
  1337. await transitionFinish()
  1338. expect(beforeEnterSpy).toBeCalledTimes(2)
  1339. expect(onEnterSpy).toBeCalledTimes(2)
  1340. expect(afterEnterSpy).toBeCalledTimes(2)
  1341. expect(beforeLeaveSpy).toBeCalledTimes(2)
  1342. expect(onLeaveSpy).toBeCalledTimes(2)
  1343. expect(afterLeaveSpy).toBeCalledTimes(2)
  1344. expect(await html('#container')).toBe('<div foo="1" class="">one</div>')
  1345. },
  1346. E2E_TIMEOUT,
  1347. )
  1348. })
  1349. describe('transition with KeepAlive', () => {
  1350. test(
  1351. 'unmount innerChild (out-in mode)',
  1352. async () => {
  1353. const unmountSpy = vi.fn()
  1354. await page().exposeFunction('unmountSpy', unmountSpy)
  1355. await page().evaluate(() => {
  1356. const { unmountSpy } = window as any
  1357. const { createApp, ref, h, onUnmounted } = (window as any).Vue
  1358. createApp({
  1359. template: `
  1360. <div id="container">
  1361. <transition mode="out-in">
  1362. <KeepAlive :include="includeRef">
  1363. <TrueBranch v-if="toggle"></TrueBranch>
  1364. </KeepAlive>
  1365. </transition>
  1366. </div>
  1367. <button id="toggleBtn" @click="click">button</button>
  1368. `,
  1369. components: {
  1370. TrueBranch: {
  1371. name: 'TrueBranch',
  1372. setup() {
  1373. onUnmounted(unmountSpy)
  1374. const count = ref(0)
  1375. return () => h('div', count.value)
  1376. },
  1377. },
  1378. },
  1379. setup: () => {
  1380. const includeRef = ref(['TrueBranch'])
  1381. const toggle = ref(true)
  1382. const click = () => {
  1383. toggle.value = !toggle.value
  1384. if (toggle.value) {
  1385. includeRef.value = ['TrueBranch']
  1386. } else {
  1387. includeRef.value = []
  1388. }
  1389. }
  1390. return { toggle, click, unmountSpy, includeRef }
  1391. },
  1392. }).mount('#app')
  1393. })
  1394. await transitionFinish()
  1395. expect(await html('#container')).toBe('<div>0</div>')
  1396. await click('#toggleBtn')
  1397. await transitionFinish()
  1398. expect(await html('#container')).toBe('<!--v-if-->')
  1399. expect(unmountSpy).toBeCalledTimes(1)
  1400. },
  1401. E2E_TIMEOUT,
  1402. )
  1403. // #11775
  1404. test(
  1405. 'switch child then update include (out-in mode)',
  1406. async () => {
  1407. const onUpdatedSpyA = vi.fn()
  1408. const onUnmountedSpyC = vi.fn()
  1409. await page().exposeFunction('onUpdatedSpyA', onUpdatedSpyA)
  1410. await page().exposeFunction('onUnmountedSpyC', onUnmountedSpyC)
  1411. await page().evaluate(() => {
  1412. const { onUpdatedSpyA, onUnmountedSpyC } = window as any
  1413. const { createApp, ref, shallowRef, h, onUpdated, onUnmounted } = (
  1414. window as any
  1415. ).Vue
  1416. createApp({
  1417. template: `
  1418. <div id="container">
  1419. <transition mode="out-in">
  1420. <KeepAlive :include="includeRef">
  1421. <component :is="current" />
  1422. </KeepAlive>
  1423. </transition>
  1424. </div>
  1425. <button id="switchToB" @click="switchToB">switchToB</button>
  1426. <button id="switchToC" @click="switchToC">switchToC</button>
  1427. <button id="switchToA" @click="switchToA">switchToA</button>
  1428. `,
  1429. components: {
  1430. CompA: {
  1431. name: 'CompA',
  1432. setup() {
  1433. onUpdated(onUpdatedSpyA)
  1434. return () => h('div', 'CompA')
  1435. },
  1436. },
  1437. CompB: {
  1438. name: 'CompB',
  1439. setup() {
  1440. return () => h('div', 'CompB')
  1441. },
  1442. },
  1443. CompC: {
  1444. name: 'CompC',
  1445. setup() {
  1446. onUnmounted(onUnmountedSpyC)
  1447. return () => h('div', 'CompC')
  1448. },
  1449. },
  1450. },
  1451. setup: () => {
  1452. const includeRef = ref(['CompA', 'CompB', 'CompC'])
  1453. const current = shallowRef('CompA')
  1454. const switchToB = () => (current.value = 'CompB')
  1455. const switchToC = () => (current.value = 'CompC')
  1456. const switchToA = () => {
  1457. current.value = 'CompA'
  1458. includeRef.value = ['CompA']
  1459. }
  1460. return { current, switchToB, switchToC, switchToA, includeRef }
  1461. },
  1462. }).mount('#app')
  1463. })
  1464. await transitionFinish()
  1465. expect(await html('#container')).toBe('<div>CompA</div>')
  1466. await click('#switchToB')
  1467. await nextTick()
  1468. await click('#switchToC')
  1469. await transitionFinish()
  1470. expect(await html('#container')).toBe('<div class="">CompC</div>')
  1471. await click('#switchToA')
  1472. await transitionFinish()
  1473. expect(await html('#container')).toBe('<div class="">CompA</div>')
  1474. // expect CompA only update once
  1475. expect(onUpdatedSpyA).toBeCalledTimes(1)
  1476. expect(onUnmountedSpyC).toBeCalledTimes(1)
  1477. },
  1478. E2E_TIMEOUT,
  1479. )
  1480. // #10827
  1481. test(
  1482. 'switch and update child then update include (out-in mode)',
  1483. async () => {
  1484. const onUnmountedSpyB = vi.fn()
  1485. await page().exposeFunction('onUnmountedSpyB', onUnmountedSpyB)
  1486. await page().evaluate(() => {
  1487. const { onUnmountedSpyB } = window as any
  1488. const {
  1489. createApp,
  1490. ref,
  1491. shallowRef,
  1492. h,
  1493. provide,
  1494. inject,
  1495. onUnmounted,
  1496. } = (window as any).Vue
  1497. createApp({
  1498. template: `
  1499. <div id="container">
  1500. <transition name="test-anim" mode="out-in">
  1501. <KeepAlive :include="includeRef">
  1502. <component :is="current" />
  1503. </KeepAlive>
  1504. </transition>
  1505. </div>
  1506. <button id="switchToA" @click="switchToA">switchToA</button>
  1507. <button id="switchToB" @click="switchToB">switchToB</button>
  1508. `,
  1509. components: {
  1510. CompA: {
  1511. name: 'CompA',
  1512. setup() {
  1513. const current = inject('current')
  1514. return () => h('div', current.value)
  1515. },
  1516. },
  1517. CompB: {
  1518. name: 'CompB',
  1519. setup() {
  1520. const current = inject('current')
  1521. onUnmounted(onUnmountedSpyB)
  1522. return () => h('div', current.value)
  1523. },
  1524. },
  1525. },
  1526. setup: () => {
  1527. const includeRef = ref(['CompA'])
  1528. const current = shallowRef('CompA')
  1529. provide('current', current)
  1530. const switchToB = () => {
  1531. current.value = 'CompB'
  1532. includeRef.value = ['CompA', 'CompB']
  1533. }
  1534. const switchToA = () => {
  1535. current.value = 'CompA'
  1536. includeRef.value = ['CompA']
  1537. }
  1538. return { current, switchToB, switchToA, includeRef }
  1539. },
  1540. }).mount('#app')
  1541. })
  1542. await transitionFinish()
  1543. expect(await html('#container')).toBe('<div>CompA</div>')
  1544. await click('#switchToB')
  1545. await transitionFinish()
  1546. await transitionFinish()
  1547. expect(await html('#container')).toBe('<div class="">CompB</div>')
  1548. await click('#switchToA')
  1549. await transitionFinish()
  1550. await transitionFinish()
  1551. expect(await html('#container')).toBe('<div class="">CompA</div>')
  1552. expect(onUnmountedSpyB).toBeCalledTimes(1)
  1553. },
  1554. E2E_TIMEOUT,
  1555. )
  1556. // #12860
  1557. test(
  1558. 'unmount children',
  1559. async () => {
  1560. const unmountSpy = vi.fn()
  1561. let storageContainer: ElementHandle<HTMLDivElement>
  1562. const setStorageContainer = (container: any) =>
  1563. (storageContainer = container)
  1564. await page().exposeFunction('unmountSpy', unmountSpy)
  1565. await page().exposeFunction('setStorageContainer', setStorageContainer)
  1566. await page().evaluate(() => {
  1567. const { unmountSpy, setStorageContainer } = window as any
  1568. const { createApp, ref, h, onUnmounted, getCurrentInstance } = (
  1569. window as any
  1570. ).Vue
  1571. createApp({
  1572. template: `
  1573. <div id="container">
  1574. <transition>
  1575. <KeepAlive :include="includeRef">
  1576. <TrueBranch v-if="toggle"></TrueBranch>
  1577. </KeepAlive>
  1578. </transition>
  1579. </div>
  1580. <button id="toggleBtn" @click="click">button</button>
  1581. `,
  1582. components: {
  1583. TrueBranch: {
  1584. name: 'TrueBranch',
  1585. setup() {
  1586. const instance = getCurrentInstance()
  1587. onUnmounted(() => {
  1588. unmountSpy()
  1589. setStorageContainer(instance.__keepAliveStorageContainer)
  1590. })
  1591. const count = ref(0)
  1592. return () => h('div', count.value)
  1593. },
  1594. },
  1595. },
  1596. setup: () => {
  1597. const includeRef = ref(['TrueBranch'])
  1598. const toggle = ref(true)
  1599. const click = () => {
  1600. toggle.value = !toggle.value
  1601. if (toggle.value) {
  1602. includeRef.value = ['TrueBranch']
  1603. } else {
  1604. includeRef.value = []
  1605. }
  1606. }
  1607. return { toggle, click, unmountSpy, includeRef }
  1608. },
  1609. }).mount('#app')
  1610. })
  1611. await transitionFinish()
  1612. expect(await html('#container')).toBe('<div>0</div>')
  1613. await click('#toggleBtn')
  1614. await transitionFinish()
  1615. expect(await html('#container')).toBe('<!--v-if-->')
  1616. expect(unmountSpy).toBeCalledTimes(1)
  1617. expect(await storageContainer!.evaluate(x => x.innerHTML)).toBe(``)
  1618. },
  1619. E2E_TIMEOUT,
  1620. )
  1621. // #13153
  1622. test(
  1623. 'move kept-alive node before v-show transition leave finishes',
  1624. async () => {
  1625. await page().evaluate(() => {
  1626. const { createApp, ref } = (window as any).Vue
  1627. const show = ref(true)
  1628. createApp({
  1629. template: `
  1630. <div id="container">
  1631. <KeepAlive :include="['Comp1', 'Comp2']">
  1632. <component :is="state === 1 ? 'Comp1' : 'Comp2'"/>
  1633. </KeepAlive>
  1634. </div>
  1635. <button id="toggleBtn" @click="click">button</button>
  1636. `,
  1637. setup: () => {
  1638. const state = ref(1)
  1639. const click = () => (state.value = state.value === 1 ? 2 : 1)
  1640. return { state, click }
  1641. },
  1642. components: {
  1643. Comp1: {
  1644. components: {
  1645. Item: {
  1646. name: 'Item',
  1647. setup() {
  1648. return { show }
  1649. },
  1650. template: `
  1651. <Transition name="test">
  1652. <div v-show="show" >
  1653. <h2>{{ show ? "I should show" : "I shouldn't show " }}</h2>
  1654. </div>
  1655. </Transition>
  1656. `,
  1657. },
  1658. },
  1659. name: 'Comp1',
  1660. setup() {
  1661. const toggle = () => (show.value = !show.value)
  1662. return { show, toggle }
  1663. },
  1664. template: `
  1665. <Item />
  1666. <h2>This is page1</h2>
  1667. <button id="changeShowBtn" @click="toggle">{{ show }}</button>
  1668. `,
  1669. },
  1670. Comp2: {
  1671. name: 'Comp2',
  1672. template: `<h2>This is page2</h2>`,
  1673. },
  1674. },
  1675. }).mount('#app')
  1676. })
  1677. expect(await html('#container')).toBe(
  1678. `<div><h2>I should show</h2></div>` +
  1679. `<h2>This is page1</h2>` +
  1680. `<button id="changeShowBtn">true</button>`,
  1681. )
  1682. // trigger v-show transition leave
  1683. await click('#changeShowBtn')
  1684. await nextTick()
  1685. expect(await html('#container')).toBe(
  1686. `<div class="test-leave-from test-leave-active"><h2>I shouldn't show </h2></div>` +
  1687. `<h2>This is page1</h2>` +
  1688. `<button id="changeShowBtn">false</button>`,
  1689. )
  1690. // switch to page2, before leave finishes
  1691. // expect v-show element's display to be none
  1692. await click('#toggleBtn')
  1693. await nextTick()
  1694. expect(await html('#container')).toBe(
  1695. `<div class="test-leave-from test-leave-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
  1696. `<h2>This is page2</h2>`,
  1697. )
  1698. // switch back to page1
  1699. // expect v-show element's display to be none
  1700. await click('#toggleBtn')
  1701. await nextTick()
  1702. expect(await html('#container')).toBe(
  1703. `<div class="test-enter-from test-enter-active" style="display: none;"><h2>I shouldn't show </h2></div>` +
  1704. `<h2>This is page1</h2>` +
  1705. `<button id="changeShowBtn">false</button>`,
  1706. )
  1707. await transitionFinish()
  1708. expect(await html('#container')).toBe(
  1709. `<div class="" style="display: none;"><h2>I shouldn't show </h2></div>` +
  1710. `<h2>This is page1</h2>` +
  1711. `<button id="changeShowBtn">false</button>`,
  1712. )
  1713. },
  1714. E2E_TIMEOUT,
  1715. )
  1716. })
  1717. describe('transition with Suspense', () => {
  1718. // #1583
  1719. test(
  1720. 'async component transition inside Suspense',
  1721. async () => {
  1722. const onLeaveSpy = vi.fn()
  1723. const onEnterSpy = vi.fn()
  1724. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1725. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1726. await page().evaluate(() => {
  1727. const { onEnterSpy, onLeaveSpy } = window as any
  1728. const { createApp, ref, h } = (window as any).Vue
  1729. createApp({
  1730. template: `
  1731. <div id="container">
  1732. <transition @enter="onEnterSpy()" @leave="onLeaveSpy()">
  1733. <Suspense>
  1734. <Comp v-if="toggle" class="test">content</Comp>
  1735. </Suspense>
  1736. </transition>
  1737. </div>
  1738. <button id="toggleBtn" @click="click">button</button>
  1739. `,
  1740. components: {
  1741. Comp: {
  1742. async setup() {
  1743. return () => h('div', { class: 'test' }, 'content')
  1744. },
  1745. },
  1746. },
  1747. setup: () => {
  1748. const toggle = ref(true)
  1749. const click = () => (toggle.value = !toggle.value)
  1750. return { toggle, click, onEnterSpy, onLeaveSpy }
  1751. },
  1752. }).mount('#app')
  1753. })
  1754. expect(onEnterSpy).toBeCalledTimes(1)
  1755. await nextFrame()
  1756. expect(await html('#container')).toBe(
  1757. '<div class="test v-enter-active v-enter-to">content</div>',
  1758. )
  1759. await transitionFinish()
  1760. expect(await html('#container')).toBe('<div class="test">content</div>')
  1761. // leave
  1762. expect(await classWhenTransitionStart()).toStrictEqual([
  1763. 'test',
  1764. 'v-leave-from',
  1765. 'v-leave-active',
  1766. ])
  1767. expect(onLeaveSpy).toBeCalledTimes(1)
  1768. await nextFrame()
  1769. expect(await classList('.test')).toStrictEqual([
  1770. 'test',
  1771. 'v-leave-active',
  1772. 'v-leave-to',
  1773. ])
  1774. await transitionFinish()
  1775. expect(await html('#container')).toBe('<!--v-if-->')
  1776. // enter
  1777. const enterClass = await page().evaluate(async () => {
  1778. ;(document.querySelector('#toggleBtn') as any)!.click()
  1779. // nextTrick for patch start
  1780. await Promise.resolve()
  1781. // nextTrick for Suspense resolve
  1782. await Promise.resolve()
  1783. // nextTrick for dom transition start
  1784. await Promise.resolve()
  1785. return document
  1786. .querySelector('#container div')!
  1787. .className.split(/\s+/g)
  1788. })
  1789. expect(enterClass).toStrictEqual([
  1790. 'test',
  1791. 'v-enter-from',
  1792. 'v-enter-active',
  1793. ])
  1794. expect(onEnterSpy).toBeCalledTimes(2)
  1795. await nextFrame()
  1796. expect(await classList('.test')).toStrictEqual([
  1797. 'test',
  1798. 'v-enter-active',
  1799. 'v-enter-to',
  1800. ])
  1801. await transitionFinish()
  1802. expect(await html('#container')).toBe('<div class="test">content</div>')
  1803. },
  1804. E2E_TIMEOUT,
  1805. )
  1806. // #1689
  1807. test(
  1808. 'static node transition inside Suspense',
  1809. async () => {
  1810. await page().evaluate(() => {
  1811. const { createApp, ref } = (window as any).Vue
  1812. createApp({
  1813. template: `
  1814. <div id="container">
  1815. <transition>
  1816. <Suspense>
  1817. <div v-if="toggle" class="test">content</div>
  1818. </Suspense>
  1819. </transition>
  1820. </div>
  1821. <button id="toggleBtn" @click="click">button</button>
  1822. `,
  1823. setup: () => {
  1824. const toggle = ref(true)
  1825. const click = () => (toggle.value = !toggle.value)
  1826. return { toggle, click }
  1827. },
  1828. }).mount('#app')
  1829. })
  1830. expect(await html('#container')).toBe('<div class="test">content</div>')
  1831. // leave
  1832. expect(await classWhenTransitionStart()).toStrictEqual([
  1833. 'test',
  1834. 'v-leave-from',
  1835. 'v-leave-active',
  1836. ])
  1837. await nextFrame()
  1838. expect(await classList('.test')).toStrictEqual([
  1839. 'test',
  1840. 'v-leave-active',
  1841. 'v-leave-to',
  1842. ])
  1843. await transitionFinish()
  1844. expect(await html('#container')).toBe('<!--v-if-->')
  1845. // enter
  1846. expect(await classWhenTransitionStart()).toStrictEqual([
  1847. 'test',
  1848. 'v-enter-from',
  1849. 'v-enter-active',
  1850. ])
  1851. await nextFrame()
  1852. expect(await classList('.test')).toStrictEqual([
  1853. 'test',
  1854. 'v-enter-active',
  1855. 'v-enter-to',
  1856. ])
  1857. await transitionFinish()
  1858. expect(await html('#container')).toBe('<div class="test">content</div>')
  1859. },
  1860. E2E_TIMEOUT,
  1861. )
  1862. test(
  1863. 'out-in mode with Suspense',
  1864. async () => {
  1865. const onLeaveSpy = vi.fn()
  1866. const onEnterSpy = vi.fn()
  1867. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1868. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1869. await page().evaluate(() => {
  1870. const { createApp, shallowRef, h } = (window as any).Vue
  1871. const One = {
  1872. async setup() {
  1873. return () => h('div', { class: 'test' }, 'one')
  1874. },
  1875. }
  1876. const Two = {
  1877. async setup() {
  1878. return () => h('div', { class: 'test' }, 'two')
  1879. },
  1880. }
  1881. createApp({
  1882. template: `
  1883. <div id="container">
  1884. <transition mode="out-in">
  1885. <Suspense>
  1886. <component :is="view"/>
  1887. </Suspense>
  1888. </transition>
  1889. </div>
  1890. <button id="toggleBtn" @click="click">button</button>
  1891. `,
  1892. setup: () => {
  1893. const view = shallowRef(One)
  1894. const click = () => {
  1895. view.value = view.value === One ? Two : One
  1896. }
  1897. return { view, click }
  1898. },
  1899. }).mount('#app')
  1900. })
  1901. await nextFrame()
  1902. expect(await html('#container')).toBe(
  1903. '<div class="test v-enter-active v-enter-to">one</div>',
  1904. )
  1905. await transitionFinish()
  1906. expect(await html('#container')).toBe('<div class="test">one</div>')
  1907. // leave
  1908. await classWhenTransitionStart()
  1909. await nextFrame()
  1910. expect(await html('#container')).toBe(
  1911. '<div class="test v-leave-active v-leave-to">one</div>',
  1912. )
  1913. await transitionFinish()
  1914. await nextFrame()
  1915. // expect(await html('#container')).toBe(
  1916. // '<div class="test v-enter-active v-enter-to">two</div>'
  1917. // )
  1918. await transitionFinish()
  1919. expect(await html('#container')).toBe('<div class="test">two</div>')
  1920. },
  1921. E2E_TIMEOUT,
  1922. )
  1923. // #3963
  1924. test(
  1925. 'Suspense fallback should work with transition',
  1926. async () => {
  1927. await page().evaluate(() => {
  1928. const { createApp, shallowRef, h } = (window as any).Vue
  1929. const One = {
  1930. template: `<div>{{ msg }}</div>`,
  1931. setup() {
  1932. return new Promise(_resolve => {
  1933. // @ts-expect-error
  1934. window.resolve = () =>
  1935. _resolve({
  1936. msg: 'success',
  1937. })
  1938. })
  1939. },
  1940. }
  1941. createApp({
  1942. template: `
  1943. <div id="container">
  1944. <transition mode="out-in">
  1945. <Suspense :timeout="0">
  1946. <template #default>
  1947. <component :is="view" />
  1948. </template>
  1949. <template #fallback>
  1950. <div>Loading...</div>
  1951. </template>
  1952. </Suspense>
  1953. </transition>
  1954. </div>
  1955. <button id="toggleBtn" @click="click">button</button>
  1956. `,
  1957. setup: () => {
  1958. const view = shallowRef(null)
  1959. const click = () => {
  1960. view.value = view.value ? null : h(One)
  1961. }
  1962. return { view, click }
  1963. },
  1964. }).mount('#app')
  1965. })
  1966. expect(await html('#container')).toBe('<!---->')
  1967. await click('#toggleBtn')
  1968. await nextFrame()
  1969. expect(await html('#container')).toBe('<div class="">Loading...</div>')
  1970. await page().evaluate(() => {
  1971. // @ts-expect-error
  1972. window.resolve()
  1973. })
  1974. await transitionFinish(duration * 2)
  1975. expect(await html('#container')).toBe('<div class="">success</div>')
  1976. },
  1977. E2E_TIMEOUT,
  1978. )
  1979. // #5844
  1980. test('children mount should be called after html changes', async () => {
  1981. const fooMountSpy = vi.fn()
  1982. const barMountSpy = vi.fn()
  1983. await page().exposeFunction('fooMountSpy', fooMountSpy)
  1984. await page().exposeFunction('barMountSpy', barMountSpy)
  1985. await page().evaluate(() => {
  1986. const { fooMountSpy, barMountSpy } = window as any
  1987. const { createApp, ref, h, onMounted } = (window as any).Vue
  1988. createApp({
  1989. template: `
  1990. <div id="container">
  1991. <transition mode="out-in">
  1992. <Suspense>
  1993. <Foo v-if="toggle" />
  1994. <Bar v-else />
  1995. </Suspense>
  1996. </transition>
  1997. </div>
  1998. <button id="toggleBtn" @click="click">button</button>
  1999. `,
  2000. components: {
  2001. Foo: {
  2002. setup() {
  2003. const el = ref(null)
  2004. onMounted(() => {
  2005. fooMountSpy(
  2006. !!el.value,
  2007. !!document.getElementById('foo'),
  2008. !!document.getElementById('bar'),
  2009. )
  2010. })
  2011. return () => h('div', { ref: el, id: 'foo' }, 'Foo')
  2012. },
  2013. },
  2014. Bar: {
  2015. setup() {
  2016. const el = ref(null)
  2017. onMounted(() => {
  2018. barMountSpy(
  2019. !!el.value,
  2020. !!document.getElementById('foo'),
  2021. !!document.getElementById('bar'),
  2022. )
  2023. })
  2024. return () => h('div', { ref: el, id: 'bar' }, 'Bar')
  2025. },
  2026. },
  2027. },
  2028. setup: () => {
  2029. const toggle = ref(true)
  2030. const click = () => (toggle.value = !toggle.value)
  2031. return { toggle, click }
  2032. },
  2033. }).mount('#app')
  2034. })
  2035. await nextFrame()
  2036. expect(await html('#container')).toBe('<div id="foo">Foo</div>')
  2037. await transitionFinish()
  2038. expect(fooMountSpy).toBeCalledTimes(1)
  2039. expect(fooMountSpy).toHaveBeenNthCalledWith(1, true, true, false)
  2040. await page().evaluate(async () => {
  2041. ;(document.querySelector('#toggleBtn') as any)!.click()
  2042. // nextTrick for patch start
  2043. await Promise.resolve()
  2044. // nextTrick for Suspense resolve
  2045. await Promise.resolve()
  2046. // nextTrick for dom transition start
  2047. await Promise.resolve()
  2048. return document.querySelector('#container div')!.className.split(/\s+/g)
  2049. })
  2050. await nextFrame()
  2051. await transitionFinish()
  2052. expect(await html('#container')).toBe('<div id="bar" class="">Bar</div>')
  2053. expect(barMountSpy).toBeCalledTimes(1)
  2054. expect(barMountSpy).toHaveBeenNthCalledWith(1, true, false, true)
  2055. })
  2056. // #8105
  2057. test(
  2058. 'trigger again when transition is not finished',
  2059. async () => {
  2060. await page().evaluate(duration => {
  2061. const { createApp, shallowRef, h } = (window as any).Vue
  2062. const One = {
  2063. async setup() {
  2064. return () => h('div', { class: 'test' }, 'one')
  2065. },
  2066. }
  2067. const Two = {
  2068. async setup() {
  2069. return () => h('div', { class: 'test' }, 'two')
  2070. },
  2071. }
  2072. createApp({
  2073. template: `
  2074. <div id="container">
  2075. <transition name="test" mode="out-in" duration="${duration}">
  2076. <Suspense>
  2077. <component :is="view"/>
  2078. </Suspense>
  2079. </transition>
  2080. </div>
  2081. <button id="toggleBtn" @click="click">button</button>
  2082. `,
  2083. setup: () => {
  2084. const view = shallowRef(One)
  2085. const click = () => {
  2086. view.value = view.value === One ? Two : One
  2087. }
  2088. return { view, click }
  2089. },
  2090. }).mount('#app')
  2091. }, duration)
  2092. await nextFrame()
  2093. expect(await html('#container')).toBe(
  2094. '<div class="test test-enter-active test-enter-to">one</div>',
  2095. )
  2096. await transitionFinish()
  2097. expect(await html('#container')).toBe('<div class="test">one</div>')
  2098. // trigger twice
  2099. classWhenTransitionStart()
  2100. classWhenTransitionStart()
  2101. await nextFrame()
  2102. expect(await html('#container')).toBe(
  2103. '<div class="test test-leave-active test-leave-to">one</div>',
  2104. )
  2105. await transitionFinish()
  2106. await nextFrame()
  2107. expect(await html('#container')).toBe(
  2108. '<div class="test test-enter-active test-enter-to">one</div>',
  2109. )
  2110. await transitionFinish()
  2111. await nextFrame()
  2112. expect(await html('#container')).toBe('<div class="test">one</div>')
  2113. },
  2114. E2E_TIMEOUT,
  2115. )
  2116. // #9996
  2117. test(
  2118. 'trigger again when transition is not finished & correctly anchor',
  2119. async () => {
  2120. await page().evaluate(duration => {
  2121. const { createApp, shallowRef, h } = (window as any).Vue
  2122. const One = {
  2123. async setup() {
  2124. return () => h('div', { class: 'test' }, 'one')
  2125. },
  2126. }
  2127. const Two = {
  2128. async setup() {
  2129. return () => h('div', { class: 'test' }, 'two')
  2130. },
  2131. }
  2132. createApp({
  2133. template: `
  2134. <div id="container">
  2135. <div>Top</div>
  2136. <transition name="test" mode="out-in" :duration="${duration}">
  2137. <Suspense>
  2138. <component :is="view"/>
  2139. </Suspense>
  2140. </transition>
  2141. <div>Bottom</div>
  2142. </div>
  2143. <button id="toggleBtn" @click="click">button</button>
  2144. `,
  2145. setup: () => {
  2146. const view = shallowRef(One)
  2147. const click = () => {
  2148. view.value = view.value === One ? Two : One
  2149. }
  2150. return { view, click }
  2151. },
  2152. }).mount('#app')
  2153. }, duration)
  2154. await nextFrame()
  2155. expect(await html('#container')).toBe(
  2156. '<div>Top</div><div class="test test-enter-active test-enter-to">one</div><div>Bottom</div>',
  2157. )
  2158. await transitionFinish()
  2159. expect(await html('#container')).toBe(
  2160. '<div>Top</div><div class="test">one</div><div>Bottom</div>',
  2161. )
  2162. // trigger twice
  2163. classWhenTransitionStart()
  2164. await nextFrame()
  2165. expect(await html('#container')).toBe(
  2166. '<div>Top</div><div class="test test-leave-active test-leave-to">one</div><div>Bottom</div>',
  2167. )
  2168. await transitionFinish()
  2169. await nextFrame()
  2170. expect(await html('#container')).toBe(
  2171. '<div>Top</div><div class="test test-enter-active test-enter-to">two</div><div>Bottom</div>',
  2172. )
  2173. await transitionFinish()
  2174. await nextFrame()
  2175. expect(await html('#container')).toBe(
  2176. '<div>Top</div><div class="test">two</div><div>Bottom</div>',
  2177. )
  2178. },
  2179. E2E_TIMEOUT,
  2180. )
  2181. // #11806
  2182. test(
  2183. 'switch between Async and Sync child when transition is not finished',
  2184. async () => {
  2185. await page().evaluate(() => {
  2186. const { createApp, shallowRef, h, nextTick } = (window as any).Vue
  2187. createApp({
  2188. template: `
  2189. <div id="container">
  2190. <Transition mode="out-in">
  2191. <Suspense>
  2192. <component :is="view"/>
  2193. </Suspense>
  2194. </Transition>
  2195. </div>
  2196. <button id="toggleBtn" @click="click">button</button>
  2197. `,
  2198. setup: () => {
  2199. const view = shallowRef('SyncB')
  2200. const click = async () => {
  2201. view.value = 'SyncA'
  2202. await nextTick()
  2203. view.value = 'AsyncB'
  2204. await nextTick()
  2205. view.value = 'SyncB'
  2206. }
  2207. return { view, click }
  2208. },
  2209. components: {
  2210. SyncA: {
  2211. setup() {
  2212. return () => h('div', 'SyncA')
  2213. },
  2214. },
  2215. AsyncB: {
  2216. async setup() {
  2217. await nextTick()
  2218. return () => h('div', 'AsyncB')
  2219. },
  2220. },
  2221. SyncB: {
  2222. setup() {
  2223. return () => h('div', 'SyncB')
  2224. },
  2225. },
  2226. },
  2227. }).mount('#app')
  2228. })
  2229. expect(await html('#container')).toBe('<div>SyncB</div>')
  2230. await click('#toggleBtn')
  2231. await nextFrame()
  2232. await transitionFinish()
  2233. await transitionFinish()
  2234. expect(await html('#container')).toBe('<div class="">SyncB</div>')
  2235. },
  2236. E2E_TIMEOUT,
  2237. )
  2238. })
  2239. describe('transition with Teleport', () => {
  2240. test(
  2241. 'apply transition to teleport child',
  2242. async () => {
  2243. await page().evaluate(() => {
  2244. const { createApp, ref, h } = (window as any).Vue
  2245. createApp({
  2246. template: `
  2247. <div id="target"></div>
  2248. <div id="container">
  2249. <transition>
  2250. <Teleport to="#target">
  2251. <!-- comment -->
  2252. <Comp v-if="toggle" class="test">content</Comp>
  2253. </Teleport>
  2254. </transition>
  2255. </div>
  2256. <button id="toggleBtn" @click="click">button</button>
  2257. `,
  2258. components: {
  2259. Comp: {
  2260. setup() {
  2261. return () => h('div', { class: 'test' }, 'content')
  2262. },
  2263. },
  2264. },
  2265. setup: () => {
  2266. const toggle = ref(false)
  2267. const click = () => (toggle.value = !toggle.value)
  2268. return { toggle, click }
  2269. },
  2270. }).mount('#app')
  2271. })
  2272. expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
  2273. expect(await html('#container')).toBe(
  2274. '<!--teleport start--><!--teleport end-->',
  2275. )
  2276. const classWhenTransitionStart = () =>
  2277. page().evaluate(() => {
  2278. ;(document.querySelector('#toggleBtn') as any)!.click()
  2279. return Promise.resolve().then(() => {
  2280. // find the class of teleported node
  2281. return document
  2282. .querySelector('#target div')!
  2283. .className.split(/\s+/g)
  2284. })
  2285. })
  2286. // enter
  2287. expect(await classWhenTransitionStart()).toStrictEqual([
  2288. 'test',
  2289. 'v-enter-from',
  2290. 'v-enter-active',
  2291. ])
  2292. await nextFrame()
  2293. expect(await classList('.test')).toStrictEqual([
  2294. 'test',
  2295. 'v-enter-active',
  2296. 'v-enter-to',
  2297. ])
  2298. await transitionFinish()
  2299. expect(await html('#target')).toBe(
  2300. '<!-- comment --><div class="test">content</div>',
  2301. )
  2302. // leave
  2303. expect(await classWhenTransitionStart()).toStrictEqual([
  2304. 'test',
  2305. 'v-leave-from',
  2306. 'v-leave-active',
  2307. ])
  2308. await nextFrame()
  2309. expect(await classList('.test')).toStrictEqual([
  2310. 'test',
  2311. 'v-leave-active',
  2312. 'v-leave-to',
  2313. ])
  2314. await transitionFinish()
  2315. expect(await html('#target')).toBe('<!-- comment --><!--v-if-->')
  2316. expect(await html('#container')).toBe(
  2317. '<!--teleport start--><!--teleport end-->',
  2318. )
  2319. },
  2320. E2E_TIMEOUT,
  2321. )
  2322. })
  2323. describe('transition with v-show', () => {
  2324. test(
  2325. 'named transition with v-show',
  2326. async () => {
  2327. await page().evaluate(() => {
  2328. const { createApp, ref } = (window as any).Vue
  2329. createApp({
  2330. template: `
  2331. <div id="container">
  2332. <transition name="test">
  2333. <div v-show="toggle" class="test">content</div>
  2334. </transition>
  2335. </div>
  2336. <button id="toggleBtn" @click="click">button</button>
  2337. `,
  2338. setup: () => {
  2339. const toggle = ref(true)
  2340. const click = () => (toggle.value = !toggle.value)
  2341. return { toggle, click }
  2342. },
  2343. }).mount('#app')
  2344. })
  2345. expect(await html('#container')).toBe('<div class="test">content</div>')
  2346. expect(await isVisible('.test')).toBe(true)
  2347. // leave
  2348. expect(await classWhenTransitionStart()).toStrictEqual([
  2349. 'test',
  2350. 'test-leave-from',
  2351. 'test-leave-active',
  2352. ])
  2353. await nextFrame()
  2354. expect(await classList('.test')).toStrictEqual([
  2355. 'test',
  2356. 'test-leave-active',
  2357. 'test-leave-to',
  2358. ])
  2359. await transitionFinish()
  2360. expect(await isVisible('.test')).toBe(false)
  2361. // enter
  2362. expect(await classWhenTransitionStart()).toStrictEqual([
  2363. 'test',
  2364. 'test-enter-from',
  2365. 'test-enter-active',
  2366. ])
  2367. await nextFrame()
  2368. expect(await classList('.test')).toStrictEqual([
  2369. 'test',
  2370. 'test-enter-active',
  2371. 'test-enter-to',
  2372. ])
  2373. await transitionFinish()
  2374. expect(await html('#container')).toBe(
  2375. '<div class="test" style="">content</div>',
  2376. )
  2377. },
  2378. E2E_TIMEOUT,
  2379. )
  2380. test(
  2381. 'transition events with v-show',
  2382. async () => {
  2383. const beforeLeaveSpy = vi.fn()
  2384. const onLeaveSpy = vi.fn()
  2385. const afterLeaveSpy = vi.fn()
  2386. const beforeEnterSpy = vi.fn()
  2387. const onEnterSpy = vi.fn()
  2388. const afterEnterSpy = vi.fn()
  2389. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  2390. await page().exposeFunction('onEnterSpy', onEnterSpy)
  2391. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  2392. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  2393. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  2394. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  2395. await page().evaluate(() => {
  2396. const {
  2397. beforeEnterSpy,
  2398. onEnterSpy,
  2399. afterEnterSpy,
  2400. beforeLeaveSpy,
  2401. onLeaveSpy,
  2402. afterLeaveSpy,
  2403. } = window as any
  2404. const { createApp, ref } = (window as any).Vue
  2405. createApp({
  2406. template: `
  2407. <div id="container">
  2408. <transition
  2409. name="test"
  2410. @before-enter="beforeEnterSpy()"
  2411. @enter="onEnterSpy()"
  2412. @after-enter="afterEnterSpy()"
  2413. @before-leave="beforeLeaveSpy()"
  2414. @leave="onLeaveSpy()"
  2415. @after-leave="afterLeaveSpy()">
  2416. <div v-show="toggle" class="test">content</div>
  2417. </transition>
  2418. </div>
  2419. <button id="toggleBtn" @click="click">button</button>
  2420. `,
  2421. setup: () => {
  2422. const toggle = ref(true)
  2423. const click = () => (toggle.value = !toggle.value)
  2424. return {
  2425. toggle,
  2426. click,
  2427. beforeEnterSpy,
  2428. onEnterSpy,
  2429. afterEnterSpy,
  2430. beforeLeaveSpy,
  2431. onLeaveSpy,
  2432. afterLeaveSpy,
  2433. }
  2434. },
  2435. }).mount('#app')
  2436. })
  2437. expect(await html('#container')).toBe('<div class="test">content</div>')
  2438. // leave
  2439. expect(await classWhenTransitionStart()).toStrictEqual([
  2440. 'test',
  2441. 'test-leave-from',
  2442. 'test-leave-active',
  2443. ])
  2444. expect(beforeLeaveSpy).toBeCalled()
  2445. expect(onLeaveSpy).toBeCalled()
  2446. expect(afterLeaveSpy).not.toBeCalled()
  2447. await nextFrame()
  2448. expect(await classList('.test')).toStrictEqual([
  2449. 'test',
  2450. 'test-leave-active',
  2451. 'test-leave-to',
  2452. ])
  2453. expect(afterLeaveSpy).not.toBeCalled()
  2454. await transitionFinish()
  2455. expect(await isVisible('.test')).toBe(false)
  2456. expect(afterLeaveSpy).toBeCalled()
  2457. // enter
  2458. expect(await classWhenTransitionStart()).toStrictEqual([
  2459. 'test',
  2460. 'test-enter-from',
  2461. 'test-enter-active',
  2462. ])
  2463. expect(beforeEnterSpy).toBeCalled()
  2464. expect(onEnterSpy).toBeCalled()
  2465. expect(afterEnterSpy).not.toBeCalled()
  2466. await nextFrame()
  2467. expect(await classList('.test')).toStrictEqual([
  2468. 'test',
  2469. 'test-enter-active',
  2470. 'test-enter-to',
  2471. ])
  2472. expect(afterEnterSpy).not.toBeCalled()
  2473. await transitionFinish()
  2474. expect(await html('#container')).toBe(
  2475. '<div class="test" style="">content</div>',
  2476. )
  2477. expect(afterEnterSpy).toBeCalled()
  2478. },
  2479. E2E_TIMEOUT,
  2480. )
  2481. test(
  2482. 'onLeaveCancelled (v-show only)',
  2483. async () => {
  2484. const onLeaveCancelledSpy = vi.fn()
  2485. await page().exposeFunction('onLeaveCancelledSpy', onLeaveCancelledSpy)
  2486. await page().evaluate(() => {
  2487. const { onLeaveCancelledSpy } = window as any
  2488. const { createApp, ref } = (window as any).Vue
  2489. createApp({
  2490. template: `
  2491. <div id="container">
  2492. <transition name="test" @leave-cancelled="onLeaveCancelledSpy()">
  2493. <div v-show="toggle" class="test">content</div>
  2494. </transition>
  2495. </div>
  2496. <button id="toggleBtn" @click="click">button</button>
  2497. `,
  2498. setup: () => {
  2499. const toggle = ref(true)
  2500. const click = () => (toggle.value = !toggle.value)
  2501. return { toggle, click, onLeaveCancelledSpy }
  2502. },
  2503. }).mount('#app')
  2504. })
  2505. expect(await html('#container')).toBe('<div class="test">content</div>')
  2506. expect(await isVisible('.test')).toBe(true)
  2507. // leave
  2508. expect(await classWhenTransitionStart()).toStrictEqual([
  2509. 'test',
  2510. 'test-leave-from',
  2511. 'test-leave-active',
  2512. ])
  2513. await nextFrame()
  2514. expect(await classList('.test')).toStrictEqual([
  2515. 'test',
  2516. 'test-leave-active',
  2517. 'test-leave-to',
  2518. ])
  2519. // cancel (enter)
  2520. expect(await classWhenTransitionStart()).toStrictEqual([
  2521. 'test',
  2522. 'test-enter-from',
  2523. 'test-enter-active',
  2524. ])
  2525. expect(onLeaveCancelledSpy).toBeCalled()
  2526. await nextFrame()
  2527. expect(await classList('.test')).toStrictEqual([
  2528. 'test',
  2529. 'test-enter-active',
  2530. 'test-enter-to',
  2531. ])
  2532. await transitionFinish()
  2533. expect(await html('#container')).toBe(
  2534. '<div class="test" style="">content</div>',
  2535. )
  2536. },
  2537. E2E_TIMEOUT,
  2538. )
  2539. test(
  2540. 'transition on appear with v-show',
  2541. async () => {
  2542. const beforeEnterSpy = vi.fn()
  2543. const onEnterSpy = vi.fn()
  2544. const afterEnterSpy = vi.fn()
  2545. await page().exposeFunction('onEnterSpy', onEnterSpy)
  2546. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  2547. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  2548. const appearClass = await page().evaluate(async () => {
  2549. const { createApp, ref } = (window as any).Vue
  2550. const { beforeEnterSpy, onEnterSpy, afterEnterSpy } = window as any
  2551. createApp({
  2552. template: `
  2553. <div id="container">
  2554. <transition name="test"
  2555. appear
  2556. appear-from-class="test-appear-from"
  2557. appear-to-class="test-appear-to"
  2558. appear-active-class="test-appear-active"
  2559. @before-enter="beforeEnterSpy()"
  2560. @enter="onEnterSpy()"
  2561. @after-enter="afterEnterSpy()">
  2562. <div v-show="toggle" class="test">content</div>
  2563. </transition>
  2564. </div>
  2565. <button id="toggleBtn" @click="click">button</button>
  2566. `,
  2567. setup: () => {
  2568. const toggle = ref(true)
  2569. const click = () => (toggle.value = !toggle.value)
  2570. return {
  2571. toggle,
  2572. click,
  2573. beforeEnterSpy,
  2574. onEnterSpy,
  2575. afterEnterSpy,
  2576. }
  2577. },
  2578. }).mount('#app')
  2579. return Promise.resolve().then(() => {
  2580. return document.querySelector('.test')!.className.split(/\s+/g)
  2581. })
  2582. })
  2583. expect(beforeEnterSpy).toBeCalledTimes(1)
  2584. expect(onEnterSpy).toBeCalledTimes(1)
  2585. expect(afterEnterSpy).toBeCalledTimes(0)
  2586. // appear
  2587. expect(appearClass).toStrictEqual([
  2588. 'test',
  2589. 'test-appear-from',
  2590. 'test-appear-active',
  2591. ])
  2592. await nextFrame()
  2593. expect(await classList('.test')).toStrictEqual([
  2594. 'test',
  2595. 'test-appear-active',
  2596. 'test-appear-to',
  2597. ])
  2598. await transitionFinish()
  2599. expect(await html('#container')).toBe('<div class="test">content</div>')
  2600. expect(beforeEnterSpy).toBeCalledTimes(1)
  2601. expect(onEnterSpy).toBeCalledTimes(1)
  2602. expect(afterEnterSpy).toBeCalledTimes(1)
  2603. // leave
  2604. expect(await classWhenTransitionStart()).toStrictEqual([
  2605. 'test',
  2606. 'test-leave-from',
  2607. 'test-leave-active',
  2608. ])
  2609. await nextFrame()
  2610. expect(await classList('.test')).toStrictEqual([
  2611. 'test',
  2612. 'test-leave-active',
  2613. 'test-leave-to',
  2614. ])
  2615. await transitionFinish()
  2616. expect(await isVisible('.test')).toBe(false)
  2617. // enter
  2618. expect(await classWhenTransitionStart()).toStrictEqual([
  2619. 'test',
  2620. 'test-enter-from',
  2621. 'test-enter-active',
  2622. ])
  2623. await nextFrame()
  2624. expect(await classList('.test')).toStrictEqual([
  2625. 'test',
  2626. 'test-enter-active',
  2627. 'test-enter-to',
  2628. ])
  2629. await transitionFinish()
  2630. expect(await html('#container')).toBe(
  2631. '<div class="test" style="">content</div>',
  2632. )
  2633. },
  2634. E2E_TIMEOUT,
  2635. )
  2636. // #4845
  2637. test(
  2638. 'transition events should not call onEnter with v-show false',
  2639. async () => {
  2640. const beforeEnterSpy = vi.fn()
  2641. const onEnterSpy = vi.fn()
  2642. const afterEnterSpy = vi.fn()
  2643. await page().exposeFunction('onEnterSpy', onEnterSpy)
  2644. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  2645. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  2646. await page().evaluate(() => {
  2647. const { beforeEnterSpy, onEnterSpy, afterEnterSpy } = window as any
  2648. const { createApp, ref } = (window as any).Vue
  2649. createApp({
  2650. template: `
  2651. <div id="container">
  2652. <transition
  2653. name="test"
  2654. appear
  2655. @before-enter="beforeEnterSpy()"
  2656. @enter="onEnterSpy()"
  2657. @after-enter="afterEnterSpy()">
  2658. <div v-show="toggle" class="test">content</div>
  2659. </transition>
  2660. </div>
  2661. <button id="toggleBtn" @click="click">button</button>
  2662. `,
  2663. setup: () => {
  2664. const toggle = ref(false)
  2665. const click = () => (toggle.value = !toggle.value)
  2666. return {
  2667. toggle,
  2668. click,
  2669. beforeEnterSpy,
  2670. onEnterSpy,
  2671. afterEnterSpy,
  2672. }
  2673. },
  2674. }).mount('#app')
  2675. })
  2676. await nextTick()
  2677. expect(await isVisible('.test')).toBe(false)
  2678. expect(beforeEnterSpy).toBeCalledTimes(0)
  2679. expect(onEnterSpy).toBeCalledTimes(0)
  2680. // enter
  2681. expect(await classWhenTransitionStart()).toStrictEqual([
  2682. 'test',
  2683. 'test-enter-from',
  2684. 'test-enter-active',
  2685. ])
  2686. expect(beforeEnterSpy).toBeCalledTimes(1)
  2687. expect(onEnterSpy).toBeCalledTimes(1)
  2688. expect(afterEnterSpy).not.toBeCalled()
  2689. await nextFrame()
  2690. expect(await classList('.test')).toStrictEqual([
  2691. 'test',
  2692. 'test-enter-active',
  2693. 'test-enter-to',
  2694. ])
  2695. expect(afterEnterSpy).not.toBeCalled()
  2696. await transitionFinish()
  2697. expect(await html('#container')).toBe(
  2698. '<div class="test" style="">content</div>',
  2699. )
  2700. expect(afterEnterSpy).toBeCalled()
  2701. },
  2702. E2E_TIMEOUT,
  2703. )
  2704. })
  2705. describe('explicit durations', () => {
  2706. test(
  2707. 'single value',
  2708. async () => {
  2709. await page().evaluate(duration => {
  2710. const { createApp, ref } = (window as any).Vue
  2711. createApp({
  2712. template: `
  2713. <div id="container">
  2714. <transition name="test" duration="${duration * 2}">
  2715. <div v-if="toggle" class="test">content</div>
  2716. </transition>
  2717. </div>
  2718. <button id="toggleBtn" @click="click">button</button>
  2719. `,
  2720. setup: () => {
  2721. const toggle = ref(true)
  2722. const click = () => (toggle.value = !toggle.value)
  2723. return { toggle, click }
  2724. },
  2725. }).mount('#app')
  2726. }, duration)
  2727. expect(await html('#container')).toBe('<div class="test">content</div>')
  2728. // leave
  2729. expect(await classWhenTransitionStart()).toStrictEqual([
  2730. 'test',
  2731. 'test-leave-from',
  2732. 'test-leave-active',
  2733. ])
  2734. await nextFrame()
  2735. expect(await classList('.test')).toStrictEqual([
  2736. 'test',
  2737. 'test-leave-active',
  2738. 'test-leave-to',
  2739. ])
  2740. await transitionFinish(duration * 2)
  2741. expect(await html('#container')).toBe('<!--v-if-->')
  2742. // enter
  2743. expect(await classWhenTransitionStart()).toStrictEqual([
  2744. 'test',
  2745. 'test-enter-from',
  2746. 'test-enter-active',
  2747. ])
  2748. await nextFrame()
  2749. expect(await classList('.test')).toStrictEqual([
  2750. 'test',
  2751. 'test-enter-active',
  2752. 'test-enter-to',
  2753. ])
  2754. await transitionFinish(duration * 2)
  2755. expect(await html('#container')).toBe('<div class="test">content</div>')
  2756. },
  2757. E2E_TIMEOUT,
  2758. )
  2759. test(
  2760. 'enter with explicit durations',
  2761. async () => {
  2762. await page().evaluate(duration => {
  2763. const { createApp, ref } = (window as any).Vue
  2764. createApp({
  2765. template: `
  2766. <div id="container">
  2767. <transition name="test" :duration="{ enter: ${duration * 2} }">
  2768. <div v-if="toggle" class="test">content</div>
  2769. </transition>
  2770. </div>
  2771. <button id="toggleBtn" @click="click">button</button>
  2772. `,
  2773. setup: () => {
  2774. const toggle = ref(true)
  2775. const click = () => (toggle.value = !toggle.value)
  2776. return { toggle, click }
  2777. },
  2778. }).mount('#app')
  2779. }, duration)
  2780. expect(await html('#container')).toBe('<div class="test">content</div>')
  2781. // leave
  2782. expect(await classWhenTransitionStart()).toStrictEqual([
  2783. 'test',
  2784. 'test-leave-from',
  2785. 'test-leave-active',
  2786. ])
  2787. await nextFrame()
  2788. expect(await classList('.test')).toStrictEqual([
  2789. 'test',
  2790. 'test-leave-active',
  2791. 'test-leave-to',
  2792. ])
  2793. await transitionFinish()
  2794. expect(await html('#container')).toBe('<!--v-if-->')
  2795. // enter
  2796. expect(await classWhenTransitionStart()).toStrictEqual([
  2797. 'test',
  2798. 'test-enter-from',
  2799. 'test-enter-active',
  2800. ])
  2801. await nextFrame()
  2802. expect(await classList('.test')).toStrictEqual([
  2803. 'test',
  2804. 'test-enter-active',
  2805. 'test-enter-to',
  2806. ])
  2807. await transitionFinish(duration * 2)
  2808. expect(await html('#container')).toBe('<div class="test">content</div>')
  2809. },
  2810. E2E_TIMEOUT,
  2811. )
  2812. test(
  2813. 'leave with explicit durations',
  2814. async () => {
  2815. await page().evaluate(duration => {
  2816. const { createApp, ref } = (window as any).Vue
  2817. createApp({
  2818. template: `
  2819. <div id="container">
  2820. <transition name="test" :duration="{ leave: ${duration * 2} }">
  2821. <div v-if="toggle" class="test">content</div>
  2822. </transition>
  2823. </div>
  2824. <button id="toggleBtn" @click="click">button</button>
  2825. `,
  2826. setup: () => {
  2827. const toggle = ref(true)
  2828. const click = () => (toggle.value = !toggle.value)
  2829. return { toggle, click }
  2830. },
  2831. }).mount('#app')
  2832. }, duration)
  2833. expect(await html('#container')).toBe('<div class="test">content</div>')
  2834. // leave
  2835. expect(await classWhenTransitionStart()).toStrictEqual([
  2836. 'test',
  2837. 'test-leave-from',
  2838. 'test-leave-active',
  2839. ])
  2840. await nextFrame()
  2841. expect(await classList('.test')).toStrictEqual([
  2842. 'test',
  2843. 'test-leave-active',
  2844. 'test-leave-to',
  2845. ])
  2846. await transitionFinish(duration * 2)
  2847. expect(await html('#container')).toBe('<!--v-if-->')
  2848. // enter
  2849. expect(await classWhenTransitionStart()).toStrictEqual([
  2850. 'test',
  2851. 'test-enter-from',
  2852. 'test-enter-active',
  2853. ])
  2854. await nextFrame()
  2855. expect(await classList('.test')).toStrictEqual([
  2856. 'test',
  2857. 'test-enter-active',
  2858. 'test-enter-to',
  2859. ])
  2860. await transitionFinish()
  2861. expect(await html('#container')).toBe('<div class="test">content</div>')
  2862. },
  2863. E2E_TIMEOUT,
  2864. )
  2865. test(
  2866. 'separate enter and leave',
  2867. async () => {
  2868. await page().evaluate(duration => {
  2869. const { createApp, ref } = (window as any).Vue
  2870. createApp({
  2871. template: `
  2872. <div id="container">
  2873. <transition name="test" :duration="{
  2874. enter: ${duration * 4},
  2875. leave: ${duration * 2}
  2876. }">
  2877. <div v-if="toggle" class="test">content</div>
  2878. </transition>
  2879. </div>
  2880. <button id="toggleBtn" @click="click">button</button>
  2881. `,
  2882. setup: () => {
  2883. const toggle = ref(true)
  2884. const click = () => (toggle.value = !toggle.value)
  2885. return { toggle, click }
  2886. },
  2887. }).mount('#app')
  2888. }, duration)
  2889. expect(await html('#container')).toBe('<div class="test">content</div>')
  2890. // leave
  2891. expect(await classWhenTransitionStart()).toStrictEqual([
  2892. 'test',
  2893. 'test-leave-from',
  2894. 'test-leave-active',
  2895. ])
  2896. await nextFrame()
  2897. expect(await classList('.test')).toStrictEqual([
  2898. 'test',
  2899. 'test-leave-active',
  2900. 'test-leave-to',
  2901. ])
  2902. await transitionFinish(duration * 2)
  2903. expect(await html('#container')).toBe('<!--v-if-->')
  2904. // enter
  2905. expect(await classWhenTransitionStart()).toStrictEqual([
  2906. 'test',
  2907. 'test-enter-from',
  2908. 'test-enter-active',
  2909. ])
  2910. await nextFrame()
  2911. expect(await classList('.test')).toStrictEqual([
  2912. 'test',
  2913. 'test-enter-active',
  2914. 'test-enter-to',
  2915. ])
  2916. await transitionFinish(duration * 4)
  2917. expect(await html('#container')).toBe('<div class="test">content</div>')
  2918. },
  2919. E2E_TIMEOUT,
  2920. )
  2921. test(
  2922. 'warn invalid durations',
  2923. async () => {
  2924. createApp({
  2925. template: `
  2926. <div id="container">
  2927. <transition name="test" :duration="NaN">
  2928. <div class="test">content</div>
  2929. </transition>
  2930. </div>
  2931. `,
  2932. }).mount(document.createElement('div'))
  2933. expect(
  2934. `[Vue warn]: <transition> explicit duration is NaN - ` +
  2935. 'the duration expression might be incorrect.',
  2936. ).toHaveBeenWarned()
  2937. createApp({
  2938. template: `
  2939. <div id="container">
  2940. <transition name="test" :duration="{
  2941. enter: {},
  2942. leave: {}
  2943. }">
  2944. <div class="test">content</div>
  2945. </transition>
  2946. </div>
  2947. `,
  2948. }).mount(document.createElement('div'))
  2949. expect(
  2950. `[Vue warn]: <transition> explicit duration is not a valid number - ` +
  2951. `got ${JSON.stringify({})}`,
  2952. ).toHaveBeenWarned()
  2953. },
  2954. E2E_TIMEOUT,
  2955. )
  2956. })
  2957. test('reflow after *-leave-from before *-leave-active', async () => {
  2958. await page().evaluate(() => {
  2959. const { createApp, ref } = (window as any).Vue
  2960. createApp({
  2961. template: `
  2962. <div id="container">
  2963. <transition name="test-reflow">
  2964. <div v-if="toggle" class="test-reflow">content</div>
  2965. </transition>
  2966. </div>
  2967. <button id="toggleBtn" @click="click">button</button>
  2968. `,
  2969. setup: () => {
  2970. const toggle = ref(false)
  2971. const click = () => (toggle.value = !toggle.value)
  2972. return {
  2973. toggle,
  2974. click,
  2975. }
  2976. },
  2977. }).mount('#app')
  2978. })
  2979. // if transition starts while there's v-leave-active added along with v-leave-from, its bad, it has to start when it doesnt have the v-leave-from
  2980. // enter
  2981. await classWhenTransitionStart()
  2982. await transitionFinish()
  2983. // leave
  2984. expect(await classWhenTransitionStart()).toStrictEqual([
  2985. 'test-reflow',
  2986. 'test-reflow-leave-from',
  2987. 'test-reflow-leave-active',
  2988. ])
  2989. expect(await style('.test-reflow', 'opacity')).toStrictEqual('0.9')
  2990. await nextFrame()
  2991. expect(await classList('.test-reflow')).toStrictEqual([
  2992. 'test-reflow',
  2993. 'test-reflow-leave-active',
  2994. 'test-reflow-leave-to',
  2995. ])
  2996. await transitionFinish()
  2997. expect(await html('#container')).toBe('<!--v-if-->')
  2998. })
  2999. test('warn when used on multiple elements', async () => {
  3000. createApp({
  3001. render() {
  3002. return h(Transition, null, {
  3003. default: () => [h('div'), h('div')],
  3004. })
  3005. },
  3006. }).mount(document.createElement('div'))
  3007. expect(
  3008. '<transition> can only be used on a single element or component',
  3009. ).toHaveBeenWarned()
  3010. })
  3011. test('warn when invalid transition mode', () => {
  3012. createApp({
  3013. template: `
  3014. <div id="container">
  3015. <transition name="test" mode="none">
  3016. <div class="test">content</div>
  3017. </transition>
  3018. </div>
  3019. `,
  3020. }).mount(document.createElement('div'))
  3021. expect(`invalid <transition> mode: none`).toHaveBeenWarned()
  3022. })
  3023. // #3227
  3024. test(`HOC w/ merged hooks`, async () => {
  3025. const innerSpy = vi.fn()
  3026. const outerSpy = vi.fn()
  3027. const MyTransition = {
  3028. render(this: any) {
  3029. return h(
  3030. Transition,
  3031. {
  3032. onLeave(el, end) {
  3033. innerSpy()
  3034. end()
  3035. },
  3036. },
  3037. this.$slots.default,
  3038. )
  3039. },
  3040. }
  3041. const toggle = ref(true)
  3042. const root = document.createElement('div')
  3043. createApp({
  3044. render() {
  3045. return h(MyTransition, { onLeave: () => outerSpy() }, () =>
  3046. toggle.value ? h('div') : null,
  3047. )
  3048. },
  3049. }).mount(root)
  3050. expect(root.innerHTML).toBe(`<div></div>`)
  3051. toggle.value = false
  3052. await nextTick()
  3053. expect(innerSpy).toHaveBeenCalledTimes(1)
  3054. expect(outerSpy).toHaveBeenCalledTimes(1)
  3055. expect(root.innerHTML).toBe(`<!---->`)
  3056. })
  3057. test(
  3058. 'should work with dev root fragment',
  3059. async () => {
  3060. await page().evaluate(() => {
  3061. const { createApp, ref } = (window as any).Vue
  3062. createApp({
  3063. components: {
  3064. Comp: {
  3065. template: `
  3066. <!-- Broken! -->
  3067. <div><slot /></div>
  3068. `,
  3069. },
  3070. },
  3071. template: `
  3072. <div id="container">
  3073. <transition>
  3074. <Comp class="test" v-if="toggle">
  3075. <div>content</div>
  3076. </Comp>
  3077. </transition>
  3078. </div>
  3079. <button id="toggleBtn" @click="click">button</button>
  3080. `,
  3081. setup: () => {
  3082. const toggle = ref(true)
  3083. const click = () => (toggle.value = !toggle.value)
  3084. return { toggle, click }
  3085. },
  3086. }).mount('#app')
  3087. })
  3088. expect(await html('#container')).toBe(
  3089. '<!-- Broken! --><div class="test"><div>content</div></div>',
  3090. )
  3091. // leave
  3092. expect(await classWhenTransitionStart()).toStrictEqual([
  3093. 'test',
  3094. 'v-leave-from',
  3095. 'v-leave-active',
  3096. ])
  3097. await nextFrame()
  3098. expect(await classList('.test')).toStrictEqual([
  3099. 'test',
  3100. 'v-leave-active',
  3101. 'v-leave-to',
  3102. ])
  3103. await transitionFinish()
  3104. expect(await html('#container')).toBe('<!--v-if-->')
  3105. // enter
  3106. expect(await classWhenTransitionStart()).toStrictEqual([
  3107. 'test',
  3108. 'v-enter-from',
  3109. 'v-enter-active',
  3110. ])
  3111. await nextFrame()
  3112. expect(await classList('.test')).toStrictEqual([
  3113. 'test',
  3114. 'v-enter-active',
  3115. 'v-enter-to',
  3116. ])
  3117. await transitionFinish()
  3118. expect(await html('#container')).toBe(
  3119. '<!-- Broken! --><div class="test"><div>content</div></div>',
  3120. )
  3121. },
  3122. E2E_TIMEOUT,
  3123. )
  3124. // #12091
  3125. test(
  3126. 'prevent enter when leaving',
  3127. async () => {
  3128. const hooks: string[] = []
  3129. const pushHook = (hook: string) => hooks.push(hook)
  3130. await page().exposeFunction('pushHook', pushHook)
  3131. await page().evaluate(() => {
  3132. const { pushHook } = window as any
  3133. const { createApp, ref } = (window as any).Vue
  3134. const visible = ref(true)
  3135. createApp({
  3136. components: {
  3137. Comp: {
  3138. setup() {
  3139. visible.value = false
  3140. return () => null
  3141. },
  3142. },
  3143. },
  3144. template: `
  3145. <div id="content" v-if="toggle">
  3146. <div id="container">
  3147. <transition
  3148. appear
  3149. @before-enter="pushHook('beforeEnter')"
  3150. @enter="pushHook('enter')"
  3151. @enter-cancelled="pushHook('enterCancelled')"
  3152. @after-enter="pushHook('afterEnter')"
  3153. @before-leave="pushHook('beforeLeave')"
  3154. @leave="pushHook('leave')"
  3155. @after-leave="pushHook('afterLeave')"
  3156. >
  3157. <div v-if="visible">content</div>
  3158. </transition>
  3159. </div>
  3160. <Comp />
  3161. </div>
  3162. <button id="toggleBtn" @click="click">button</button>
  3163. `,
  3164. setup: () => {
  3165. const toggle = ref(false)
  3166. const click = () => (toggle.value = !toggle.value)
  3167. return {
  3168. toggle,
  3169. click,
  3170. pushHook,
  3171. visible,
  3172. }
  3173. },
  3174. }).mount('#app')
  3175. })
  3176. await click('#toggleBtn')
  3177. await nextTick()
  3178. await transitionFinish()
  3179. expect(hooks).toStrictEqual([
  3180. 'beforeEnter',
  3181. 'beforeLeave',
  3182. 'leave',
  3183. 'afterLeave',
  3184. ])
  3185. expect(await html('#content')).toBe(
  3186. '<div id="container"><!--v-if--></div><!---->',
  3187. )
  3188. },
  3189. E2E_TIMEOUT,
  3190. )
  3191. // https://github.com/vuejs/core/issues/12181#issuecomment-2414380955
  3192. describe('not leaking', async () => {
  3193. test('switching VNodes', async () => {
  3194. const client = await page().createCDPSession()
  3195. await page().evaluate(async () => {
  3196. const { createApp, ref, nextTick } = (window as any).Vue
  3197. const empty = ref(true)
  3198. createApp({
  3199. components: {
  3200. Child: {
  3201. setup: () => {
  3202. // Big arrays kick GC earlier
  3203. const test = ref([...Array(30_000_000)].map((_, i) => ({ i })))
  3204. // TODO: Use a different TypeScript env for testing
  3205. // @ts-expect-error - Custom property and same lib as runtime is used
  3206. window.__REF__ = new WeakRef(test)
  3207. return { test }
  3208. },
  3209. template: `
  3210. <p>{{ test.length }}</p>
  3211. `,
  3212. },
  3213. Empty: {
  3214. template: '<div></div>',
  3215. },
  3216. },
  3217. template: `
  3218. <transition>
  3219. <component :is="empty ? 'Empty' : 'Child'" />
  3220. </transition>
  3221. `,
  3222. setup() {
  3223. return { empty }
  3224. },
  3225. }).mount('#app')
  3226. await nextTick()
  3227. empty.value = false
  3228. await nextTick()
  3229. empty.value = true
  3230. await nextTick()
  3231. })
  3232. const isCollected = async () =>
  3233. // @ts-expect-error - Custom property
  3234. await page().evaluate(() => window.__REF__.deref() === undefined)
  3235. while ((await isCollected()) === false) {
  3236. await client.send('HeapProfiler.collectGarbage')
  3237. }
  3238. expect(await isCollected()).toBe(true)
  3239. })
  3240. // https://github.com/vuejs/core/issues/12181#issue-2588232334
  3241. test('switching deep vnodes edge case', async () => {
  3242. const client = await page().createCDPSession()
  3243. await page().evaluate(async () => {
  3244. const { createApp, ref, nextTick } = (window as any).Vue
  3245. const shown = ref(false)
  3246. createApp({
  3247. components: {
  3248. Child: {
  3249. setup: () => {
  3250. // Big arrays kick GC earlier
  3251. const test = ref([...Array(30_000_000)].map((_, i) => ({ i })))
  3252. // TODO: Use a different TypeScript env for testing
  3253. // @ts-expect-error - Custom property and same lib as runtime is used
  3254. window.__REF__ = new WeakRef(test)
  3255. return { test }
  3256. },
  3257. template: `
  3258. <p>{{ test.length }}</p>
  3259. `,
  3260. },
  3261. Wrapper: {
  3262. template: `
  3263. <transition>
  3264. <div v-if="true">
  3265. <slot />
  3266. </div>
  3267. </transition>
  3268. `,
  3269. },
  3270. },
  3271. template: `
  3272. <button id="toggleBtn" @click="shown = !shown">{{ shown ? 'Hide' : 'Show' }}</button>
  3273. <Wrapper>
  3274. <Child v-if="shown" />
  3275. <div v-else></div>
  3276. </Wrapper>
  3277. `,
  3278. setup() {
  3279. return { shown }
  3280. },
  3281. }).mount('#app')
  3282. await nextTick()
  3283. shown.value = true
  3284. await nextTick()
  3285. shown.value = false
  3286. await nextTick()
  3287. })
  3288. const isCollected = async () =>
  3289. // @ts-expect-error - Custom property
  3290. await page().evaluate(() => window.__REF__.deref() === undefined)
  3291. while ((await isCollected()) === false) {
  3292. await client.send('HeapProfiler.collectGarbage')
  3293. }
  3294. expect(await isCollected()).toBe(true)
  3295. })
  3296. })
  3297. })