Transition.spec.ts 71 KB

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