Transition.spec.ts 71 KB

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