transition.spec.js 18 KB

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