describe('Transition', function () { var transition = require('vue/src/transition'), config = require('vue/src/config'), codes = transition.codes, endEvents = transition.sniff(), enterClass = config.enterClass, leaveClass = config.leaveClass, nextTick = Vue.nextTick describe('General', function () { it('should skip if compiler is in init stage', function () { var c = mockChange(), compiler = mockCompiler() compiler.init = true var code = transition(null, 1, c.change, compiler) assert.ok(c.called) assert.strictEqual(code, codes.INIT) assert.ok(compiler.attached) }) it('should skip if no transition is found on the node', function () { var c = mockChange(), compiler = mockCompiler(), code = transition(mockEl(), 1, c.change, compiler) assert.ok(c.called) assert.strictEqual(code, codes.SKIP) assert.ok(compiler.attached) }) }) describe('CSS Transitions', function () { if (!endEvents.trans) { // IE9 only test case it('should skip if transition is not available', function () { var c = mockChange(), compiler = mockCompiler(), code = transition(mockEl('css'), 1, c.change, compiler) assert.ok(c.called) assert.strictEqual(code, codes.CSS_SKIP) assert.ok(compiler.attached) }) // skip the rest return } describe('enter: transition', function () { var el = mockEl('css'), c = mockChange(function () { c.called = true assert.ok(el.classList.contains(enterClass)) }), compiler = mockCompiler(), code, cbCalled = false el.vue_trans_cb = function () { cbCalled = true } el.addEventListener(endEvents.trans, el.vue_trans_cb) it('should add the class before calling changeState()', function () { code = transition(el, 1, c.change, compiler) assert.ok(c.called) }) it('should remove unfinished leave callback if exists', function () { assert.notOk(el.vue_trans_cb) var e = mockHTMLEvent(endEvents.trans) el.dispatchEvent(e) assert.notOk(cbCalled) }) it('should remove the v-leave class if the leave callback exists', function () { var el = mockEl('css') document.body.appendChild(el) el.style.width = '1px' code = transition(el, -1, function(){}, compiler) code = transition(el, 1, function(){}, compiler) assert.notOk(el.classList.contains(leaveClass)) }) it('should remove the class afterwards', function (done) { nextTick(function () { assert.notOk(el.classList.contains(enterClass)) done() }) }) it('should return correct code', function () { assert.strictEqual(code, codes.CSS_E) }) it('should have called attached hook', function () { assert.ok(compiler.attached) }) }) describe('enter: animation', function () { var el = mockEl('css'), c = mockChange(function () { c.called = true assert.ok(el.classList.contains(enterClass)) }), compiler = mockCompiler(), code // mark it to use CSS animation instead of transition el.vue_anim = '' before(function () { document.body.appendChild(el) }) after(function () { document.body.removeChild(el) }) it('should add enterClass before calling changeState()', function () { code = transition(el, 1, c.change, compiler) assert.ok(c.called) }) it('should still have the class on nextTick', function (done) { nextTick(function () { assert.ok(el.classList.contains(enterClass)) done() }) }) it('should remove the class when the animation is done', function (done) { el.addEventListener(endEvents.anim, function () { assert.notOk(el.vue_trans_cb) assert.notOk(el.classList.contains(enterClass)) done() }) var e = mockHTMLEvent(endEvents.anim) el.dispatchEvent(e) }) }) describe('leave', function () { var el = mockEl('css'), c = mockChange(), compiler = mockCompiler(), code before(function () { document.body.appendChild(el) }) after(function () { document.body.removeChild(el) }) it('should call change immediately if el is invisible', function () { var el = mockEl('css'), c = mockChange(), compiler = mockCompiler() code = transition(el, -1, c.change, compiler) assert.ok(c.called) assert.ok(compiler.detached) }) it('should attach an ontransitionend listener', function () { el.style.width = '1px' code = transition(el, -1, c.change, compiler) assert.ok(typeof el.vue_trans_cb === 'function') }) it('should add the class', function (done) { nextTick(function () { assert.ok(el.classList.contains(leaveClass)) done() }) }) it('should call changeState on transitionend', function () { var e = mockHTMLEvent(endEvents.trans) el.dispatchEvent(e) assert.ok(c.called) }) it('should remove the callback after called', function () { assert.notOk(el.vue_trans_cb) var e = mockHTMLEvent(endEvents.trans) el.dispatchEvent(e) assert.strictEqual(c.n, 1) }) it('should remove the class after called', function () { assert.notOk(el.classList.contains(leaveClass)) }) it('should return correct code', function () { assert.strictEqual(code, codes.CSS_L) }) it('should have called detached hook', function () { assert.ok(compiler.detached) }) }) }) describe('JavaScript Transitions', function () { it('should skip if correspinding option is not defined', function () { var c = mockChange(), compiler = mockCompiler(), code = transition(mockEl('js'), 1, c.change, compiler) assert.ok(c.called) assert.strictEqual(code, codes.JS_SKIP) assert.ok(compiler.attached) }) it('should skip if the option is given but the enter/leave func is not defined', function () { var c = mockChange(), compiler = mockCompiler({}), code = transition(mockEl('js'), 1, c.change, compiler) assert.ok(c.called) assert.strictEqual(code, codes.JS_SKIP_E) assert.ok(compiler.attached) c = mockChange() compiler = mockCompiler({}) code = transition(mockEl('js'), -1, c.change, compiler) assert.ok(c.called) assert.strictEqual(code, codes.JS_SKIP_L) assert.ok(compiler.detached) }) describe('enter', function () { var code, c = mockChange(), el = mockEl('js'), def = { enter: function (element, change) { assert.strictEqual(el, element) change() } }, compiler = mockCompiler(def) it('should call the enter function', function () { code = transition(el, 1, c.change, compiler) assert.ok(c.called) }) it('should return correct code', function () { assert.strictEqual(code, codes.JS_E) }) it('should have called attached hook', function () { assert.ok(compiler.attached) }) }) describe('leave', function () { var code, c = mockChange(), el = mockEl('js'), def = { leave: function (element, change) { assert.strictEqual(el, element) change() } }, compiler = mockCompiler(def) it('should call the leave function', function () { code = transition(el, -1, c.change, compiler) assert.ok(c.called) }) it('should return correct code', function () { assert.strictEqual(code, codes.JS_L) }) it('should have called detached hook', function () { assert.ok(compiler.detached) }) }) describe('wrapped timeout', function () { var el = mockEl('js'), c = mockChange(), timerFired = false, def = { enter: function (el, change, timeout) { change() timeout(function () { timerFired = true }, 0) }, leave: function () {} }, compiler = mockCompiler(def) it('should cancel previous unfired timers', function (done) { transition(el, 1, c.change, compiler) assert.strictEqual(el.vue_timeouts.length, 1) transition(el, -1, c.change, compiler) assert.strictEqual(el.vue_timeouts.length, 0) setTimeout(function () { assert.notOk(timerFired) done() }, 0) }) }) }) function mockChange (change) { var c = { called: false, n: 0, change: change || function () { c.called = true c.n += 1 } } return c } function mockEl (type) { var el = document.createElement('div') if (type === 'css') { el.vue_trans = '' } else if (type === 'js') { el.vue_effect = 'test' } return el } function mockCompiler (opt) { return { getOption: function () { return opt }, execHook: function (hook) { this[hook] = true } } } })