transition-mode.spec.js 19 KB

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