Transition.spec.ts 60 KB

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