transition.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. describe('Transition', function () {
  2. var transition = require('vue/src/transition'),
  3. config = require('vue/src/config'),
  4. codes = transition.codes,
  5. endEvents = sniffEndEvents(),
  6. enterClass = config.enterClass,
  7. leaveClass = config.leaveClass,
  8. nextTick = Vue.nextTick
  9. describe('General', function () {
  10. it('should skip if compiler is in init stage', function () {
  11. var c = mockChange(),
  12. compiler = mockCompiler()
  13. compiler.init = true
  14. var code = transition(null, 1, c.change, compiler)
  15. assert.ok(c.called)
  16. assert.strictEqual(code, codes.INIT)
  17. assert.ok(compiler.attached)
  18. })
  19. it('should skip if no transition is found on the node', function () {
  20. var c = mockChange(),
  21. compiler = mockCompiler(),
  22. code = transition(mockEl(), 1, c.change, compiler)
  23. assert.ok(c.called)
  24. assert.strictEqual(code, codes.SKIP)
  25. assert.ok(compiler.attached)
  26. })
  27. })
  28. describe('CSS Transitions', function () {
  29. if (!endEvents.trans) { // IE9 only test case
  30. it('should skip if transition is not available', function () {
  31. var c = mockChange(),
  32. compiler = mockCompiler(),
  33. code = transition(mockEl('css'), 1, c.change, compiler)
  34. assert.ok(c.called)
  35. assert.strictEqual(code, codes.CSS_SKIP)
  36. assert.ok(compiler.attached)
  37. })
  38. // skip the rest
  39. return
  40. }
  41. describe('enter: transition', function () {
  42. var el = mockEl('css'),
  43. c = mockChange(function () {
  44. c.called = true
  45. assert.ok(el.classList.contains(enterClass))
  46. }),
  47. compiler = mockCompiler(),
  48. code,
  49. cbCalled = false
  50. el.vue_trans_cb = function () {
  51. cbCalled = true
  52. }
  53. el.addEventListener(endEvents.trans, el.vue_trans_cb)
  54. it('should add the class before calling changeState()', function () {
  55. code = transition(el, 1, c.change, compiler)
  56. assert.ok(c.called)
  57. })
  58. it('should remove unfinished leave callback if exists', function () {
  59. assert.notOk(el.vue_trans_cb)
  60. var e = mockHTMLEvent(endEvents.trans)
  61. el.dispatchEvent(e)
  62. assert.notOk(cbCalled)
  63. })
  64. it('should remove the v-leave class if the leave callback exists', function () {
  65. var el = mockEl('css')
  66. document.body.appendChild(el)
  67. el.style.width = '1px'
  68. code = transition(el, -1, function(){}, compiler)
  69. code = transition(el, 1, function(){}, compiler)
  70. assert.notOk(el.classList.contains(leaveClass))
  71. })
  72. it('should remove the class afterwards', function (done) {
  73. nextTick(function () {
  74. assert.notOk(el.classList.contains(enterClass))
  75. done()
  76. })
  77. })
  78. it('should return correct code', function () {
  79. assert.strictEqual(code, codes.CSS_E)
  80. })
  81. it('should have called attached hook', function () {
  82. assert.ok(compiler.attached)
  83. })
  84. })
  85. describe('enter: animation', function () {
  86. var el = mockEl('css'),
  87. c = mockChange(function () {
  88. c.called = true
  89. assert.ok(el.classList.contains(enterClass))
  90. }),
  91. compiler = mockCompiler(),
  92. code
  93. // mark it to use CSS animation instead of transition
  94. el.vue_anim = ''
  95. before(function () {
  96. document.body.appendChild(el)
  97. })
  98. after(function () {
  99. document.body.removeChild(el)
  100. })
  101. it('should add enterClass before calling changeState()', function () {
  102. code = transition(el, 1, c.change, compiler)
  103. assert.ok(c.called)
  104. })
  105. it('should still have the class on nextTick', function (done) {
  106. nextTick(function () {
  107. assert.ok(el.classList.contains(enterClass))
  108. done()
  109. })
  110. })
  111. it('should remove the class when the animation is done', function (done) {
  112. el.addEventListener(endEvents.anim, function () {
  113. assert.notOk(el.vue_trans_cb)
  114. assert.notOk(el.classList.contains(enterClass))
  115. done()
  116. })
  117. var e = mockHTMLEvent(endEvents.anim)
  118. el.dispatchEvent(e)
  119. })
  120. })
  121. describe('leave', function () {
  122. var el = mockEl('css'),
  123. c = mockChange(),
  124. compiler = mockCompiler(),
  125. code
  126. before(function () {
  127. document.body.appendChild(el)
  128. })
  129. after(function () {
  130. document.body.removeChild(el)
  131. })
  132. it('should call change immediately if el is invisible', function () {
  133. var el = mockEl('css'),
  134. c = mockChange(),
  135. compiler = mockCompiler()
  136. code = transition(el, -1, c.change, compiler)
  137. assert.ok(c.called)
  138. assert.ok(compiler.detached)
  139. })
  140. it('should attach an ontransitionend listener', function () {
  141. el.style.width = '1px'
  142. code = transition(el, -1, c.change, compiler)
  143. assert.ok(typeof el.vue_trans_cb === 'function')
  144. })
  145. it('should add the class', function () {
  146. assert.ok(el.classList.contains(leaveClass))
  147. })
  148. it('should call changeState on transitionend', function () {
  149. var e = mockHTMLEvent(endEvents.trans)
  150. el.dispatchEvent(e)
  151. assert.ok(c.called)
  152. })
  153. it('should remove the callback after called', function () {
  154. assert.notOk(el.vue_trans_cb)
  155. var e = mockHTMLEvent(endEvents.trans)
  156. el.dispatchEvent(e)
  157. assert.strictEqual(c.n, 1)
  158. })
  159. it('should remove the class after called', function () {
  160. assert.notOk(el.classList.contains(leaveClass))
  161. })
  162. it('should return correct code', function () {
  163. assert.strictEqual(code, codes.CSS_L)
  164. })
  165. it('should have called detached hook', function () {
  166. assert.ok(compiler.detached)
  167. })
  168. })
  169. })
  170. describe('JavaScript Transitions', function () {
  171. it('should skip if correspinding option is not defined', function () {
  172. var c = mockChange(),
  173. compiler = mockCompiler(),
  174. code = transition(mockEl('js'), 1, c.change, compiler)
  175. assert.ok(c.called)
  176. assert.strictEqual(code, codes.JS_SKIP)
  177. assert.ok(compiler.attached)
  178. })
  179. it('should skip if the option is given but the enter/leave func is not defined', function () {
  180. var c = mockChange(),
  181. compiler = mockCompiler({}),
  182. code = transition(mockEl('js'), 1, c.change, compiler)
  183. assert.ok(c.called)
  184. assert.strictEqual(code, codes.JS_SKIP_E)
  185. assert.ok(compiler.attached)
  186. c = mockChange()
  187. compiler = mockCompiler({})
  188. code = transition(mockEl('js'), -1, c.change, compiler)
  189. assert.ok(c.called)
  190. assert.strictEqual(code, codes.JS_SKIP_L)
  191. assert.ok(compiler.detached)
  192. })
  193. describe('enter', function () {
  194. var code,
  195. c = mockChange(),
  196. el = mockEl('js'),
  197. def = {
  198. enter: function (element, change) {
  199. assert.strictEqual(el, element)
  200. change()
  201. }
  202. },
  203. compiler = mockCompiler(def)
  204. it('should call the enter function', function () {
  205. code = transition(el, 1, c.change, compiler)
  206. assert.ok(c.called)
  207. })
  208. it('should return correct code', function () {
  209. assert.strictEqual(code, codes.JS_E)
  210. })
  211. it('should have called attached hook', function () {
  212. assert.ok(compiler.attached)
  213. })
  214. })
  215. describe('leave', function () {
  216. var code,
  217. c = mockChange(),
  218. el = mockEl('js'),
  219. def = {
  220. leave: function (element, change) {
  221. assert.strictEqual(el, element)
  222. change()
  223. }
  224. },
  225. compiler = mockCompiler(def)
  226. it('should call the leave function', function () {
  227. code = transition(el, -1, c.change, compiler)
  228. assert.ok(c.called)
  229. })
  230. it('should return correct code', function () {
  231. assert.strictEqual(code, codes.JS_L)
  232. })
  233. it('should have called detached hook', function () {
  234. assert.ok(compiler.detached)
  235. })
  236. })
  237. describe('wrapped timeout', function () {
  238. var el = mockEl('js'),
  239. c = mockChange(),
  240. timerFired = false,
  241. def = {
  242. enter: function (el, change, timeout) {
  243. change()
  244. timeout(function () {
  245. timerFired = true
  246. }, 0)
  247. },
  248. leave: function () {}
  249. },
  250. compiler = mockCompiler(def)
  251. it('should cancel previous unfired timers', function (done) {
  252. transition(el, 1, c.change, compiler)
  253. assert.strictEqual(el.vue_timeouts.length, 1)
  254. transition(el, -1, c.change, compiler)
  255. assert.strictEqual(el.vue_timeouts.length, 0)
  256. setTimeout(function () {
  257. assert.notOk(timerFired)
  258. done()
  259. }, 0)
  260. })
  261. })
  262. })
  263. function mockChange (change) {
  264. var c = {
  265. called: false,
  266. n: 0,
  267. change: change || function () {
  268. c.called = true
  269. c.n += 1
  270. }
  271. }
  272. return c
  273. }
  274. function mockEl (type) {
  275. var el = document.createElement('div')
  276. if (type === 'css') {
  277. el.vue_trans = ''
  278. } else if (type === 'js') {
  279. el.vue_effect = 'test'
  280. }
  281. return el
  282. }
  283. function mockCompiler (opt) {
  284. return {
  285. getOption: function () {
  286. return opt
  287. },
  288. execHook: function (hook) {
  289. this[hook] = true
  290. }
  291. }
  292. }
  293. function sniffEndEvents () {
  294. var el = document.createElement('vue'),
  295. defaultEvent = 'transitionend',
  296. events = {
  297. 'transition' : defaultEvent,
  298. 'mozTransition' : defaultEvent,
  299. 'webkitTransition' : 'webkitTransitionEnd'
  300. },
  301. ret = {}
  302. for (var name in events) {
  303. if (el.style[name] !== undefined) {
  304. ret.trans = events[name]
  305. break
  306. }
  307. }
  308. ret.anim = el.style.animation === ''
  309. ? 'animationend'
  310. : 'webkitAnimationEnd'
  311. return ret
  312. }
  313. })