Transition.spec.ts 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911
  1. import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
  2. import path from 'path'
  3. import { h, createApp, Transition } 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 ? 100 : 50
  16. const buffer = 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. await new Promise(r => {
  816. setTimeout(r, duration + 5)
  817. })
  818. expect(await classList('#container div')).toStrictEqual([
  819. 'test-anim-long-leave-active',
  820. 'test-anim-long-leave-to'
  821. ])
  822. await transitionFinish(duration * 2)
  823. expect(await html('#container')).toBe('<!--v-if-->')
  824. // enter
  825. expect(await classWhenTransitionStart()).toStrictEqual([
  826. 'test-anim-long-enter-from',
  827. 'test-anim-long-enter-active'
  828. ])
  829. await nextFrame()
  830. expect(await classList('#container div')).toStrictEqual([
  831. 'test-anim-long-enter-active',
  832. 'test-anim-long-enter-to'
  833. ])
  834. await new Promise(r => {
  835. setTimeout(r, duration + 5)
  836. })
  837. expect(await classList('#container div')).toStrictEqual([
  838. 'test-anim-long-enter-active',
  839. 'test-anim-long-enter-to'
  840. ])
  841. await transitionFinish(duration * 2)
  842. expect(await html('#container')).toBe('<div class="">content</div>')
  843. },
  844. E2E_TIMEOUT
  845. )
  846. test(
  847. 'transition on SVG elements',
  848. async () => {
  849. await page().evaluate(() => {
  850. const { createApp, ref } = (window as any).Vue
  851. createApp({
  852. template: `
  853. <svg id="container">
  854. <transition name="test">
  855. <circle v-if="toggle" cx="0" cy="0" r="10" class="test"></circle>
  856. </transition>
  857. </svg>
  858. <button id="toggleBtn" @click="click">button</button>
  859. `,
  860. setup: () => {
  861. const toggle = ref(true)
  862. const click = () => (toggle.value = !toggle.value)
  863. return { toggle, click }
  864. }
  865. }).mount('#app')
  866. })
  867. expect(await html('#container')).toBe(
  868. '<circle cx="0" cy="0" r="10" class="test"></circle>'
  869. )
  870. const svgTransitionStart = () =>
  871. page().evaluate(() => {
  872. document.querySelector('button')!.click()
  873. return Promise.resolve().then(() => {
  874. return document
  875. .querySelector('.test')!
  876. .getAttribute('class')!
  877. .split(/\s+/g)
  878. })
  879. })
  880. // leave
  881. expect(await svgTransitionStart()).toStrictEqual([
  882. 'test',
  883. 'test-leave-from',
  884. 'test-leave-active'
  885. ])
  886. await nextFrame()
  887. expect(await classList('.test')).toStrictEqual([
  888. 'test',
  889. 'test-leave-active',
  890. 'test-leave-to'
  891. ])
  892. await transitionFinish()
  893. expect(await html('#container')).toBe('<!--v-if-->')
  894. // enter
  895. expect(await svgTransitionStart()).toStrictEqual([
  896. 'test',
  897. 'test-enter-from',
  898. 'test-enter-active'
  899. ])
  900. await nextFrame()
  901. expect(await classList('.test')).toStrictEqual([
  902. 'test',
  903. 'test-enter-active',
  904. 'test-enter-to'
  905. ])
  906. await transitionFinish()
  907. expect(await html('#container')).toBe(
  908. '<circle cx="0" cy="0" r="10" class="test"></circle>'
  909. )
  910. },
  911. E2E_TIMEOUT
  912. )
  913. test(
  914. 'custom transition higher-order component',
  915. async () => {
  916. await page().evaluate(() => {
  917. const { createApp, ref, h, Transition } = (window as any).Vue
  918. createApp({
  919. template: `
  920. <div id="container"><my-transition><div v-if="toggle" class="test">content</div></my-transition></div>
  921. <button id="toggleBtn" @click="click">button</button>
  922. `,
  923. components: {
  924. 'my-transition': (props: any, { slots }: any) => {
  925. return h(Transition, { name: 'test' }, slots)
  926. }
  927. },
  928. setup: () => {
  929. const toggle = ref(true)
  930. const click = () => (toggle.value = !toggle.value)
  931. return { toggle, click }
  932. }
  933. }).mount('#app')
  934. })
  935. expect(await html('#container')).toBe('<div class="test">content</div>')
  936. // leave
  937. expect(await classWhenTransitionStart()).toStrictEqual([
  938. 'test',
  939. 'test-leave-from',
  940. 'test-leave-active'
  941. ])
  942. await nextFrame()
  943. expect(await classList('.test')).toStrictEqual([
  944. 'test',
  945. 'test-leave-active',
  946. 'test-leave-to'
  947. ])
  948. await transitionFinish()
  949. expect(await html('#container')).toBe('<!--v-if-->')
  950. // enter
  951. expect(await classWhenTransitionStart()).toStrictEqual([
  952. 'test',
  953. 'test-enter-from',
  954. 'test-enter-active'
  955. ])
  956. await nextFrame()
  957. expect(await classList('.test')).toStrictEqual([
  958. 'test',
  959. 'test-enter-active',
  960. 'test-enter-to'
  961. ])
  962. await transitionFinish()
  963. expect(await html('#container')).toBe('<div class="test">content</div>')
  964. },
  965. E2E_TIMEOUT
  966. )
  967. test(
  968. 'transition on child components with empty root node',
  969. async () => {
  970. await page().evaluate(() => {
  971. const { createApp, ref } = (window as any).Vue
  972. createApp({
  973. template: `
  974. <div id="container">
  975. <transition name="test">
  976. <component class="test" :is="view"></component>
  977. </transition>
  978. </div>
  979. <button id="toggleBtn" @click="click">button</button>
  980. <button id="changeViewBtn" @click="change">button</button>
  981. `,
  982. components: {
  983. one: {
  984. template: '<div v-if="false">one</div>'
  985. },
  986. two: {
  987. template: '<div>two</div>'
  988. }
  989. },
  990. setup: () => {
  991. const toggle = ref(true)
  992. const view = ref('one')
  993. const click = () => (toggle.value = !toggle.value)
  994. const change = () =>
  995. (view.value = view.value === 'one' ? 'two' : 'one')
  996. return { toggle, click, change, view }
  997. }
  998. }).mount('#app')
  999. })
  1000. expect(await html('#container')).toBe('<!--v-if-->')
  1001. // change view -> 'two'
  1002. await page().evaluate(() => {
  1003. (document.querySelector('#changeViewBtn') as any)!.click()
  1004. })
  1005. // enter
  1006. expect(await classWhenTransitionStart()).toStrictEqual([
  1007. 'test',
  1008. 'test-enter-from',
  1009. 'test-enter-active'
  1010. ])
  1011. await nextFrame()
  1012. expect(await classList('.test')).toStrictEqual([
  1013. 'test',
  1014. 'test-enter-active',
  1015. 'test-enter-to'
  1016. ])
  1017. await transitionFinish()
  1018. expect(await html('#container')).toBe('<div class="test">two</div>')
  1019. // change view -> 'one'
  1020. await page().evaluate(() => {
  1021. (document.querySelector('#changeViewBtn') as any)!.click()
  1022. })
  1023. // leave
  1024. expect(await classWhenTransitionStart()).toStrictEqual([
  1025. 'test',
  1026. 'test-leave-from',
  1027. 'test-leave-active'
  1028. ])
  1029. await nextFrame()
  1030. expect(await classList('.test')).toStrictEqual([
  1031. 'test',
  1032. 'test-leave-active',
  1033. 'test-leave-to'
  1034. ])
  1035. await transitionFinish()
  1036. expect(await html('#container')).toBe('<!--v-if-->')
  1037. },
  1038. E2E_TIMEOUT
  1039. )
  1040. })
  1041. describe('transition with Suspense', () => {
  1042. // #1583
  1043. test(
  1044. 'async component transition inside Suspense',
  1045. async () => {
  1046. const onLeaveSpy = jest.fn()
  1047. const onEnterSpy = jest.fn()
  1048. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1049. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1050. await page().evaluate(() => {
  1051. const { onEnterSpy, onLeaveSpy } = window as any
  1052. const { createApp, ref, h } = (window as any).Vue
  1053. createApp({
  1054. template: `
  1055. <div id="container">
  1056. <transition @enter="onEnterSpy" @leave="onLeaveSpy">
  1057. <Suspense>
  1058. <Comp v-if="toggle" class="test">content</Comp>
  1059. </Suspense>
  1060. </transition>
  1061. </div>
  1062. <button id="toggleBtn" @click="click">button</button>
  1063. `,
  1064. components: {
  1065. Comp: {
  1066. async setup() {
  1067. return () => h('div', { class: 'test' }, 'content')
  1068. }
  1069. }
  1070. },
  1071. setup: () => {
  1072. const toggle = ref(true)
  1073. const click = () => (toggle.value = !toggle.value)
  1074. return { toggle, click, onEnterSpy, onLeaveSpy }
  1075. }
  1076. }).mount('#app')
  1077. })
  1078. expect(onEnterSpy).toBeCalledTimes(1)
  1079. await nextFrame()
  1080. expect(await html('#container')).toBe(
  1081. '<div class="test v-enter-active v-enter-to">content</div>'
  1082. )
  1083. await transitionFinish()
  1084. expect(await html('#container')).toBe('<div class="test">content</div>')
  1085. // leave
  1086. expect(await classWhenTransitionStart()).toStrictEqual([
  1087. 'test',
  1088. 'v-leave-from',
  1089. 'v-leave-active'
  1090. ])
  1091. expect(onLeaveSpy).toBeCalledTimes(1)
  1092. await nextFrame()
  1093. expect(await classList('.test')).toStrictEqual([
  1094. 'test',
  1095. 'v-leave-active',
  1096. 'v-leave-to'
  1097. ])
  1098. await transitionFinish()
  1099. expect(await html('#container')).toBe('<!--v-if-->')
  1100. // enter
  1101. const enterClass = await page().evaluate(async () => {
  1102. (document.querySelector('#toggleBtn') as any)!.click()
  1103. // nextTrick for patch start
  1104. await Promise.resolve()
  1105. // nextTrick for Suspense resolve
  1106. await Promise.resolve()
  1107. // nextTrick for dom transition start
  1108. await Promise.resolve()
  1109. return document
  1110. .querySelector('#container div')!
  1111. .className.split(/\s+/g)
  1112. })
  1113. expect(enterClass).toStrictEqual([
  1114. 'test',
  1115. 'v-enter-from',
  1116. 'v-enter-active'
  1117. ])
  1118. expect(onEnterSpy).toBeCalledTimes(2)
  1119. await nextFrame()
  1120. expect(await classList('.test')).toStrictEqual([
  1121. 'test',
  1122. 'v-enter-active',
  1123. 'v-enter-to'
  1124. ])
  1125. await transitionFinish()
  1126. expect(await html('#container')).toBe('<div class="test">content</div>')
  1127. },
  1128. E2E_TIMEOUT
  1129. )
  1130. // #1689
  1131. test(
  1132. 'static node transition inside Suspense',
  1133. async () => {
  1134. await page().evaluate(() => {
  1135. const { createApp, ref } = (window as any).Vue
  1136. createApp({
  1137. template: `
  1138. <div id="container">
  1139. <transition>
  1140. <Suspense>
  1141. <div v-if="toggle" class="test">content</div>
  1142. </Suspense>
  1143. </transition>
  1144. </div>
  1145. <button id="toggleBtn" @click="click">button</button>
  1146. `,
  1147. setup: () => {
  1148. const toggle = ref(true)
  1149. const click = () => (toggle.value = !toggle.value)
  1150. return { toggle, click }
  1151. }
  1152. }).mount('#app')
  1153. })
  1154. expect(await html('#container')).toBe('<div class="test">content</div>')
  1155. // leave
  1156. expect(await classWhenTransitionStart()).toStrictEqual([
  1157. 'test',
  1158. 'v-leave-from',
  1159. 'v-leave-active'
  1160. ])
  1161. await nextFrame()
  1162. expect(await classList('.test')).toStrictEqual([
  1163. 'test',
  1164. 'v-leave-active',
  1165. 'v-leave-to'
  1166. ])
  1167. await transitionFinish()
  1168. expect(await html('#container')).toBe('<!--v-if-->')
  1169. // enter
  1170. expect(await classWhenTransitionStart()).toStrictEqual([
  1171. 'test',
  1172. 'v-enter-from',
  1173. 'v-enter-active'
  1174. ])
  1175. await nextFrame()
  1176. expect(await classList('.test')).toStrictEqual([
  1177. 'test',
  1178. 'v-enter-active',
  1179. 'v-enter-to'
  1180. ])
  1181. await transitionFinish()
  1182. expect(await html('#container')).toBe('<div class="test">content</div>')
  1183. },
  1184. E2E_TIMEOUT
  1185. )
  1186. test(
  1187. 'out-in mode with Suspense',
  1188. async () => {
  1189. const onLeaveSpy = jest.fn()
  1190. const onEnterSpy = jest.fn()
  1191. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1192. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1193. await page().evaluate(() => {
  1194. const { createApp, shallowRef, h } = (window as any).Vue
  1195. const One = {
  1196. async setup() {
  1197. return () => h('div', { class: 'test' }, 'one')
  1198. }
  1199. }
  1200. const Two = {
  1201. async setup() {
  1202. return () => h('div', { class: 'test' }, 'two')
  1203. }
  1204. }
  1205. createApp({
  1206. template: `
  1207. <div id="container">
  1208. <transition mode="out-in">
  1209. <Suspense>
  1210. <component :is="view"/>
  1211. </Suspense>
  1212. </transition>
  1213. </div>
  1214. <button id="toggleBtn" @click="click">button</button>
  1215. `,
  1216. setup: () => {
  1217. const view = shallowRef(One)
  1218. const click = () => {
  1219. view.value = view.value === One ? Two : One
  1220. }
  1221. return { view, click }
  1222. }
  1223. }).mount('#app')
  1224. })
  1225. await nextFrame()
  1226. expect(await html('#container')).toBe(
  1227. '<div class="test v-enter-active v-enter-to">one</div>'
  1228. )
  1229. await transitionFinish()
  1230. expect(await html('#container')).toBe('<div class="test">one</div>')
  1231. // leave
  1232. await classWhenTransitionStart()
  1233. await nextFrame()
  1234. expect(await html('#container')).toBe(
  1235. '<div class="test v-leave-active v-leave-to">one</div>'
  1236. )
  1237. await transitionFinish()
  1238. await nextFrame()
  1239. expect(await html('#container')).toBe(
  1240. '<div class="test v-enter-active v-enter-to">two</div>'
  1241. )
  1242. await transitionFinish()
  1243. expect(await html('#container')).toBe('<div class="test">two</div>')
  1244. },
  1245. E2E_TIMEOUT
  1246. )
  1247. })
  1248. describe('transition with v-show', () => {
  1249. test(
  1250. 'named transition with v-show',
  1251. async () => {
  1252. await page().evaluate(() => {
  1253. const { createApp, ref } = (window as any).Vue
  1254. createApp({
  1255. template: `
  1256. <div id="container">
  1257. <transition name="test">
  1258. <div v-show="toggle" class="test">content</div>
  1259. </transition>
  1260. </div>
  1261. <button id="toggleBtn" @click="click">button</button>
  1262. `,
  1263. setup: () => {
  1264. const toggle = ref(true)
  1265. const click = () => (toggle.value = !toggle.value)
  1266. return { toggle, click }
  1267. }
  1268. }).mount('#app')
  1269. })
  1270. expect(await html('#container')).toBe('<div class="test">content</div>')
  1271. expect(await isVisible('.test')).toBe(true)
  1272. // leave
  1273. expect(await classWhenTransitionStart()).toStrictEqual([
  1274. 'test',
  1275. 'test-leave-from',
  1276. 'test-leave-active'
  1277. ])
  1278. await nextFrame()
  1279. expect(await classList('.test')).toStrictEqual([
  1280. 'test',
  1281. 'test-leave-active',
  1282. 'test-leave-to'
  1283. ])
  1284. await transitionFinish()
  1285. expect(await isVisible('.test')).toBe(false)
  1286. // enter
  1287. expect(await classWhenTransitionStart()).toStrictEqual([
  1288. 'test',
  1289. 'test-enter-from',
  1290. 'test-enter-active'
  1291. ])
  1292. await nextFrame()
  1293. expect(await classList('.test')).toStrictEqual([
  1294. 'test',
  1295. 'test-enter-active',
  1296. 'test-enter-to'
  1297. ])
  1298. await transitionFinish()
  1299. expect(await html('#container')).toBe(
  1300. '<div class="test" style="">content</div>'
  1301. )
  1302. },
  1303. E2E_TIMEOUT
  1304. )
  1305. test(
  1306. 'transition events with v-show',
  1307. async () => {
  1308. const beforeLeaveSpy = jest.fn()
  1309. const onLeaveSpy = jest.fn()
  1310. const afterLeaveSpy = jest.fn()
  1311. const beforeEnterSpy = jest.fn()
  1312. const onEnterSpy = jest.fn()
  1313. const afterEnterSpy = jest.fn()
  1314. await page().exposeFunction('onLeaveSpy', onLeaveSpy)
  1315. await page().exposeFunction('onEnterSpy', onEnterSpy)
  1316. await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
  1317. await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
  1318. await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
  1319. await page().exposeFunction('afterEnterSpy', afterEnterSpy)
  1320. await page().evaluate(() => {
  1321. const {
  1322. beforeEnterSpy,
  1323. onEnterSpy,
  1324. afterEnterSpy,
  1325. beforeLeaveSpy,
  1326. onLeaveSpy,
  1327. afterLeaveSpy
  1328. } = window as any
  1329. const { createApp, ref } = (window as any).Vue
  1330. createApp({
  1331. template: `
  1332. <div id="container">
  1333. <transition
  1334. name="test"
  1335. @before-enter="beforeEnterSpy"
  1336. @enter="onEnterSpy"
  1337. @after-enter="afterEnterSpy"
  1338. @before-leave="beforeLeaveSpy"
  1339. @leave="onLeaveSpy"
  1340. @after-leave="afterLeaveSpy">
  1341. <div v-show="toggle" class="test">content</div>
  1342. </transition>
  1343. </div>
  1344. <button id="toggleBtn" @click="click">button</button>
  1345. `,
  1346. setup: () => {
  1347. const toggle = ref(true)
  1348. const click = () => (toggle.value = !toggle.value)
  1349. return {
  1350. toggle,
  1351. click,
  1352. beforeEnterSpy,
  1353. onEnterSpy,
  1354. afterEnterSpy,
  1355. beforeLeaveSpy,
  1356. onLeaveSpy,
  1357. afterLeaveSpy
  1358. }
  1359. }
  1360. }).mount('#app')
  1361. })
  1362. expect(await html('#container')).toBe('<div class="test">content</div>')
  1363. // leave
  1364. expect(await classWhenTransitionStart()).toStrictEqual([
  1365. 'test',
  1366. 'test-leave-from',
  1367. 'test-leave-active'
  1368. ])
  1369. expect(beforeLeaveSpy).toBeCalled()
  1370. expect(onLeaveSpy).toBeCalled()
  1371. expect(afterLeaveSpy).not.toBeCalled()
  1372. await nextFrame()
  1373. expect(await classList('.test')).toStrictEqual([
  1374. 'test',
  1375. 'test-leave-active',
  1376. 'test-leave-to'
  1377. ])
  1378. expect(afterLeaveSpy).not.toBeCalled()
  1379. await transitionFinish()
  1380. expect(await isVisible('.test')).toBe(false)
  1381. expect(afterLeaveSpy).toBeCalled()
  1382. // enter
  1383. expect(await classWhenTransitionStart()).toStrictEqual([
  1384. 'test',
  1385. 'test-enter-from',
  1386. 'test-enter-active'
  1387. ])
  1388. expect(beforeEnterSpy).toBeCalled()
  1389. expect(onEnterSpy).toBeCalled()
  1390. expect(afterEnterSpy).not.toBeCalled()
  1391. await nextFrame()
  1392. expect(await classList('.test')).toStrictEqual([
  1393. 'test',
  1394. 'test-enter-active',
  1395. 'test-enter-to'
  1396. ])
  1397. expect(afterEnterSpy).not.toBeCalled()
  1398. await transitionFinish()
  1399. expect(await html('#container')).toBe(
  1400. '<div class="test" style="">content</div>'
  1401. )
  1402. expect(afterEnterSpy).toBeCalled()
  1403. },
  1404. E2E_TIMEOUT
  1405. )
  1406. test(
  1407. 'onLeaveCancelled (v-show only)',
  1408. async () => {
  1409. const onLeaveCancelledSpy = jest.fn()
  1410. await page().exposeFunction('onLeaveCancelledSpy', onLeaveCancelledSpy)
  1411. await page().evaluate(() => {
  1412. const { onLeaveCancelledSpy } = window as any
  1413. const { createApp, ref } = (window as any).Vue
  1414. createApp({
  1415. template: `
  1416. <div id="container">
  1417. <transition name="test" @leave-cancelled="onLeaveCancelledSpy">
  1418. <div v-show="toggle" class="test">content</div>
  1419. </transition>
  1420. </div>
  1421. <button id="toggleBtn" @click="click">button</button>
  1422. `,
  1423. setup: () => {
  1424. const toggle = ref(true)
  1425. const click = () => (toggle.value = !toggle.value)
  1426. return { toggle, click, onLeaveCancelledSpy }
  1427. }
  1428. }).mount('#app')
  1429. })
  1430. expect(await html('#container')).toBe('<div class="test">content</div>')
  1431. expect(await isVisible('.test')).toBe(true)
  1432. // leave
  1433. expect(await classWhenTransitionStart()).toStrictEqual([
  1434. 'test',
  1435. 'test-leave-from',
  1436. 'test-leave-active'
  1437. ])
  1438. await nextFrame()
  1439. expect(await classList('.test')).toStrictEqual([
  1440. 'test',
  1441. 'test-leave-active',
  1442. 'test-leave-to'
  1443. ])
  1444. // cancel (enter)
  1445. expect(await classWhenTransitionStart()).toStrictEqual([
  1446. 'test',
  1447. 'test-enter-from',
  1448. 'test-enter-active'
  1449. ])
  1450. expect(onLeaveCancelledSpy).toBeCalled()
  1451. await nextFrame()
  1452. expect(await classList('.test')).toStrictEqual([
  1453. 'test',
  1454. 'test-enter-active',
  1455. 'test-enter-to'
  1456. ])
  1457. await transitionFinish()
  1458. expect(await html('#container')).toBe(
  1459. '<div class="test" style="">content</div>'
  1460. )
  1461. },
  1462. E2E_TIMEOUT
  1463. )
  1464. test(
  1465. 'transition on appear with v-show',
  1466. async () => {
  1467. const appearClass = await page().evaluate(async () => {
  1468. const { createApp, ref } = (window as any).Vue
  1469. createApp({
  1470. template: `
  1471. <div id="container">
  1472. <transition name="test"
  1473. appear
  1474. appear-from-class="test-appear-from"
  1475. appear-to-class="test-appear-to"
  1476. appear-active-class="test-appear-active">
  1477. <div v-show="toggle" class="test">content</div>
  1478. </transition>
  1479. </div>
  1480. <button id="toggleBtn" @click="click">button</button>
  1481. `,
  1482. setup: () => {
  1483. const toggle = ref(true)
  1484. const click = () => (toggle.value = !toggle.value)
  1485. return { toggle, click }
  1486. }
  1487. }).mount('#app')
  1488. return Promise.resolve().then(() => {
  1489. return document.querySelector('.test')!.className.split(/\s+/g)
  1490. })
  1491. })
  1492. // appear
  1493. expect(appearClass).toStrictEqual([
  1494. 'test',
  1495. 'test-appear-from',
  1496. 'test-appear-active'
  1497. ])
  1498. await nextFrame()
  1499. expect(await classList('.test')).toStrictEqual([
  1500. 'test',
  1501. 'test-appear-active',
  1502. 'test-appear-to'
  1503. ])
  1504. await transitionFinish()
  1505. expect(await html('#container')).toBe('<div class="test">content</div>')
  1506. // leave
  1507. expect(await classWhenTransitionStart()).toStrictEqual([
  1508. 'test',
  1509. 'test-leave-from',
  1510. 'test-leave-active'
  1511. ])
  1512. await nextFrame()
  1513. expect(await classList('.test')).toStrictEqual([
  1514. 'test',
  1515. 'test-leave-active',
  1516. 'test-leave-to'
  1517. ])
  1518. await transitionFinish()
  1519. expect(await isVisible('.test')).toBe(false)
  1520. // enter
  1521. expect(await classWhenTransitionStart()).toStrictEqual([
  1522. 'test',
  1523. 'test-enter-from',
  1524. 'test-enter-active'
  1525. ])
  1526. await nextFrame()
  1527. expect(await classList('.test')).toStrictEqual([
  1528. 'test',
  1529. 'test-enter-active',
  1530. 'test-enter-to'
  1531. ])
  1532. await transitionFinish()
  1533. expect(await html('#container')).toBe(
  1534. '<div class="test" style="">content</div>'
  1535. )
  1536. },
  1537. E2E_TIMEOUT
  1538. )
  1539. })
  1540. test(
  1541. 'warn when used on multiple elements',
  1542. async () => {
  1543. createApp({
  1544. render() {
  1545. return h(Transition, null, {
  1546. default: () => [h('div'), h('div')]
  1547. })
  1548. }
  1549. }).mount(document.createElement('div'))
  1550. expect(
  1551. '<transition> can only be used on a single element or component'
  1552. ).toHaveBeenWarned()
  1553. },
  1554. E2E_TIMEOUT
  1555. )
  1556. describe('explicit durations', () => {
  1557. test(
  1558. 'single value',
  1559. async () => {
  1560. await page().evaluate(duration => {
  1561. const { createApp, ref } = (window as any).Vue
  1562. createApp({
  1563. template: `
  1564. <div id="container">
  1565. <transition name="test" duration="${duration * 2}">
  1566. <div v-if="toggle" class="test">content</div>
  1567. </transition>
  1568. </div>
  1569. <button id="toggleBtn" @click="click">button</button>
  1570. `,
  1571. setup: () => {
  1572. const toggle = ref(true)
  1573. const click = () => (toggle.value = !toggle.value)
  1574. return { toggle, click }
  1575. }
  1576. }).mount('#app')
  1577. }, duration)
  1578. expect(await html('#container')).toBe('<div class="test">content</div>')
  1579. // leave
  1580. expect(await classWhenTransitionStart()).toStrictEqual([
  1581. 'test',
  1582. 'test-leave-from',
  1583. 'test-leave-active'
  1584. ])
  1585. await nextFrame()
  1586. expect(await classList('.test')).toStrictEqual([
  1587. 'test',
  1588. 'test-leave-active',
  1589. 'test-leave-to'
  1590. ])
  1591. await transitionFinish(duration * 2)
  1592. expect(await html('#container')).toBe('<!--v-if-->')
  1593. // enter
  1594. expect(await classWhenTransitionStart()).toStrictEqual([
  1595. 'test',
  1596. 'test-enter-from',
  1597. 'test-enter-active'
  1598. ])
  1599. await nextFrame()
  1600. expect(await classList('.test')).toStrictEqual([
  1601. 'test',
  1602. 'test-enter-active',
  1603. 'test-enter-to'
  1604. ])
  1605. await transitionFinish(duration * 2)
  1606. expect(await html('#container')).toBe('<div class="test">content</div>')
  1607. },
  1608. E2E_TIMEOUT
  1609. )
  1610. test(
  1611. 'enter with explicit durations',
  1612. async () => {
  1613. await page().evaluate(duration => {
  1614. const { createApp, ref } = (window as any).Vue
  1615. createApp({
  1616. template: `
  1617. <div id="container">
  1618. <transition name="test" :duration="{ enter: ${duration * 2} }">
  1619. <div v-if="toggle" class="test">content</div>
  1620. </transition>
  1621. </div>
  1622. <button id="toggleBtn" @click="click">button</button>
  1623. `,
  1624. setup: () => {
  1625. const toggle = ref(true)
  1626. const click = () => (toggle.value = !toggle.value)
  1627. return { toggle, click }
  1628. }
  1629. }).mount('#app')
  1630. }, duration)
  1631. expect(await html('#container')).toBe('<div class="test">content</div>')
  1632. // leave
  1633. expect(await classWhenTransitionStart()).toStrictEqual([
  1634. 'test',
  1635. 'test-leave-from',
  1636. 'test-leave-active'
  1637. ])
  1638. await nextFrame()
  1639. expect(await classList('.test')).toStrictEqual([
  1640. 'test',
  1641. 'test-leave-active',
  1642. 'test-leave-to'
  1643. ])
  1644. await transitionFinish()
  1645. expect(await html('#container')).toBe('<!--v-if-->')
  1646. // enter
  1647. expect(await classWhenTransitionStart()).toStrictEqual([
  1648. 'test',
  1649. 'test-enter-from',
  1650. 'test-enter-active'
  1651. ])
  1652. await nextFrame()
  1653. expect(await classList('.test')).toStrictEqual([
  1654. 'test',
  1655. 'test-enter-active',
  1656. 'test-enter-to'
  1657. ])
  1658. await transitionFinish(duration * 2)
  1659. expect(await html('#container')).toBe('<div class="test">content</div>')
  1660. },
  1661. E2E_TIMEOUT
  1662. )
  1663. test(
  1664. 'leave with explicit durations',
  1665. async () => {
  1666. await page().evaluate(duration => {
  1667. const { createApp, ref } = (window as any).Vue
  1668. createApp({
  1669. template: `
  1670. <div id="container">
  1671. <transition name="test" :duration="{ leave: ${duration * 2} }">
  1672. <div v-if="toggle" class="test">content</div>
  1673. </transition>
  1674. </div>
  1675. <button id="toggleBtn" @click="click">button</button>
  1676. `,
  1677. setup: () => {
  1678. const toggle = ref(true)
  1679. const click = () => (toggle.value = !toggle.value)
  1680. return { toggle, click }
  1681. }
  1682. }).mount('#app')
  1683. }, duration)
  1684. expect(await html('#container')).toBe('<div class="test">content</div>')
  1685. // leave
  1686. expect(await classWhenTransitionStart()).toStrictEqual([
  1687. 'test',
  1688. 'test-leave-from',
  1689. 'test-leave-active'
  1690. ])
  1691. await nextFrame()
  1692. expect(await classList('.test')).toStrictEqual([
  1693. 'test',
  1694. 'test-leave-active',
  1695. 'test-leave-to'
  1696. ])
  1697. await transitionFinish(duration * 2)
  1698. expect(await html('#container')).toBe('<!--v-if-->')
  1699. // enter
  1700. expect(await classWhenTransitionStart()).toStrictEqual([
  1701. 'test',
  1702. 'test-enter-from',
  1703. 'test-enter-active'
  1704. ])
  1705. await nextFrame()
  1706. expect(await classList('.test')).toStrictEqual([
  1707. 'test',
  1708. 'test-enter-active',
  1709. 'test-enter-to'
  1710. ])
  1711. await transitionFinish()
  1712. expect(await html('#container')).toBe('<div class="test">content</div>')
  1713. },
  1714. E2E_TIMEOUT
  1715. )
  1716. test(
  1717. 'separate enter and leave',
  1718. async () => {
  1719. await page().evaluate(duration => {
  1720. const { createApp, ref } = (window as any).Vue
  1721. createApp({
  1722. template: `
  1723. <div id="container">
  1724. <transition name="test" :duration="{
  1725. enter: ${duration * 4},
  1726. leave: ${duration * 2}
  1727. }">
  1728. <div v-if="toggle" class="test">content</div>
  1729. </transition>
  1730. </div>
  1731. <button id="toggleBtn" @click="click">button</button>
  1732. `,
  1733. setup: () => {
  1734. const toggle = ref(true)
  1735. const click = () => (toggle.value = !toggle.value)
  1736. return { toggle, click }
  1737. }
  1738. }).mount('#app')
  1739. }, duration)
  1740. expect(await html('#container')).toBe('<div class="test">content</div>')
  1741. // leave
  1742. expect(await classWhenTransitionStart()).toStrictEqual([
  1743. 'test',
  1744. 'test-leave-from',
  1745. 'test-leave-active'
  1746. ])
  1747. await nextFrame()
  1748. expect(await classList('.test')).toStrictEqual([
  1749. 'test',
  1750. 'test-leave-active',
  1751. 'test-leave-to'
  1752. ])
  1753. await transitionFinish(duration * 2)
  1754. expect(await html('#container')).toBe('<!--v-if-->')
  1755. // enter
  1756. expect(await classWhenTransitionStart()).toStrictEqual([
  1757. 'test',
  1758. 'test-enter-from',
  1759. 'test-enter-active'
  1760. ])
  1761. await nextFrame()
  1762. expect(await classList('.test')).toStrictEqual([
  1763. 'test',
  1764. 'test-enter-active',
  1765. 'test-enter-to'
  1766. ])
  1767. await transitionFinish(200)
  1768. expect(await html('#container')).toBe('<div class="test">content</div>')
  1769. },
  1770. E2E_TIMEOUT
  1771. )
  1772. test(
  1773. 'warn invalid durations',
  1774. async () => {
  1775. createApp({
  1776. template: `
  1777. <div id="container">
  1778. <transition name="test" :duration="NaN">
  1779. <div class="test">content</div>
  1780. </transition>
  1781. </div>
  1782. `
  1783. }).mount(document.createElement('div'))
  1784. expect(
  1785. `[Vue warn]: <transition> explicit duration is NaN - ` +
  1786. 'the duration expression might be incorrect.'
  1787. ).toHaveBeenWarned()
  1788. createApp({
  1789. template: `
  1790. <div id="container">
  1791. <transition name="test" :duration="{
  1792. enter: {},
  1793. leave: {}
  1794. }">
  1795. <div class="test">content</div>
  1796. </transition>
  1797. </div>
  1798. `
  1799. }).mount(document.createElement('div'))
  1800. expect(
  1801. `[Vue warn]: <transition> explicit duration is not a valid number - ` +
  1802. `got ${JSON.stringify({})}`
  1803. ).toHaveBeenWarned()
  1804. },
  1805. E2E_TIMEOUT
  1806. )
  1807. })
  1808. })