Transition.spec.ts 52 KB

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