Transition.spec.ts 90 KB

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