Transition.spec.ts 52 KB

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