transition.spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import Vue from 'vue'
  2. import injectStyles from './inject-styles'
  3. import { isIE9 } from 'web/util/index'
  4. import { nextFrame } from 'web/runtime/modules/transition'
  5. if (!isIE9) {
  6. describe('Transition system', () => {
  7. const duration = injectStyles()
  8. let el
  9. beforeEach(() => {
  10. el = document.createElement('div')
  11. document.body.appendChild(el)
  12. })
  13. it('basic transition', done => {
  14. const vm = new Vue({
  15. template: '<div><div v-if="ok" class="test" transition>foo</div></div>',
  16. data: { ok: true }
  17. }).$mount(el)
  18. // should not apply transition on initial render by default
  19. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  20. vm.ok = false
  21. waitForUpdate(() => {
  22. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  23. }).thenWaitFor(nextFrame).then(() => {
  24. expect(vm.$el.children[0].className).toBe('test v-leave-active')
  25. }).thenWaitFor(duration + 10).then(() => {
  26. expect(vm.$el.children.length).toBe(0)
  27. vm.ok = true
  28. }).then(() => {
  29. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  30. }).thenWaitFor(nextFrame).then(() => {
  31. expect(vm.$el.children[0].className).toBe('test v-enter-active')
  32. }).thenWaitFor(duration + 10).then(() => {
  33. expect(vm.$el.children[0].className).toBe('test')
  34. }).then(done)
  35. })
  36. it('named transition', done => {
  37. const vm = new Vue({
  38. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  39. data: { ok: true }
  40. }).$mount(el)
  41. // should not apply transition on initial render by default
  42. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  43. vm.ok = false
  44. waitForUpdate(() => {
  45. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  46. }).thenWaitFor(nextFrame).then(() => {
  47. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  48. }).thenWaitFor(duration + 10).then(() => {
  49. expect(vm.$el.children.length).toBe(0)
  50. vm.ok = true
  51. }).then(() => {
  52. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  53. }).thenWaitFor(nextFrame).then(() => {
  54. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  55. }).thenWaitFor(duration + 10).then(() => {
  56. expect(vm.$el.children[0].className).toBe('test')
  57. }).then(done)
  58. })
  59. it('custom transition classes', done => {
  60. const vm = new Vue({
  61. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  62. data: { ok: true },
  63. transitions: {
  64. test: {
  65. enterClass: 'hello',
  66. enterActiveClass: 'hello-active',
  67. leaveClass: 'bye',
  68. leaveActiveClass: 'byebye active' // testing multi classes
  69. }
  70. }
  71. }).$mount(el)
  72. // should not apply transition on initial render by default
  73. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  74. vm.ok = false
  75. waitForUpdate(() => {
  76. expect(vm.$el.children[0].className).toBe('test bye byebye active')
  77. }).thenWaitFor(nextFrame).then(() => {
  78. expect(vm.$el.children[0].className).toBe('test byebye active')
  79. }).thenWaitFor(duration + 10).then(() => {
  80. expect(vm.$el.children.length).toBe(0)
  81. vm.ok = true
  82. }).then(() => {
  83. expect(vm.$el.children[0].className).toBe('test hello hello-active')
  84. }).thenWaitFor(nextFrame).then(() => {
  85. expect(vm.$el.children[0].className).toBe('test hello-active')
  86. }).thenWaitFor(duration + 10).then(() => {
  87. expect(vm.$el.children[0].className).toBe('test')
  88. }).then(done)
  89. })
  90. it('dynamic transition', done => {
  91. const vm = new Vue({
  92. template: '<div><div v-if="ok" class="test" :transition="trans">foo</div></div>',
  93. data: {
  94. ok: true,
  95. trans: 'test'
  96. }
  97. }).$mount(el)
  98. // should not apply transition on initial render by default
  99. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  100. vm.ok = false
  101. waitForUpdate(() => {
  102. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  103. }).thenWaitFor(nextFrame).then(() => {
  104. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  105. }).thenWaitFor(duration + 10).then(() => {
  106. expect(vm.$el.children.length).toBe(0)
  107. vm.ok = true
  108. vm.trans = 'changed'
  109. }).then(() => {
  110. expect(vm.$el.children[0].className).toBe('test changed-enter changed-enter-active')
  111. }).thenWaitFor(nextFrame).then(() => {
  112. expect(vm.$el.children[0].className).toBe('test changed-enter-active')
  113. }).thenWaitFor(duration + 10).then(() => {
  114. expect(vm.$el.children[0].className).toBe('test')
  115. }).then(done)
  116. })
  117. it('inline transition object', done => {
  118. const onEnter = jasmine.createSpy('enter')
  119. const onLeave = jasmine.createSpy('leave')
  120. const vm = new Vue({
  121. template: `<div><div v-if="ok" class="test" :transition="{
  122. name: 'inline',
  123. enterClass: 'hello',
  124. enterActiveClass: 'hello-active',
  125. leaveClass: 'bye',
  126. leaveActiveClass: 'byebye active'
  127. }">foo</div></div>`,
  128. data: { ok: true },
  129. transitions: {
  130. inline: {
  131. onEnter,
  132. onLeave
  133. }
  134. }
  135. }).$mount(el)
  136. // should not apply transition on initial render by default
  137. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  138. vm.ok = false
  139. waitForUpdate(() => {
  140. expect(vm.$el.children[0].className).toBe('test bye byebye active')
  141. expect(onLeave).toHaveBeenCalled()
  142. }).thenWaitFor(nextFrame).then(() => {
  143. expect(vm.$el.children[0].className).toBe('test byebye active')
  144. }).thenWaitFor(duration + 10).then(() => {
  145. expect(vm.$el.children.length).toBe(0)
  146. vm.ok = true
  147. }).then(() => {
  148. expect(vm.$el.children[0].className).toBe('test hello hello-active')
  149. expect(onEnter).toHaveBeenCalled()
  150. }).thenWaitFor(nextFrame).then(() => {
  151. expect(vm.$el.children[0].className).toBe('test hello-active')
  152. }).thenWaitFor(duration + 10).then(() => {
  153. expect(vm.$el.children[0].className).toBe('test')
  154. }).then(done)
  155. })
  156. it('transition with JavaScript hooks', done => {
  157. const beforeLeaveSpy = jasmine.createSpy('beforeLeave')
  158. const beforeEnterSpy = jasmine.createSpy('beforeEnter')
  159. const hooks = {
  160. beforeLeave: el => {
  161. expect(el).toBe(vm.$el.children[0])
  162. expect(el.className).toBe('test')
  163. beforeLeaveSpy()
  164. },
  165. onLeave: jasmine.createSpy('leave'),
  166. afterLeave: jasmine.createSpy('afterLeave'),
  167. beforeEnter: el => {
  168. expect(vm.$el.contains(el)).toBe(false)
  169. expect(el.className).toBe('test')
  170. beforeEnterSpy()
  171. },
  172. onEnter: jasmine.createSpy('enter'),
  173. afterEnter: jasmine.createSpy('afterEnter')
  174. }
  175. const vm = new Vue({
  176. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  177. data: { ok: true },
  178. transitions: {
  179. test: hooks
  180. }
  181. }).$mount(el)
  182. // should not apply transition on initial render by default
  183. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  184. vm.ok = false
  185. waitForUpdate(() => {
  186. expect(beforeLeaveSpy).toHaveBeenCalled()
  187. expect(hooks.onLeave).toHaveBeenCalled()
  188. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  189. }).thenWaitFor(nextFrame).then(() => {
  190. expect(hooks.afterLeave).not.toHaveBeenCalled()
  191. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  192. }).thenWaitFor(duration + 10).then(() => {
  193. expect(hooks.afterLeave).toHaveBeenCalled()
  194. expect(vm.$el.children.length).toBe(0)
  195. vm.ok = true
  196. }).then(() => {
  197. expect(beforeEnterSpy).toHaveBeenCalled()
  198. expect(hooks.onEnter).toHaveBeenCalled()
  199. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  200. }).thenWaitFor(nextFrame).then(() => {
  201. expect(hooks.afterEnter).not.toHaveBeenCalled()
  202. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  203. }).thenWaitFor(duration + 10).then(() => {
  204. expect(hooks.afterEnter).toHaveBeenCalled()
  205. expect(vm.$el.children[0].className).toBe('test')
  206. }).then(done)
  207. })
  208. it('explicit user callback in JavaScript hooks', done => {
  209. let next
  210. const vm = new Vue({
  211. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  212. data: { ok: true },
  213. transitions: {
  214. test: {
  215. onEnter: (el, cb) => {
  216. next = cb
  217. },
  218. onLeave: (el, cb) => {
  219. next = cb
  220. }
  221. }
  222. }
  223. }).$mount(el)
  224. vm.ok = false
  225. waitForUpdate(() => {
  226. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  227. }).thenWaitFor(nextFrame).then(() => {
  228. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  229. }).thenWaitFor(duration + 10).then(() => {
  230. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  231. expect(next).toBeTruthy()
  232. next()
  233. expect(vm.$el.children.length).toBe(0)
  234. }).then(() => {
  235. vm.ok = true
  236. }).then(() => {
  237. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  238. }).thenWaitFor(nextFrame).then(() => {
  239. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  240. }).thenWaitFor(duration + 10).then(() => {
  241. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  242. expect(next).toBeTruthy()
  243. next()
  244. expect(vm.$el.children[0].className).toBe('test')
  245. }).then(done)
  246. })
  247. it('css: false', done => {
  248. const enterSpy = jasmine.createSpy('enter')
  249. const leaveSpy = jasmine.createSpy('leave')
  250. const vm = new Vue({
  251. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  252. data: { ok: true },
  253. transitions: {
  254. test: {
  255. css: false,
  256. onEnter: enterSpy,
  257. onLeave: leaveSpy
  258. }
  259. }
  260. }).$mount(el)
  261. vm.ok = false
  262. waitForUpdate(() => {
  263. expect(leaveSpy).toHaveBeenCalled()
  264. expect(vm.$el.innerHTML).toBe('')
  265. vm.ok = true
  266. }).then(() => {
  267. expect(enterSpy).toHaveBeenCalled()
  268. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  269. }).then(done)
  270. })
  271. it('no transition detected', done => {
  272. const enterSpy = jasmine.createSpy('enter')
  273. const leaveSpy = jasmine.createSpy('leave')
  274. const vm = new Vue({
  275. template: '<div><div v-if="ok" transition="nope">foo</div></div>',
  276. data: { ok: true },
  277. transitions: {
  278. nope: {
  279. onEnter: enterSpy,
  280. onLeave: leaveSpy
  281. }
  282. }
  283. }).$mount(el)
  284. vm.ok = false
  285. waitForUpdate(() => {
  286. expect(leaveSpy).toHaveBeenCalled()
  287. expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div>')
  288. }).thenWaitFor(nextFrame).then(() => {
  289. expect(vm.$el.innerHTML).toBe('')
  290. vm.ok = true
  291. }).then(() => {
  292. expect(enterSpy).toHaveBeenCalled()
  293. expect(vm.$el.innerHTML).toBe('<div class="nope-enter nope-enter-active">foo</div>')
  294. }).thenWaitFor(nextFrame).then(() => {
  295. expect(vm.$el.innerHTML).toMatch(/<div( class="")?>foo<\/div>/)
  296. }).then(done)
  297. })
  298. it('enterCancelled', done => {
  299. const spy = jasmine.createSpy('enterCancelled')
  300. const vm = new Vue({
  301. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  302. data: { ok: false },
  303. transitions: {
  304. test: {
  305. enterCancelled: spy
  306. }
  307. }
  308. }).$mount(el)
  309. expect(vm.$el.innerHTML).toBe('')
  310. vm.ok = true
  311. waitForUpdate(() => {
  312. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  313. }).thenWaitFor(duration / 2).then(() => {
  314. vm.ok = false
  315. }).then(() => {
  316. expect(spy).toHaveBeenCalled()
  317. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  318. }).thenWaitFor(nextFrame).then(() => {
  319. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  320. }).thenWaitFor(duration + 10).then(() => {
  321. expect(vm.$el.children.length).toBe(0)
  322. }).then(done)
  323. })
  324. it('transition with v-show', done => {
  325. const vm = new Vue({
  326. template: '<div><div v-show="ok" class="test" transition="test">foo</div></div>',
  327. data: { ok: true }
  328. }).$mount(el)
  329. // should not apply transition on initial render by default
  330. expect(vm.$el.textContent).toBe('foo')
  331. expect(vm.$el.children[0].style.display).toBe('')
  332. vm.ok = false
  333. waitForUpdate(() => {
  334. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  335. }).thenWaitFor(nextFrame).then(() => {
  336. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  337. }).thenWaitFor(duration + 10).then(() => {
  338. expect(vm.$el.children[0].style.display).toBe('none')
  339. vm.ok = true
  340. }).then(() => {
  341. expect(vm.$el.children[0].style.display).toBe('')
  342. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  343. }).thenWaitFor(nextFrame).then(() => {
  344. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  345. }).thenWaitFor(duration + 10).then(() => {
  346. expect(vm.$el.children[0].className).toBe('test')
  347. }).then(done)
  348. })
  349. it('leaveCancelled (v-show only)', done => {
  350. const spy = jasmine.createSpy('leaveCancelled')
  351. const vm = new Vue({
  352. template: '<div><div v-show="ok" class="test" transition="test">foo</div></div>',
  353. data: { ok: true },
  354. transitions: {
  355. test: {
  356. leaveCancelled: spy
  357. }
  358. }
  359. }).$mount(el)
  360. expect(vm.$el.children[0].style.display).toBe('')
  361. vm.ok = false
  362. waitForUpdate(() => {
  363. expect(vm.$el.children[0].className).toBe('test test-leave test-leave-active')
  364. }).thenWaitFor(nextFrame).then(() => {
  365. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  366. }).thenWaitFor(10).then(() => {
  367. vm.ok = true
  368. }).then(() => {
  369. expect(spy).toHaveBeenCalled()
  370. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  371. }).thenWaitFor(nextFrame).then(() => {
  372. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  373. }).thenWaitFor(duration + 10).then(() => {
  374. expect(vm.$el.children[0].style.display).toBe('')
  375. }).then(done)
  376. })
  377. it('animations', done => {
  378. const vm = new Vue({
  379. template: '<div><div v-if="ok" class="test" transition="test-anim">foo</div></div>',
  380. data: { ok: true }
  381. }).$mount(el)
  382. // should not apply transition on initial render by default
  383. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  384. vm.ok = false
  385. waitForUpdate(() => {
  386. expect(vm.$el.children[0].className).toBe('test test-anim-leave test-anim-leave-active')
  387. }).thenWaitFor(nextFrame).then(() => {
  388. expect(vm.$el.children[0].className).toBe('test test-anim-leave-active')
  389. }).thenWaitFor(duration + 10).then(() => {
  390. expect(vm.$el.children.length).toBe(0)
  391. vm.ok = true
  392. }).then(() => {
  393. expect(vm.$el.children[0].className).toBe('test test-anim-enter test-anim-enter-active')
  394. }).thenWaitFor(nextFrame).then(() => {
  395. expect(vm.$el.children[0].className).toBe('test test-anim-enter-active')
  396. }).thenWaitFor(duration + 10).then(() => {
  397. expect(vm.$el.children[0].className).toBe('test')
  398. }).then(done)
  399. })
  400. it('transition on appear', done => {
  401. const vm = new Vue({
  402. template: `
  403. <div>
  404. <div v-if="ok"
  405. class="test"
  406. :transition="{
  407. name:'test',
  408. appear:true,
  409. appearClass: 'test-appear',
  410. appearActiveClass: 'test-appear-active'
  411. }">foo</div>
  412. </div>
  413. `,
  414. data: { ok: true }
  415. }).$mount(el)
  416. waitForUpdate(() => {
  417. expect(vm.$el.children[0].className).toBe('test test-appear test-appear-active')
  418. }).thenWaitFor(nextFrame).then(() => {
  419. expect(vm.$el.children[0].className).toBe('test test-appear-active')
  420. }).thenWaitFor(duration + 10).then(() => {
  421. expect(vm.$el.children[0].className).toBe('test')
  422. }).then(done)
  423. })
  424. it('transition on appear with v-show', done => {
  425. const vm = new Vue({
  426. template: `
  427. <div>
  428. <div v-show="ok"
  429. class="test"
  430. :transition="{name:'test',appear:true}">foo</div>
  431. </div>
  432. `,
  433. data: { ok: true }
  434. }).$mount(el)
  435. waitForUpdate(() => {
  436. expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
  437. }).thenWaitFor(nextFrame).then(() => {
  438. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  439. }).thenWaitFor(duration + 10).then(() => {
  440. expect(vm.$el.children[0].className).toBe('test')
  441. }).then(done)
  442. })
  443. it('transition on SVG elements', done => {
  444. const vm = new Vue({
  445. template: '<svg><circle cx="0" cy="0" r="10" v-if="ok" class="test" transition></circle></svg>',
  446. data: { ok: true }
  447. }).$mount(el)
  448. // should not apply transition on initial render by default
  449. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  450. vm.ok = false
  451. waitForUpdate(() => {
  452. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave v-leave-active')
  453. }).thenWaitFor(nextFrame).then(() => {
  454. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave-active')
  455. }).thenWaitFor(duration + 10).then(() => {
  456. expect(vm.$el.childNodes.length).toBe(0)
  457. vm.ok = true
  458. }).then(() => {
  459. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter v-enter-active')
  460. }).thenWaitFor(nextFrame).then(() => {
  461. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-enter-active')
  462. }).thenWaitFor(duration + 10).then(() => {
  463. expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test')
  464. }).then(done)
  465. })
  466. it('transition on child components', done => {
  467. const vm = new Vue({
  468. template: '<div><test v-if="ok" class="test" transition></test></div>',
  469. data: { ok: true },
  470. components: {
  471. test: {
  472. template: '<div transition="test">foo</div>' // test transition override from parent
  473. }
  474. }
  475. }).$mount(el)
  476. // should not apply transition on initial render by default
  477. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  478. vm.ok = false
  479. waitForUpdate(() => {
  480. expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
  481. }).thenWaitFor(nextFrame).then(() => {
  482. expect(vm.$el.children[0].className).toBe('test v-leave-active')
  483. }).thenWaitFor(duration + 10).then(() => {
  484. expect(vm.$el.children.length).toBe(0)
  485. vm.ok = true
  486. }).then(() => {
  487. expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
  488. }).thenWaitFor(nextFrame).then(() => {
  489. expect(vm.$el.children[0].className).toBe('test v-enter-active')
  490. }).thenWaitFor(duration + 10).then(() => {
  491. expect(vm.$el.children[0].className).toBe('test')
  492. }).then(done)
  493. })
  494. })
  495. }