Transition.spec.ts 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020
  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. // #3963
  1252. test(
  1253. 'Suspense fallback should work with transition',
  1254. async () => {
  1255. await page().evaluate(() => {
  1256. const { createApp, shallowRef, h } = (window as any).Vue
  1257. const One = {
  1258. template: `<div>{{ msg }}</div>`,
  1259. setup() {
  1260. return new Promise(_resolve => {
  1261. // @ts-ignore
  1262. window.resolve = () =>
  1263. _resolve({
  1264. msg: 'success'
  1265. })
  1266. })
  1267. }
  1268. }
  1269. createApp({
  1270. template: `
  1271. <div id="container">
  1272. <transition mode="out-in">
  1273. <Suspense :timeout="0">
  1274. <template #default>
  1275. <component :is="view" />
  1276. </template>
  1277. <template #fallback>
  1278. <div>Loading...</div>
  1279. </template>
  1280. </Suspense>
  1281. </transition>
  1282. </div>
  1283. <button id="toggleBtn" @click="click">button</button>
  1284. `,
  1285. setup: () => {
  1286. const view = shallowRef(null)
  1287. const click = () => {
  1288. view.value = view.value ? null : h(One)
  1289. }
  1290. return { view, click }
  1291. }
  1292. }).mount('#app')
  1293. })
  1294. expect(await html('#container')).toBe('<!---->')
  1295. await click('#toggleBtn')
  1296. await nextFrame()
  1297. expect(await html('#container')).toBe('<div class="">Loading...</div>')
  1298. await page().evaluate(() => {
  1299. // @ts-ignore
  1300. window.resolve()
  1301. })
  1302. await transitionFinish(duration * 2)
  1303. expect(await html('#container')).toBe('<div class="">success</div>')
  1304. },
  1305. E2E_TIMEOUT
  1306. )
  1307. })
  1308. describe('transition with v-show', () => {
  1309. test(
  1310. 'named transition with v-show',
  1311. async () => {
  1312. await page().evaluate(() => {
  1313. const { createApp, ref } = (window as any).Vue
  1314. createApp({
  1315. template: `
  1316. <div id="container">
  1317. <transition name="test">
  1318. <div v-show="toggle" class="test">content</div>
  1319. </transition>
  1320. </div>
  1321. <button id="toggleBtn" @click="click">button</button>
  1322. `,
  1323. setup: () => {
  1324. const toggle = ref(true)
  1325. const click = () => (toggle.value = !toggle.value)
  1326. return { toggle, click }
  1327. }
  1328. }).mount('#app')
  1329. })
  1330. expect(await html('#container')).toBe('<div class="test">content</div>')
  1331. expect(await isVisible('.test')).toBe(true)
  1332. // leave
  1333. expect(await classWhenTransitionStart()).toStrictEqual([
  1334. 'test',
  1335. 'test-leave-from',
  1336. 'test-leave-active'
  1337. ])
  1338. await nextFrame()
  1339. expect(await classList('.test')).toStrictEqual([
  1340. 'test',
  1341. 'test-leave-active',
  1342. 'test-leave-to'
  1343. ])
  1344. await transitionFinish()
  1345. expect(await isVisible('.test')).toBe(false)
  1346. // enter
  1347. expect(await classWhenTransitionStart()).toStrictEqual([
  1348. 'test',
  1349. 'test-enter-from',
  1350. 'test-enter-active'
  1351. ])
  1352. await nextFrame()
  1353. expect(await classList('.test')).toStrictEqual([
  1354. 'test',
  1355. 'test-enter-active',
  1356. 'test-enter-to'
  1357. ])
  1358. await transitionFinish()
  1359. expect(await html('#container')).toBe(
  1360. '<div class="test" style="">content</div>'
  1361. )
  1362. },
  1363. E2E_TIMEOUT
  1364. )
  1365. test(
  1366. 'transition events with v-show',
  1367. async () => {
  1368. const beforeLeaveSpy = jest.fn()
  1369. const onLeaveSpy = jest.fn()
  1370. const afterLeaveSpy = jest.fn()
  1371. const beforeEnterSpy = jest.fn()
  1372. const onEnterSpy = jest.fn()
  1373. const afterEnterSpy = jest.fn()
  1374. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1375. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1376. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  1377. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  1378. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  1379. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  1380. await page().evaluate(() => {
  1381. const {
  1382. beforeEnterSpy,
  1383. onEnterSpy,
  1384. afterEnterSpy,
  1385. beforeLeaveSpy,
  1386. onLeaveSpy,
  1387. afterLeaveSpy
  1388. } = window as any
  1389. const { createApp, ref } = (window as any).Vue
  1390. createApp({
  1391. template: `
  1392. <div id="container">
  1393. <transition
  1394. name="test"
  1395. @before-enter="beforeEnterSpy"
  1396. @enter="onEnterSpy"
  1397. @after-enter="afterEnterSpy"
  1398. @before-leave="beforeLeaveSpy"
  1399. @leave="onLeaveSpy"
  1400. @after-leave="afterLeaveSpy">
  1401. <div v-show="toggle" class="test">content</div>
  1402. </transition>
  1403. </div>
  1404. <button id="toggleBtn" @click="click">button</button>
  1405. `,
  1406. setup: () => {
  1407. const toggle = ref(true)
  1408. const click = () => (toggle.value = !toggle.value)
  1409. return {
  1410. toggle,
  1411. click,
  1412. beforeEnterSpy,
  1413. onEnterSpy,
  1414. afterEnterSpy,
  1415. beforeLeaveSpy,
  1416. onLeaveSpy,
  1417. afterLeaveSpy
  1418. }
  1419. }
  1420. }).mount('#app')
  1421. })
  1422. expect(await html('#container')).toBe('<div class="test">content</div>')
  1423. // leave
  1424. expect(await classWhenTransitionStart()).toStrictEqual([
  1425. 'test',
  1426. 'test-leave-from',
  1427. 'test-leave-active'
  1428. ])
  1429. expect(beforeLeaveSpy).toBeCalled()
  1430. expect(onLeaveSpy).toBeCalled()
  1431. expect(afterLeaveSpy).not.toBeCalled()
  1432. await nextFrame()
  1433. expect(await classList('.test')).toStrictEqual([
  1434. 'test',
  1435. 'test-leave-active',
  1436. 'test-leave-to'
  1437. ])
  1438. expect(afterLeaveSpy).not.toBeCalled()
  1439. await transitionFinish()
  1440. expect(await isVisible('.test')).toBe(false)
  1441. expect(afterLeaveSpy).toBeCalled()
  1442. // enter
  1443. expect(await classWhenTransitionStart()).toStrictEqual([
  1444. 'test',
  1445. 'test-enter-from',
  1446. 'test-enter-active'
  1447. ])
  1448. expect(beforeEnterSpy).toBeCalled()
  1449. expect(onEnterSpy).toBeCalled()
  1450. expect(afterEnterSpy).not.toBeCalled()
  1451. await nextFrame()
  1452. expect(await classList('.test')).toStrictEqual([
  1453. 'test',
  1454. 'test-enter-active',
  1455. 'test-enter-to'
  1456. ])
  1457. expect(afterEnterSpy).not.toBeCalled()
  1458. await transitionFinish()
  1459. expect(await html('#container')).toBe(
  1460. '<div class="test" style="">content</div>'
  1461. )
  1462. expect(afterEnterSpy).toBeCalled()
  1463. },
  1464. E2E_TIMEOUT
  1465. )
  1466. test(
  1467. 'onLeaveCancelled (v-show only)',
  1468. async () => {
  1469. const onLeaveCancelledSpy = jest.fn()
  1470. await page().exposeFunction('onLeaveCancelledSpy', onLeaveCancelledSpy)
  1471. await page().evaluate(() => {
  1472. const { onLeaveCancelledSpy } = window as any
  1473. const { createApp, ref } = (window as any).Vue
  1474. createApp({
  1475. template: `
  1476. <div id="container">
  1477. <transition name="test" @leave-cancelled="onLeaveCancelledSpy">
  1478. <div v-show="toggle" class="test">content</div>
  1479. </transition>
  1480. </div>
  1481. <button id="toggleBtn" @click="click">button</button>
  1482. `,
  1483. setup: () => {
  1484. const toggle = ref(true)
  1485. const click = () => (toggle.value = !toggle.value)
  1486. return { toggle, click, onLeaveCancelledSpy }
  1487. }
  1488. }).mount('#app')
  1489. })
  1490. expect(await html('#container')).toBe('<div class="test">content</div>')
  1491. expect(await isVisible('.test')).toBe(true)
  1492. // leave
  1493. expect(await classWhenTransitionStart()).toStrictEqual([
  1494. 'test',
  1495. 'test-leave-from',
  1496. 'test-leave-active'
  1497. ])
  1498. await nextFrame()
  1499. expect(await classList('.test')).toStrictEqual([
  1500. 'test',
  1501. 'test-leave-active',
  1502. 'test-leave-to'
  1503. ])
  1504. // cancel (enter)
  1505. expect(await classWhenTransitionStart()).toStrictEqual([
  1506. 'test',
  1507. 'test-enter-from',
  1508. 'test-enter-active'
  1509. ])
  1510. expect(onLeaveCancelledSpy).toBeCalled()
  1511. await nextFrame()
  1512. expect(await classList('.test')).toStrictEqual([
  1513. 'test',
  1514. 'test-enter-active',
  1515. 'test-enter-to'
  1516. ])
  1517. await transitionFinish()
  1518. expect(await html('#container')).toBe(
  1519. '<div class="test" style="">content</div>'
  1520. )
  1521. },
  1522. E2E_TIMEOUT
  1523. )
  1524. test(
  1525. 'transition on appear with v-show',
  1526. async () => {
  1527. const appearClass = await page().evaluate(async () => {
  1528. const { createApp, ref } = (window as any).Vue
  1529. createApp({
  1530. template: `
  1531. <div id="container">
  1532. <transition name="test"
  1533. appear
  1534. appear-from-class="test-appear-from"
  1535. appear-to-class="test-appear-to"
  1536. appear-active-class="test-appear-active">
  1537. <div v-show="toggle" class="test">content</div>
  1538. </transition>
  1539. </div>
  1540. <button id="toggleBtn" @click="click">button</button>
  1541. `,
  1542. setup: () => {
  1543. const toggle = ref(true)
  1544. const click = () => (toggle.value = !toggle.value)
  1545. return { toggle, click }
  1546. }
  1547. }).mount('#app')
  1548. return Promise.resolve().then(() => {
  1549. return document.querySelector('.test')!.className.split(/\s+/g)
  1550. })
  1551. })
  1552. // appear
  1553. expect(appearClass).toStrictEqual([
  1554. 'test',
  1555. 'test-appear-from',
  1556. 'test-appear-active'
  1557. ])
  1558. await nextFrame()
  1559. expect(await classList('.test')).toStrictEqual([
  1560. 'test',
  1561. 'test-appear-active',
  1562. 'test-appear-to'
  1563. ])
  1564. await transitionFinish()
  1565. expect(await html('#container')).toBe('<div class="test">content</div>')
  1566. // leave
  1567. expect(await classWhenTransitionStart()).toStrictEqual([
  1568. 'test',
  1569. 'test-leave-from',
  1570. 'test-leave-active'
  1571. ])
  1572. await nextFrame()
  1573. expect(await classList('.test')).toStrictEqual([
  1574. 'test',
  1575. 'test-leave-active',
  1576. 'test-leave-to'
  1577. ])
  1578. await transitionFinish()
  1579. expect(await isVisible('.test')).toBe(false)
  1580. // enter
  1581. expect(await classWhenTransitionStart()).toStrictEqual([
  1582. 'test',
  1583. 'test-enter-from',
  1584. 'test-enter-active'
  1585. ])
  1586. await nextFrame()
  1587. expect(await classList('.test')).toStrictEqual([
  1588. 'test',
  1589. 'test-enter-active',
  1590. 'test-enter-to'
  1591. ])
  1592. await transitionFinish()
  1593. expect(await html('#container')).toBe(
  1594. '<div class="test" style="">content</div>'
  1595. )
  1596. },
  1597. E2E_TIMEOUT
  1598. )
  1599. })
  1600. describe('explicit durations', () => {
  1601. test(
  1602. 'single value',
  1603. async () => {
  1604. await page().evaluate(duration => {
  1605. const { createApp, ref } = (window as any).Vue
  1606. createApp({
  1607. template: `
  1608. <div id="container">
  1609. <transition name="test" duration="${duration * 2}">
  1610. <div v-if="toggle" class="test">content</div>
  1611. </transition>
  1612. </div>
  1613. <button id="toggleBtn" @click="click">button</button>
  1614. `,
  1615. setup: () => {
  1616. const toggle = ref(true)
  1617. const click = () => (toggle.value = !toggle.value)
  1618. return { toggle, click }
  1619. }
  1620. }).mount('#app')
  1621. }, duration)
  1622. expect(await html('#container')).toBe('<div class="test">content</div>')
  1623. // leave
  1624. expect(await classWhenTransitionStart()).toStrictEqual([
  1625. 'test',
  1626. 'test-leave-from',
  1627. 'test-leave-active'
  1628. ])
  1629. await nextFrame()
  1630. expect(await classList('.test')).toStrictEqual([
  1631. 'test',
  1632. 'test-leave-active',
  1633. 'test-leave-to'
  1634. ])
  1635. await transitionFinish(duration * 2)
  1636. expect(await html('#container')).toBe('<!--v-if-->')
  1637. // enter
  1638. expect(await classWhenTransitionStart()).toStrictEqual([
  1639. 'test',
  1640. 'test-enter-from',
  1641. 'test-enter-active'
  1642. ])
  1643. await nextFrame()
  1644. expect(await classList('.test')).toStrictEqual([
  1645. 'test',
  1646. 'test-enter-active',
  1647. 'test-enter-to'
  1648. ])
  1649. await transitionFinish(duration * 2)
  1650. expect(await html('#container')).toBe('<div class="test">content</div>')
  1651. },
  1652. E2E_TIMEOUT
  1653. )
  1654. test(
  1655. 'enter with explicit durations',
  1656. async () => {
  1657. await page().evaluate(duration => {
  1658. const { createApp, ref } = (window as any).Vue
  1659. createApp({
  1660. template: `
  1661. <div id="container">
  1662. <transition name="test" :duration="{ enter: ${duration * 2} }">
  1663. <div v-if="toggle" class="test">content</div>
  1664. </transition>
  1665. </div>
  1666. <button id="toggleBtn" @click="click">button</button>
  1667. `,
  1668. setup: () => {
  1669. const toggle = ref(true)
  1670. const click = () => (toggle.value = !toggle.value)
  1671. return { toggle, click }
  1672. }
  1673. }).mount('#app')
  1674. }, duration)
  1675. expect(await html('#container')).toBe('<div class="test">content</div>')
  1676. // leave
  1677. expect(await classWhenTransitionStart()).toStrictEqual([
  1678. 'test',
  1679. 'test-leave-from',
  1680. 'test-leave-active'
  1681. ])
  1682. await nextFrame()
  1683. expect(await classList('.test')).toStrictEqual([
  1684. 'test',
  1685. 'test-leave-active',
  1686. 'test-leave-to'
  1687. ])
  1688. await transitionFinish()
  1689. expect(await html('#container')).toBe('<!--v-if-->')
  1690. // enter
  1691. expect(await classWhenTransitionStart()).toStrictEqual([
  1692. 'test',
  1693. 'test-enter-from',
  1694. 'test-enter-active'
  1695. ])
  1696. await nextFrame()
  1697. expect(await classList('.test')).toStrictEqual([
  1698. 'test',
  1699. 'test-enter-active',
  1700. 'test-enter-to'
  1701. ])
  1702. await transitionFinish(duration * 2)
  1703. expect(await html('#container')).toBe('<div class="test">content</div>')
  1704. },
  1705. E2E_TIMEOUT
  1706. )
  1707. test(
  1708. 'leave with explicit durations',
  1709. async () => {
  1710. await page().evaluate(duration => {
  1711. const { createApp, ref } = (window as any).Vue
  1712. createApp({
  1713. template: `
  1714. <div id="container">
  1715. <transition name="test" :duration="{ leave: ${duration * 2} }">
  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()
  1756. expect(await html('#container')).toBe('<div class="test">content</div>')
  1757. },
  1758. E2E_TIMEOUT
  1759. )
  1760. test(
  1761. 'separate enter and leave',
  1762. async () => {
  1763. await page().evaluate(duration => {
  1764. const { createApp, ref } = (window as any).Vue
  1765. createApp({
  1766. template: `
  1767. <div id="container">
  1768. <transition name="test" :duration="{
  1769. enter: ${duration * 4},
  1770. leave: ${duration * 2}
  1771. }">
  1772. <div v-if="toggle" class="test">content</div>
  1773. </transition>
  1774. </div>
  1775. <button id="toggleBtn" @click="click">button</button>
  1776. `,
  1777. setup: () => {
  1778. const toggle = ref(true)
  1779. const click = () => (toggle.value = !toggle.value)
  1780. return { toggle, click }
  1781. }
  1782. }).mount('#app')
  1783. }, duration)
  1784. expect(await html('#container')).toBe('<div class="test">content</div>')
  1785. // leave
  1786. expect(await classWhenTransitionStart()).toStrictEqual([
  1787. 'test',
  1788. 'test-leave-from',
  1789. 'test-leave-active'
  1790. ])
  1791. await nextFrame()
  1792. expect(await classList('.test')).toStrictEqual([
  1793. 'test',
  1794. 'test-leave-active',
  1795. 'test-leave-to'
  1796. ])
  1797. await transitionFinish(duration * 2)
  1798. expect(await html('#container')).toBe('<!--v-if-->')
  1799. // enter
  1800. expect(await classWhenTransitionStart()).toStrictEqual([
  1801. 'test',
  1802. 'test-enter-from',
  1803. 'test-enter-active'
  1804. ])
  1805. await nextFrame()
  1806. expect(await classList('.test')).toStrictEqual([
  1807. 'test',
  1808. 'test-enter-active',
  1809. 'test-enter-to'
  1810. ])
  1811. await transitionFinish(duration * 4)
  1812. expect(await html('#container')).toBe('<div class="test">content</div>')
  1813. },
  1814. E2E_TIMEOUT
  1815. )
  1816. test(
  1817. 'warn invalid durations',
  1818. async () => {
  1819. createApp({
  1820. template: `
  1821. <div id="container">
  1822. <transition name="test" :duration="NaN">
  1823. <div class="test">content</div>
  1824. </transition>
  1825. </div>
  1826. `
  1827. }).mount(document.createElement('div'))
  1828. expect(
  1829. `[Vue warn]: <transition> explicit duration is NaN - ` +
  1830. 'the duration expression might be incorrect.'
  1831. ).toHaveBeenWarned()
  1832. createApp({
  1833. template: `
  1834. <div id="container">
  1835. <transition name="test" :duration="{
  1836. enter: {},
  1837. leave: {}
  1838. }">
  1839. <div class="test">content</div>
  1840. </transition>
  1841. </div>
  1842. `
  1843. }).mount(document.createElement('div'))
  1844. expect(
  1845. `[Vue warn]: <transition> explicit duration is not a valid number - ` +
  1846. `got ${JSON.stringify({})}`
  1847. ).toHaveBeenWarned()
  1848. },
  1849. E2E_TIMEOUT
  1850. )
  1851. })
  1852. test('warn when used on multiple elements', async () => {
  1853. createApp({
  1854. render() {
  1855. return h(Transition, null, {
  1856. default: () => [h('div'), h('div')]
  1857. })
  1858. }
  1859. }).mount(document.createElement('div'))
  1860. expect(
  1861. '<transition> can only be used on a single element or component'
  1862. ).toHaveBeenWarned()
  1863. })
  1864. // #3227
  1865. test(`HOC w/ merged hooks`, async () => {
  1866. const innerSpy = jest.fn()
  1867. const outerSpy = jest.fn()
  1868. const MyTransition = {
  1869. render(this: any) {
  1870. return h(
  1871. Transition,
  1872. {
  1873. onLeave(el, end) {
  1874. innerSpy()
  1875. end()
  1876. }
  1877. },
  1878. this.$slots.default
  1879. )
  1880. }
  1881. }
  1882. const toggle = ref(true)
  1883. const root = document.createElement('div')
  1884. createApp({
  1885. render() {
  1886. return h(
  1887. MyTransition,
  1888. { onLeave: () => outerSpy() },
  1889. () => (toggle.value ? h('div') : null)
  1890. )
  1891. }
  1892. }).mount(root)
  1893. expect(root.innerHTML).toBe(`<div></div>`)
  1894. toggle.value = false
  1895. await nextTick()
  1896. expect(innerSpy).toHaveBeenCalledTimes(1)
  1897. expect(outerSpy).toHaveBeenCalledTimes(1)
  1898. expect(root.innerHTML).toBe(`<!---->`)
  1899. })
  1900. })