Transition.spec.ts 76 KB

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