Transition.spec.ts 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194
  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 beforeEnterSpy = jest.fn()
  1521. const onEnterSpy = jest.fn()
  1522. const afterEnterSpy = jest.fn()
  1523. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1524. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  1525. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  1526. const appearClass = await page().evaluate(async () => {
  1527. const { createApp, ref } = (window as any).Vue
  1528. const { beforeEnterSpy, onEnterSpy, afterEnterSpy } = window as any
  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. @before-enter="beforeEnterSpy"
  1538. @enter="onEnterSpy"
  1539. @after-enter="afterEnterSpy">
  1540. <div v-show="toggle" class="test">content</div>
  1541. </transition>
  1542. </div>
  1543. <button id="toggleBtn" @click="click">button</button>
  1544. `,
  1545. setup: () => {
  1546. const toggle = ref(true)
  1547. const click = () => (toggle.value = !toggle.value)
  1548. return {
  1549. toggle,
  1550. click,
  1551. beforeEnterSpy,
  1552. onEnterSpy,
  1553. afterEnterSpy
  1554. }
  1555. }
  1556. }).mount('#app')
  1557. return Promise.resolve().then(() => {
  1558. return document.querySelector('.test')!.className.split(/\s+/g)
  1559. })
  1560. })
  1561. expect(beforeEnterSpy).toBeCalledTimes(1)
  1562. expect(onEnterSpy).toBeCalledTimes(1)
  1563. expect(afterEnterSpy).toBeCalledTimes(0)
  1564. // appear
  1565. expect(appearClass).toStrictEqual([
  1566. 'test',
  1567. 'test-appear-from',
  1568. 'test-appear-active'
  1569. ])
  1570. await nextFrame()
  1571. expect(await classList('.test')).toStrictEqual([
  1572. 'test',
  1573. 'test-appear-active',
  1574. 'test-appear-to'
  1575. ])
  1576. await transitionFinish()
  1577. expect(await html('#container')).toBe('<div class="test">content</div>')
  1578. expect(beforeEnterSpy).toBeCalledTimes(1)
  1579. expect(onEnterSpy).toBeCalledTimes(1)
  1580. expect(afterEnterSpy).toBeCalledTimes(1)
  1581. // leave
  1582. expect(await classWhenTransitionStart()).toStrictEqual([
  1583. 'test',
  1584. 'test-leave-from',
  1585. 'test-leave-active'
  1586. ])
  1587. await nextFrame()
  1588. expect(await classList('.test')).toStrictEqual([
  1589. 'test',
  1590. 'test-leave-active',
  1591. 'test-leave-to'
  1592. ])
  1593. await transitionFinish()
  1594. expect(await isVisible('.test')).toBe(false)
  1595. // enter
  1596. expect(await classWhenTransitionStart()).toStrictEqual([
  1597. 'test',
  1598. 'test-enter-from',
  1599. 'test-enter-active'
  1600. ])
  1601. await nextFrame()
  1602. expect(await classList('.test')).toStrictEqual([
  1603. 'test',
  1604. 'test-enter-active',
  1605. 'test-enter-to'
  1606. ])
  1607. await transitionFinish()
  1608. expect(await html('#container')).toBe(
  1609. '<div class="test" style="">content</div>'
  1610. )
  1611. },
  1612. E2E_TIMEOUT
  1613. )
  1614. // #4845
  1615. test(
  1616. 'transition events should not call onEnter with v-show false',
  1617. async () => {
  1618. const beforeEnterSpy = jest.fn()
  1619. const onEnterSpy = jest.fn()
  1620. const afterEnterSpy = jest.fn()
  1621. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1622. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  1623. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  1624. await page().evaluate(() => {
  1625. const { beforeEnterSpy, onEnterSpy, afterEnterSpy } = window as any
  1626. const { createApp, ref } = (window as any).Vue
  1627. createApp({
  1628. template: `
  1629. <div id="container">
  1630. <transition
  1631. name="test"
  1632. appear
  1633. @before-enter="beforeEnterSpy"
  1634. @enter="onEnterSpy"
  1635. @after-enter="afterEnterSpy">
  1636. <div v-show="toggle" class="test">content</div>
  1637. </transition>
  1638. </div>
  1639. <button id="toggleBtn" @click="click">button</button>
  1640. `,
  1641. setup: () => {
  1642. const toggle = ref(false)
  1643. const click = () => (toggle.value = !toggle.value)
  1644. return {
  1645. toggle,
  1646. click,
  1647. beforeEnterSpy,
  1648. onEnterSpy,
  1649. afterEnterSpy
  1650. }
  1651. }
  1652. }).mount('#app')
  1653. })
  1654. await nextTick()
  1655. expect(await isVisible('.test')).toBe(false)
  1656. expect(beforeEnterSpy).toBeCalledTimes(0)
  1657. expect(onEnterSpy).toBeCalledTimes(0)
  1658. // enter
  1659. expect(await classWhenTransitionStart()).toStrictEqual([
  1660. 'test',
  1661. 'test-enter-from',
  1662. 'test-enter-active'
  1663. ])
  1664. expect(beforeEnterSpy).toBeCalledTimes(1)
  1665. expect(onEnterSpy).toBeCalledTimes(1)
  1666. expect(afterEnterSpy).not.toBeCalled()
  1667. await nextFrame()
  1668. expect(await classList('.test')).toStrictEqual([
  1669. 'test',
  1670. 'test-enter-active',
  1671. 'test-enter-to'
  1672. ])
  1673. expect(afterEnterSpy).not.toBeCalled()
  1674. await transitionFinish()
  1675. expect(await html('#container')).toBe(
  1676. '<div class="test" style="">content</div>'
  1677. )
  1678. expect(afterEnterSpy).toBeCalled()
  1679. },
  1680. E2E_TIMEOUT
  1681. )
  1682. })
  1683. describe('explicit durations', () => {
  1684. test(
  1685. 'single value',
  1686. async () => {
  1687. await page().evaluate(duration => {
  1688. const { createApp, ref } = (window as any).Vue
  1689. createApp({
  1690. template: `
  1691. <div id="container">
  1692. <transition name="test" duration="${duration * 2}">
  1693. <div v-if="toggle" class="test">content</div>
  1694. </transition>
  1695. </div>
  1696. <button id="toggleBtn" @click="click">button</button>
  1697. `,
  1698. setup: () => {
  1699. const toggle = ref(true)
  1700. const click = () => (toggle.value = !toggle.value)
  1701. return { toggle, click }
  1702. }
  1703. }).mount('#app')
  1704. }, duration)
  1705. expect(await html('#container')).toBe('<div class="test">content</div>')
  1706. // leave
  1707. expect(await classWhenTransitionStart()).toStrictEqual([
  1708. 'test',
  1709. 'test-leave-from',
  1710. 'test-leave-active'
  1711. ])
  1712. await nextFrame()
  1713. expect(await classList('.test')).toStrictEqual([
  1714. 'test',
  1715. 'test-leave-active',
  1716. 'test-leave-to'
  1717. ])
  1718. await transitionFinish(duration * 2)
  1719. expect(await html('#container')).toBe('<!--v-if-->')
  1720. // enter
  1721. expect(await classWhenTransitionStart()).toStrictEqual([
  1722. 'test',
  1723. 'test-enter-from',
  1724. 'test-enter-active'
  1725. ])
  1726. await nextFrame()
  1727. expect(await classList('.test')).toStrictEqual([
  1728. 'test',
  1729. 'test-enter-active',
  1730. 'test-enter-to'
  1731. ])
  1732. await transitionFinish(duration * 2)
  1733. expect(await html('#container')).toBe('<div class="test">content</div>')
  1734. },
  1735. E2E_TIMEOUT
  1736. )
  1737. test(
  1738. 'enter with explicit durations',
  1739. async () => {
  1740. await page().evaluate(duration => {
  1741. const { createApp, ref } = (window as any).Vue
  1742. createApp({
  1743. template: `
  1744. <div id="container">
  1745. <transition name="test" :duration="{ enter: ${duration * 2} }">
  1746. <div v-if="toggle" class="test">content</div>
  1747. </transition>
  1748. </div>
  1749. <button id="toggleBtn" @click="click">button</button>
  1750. `,
  1751. setup: () => {
  1752. const toggle = ref(true)
  1753. const click = () => (toggle.value = !toggle.value)
  1754. return { toggle, click }
  1755. }
  1756. }).mount('#app')
  1757. }, duration)
  1758. expect(await html('#container')).toBe('<div class="test">content</div>')
  1759. // leave
  1760. expect(await classWhenTransitionStart()).toStrictEqual([
  1761. 'test',
  1762. 'test-leave-from',
  1763. 'test-leave-active'
  1764. ])
  1765. await nextFrame()
  1766. expect(await classList('.test')).toStrictEqual([
  1767. 'test',
  1768. 'test-leave-active',
  1769. 'test-leave-to'
  1770. ])
  1771. await transitionFinish()
  1772. expect(await html('#container')).toBe('<!--v-if-->')
  1773. // enter
  1774. expect(await classWhenTransitionStart()).toStrictEqual([
  1775. 'test',
  1776. 'test-enter-from',
  1777. 'test-enter-active'
  1778. ])
  1779. await nextFrame()
  1780. expect(await classList('.test')).toStrictEqual([
  1781. 'test',
  1782. 'test-enter-active',
  1783. 'test-enter-to'
  1784. ])
  1785. await transitionFinish(duration * 2)
  1786. expect(await html('#container')).toBe('<div class="test">content</div>')
  1787. },
  1788. E2E_TIMEOUT
  1789. )
  1790. test(
  1791. 'leave with explicit durations',
  1792. async () => {
  1793. await page().evaluate(duration => {
  1794. const { createApp, ref } = (window as any).Vue
  1795. createApp({
  1796. template: `
  1797. <div id="container">
  1798. <transition name="test" :duration="{ leave: ${duration * 2} }">
  1799. <div v-if="toggle" class="test">content</div>
  1800. </transition>
  1801. </div>
  1802. <button id="toggleBtn" @click="click">button</button>
  1803. `,
  1804. setup: () => {
  1805. const toggle = ref(true)
  1806. const click = () => (toggle.value = !toggle.value)
  1807. return { toggle, click }
  1808. }
  1809. }).mount('#app')
  1810. }, duration)
  1811. expect(await html('#container')).toBe('<div class="test">content</div>')
  1812. // leave
  1813. expect(await classWhenTransitionStart()).toStrictEqual([
  1814. 'test',
  1815. 'test-leave-from',
  1816. 'test-leave-active'
  1817. ])
  1818. await nextFrame()
  1819. expect(await classList('.test')).toStrictEqual([
  1820. 'test',
  1821. 'test-leave-active',
  1822. 'test-leave-to'
  1823. ])
  1824. await transitionFinish(duration * 2)
  1825. expect(await html('#container')).toBe('<!--v-if-->')
  1826. // enter
  1827. expect(await classWhenTransitionStart()).toStrictEqual([
  1828. 'test',
  1829. 'test-enter-from',
  1830. 'test-enter-active'
  1831. ])
  1832. await nextFrame()
  1833. expect(await classList('.test')).toStrictEqual([
  1834. 'test',
  1835. 'test-enter-active',
  1836. 'test-enter-to'
  1837. ])
  1838. await transitionFinish()
  1839. expect(await html('#container')).toBe('<div class="test">content</div>')
  1840. },
  1841. E2E_TIMEOUT
  1842. )
  1843. test(
  1844. 'separate enter and leave',
  1845. async () => {
  1846. await page().evaluate(duration => {
  1847. const { createApp, ref } = (window as any).Vue
  1848. createApp({
  1849. template: `
  1850. <div id="container">
  1851. <transition name="test" :duration="{
  1852. enter: ${duration * 4},
  1853. leave: ${duration * 2}
  1854. }">
  1855. <div v-if="toggle" class="test">content</div>
  1856. </transition>
  1857. </div>
  1858. <button id="toggleBtn" @click="click">button</button>
  1859. `,
  1860. setup: () => {
  1861. const toggle = ref(true)
  1862. const click = () => (toggle.value = !toggle.value)
  1863. return { toggle, click }
  1864. }
  1865. }).mount('#app')
  1866. }, duration)
  1867. expect(await html('#container')).toBe('<div class="test">content</div>')
  1868. // leave
  1869. expect(await classWhenTransitionStart()).toStrictEqual([
  1870. 'test',
  1871. 'test-leave-from',
  1872. 'test-leave-active'
  1873. ])
  1874. await nextFrame()
  1875. expect(await classList('.test')).toStrictEqual([
  1876. 'test',
  1877. 'test-leave-active',
  1878. 'test-leave-to'
  1879. ])
  1880. await transitionFinish(duration * 2)
  1881. expect(await html('#container')).toBe('<!--v-if-->')
  1882. // enter
  1883. expect(await classWhenTransitionStart()).toStrictEqual([
  1884. 'test',
  1885. 'test-enter-from',
  1886. 'test-enter-active'
  1887. ])
  1888. await nextFrame()
  1889. expect(await classList('.test')).toStrictEqual([
  1890. 'test',
  1891. 'test-enter-active',
  1892. 'test-enter-to'
  1893. ])
  1894. await transitionFinish(duration * 4)
  1895. expect(await html('#container')).toBe('<div class="test">content</div>')
  1896. },
  1897. E2E_TIMEOUT
  1898. )
  1899. test(
  1900. 'warn invalid durations',
  1901. async () => {
  1902. createApp({
  1903. template: `
  1904. <div id="container">
  1905. <transition name="test" :duration="NaN">
  1906. <div class="test">content</div>
  1907. </transition>
  1908. </div>
  1909. `
  1910. }).mount(document.createElement('div'))
  1911. expect(
  1912. `[Vue warn]: <transition> explicit duration is NaN - ` +
  1913. 'the duration expression might be incorrect.'
  1914. ).toHaveBeenWarned()
  1915. createApp({
  1916. template: `
  1917. <div id="container">
  1918. <transition name="test" :duration="{
  1919. enter: {},
  1920. leave: {}
  1921. }">
  1922. <div class="test">content</div>
  1923. </transition>
  1924. </div>
  1925. `
  1926. }).mount(document.createElement('div'))
  1927. expect(
  1928. `[Vue warn]: <transition> explicit duration is not a valid number - ` +
  1929. `got ${JSON.stringify({})}`
  1930. ).toHaveBeenWarned()
  1931. },
  1932. E2E_TIMEOUT
  1933. )
  1934. })
  1935. test('warn when used on multiple elements', async () => {
  1936. createApp({
  1937. render() {
  1938. return h(Transition, null, {
  1939. default: () => [h('div'), h('div')]
  1940. })
  1941. }
  1942. }).mount(document.createElement('div'))
  1943. expect(
  1944. '<transition> can only be used on a single element or component'
  1945. ).toHaveBeenWarned()
  1946. })
  1947. test('warn when invalid transition mode', () => {
  1948. createApp({
  1949. template: `
  1950. <div id="container">
  1951. <transition name="test" mode="none">
  1952. <div class="test">content</div>
  1953. </transition>
  1954. </div>
  1955. `
  1956. }).mount(document.createElement('div'))
  1957. expect(`invalid <transition> mode: none`).toHaveBeenWarned()
  1958. })
  1959. // #3227
  1960. test(`HOC w/ merged hooks`, async () => {
  1961. const innerSpy = jest.fn()
  1962. const outerSpy = jest.fn()
  1963. const MyTransition = {
  1964. render(this: any) {
  1965. return h(
  1966. Transition,
  1967. {
  1968. onLeave(el, end) {
  1969. innerSpy()
  1970. end()
  1971. }
  1972. },
  1973. this.$slots.default
  1974. )
  1975. }
  1976. }
  1977. const toggle = ref(true)
  1978. const root = document.createElement('div')
  1979. createApp({
  1980. render() {
  1981. return h(MyTransition, { onLeave: () => outerSpy() }, () =>
  1982. toggle.value ? h('div') : null
  1983. )
  1984. }
  1985. }).mount(root)
  1986. expect(root.innerHTML).toBe(`<div></div>`)
  1987. toggle.value = false
  1988. await nextTick()
  1989. expect(innerSpy).toHaveBeenCalledTimes(1)
  1990. expect(outerSpy).toHaveBeenCalledTimes(1)
  1991. expect(root.innerHTML).toBe(`<!---->`)
  1992. })
  1993. test(
  1994. 'should work with dev root fragment',
  1995. async () => {
  1996. await page().evaluate(() => {
  1997. const { createApp, ref } = (window as any).Vue
  1998. createApp({
  1999. components: {
  2000. Comp: {
  2001. template: `
  2002. <!-- Broken! -->
  2003. <div><slot /></div>
  2004. `
  2005. }
  2006. },
  2007. template: `
  2008. <div id="container">
  2009. <transition>
  2010. <Comp class="test" v-if="toggle">
  2011. <div>content</div>
  2012. </Comp>
  2013. </transition>
  2014. </div>
  2015. <button id="toggleBtn" @click="click">button</button>
  2016. `,
  2017. setup: () => {
  2018. const toggle = ref(true)
  2019. const click = () => (toggle.value = !toggle.value)
  2020. return { toggle, click }
  2021. }
  2022. }).mount('#app')
  2023. })
  2024. expect(await html('#container')).toBe(
  2025. '<!-- Broken! --><div class="test"><div>content</div></div>'
  2026. )
  2027. // leave
  2028. expect(await classWhenTransitionStart()).toStrictEqual([
  2029. 'test',
  2030. 'v-leave-from',
  2031. 'v-leave-active'
  2032. ])
  2033. await nextFrame()
  2034. expect(await classList('.test')).toStrictEqual([
  2035. 'test',
  2036. 'v-leave-active',
  2037. 'v-leave-to'
  2038. ])
  2039. await transitionFinish()
  2040. expect(await html('#container')).toBe('<!--v-if-->')
  2041. // enter
  2042. expect(await classWhenTransitionStart()).toStrictEqual([
  2043. 'test',
  2044. 'v-enter-from',
  2045. 'v-enter-active'
  2046. ])
  2047. await nextFrame()
  2048. expect(await classList('.test')).toStrictEqual([
  2049. 'test',
  2050. 'v-enter-active',
  2051. 'v-enter-to'
  2052. ])
  2053. await transitionFinish()
  2054. expect(await html('#container')).toBe(
  2055. '<!-- Broken! --><div class="test"><div>content</div></div>'
  2056. )
  2057. },
  2058. E2E_TIMEOUT
  2059. )
  2060. })