Transition.spec.ts 62 KB

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