transition-mode.spec.ts 20 KB

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