Transition.spec.ts 110 KB

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