transition-mode.spec.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. import Vue from 'vue'
  2. import { injectStyles, waitForUpdate, nextFrame } from './helpers'
  3. describe('Transition mode', () => {
  4. const { duration, buffer } = injectStyles()
  5. const components = {
  6. one: { template: '<div>one</div>' },
  7. two: { template: '<div>two</div>' }
  8. }
  9. let el
  10. beforeEach(() => {
  11. el = document.createElement('div')
  12. document.body.appendChild(el)
  13. })
  14. it('dynamic components, simultaneous', done => {
  15. const vm = new Vue({
  16. template: `<div>
  17. <transition>
  18. <component :is="view" class="test">
  19. </component>
  20. </transition>
  21. </div>`,
  22. data: { view: 'one' },
  23. components
  24. }).$mount(el)
  25. expect(vm.$el.textContent).toBe('one')
  26. vm.view = 'two'
  27. waitForUpdate(() => {
  28. expect(vm.$el.innerHTML).toBe(
  29. '<div class="test v-leave v-leave-active">one</div>' +
  30. '<div class="test v-enter v-enter-active">two</div>'
  31. )
  32. })
  33. .thenWaitFor(nextFrame)
  34. .then(() => {
  35. expect(vm.$el.innerHTML).toBe(
  36. '<div class="test v-leave-active v-leave-to">one</div>' +
  37. '<div class="test v-enter-active v-enter-to">two</div>'
  38. )
  39. })
  40. .thenWaitFor(duration + buffer)
  41. .then(() => {
  42. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  43. })
  44. .then(done)
  45. })
  46. it('dynamic components, out-in', done => {
  47. let next
  48. const vm = new Vue({
  49. template: `<div>
  50. <transition name="test" mode="out-in" @after-leave="afterLeave">
  51. <component :is="view" class="test">
  52. </component>
  53. </transition>
  54. </div>`,
  55. data: { view: 'one' },
  56. components,
  57. methods: {
  58. afterLeave() {
  59. next()
  60. }
  61. }
  62. }).$mount(el)
  63. expect(vm.$el.textContent).toBe('one')
  64. vm.view = 'two'
  65. waitForUpdate(() => {
  66. expect(vm.$el.innerHTML).toBe(
  67. '<div class="test test-leave test-leave-active">one</div><!---->'
  68. )
  69. })
  70. .thenWaitFor(nextFrame)
  71. .then(() => {
  72. expect(vm.$el.innerHTML).toBe(
  73. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  74. )
  75. })
  76. .thenWaitFor(_next => {
  77. next = _next
  78. })
  79. .then(() => {
  80. expect(vm.$el.innerHTML).toBe('<!---->')
  81. })
  82. .thenWaitFor(nextFrame)
  83. .then(() => {
  84. expect(vm.$el.innerHTML).toBe(
  85. '<div class="test test-enter test-enter-active">two</div>'
  86. )
  87. })
  88. .thenWaitFor(nextFrame)
  89. .then(() => {
  90. expect(vm.$el.innerHTML).toBe(
  91. '<div class="test test-enter-active test-enter-to">two</div>'
  92. )
  93. })
  94. .thenWaitFor(duration + buffer)
  95. .then(() => {
  96. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  97. })
  98. .then(done)
  99. })
  100. // #3440
  101. it('dynamic components, out-in (with extra re-render)', done => {
  102. let next
  103. const vm = new Vue({
  104. template: `<div>
  105. <transition name="test" mode="out-in" @after-leave="afterLeave">
  106. <component :is="view" class="test">
  107. </component>
  108. </transition>
  109. </div>`,
  110. data: { view: 'one' },
  111. components,
  112. methods: {
  113. afterLeave() {
  114. next()
  115. }
  116. }
  117. }).$mount(el)
  118. expect(vm.$el.textContent).toBe('one')
  119. vm.view = 'two'
  120. waitForUpdate(() => {
  121. expect(vm.$el.innerHTML).toBe(
  122. '<div class="test test-leave test-leave-active">one</div><!---->'
  123. )
  124. })
  125. .thenWaitFor(nextFrame)
  126. .then(() => {
  127. expect(vm.$el.innerHTML).toBe(
  128. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  129. )
  130. // Force re-render before the element finishes leaving
  131. // this should not cause the incoming element to enter early
  132. vm.$forceUpdate()
  133. })
  134. .thenWaitFor(_next => {
  135. next = _next
  136. })
  137. .then(() => {
  138. expect(vm.$el.innerHTML).toBe('<!---->')
  139. })
  140. .thenWaitFor(nextFrame)
  141. .then(() => {
  142. expect(vm.$el.innerHTML).toBe(
  143. '<div class="test test-enter test-enter-active">two</div>'
  144. )
  145. })
  146. .thenWaitFor(nextFrame)
  147. .then(() => {
  148. expect(vm.$el.innerHTML).toBe(
  149. '<div class="test test-enter-active test-enter-to">two</div>'
  150. )
  151. })
  152. .thenWaitFor(duration + buffer)
  153. .then(() => {
  154. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  155. })
  156. .then(done)
  157. })
  158. it('dynamic components, in-out', done => {
  159. let next
  160. const vm = new Vue({
  161. template: `<div>
  162. <transition name="test" mode="in-out" @after-enter="afterEnter">
  163. <component :is="view" class="test">
  164. </component>
  165. </transition>
  166. </div>`,
  167. data: { view: 'one' },
  168. components,
  169. methods: {
  170. afterEnter() {
  171. next()
  172. }
  173. }
  174. }).$mount(el)
  175. expect(vm.$el.textContent).toBe('one')
  176. vm.view = 'two'
  177. waitForUpdate(() => {
  178. expect(vm.$el.innerHTML).toBe(
  179. '<div class="test">one</div>' +
  180. '<div class="test test-enter test-enter-active">two</div>'
  181. )
  182. })
  183. .thenWaitFor(nextFrame)
  184. .then(() => {
  185. expect(vm.$el.innerHTML).toBe(
  186. '<div class="test">one</div>' +
  187. '<div class="test test-enter-active test-enter-to">two</div>'
  188. )
  189. })
  190. .thenWaitFor(_next => {
  191. next = _next
  192. })
  193. .then(() => {
  194. expect(vm.$el.innerHTML).toBe(
  195. '<div class="test">one</div>' + '<div class="test">two</div>'
  196. )
  197. })
  198. .then(() => {
  199. expect(vm.$el.innerHTML).toBe(
  200. '<div class="test test-leave test-leave-active">one</div>' +
  201. '<div class="test">two</div>'
  202. )
  203. })
  204. .thenWaitFor(nextFrame)
  205. .then(() => {
  206. expect(vm.$el.innerHTML).toBe(
  207. '<div class="test test-leave-active test-leave-to">one</div>' +
  208. '<div class="test">two</div>'
  209. )
  210. })
  211. .thenWaitFor(duration + buffer)
  212. .then(() => {
  213. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  214. })
  215. .then(done)
  216. })
  217. it('dynamic components, in-out with early cancel', done => {
  218. let next
  219. const vm = new Vue({
  220. template: `<div>
  221. <transition name="test" mode="in-out" @after-enter="afterEnter">
  222. <component :is="view" class="test"></component>
  223. </transition>
  224. </div>`,
  225. data: { view: 'one' },
  226. components,
  227. methods: {
  228. afterEnter() {
  229. next()
  230. }
  231. }
  232. }).$mount(el)
  233. expect(vm.$el.textContent).toBe('one')
  234. vm.view = 'two'
  235. waitForUpdate(() => {
  236. expect(vm.$el.innerHTML).toBe(
  237. '<div class="test">one</div>' +
  238. '<div class="test test-enter test-enter-active">two</div>'
  239. )
  240. })
  241. .thenWaitFor(nextFrame)
  242. .then(() => {
  243. expect(vm.$el.innerHTML).toBe(
  244. '<div class="test">one</div>' +
  245. '<div class="test test-enter-active test-enter-to">two</div>'
  246. )
  247. // switch again before enter finishes,
  248. // this cancels both enter and leave.
  249. vm.view = 'one'
  250. })
  251. .then(() => {
  252. // 1. the pending leaving "one" should be removed instantly.
  253. // 2. the entering "two" should be placed into its final state instantly.
  254. // 3. a new "one" is created and entering
  255. expect(vm.$el.innerHTML).toBe(
  256. '<div class="test">two</div>' +
  257. '<div class="test test-enter test-enter-active">one</div>'
  258. )
  259. })
  260. .thenWaitFor(nextFrame)
  261. .then(() => {
  262. expect(vm.$el.innerHTML).toBe(
  263. '<div class="test">two</div>' +
  264. '<div class="test test-enter-active test-enter-to">one</div>'
  265. )
  266. })
  267. .thenWaitFor(_next => {
  268. next = _next
  269. })
  270. .then(() => {
  271. expect(vm.$el.innerHTML).toBe(
  272. '<div class="test">two</div>' + '<div class="test">one</div>'
  273. )
  274. })
  275. .then(() => {
  276. expect(vm.$el.innerHTML).toBe(
  277. '<div class="test test-leave test-leave-active">two</div>' +
  278. '<div class="test">one</div>'
  279. )
  280. })
  281. .thenWaitFor(nextFrame)
  282. .then(() => {
  283. expect(vm.$el.innerHTML).toBe(
  284. '<div class="test test-leave-active test-leave-to">two</div>' +
  285. '<div class="test">one</div>'
  286. )
  287. })
  288. .thenWaitFor(duration + buffer)
  289. .then(() => {
  290. expect(vm.$el.innerHTML).toBe('<div class="test">one</div>')
  291. })
  292. .then(done)
  293. })
  294. it('normal elements with different keys, simultaneous', done => {
  295. const vm = new Vue({
  296. template: `<div>
  297. <transition>
  298. <div :key="view" class="test">{{view}}</div>
  299. </transition>
  300. </div>`,
  301. data: { view: 'one' },
  302. components
  303. }).$mount(el)
  304. expect(vm.$el.textContent).toBe('one')
  305. vm.view = 'two'
  306. waitForUpdate(() => {
  307. expect(vm.$el.innerHTML).toBe(
  308. '<div class="test v-leave v-leave-active">one</div>' +
  309. '<div class="test v-enter v-enter-active">two</div>'
  310. )
  311. })
  312. .thenWaitFor(nextFrame)
  313. .then(() => {
  314. expect(vm.$el.innerHTML).toBe(
  315. '<div class="test v-leave-active v-leave-to">one</div>' +
  316. '<div class="test v-enter-active v-enter-to">two</div>'
  317. )
  318. })
  319. .thenWaitFor(duration + buffer)
  320. .then(() => {
  321. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  322. })
  323. .then(done)
  324. })
  325. it('normal elements with different keys, out-in', done => {
  326. let next
  327. const vm = new Vue({
  328. template: `<div>
  329. <transition name="test" mode="out-in" @after-leave="afterLeave">
  330. <div :key="view" class="test">{{view}}</div>
  331. </transition>
  332. </div>`,
  333. data: { view: 'one' },
  334. components,
  335. methods: {
  336. afterLeave() {
  337. next()
  338. }
  339. }
  340. }).$mount(el)
  341. expect(vm.$el.textContent).toBe('one')
  342. vm.view = 'two'
  343. waitForUpdate(() => {
  344. expect(vm.$el.innerHTML).toBe(
  345. '<div class="test test-leave test-leave-active">one</div><!---->'
  346. )
  347. })
  348. .thenWaitFor(nextFrame)
  349. .then(() => {
  350. expect(vm.$el.innerHTML).toBe(
  351. '<div class="test test-leave-active test-leave-to">one</div><!---->'
  352. )
  353. })
  354. .thenWaitFor(_next => {
  355. next = _next
  356. })
  357. .then(() => {
  358. expect(vm.$el.innerHTML).toBe('<!---->')
  359. })
  360. .thenWaitFor(nextFrame)
  361. .then(() => {
  362. expect(vm.$el.innerHTML).toBe(
  363. '<div class="test test-enter test-enter-active">two</div>'
  364. )
  365. })
  366. .thenWaitFor(nextFrame)
  367. .then(() => {
  368. expect(vm.$el.innerHTML).toBe(
  369. '<div class="test test-enter-active test-enter-to">two</div>'
  370. )
  371. })
  372. .thenWaitFor(duration + buffer)
  373. .then(() => {
  374. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  375. })
  376. .then(done)
  377. })
  378. it('normal elements with different keys, in-out', done => {
  379. let next
  380. const vm = new Vue({
  381. template: `<div>
  382. <transition name="test" mode="in-out" @after-enter="afterEnter">
  383. <div :key="view" class="test">{{view}}</div>
  384. </transition>
  385. </div>`,
  386. data: { view: 'one' },
  387. components,
  388. methods: {
  389. afterEnter() {
  390. next()
  391. }
  392. }
  393. }).$mount(el)
  394. expect(vm.$el.textContent).toBe('one')
  395. vm.view = 'two'
  396. waitForUpdate(() => {
  397. expect(vm.$el.innerHTML).toBe(
  398. '<div class="test">one</div>' +
  399. '<div class="test test-enter test-enter-active">two</div>'
  400. )
  401. })
  402. .thenWaitFor(nextFrame)
  403. .then(() => {
  404. expect(vm.$el.innerHTML).toBe(
  405. '<div class="test">one</div>' +
  406. '<div class="test test-enter-active test-enter-to">two</div>'
  407. )
  408. })
  409. .thenWaitFor(_next => {
  410. next = _next
  411. })
  412. .then(() => {
  413. expect(vm.$el.innerHTML).toBe(
  414. '<div class="test">one</div>' + '<div class="test">two</div>'
  415. )
  416. })
  417. .then(() => {
  418. expect(vm.$el.innerHTML).toBe(
  419. '<div class="test test-leave test-leave-active">one</div>' +
  420. '<div class="test">two</div>'
  421. )
  422. })
  423. .thenWaitFor(nextFrame)
  424. .then(() => {
  425. expect(vm.$el.innerHTML).toBe(
  426. '<div class="test test-leave-active test-leave-to">one</div>' +
  427. '<div class="test">two</div>'
  428. )
  429. })
  430. .thenWaitFor(duration + buffer)
  431. .then(() => {
  432. expect(vm.$el.innerHTML).toBe('<div class="test">two</div>')
  433. })
  434. .then(done)
  435. })
  436. it('transition out-in on async component (resolve before leave complete)', done => {
  437. const vm = new Vue({
  438. template: `
  439. <div>
  440. <transition name="test-anim" mode="out-in">
  441. <component-a v-if="ok"></component-a>
  442. <component-b v-else></component-b>
  443. </transition>
  444. </div>
  445. `,
  446. components: {
  447. componentA: resolve => {
  448. setTimeout(() => {
  449. resolve({ template: '<div><h1>component A</h1></div>' })
  450. next1()
  451. }, duration / 2)
  452. },
  453. componentB: resolve => {
  454. setTimeout(() => {
  455. resolve({ template: '<div><h1>component B</h1></div>' })
  456. }, duration / 2)
  457. }
  458. },
  459. data: {
  460. ok: true
  461. }
  462. }).$mount(el)
  463. expect(vm.$el.innerHTML).toBe('<!---->')
  464. function next1() {
  465. Vue.nextTick(() => {
  466. expect(vm.$el.children.length).toBe(1)
  467. expect(vm.$el.textContent).toBe('component A')
  468. expect(vm.$el.children[0].className).toBe(
  469. 'test-anim-enter test-anim-enter-active'
  470. )
  471. nextFrame(() => {
  472. expect(vm.$el.children[0].className).toBe(
  473. 'test-anim-enter-active test-anim-enter-to'
  474. )
  475. setTimeout(() => {
  476. expect(vm.$el.children[0].className).toBe('')
  477. vm.ok = false
  478. next2()
  479. }, duration + buffer)
  480. })
  481. })
  482. }
  483. function next2() {
  484. waitForUpdate(() => {
  485. expect(vm.$el.children.length).toBe(1)
  486. expect(vm.$el.textContent).toBe('component A')
  487. expect(vm.$el.children[0].className).toBe(
  488. 'test-anim-leave test-anim-leave-active'
  489. )
  490. })
  491. .thenWaitFor(nextFrame)
  492. .then(() => {
  493. expect(vm.$el.children[0].className).toBe(
  494. 'test-anim-leave-active test-anim-leave-to'
  495. )
  496. })
  497. .thenWaitFor(duration + buffer)
  498. .then(() => {
  499. expect(vm.$el.children.length).toBe(1)
  500. expect(vm.$el.textContent).toBe('component B')
  501. expect(vm.$el.children[0].className).toMatch('test-anim-enter-active')
  502. })
  503. .thenWaitFor(duration * 2)
  504. .then(() => {
  505. expect(vm.$el.children[0].className).toBe('')
  506. })
  507. .then(done)
  508. }
  509. })
  510. it('transition out-in on async component (resolve after leave complete)', done => {
  511. const vm = new Vue({
  512. template: `
  513. <div>
  514. <transition name="test-anim" mode="out-in">
  515. <component-a v-if="ok"></component-a>
  516. <component-b v-else></component-b>
  517. </transition>
  518. </div>
  519. `,
  520. components: {
  521. componentA: { template: '<div><h1>component A</h1></div>' },
  522. componentB: resolve => {
  523. setTimeout(() => {
  524. resolve({ template: '<div><h1>component B</h1></div>' })
  525. Vue.nextTick(next)
  526. }, (duration + buffer) * 1.7)
  527. }
  528. },
  529. data: {
  530. ok: true
  531. }
  532. }).$mount(el)
  533. expect(vm.$el.innerHTML).toBe('<div><h1>component A</h1></div>')
  534. let next
  535. vm.ok = false
  536. waitForUpdate(() => {
  537. expect(vm.$el.children.length).toBe(1)
  538. expect(vm.$el.textContent).toBe('component A')
  539. expect(vm.$el.children[0].className).toBe(
  540. 'test-anim-leave test-anim-leave-active'
  541. )
  542. })
  543. .thenWaitFor(nextFrame)
  544. .then(() => {
  545. expect(vm.$el.children[0].className).toBe(
  546. 'test-anim-leave-active test-anim-leave-to'
  547. )
  548. })
  549. .thenWaitFor(duration + buffer)
  550. .then(() => {
  551. expect(vm.$el.children.length).toBe(0)
  552. expect(vm.$el.innerHTML).toBe('<!---->')
  553. })
  554. .thenWaitFor(_next => {
  555. next = _next
  556. })
  557. .then(() => {
  558. expect(vm.$el.children.length).toBe(1)
  559. expect(vm.$el.textContent).toBe('component B')
  560. expect(vm.$el.children[0].className).toBe(
  561. 'test-anim-enter test-anim-enter-active'
  562. )
  563. })
  564. .thenWaitFor(nextFrame)
  565. .then(() => {
  566. expect(vm.$el.children[0].className).toBe(
  567. 'test-anim-enter-active test-anim-enter-to'
  568. )
  569. })
  570. .thenWaitFor(duration + buffer)
  571. .then(() => {
  572. expect(vm.$el.children.length).toBe(1)
  573. expect(vm.$el.textContent).toBe('component B')
  574. expect(vm.$el.children[0].className).toBe('')
  575. })
  576. .then(done)
  577. })
  578. it('transition in-out on async component', done => {
  579. const vm = new Vue({
  580. template: `
  581. <div>
  582. <transition name="test-anim" mode="in-out">
  583. <component-a v-if="ok"></component-a>
  584. <component-b v-else></component-b>
  585. </transition>
  586. </div>
  587. `,
  588. components: {
  589. componentA: resolve => {
  590. setTimeout(() => {
  591. resolve({ template: '<div><h1>component A</h1></div>' })
  592. next1()
  593. }, duration / 2)
  594. },
  595. componentB: resolve => {
  596. setTimeout(() => {
  597. resolve({ template: '<div><h1>component B</h1></div>' })
  598. next2()
  599. }, duration / 2)
  600. }
  601. },
  602. data: {
  603. ok: true
  604. }
  605. }).$mount(el)
  606. expect(vm.$el.innerHTML).toBe('<!---->')
  607. function next1() {
  608. Vue.nextTick(() => {
  609. expect(vm.$el.children.length).toBe(1)
  610. expect(vm.$el.textContent).toBe('component A')
  611. expect(vm.$el.children[0].className).toBe(
  612. 'test-anim-enter test-anim-enter-active'
  613. )
  614. nextFrame(() => {
  615. expect(vm.$el.children[0].className).toBe(
  616. 'test-anim-enter-active test-anim-enter-to'
  617. )
  618. setTimeout(() => {
  619. expect(vm.$el.children[0].className).toBe('')
  620. vm.ok = false
  621. }, duration + buffer)
  622. })
  623. })
  624. }
  625. function next2() {
  626. waitForUpdate(() => {
  627. expect(vm.$el.children.length).toBe(2)
  628. expect(vm.$el.textContent).toBe('component Acomponent B')
  629. expect(vm.$el.children[0].className).toBe('')
  630. expect(vm.$el.children[1].className).toBe(
  631. 'test-anim-enter test-anim-enter-active'
  632. )
  633. })
  634. .thenWaitFor(nextFrame)
  635. .then(() => {
  636. expect(vm.$el.children[1].className).toBe(
  637. 'test-anim-enter-active test-anim-enter-to'
  638. )
  639. })
  640. .thenWaitFor(duration + buffer)
  641. .then(() => {
  642. expect(vm.$el.children.length).toBe(2)
  643. expect(vm.$el.textContent).toBe('component Acomponent B')
  644. expect(vm.$el.children[0].className).toMatch('test-anim-leave-active')
  645. expect(vm.$el.children[1].className).toBe('')
  646. })
  647. .thenWaitFor(duration + buffer * 2)
  648. .then(() => {
  649. expect(vm.$el.children.length).toBe(1)
  650. expect(vm.$el.textContent).toBe('component B')
  651. expect(vm.$el.children[0].className).toBe('')
  652. })
  653. .then(done)
  654. }
  655. })
  656. it('warn invalid mode', () => {
  657. new Vue({
  658. template: '<transition mode="foo"><div>123</div></transition>'
  659. }).$mount()
  660. expect('invalid <transition> mode: foo').toHaveBeenWarned()
  661. })
  662. })