transition.spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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 = 30
  7. insertCSS(`
  8. .test {
  9. -webkit-transition: opacity ${duration}ms ease;
  10. transition: opacity ${duration}ms ease;
  11. }
  12. .test-enter, .test-leave-active, .hello, .bye.active, .changed-enter {
  13. opacity: 0;
  14. }
  15. .test-anim-enter-active {
  16. animation: test-enter ${duration}ms;
  17. -webkit-animation: test-enter ${duration}ms;
  18. }
  19. .test-anim-leave-active {
  20. animation: test-leave ${duration}ms;
  21. -webkit-animation: test-leave ${duration}ms;
  22. }
  23. @keyframes test-enter {
  24. from { opacity: 0 }
  25. to { opacity: 1 }
  26. }
  27. @-webkit-keyframes test-enter {
  28. from { opacity: 0 }
  29. to { opacity: 1 }
  30. }
  31. @keyframes test-leave {
  32. from { opacity: 1 }
  33. to { opacity: 0 }
  34. }
  35. @-webkit-keyframes test-leave {
  36. from { opacity: 1 }
  37. to { opacity: 0 }
  38. }
  39. `)
  40. let el
  41. beforeEach(() => {
  42. el = document.createElement('div')
  43. document.body.appendChild(el)
  44. })
  45. it('basic transitions', done => {
  46. const vm = new Vue({
  47. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  48. data: { ok: true }
  49. }).$mount(el)
  50. // should not apply transition on initial render by default
  51. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  52. vm.ok = false
  53. waitForUpdate(() => {
  54. expect(vm.$el.children[0].className).toBe('test test-leave')
  55. }).thenWaitFor(nextFrame).then(() => {
  56. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  57. }).thenWaitFor(timeout(duration / 2)).then(() => {
  58. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  59. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  60. expect(vm.$el.children.length).toBe(0)
  61. vm.ok = true
  62. }).then(() => {
  63. expect(vm.$el.children[0].className).toBe('test test-enter')
  64. }).thenWaitFor(nextFrame).then(() => {
  65. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  66. }).thenWaitFor(timeout(duration / 2)).then(() => {
  67. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  68. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  69. expect(vm.$el.children[0].className).toBe('test')
  70. }).then(done)
  71. })
  72. it('custom transition classes', done => {
  73. const vm = new Vue({
  74. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  75. data: { ok: true },
  76. transitions: {
  77. test: {
  78. enterClass: 'hello',
  79. enterActiveClass: 'hello-active',
  80. leaveClass: 'bye',
  81. leaveActiveClass: 'bye active' // testing multi classes
  82. }
  83. }
  84. }).$mount(el)
  85. // should not apply transition on initial render by default
  86. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  87. vm.ok = false
  88. waitForUpdate(() => {
  89. expect(vm.$el.children[0].className).toBe('test bye')
  90. }).thenWaitFor(nextFrame).then(() => {
  91. expect(vm.$el.children[0].className).toBe('test bye active')
  92. }).thenWaitFor(timeout(duration / 2)).then(() => {
  93. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  94. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  95. expect(vm.$el.children.length).toBe(0)
  96. vm.ok = true
  97. }).then(() => {
  98. expect(vm.$el.children[0].className).toBe('test hello')
  99. }).thenWaitFor(nextFrame).then(() => {
  100. expect(vm.$el.children[0].className).toBe('test hello-active')
  101. }).thenWaitFor(timeout(duration / 2)).then(() => {
  102. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  103. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  104. expect(vm.$el.children[0].className).toBe('test')
  105. }).then(done)
  106. })
  107. it('dynamic transition', done => {
  108. const vm = new Vue({
  109. template: '<div><div v-if="ok" class="test" :transition="trans">foo</div></div>',
  110. data: {
  111. ok: true,
  112. trans: 'test'
  113. }
  114. }).$mount(el)
  115. // should not apply transition on initial render by default
  116. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  117. vm.ok = false
  118. waitForUpdate(() => {
  119. expect(vm.$el.children[0].className).toBe('test test-leave')
  120. }).thenWaitFor(nextFrame).then(() => {
  121. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  122. }).thenWaitFor(timeout(duration / 2)).then(() => {
  123. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  124. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  125. expect(vm.$el.children.length).toBe(0)
  126. vm.ok = true
  127. vm.trans = 'changed'
  128. }).then(() => {
  129. expect(vm.$el.children[0].className).toBe('test changed-enter')
  130. }).thenWaitFor(nextFrame).then(() => {
  131. expect(vm.$el.children[0].className).toBe('test changed-enter-active')
  132. }).thenWaitFor(timeout(duration / 2)).then(() => {
  133. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  134. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  135. expect(vm.$el.children[0].className).toBe('test')
  136. }).then(done)
  137. })
  138. it('inline transition object', done => {
  139. const vm = new Vue({
  140. template: `<div><div v-if="ok" class="test" :transition="{
  141. enterClass: 'hello',
  142. enterActiveClass: 'hello-active',
  143. leaveClass: 'bye',
  144. leaveActiveClass: 'bye active'
  145. }">foo</div></div>`,
  146. data: { ok: true }
  147. }).$mount(el)
  148. // should not apply transition on initial render by default
  149. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  150. vm.ok = false
  151. waitForUpdate(() => {
  152. expect(vm.$el.children[0].className).toBe('test bye')
  153. }).thenWaitFor(nextFrame).then(() => {
  154. expect(vm.$el.children[0].className).toBe('test bye active')
  155. }).thenWaitFor(timeout(duration / 2)).then(() => {
  156. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  157. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  158. expect(vm.$el.children.length).toBe(0)
  159. vm.ok = true
  160. }).then(() => {
  161. expect(vm.$el.children[0].className).toBe('test hello')
  162. }).thenWaitFor(nextFrame).then(() => {
  163. expect(vm.$el.children[0].className).toBe('test hello-active')
  164. }).thenWaitFor(timeout(duration / 2)).then(() => {
  165. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  166. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  167. expect(vm.$el.children[0].className).toBe('test')
  168. }).then(done)
  169. })
  170. it('transition with JavaScript hooks', done => {
  171. const beforeLeaveSpy = jasmine.createSpy('beforeLeave')
  172. const beforeEnterSpy = jasmine.createSpy('beforeEnter')
  173. const hooks = {
  174. beforeLeave: el => {
  175. expect(el).toBe(vm.$el.children[0])
  176. expect(el.className).toBe('test')
  177. beforeLeaveSpy()
  178. },
  179. leave: jasmine.createSpy('leave'),
  180. afterLeave: jasmine.createSpy('afterLeave'),
  181. beforeEnter: el => {
  182. expect(vm.$el.contains(el)).toBe(false)
  183. expect(el.className).toBe('test')
  184. beforeEnterSpy()
  185. },
  186. enter: jasmine.createSpy('enter'),
  187. afterEnter: jasmine.createSpy('afterEnter')
  188. }
  189. const vm = new Vue({
  190. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  191. data: { ok: true },
  192. transitions: {
  193. test: hooks
  194. }
  195. }).$mount(el)
  196. // should not apply transition on initial render by default
  197. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  198. vm.ok = false
  199. waitForUpdate(() => {
  200. expect(beforeLeaveSpy).toHaveBeenCalled()
  201. expect(hooks.leave).toHaveBeenCalled()
  202. expect(vm.$el.children[0].className).toBe('test test-leave')
  203. }).thenWaitFor(nextFrame).then(() => {
  204. expect(hooks.afterLeave).not.toHaveBeenCalled()
  205. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  206. }).thenWaitFor(timeout(duration / 2)).then(() => {
  207. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  208. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  209. expect(hooks.afterLeave).toHaveBeenCalled()
  210. expect(vm.$el.children.length).toBe(0)
  211. vm.ok = true
  212. }).then(() => {
  213. expect(beforeEnterSpy).toHaveBeenCalled()
  214. expect(hooks.enter).toHaveBeenCalled()
  215. expect(vm.$el.children[0].className).toBe('test test-enter')
  216. }).thenWaitFor(nextFrame).then(() => {
  217. expect(hooks.afterEnter).not.toHaveBeenCalled()
  218. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  219. }).thenWaitFor(timeout(duration / 2)).then(() => {
  220. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  221. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  222. expect(hooks.afterEnter).toHaveBeenCalled()
  223. expect(vm.$el.children[0].className).toBe('test')
  224. }).then(done)
  225. })
  226. it('enterCancelled', done => {
  227. const spy = jasmine.createSpy('enterCancelled')
  228. const vm = new Vue({
  229. template: '<div><div v-if="ok" class="test" transition="test">foo</div></div>',
  230. data: { ok: false },
  231. transitions: {
  232. test: {
  233. enterCancelled: spy
  234. }
  235. }
  236. }).$mount(el)
  237. expect(vm.$el.innerHTML).toBe('')
  238. vm.ok = true
  239. waitForUpdate(() => {
  240. expect(vm.$el.children[0].className).toBe('test test-enter')
  241. }).thenWaitFor(timeout(duration / 2)).then(() => {
  242. vm.ok = false
  243. }).then(() => {
  244. expect(spy).toHaveBeenCalled()
  245. expect(vm.$el.children[0].className).toBe('test test-leave')
  246. }).thenWaitFor(nextFrame).then(() => {
  247. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  248. }).thenWaitFor(timeout(duration + 10)).then(() => {
  249. expect(vm.$el.children.length).toBe(0)
  250. }).then(done)
  251. })
  252. it('transition with v-show', done => {
  253. const vm = new Vue({
  254. template: '<div><div v-show="ok" class="test" transition="test">foo</div></div>',
  255. data: { ok: true }
  256. }).$mount(el)
  257. // should not apply transition on initial render by default
  258. expect(vm.$el.textContent).toBe('foo')
  259. expect(vm.$el.children[0].style.display).toBe('')
  260. vm.ok = false
  261. waitForUpdate(() => {
  262. expect(vm.$el.children[0].className).toBe('test test-leave')
  263. }).thenWaitFor(nextFrame).then(() => {
  264. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  265. }).thenWaitFor(timeout(duration / 2)).then(() => {
  266. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  267. expect(vm.$el.children[0].style.display).toBe('')
  268. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  269. expect(vm.$el.children[0].style.display).toBe('none')
  270. vm.ok = true
  271. }).then(() => {
  272. expect(vm.$el.children[0].style.display).toBe('')
  273. expect(vm.$el.children[0].className).toBe('test test-enter')
  274. }).thenWaitFor(nextFrame).then(() => {
  275. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  276. }).thenWaitFor(timeout(duration / 2)).then(() => {
  277. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  278. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  279. expect(vm.$el.children[0].className).toBe('test')
  280. }).then(done)
  281. })
  282. it('leaveCancelled (v-show only)', done => {
  283. const spy = jasmine.createSpy('leaveCancelled')
  284. const vm = new Vue({
  285. template: '<div><div v-show="ok" class="test" transition="test">foo</div></div>',
  286. data: { ok: true },
  287. transitions: {
  288. test: {
  289. leaveCancelled: spy
  290. }
  291. }
  292. }).$mount(el)
  293. expect(vm.$el.children[0].style.display).toBe('')
  294. vm.ok = false
  295. waitForUpdate(() => {
  296. expect(vm.$el.children[0].className).toBe('test test-leave')
  297. }).thenWaitFor(nextFrame).then(() => {
  298. expect(vm.$el.children[0].className).toBe('test test-leave-active')
  299. }).thenWaitFor(timeout(duration / 2)).then(() => {
  300. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  301. vm.ok = true
  302. }).then(() => {
  303. expect(spy).toHaveBeenCalled()
  304. expect(vm.$el.children[0].className).toBe('test test-enter')
  305. }).thenWaitFor(nextFrame).then(() => {
  306. expect(vm.$el.children[0].className).toBe('test test-enter-active')
  307. }).thenWaitFor(timeout(duration + 10)).then(() => {
  308. expect(vm.$el.children[0].style.display).toBe('')
  309. expect(window.getComputedStyle(vm.$el.children[0]).opacity).toBe('1')
  310. }).then(done)
  311. })
  312. it('animations', done => {
  313. const vm = new Vue({
  314. template: '<div><div v-if="ok" class="test" transition="test-anim">foo</div></div>',
  315. data: { ok: true }
  316. }).$mount(el)
  317. // should not apply transition on initial render by default
  318. expect(vm.$el.innerHTML).toBe('<div class="test">foo</div>')
  319. vm.ok = false
  320. waitForUpdate(() => {
  321. expect(vm.$el.children[0].className).toBe('test test-anim-leave')
  322. }).thenWaitFor(nextFrame).then(() => {
  323. expect(vm.$el.children[0].className).toBe('test test-anim-leave-active')
  324. }).thenWaitFor(timeout(duration / 2)).then(() => {
  325. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('1')
  326. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  327. expect(vm.$el.children.length).toBe(0)
  328. vm.ok = true
  329. }).then(() => {
  330. expect(vm.$el.children[0].className).toBe('test test-anim-enter')
  331. }).thenWaitFor(nextFrame).then(() => {
  332. expect(vm.$el.children[0].className).toBe('test test-anim-enter-active')
  333. }).thenWaitFor(timeout(duration / 2)).then(() => {
  334. expect(window.getComputedStyle(vm.$el.children[0]).opacity).not.toBe('0')
  335. }).thenWaitFor(timeout(duration / 2 + 10)).then(() => {
  336. expect(vm.$el.children[0].className).toBe('test')
  337. }).then(done)
  338. })
  339. it('transition on appear', () => {
  340. })
  341. })
  342. }
  343. function insertCSS (text) {
  344. var cssEl = document.createElement('style')
  345. cssEl.textContent = text.trim()
  346. document.head.appendChild(cssEl)
  347. }
  348. function timeout (n) {
  349. return next => setTimeout(next, n)
  350. }