Transition.spec.ts 59 KB

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