Przeglądaj źródła

improve transition coverage

Evan You 9 lat temu
rodzic
commit
ccf3a61532

+ 35 - 8
src/platforms/web/runtime/components/transition.js

@@ -39,14 +39,44 @@ export default {
   props: transitionProps,
   _abstract: true,
   render (h) {
-    const children = this.$slots.default && this.$slots.default.filter(c => c.tag)
-    if (!children || !children.length) {
+    let children = this.$slots.default
+    if (!children) {
       return
     }
+
+    // warn text nodes
+    if (process.env.NODE_ENV !== 'production' &&
+        children.length === 1 && !children[0].tag) {
+      warn(
+        '<transition> can only be used on elements or components, not text nodes.',
+        this.$parent
+      )
+    }
+
+    // filter out text nodes (possible whitespaces)
+    children = children.filter(c => c.tag)
+
+    if (!children.length) {
+      return
+    }
+
+    // warn multiple elements
     if (process.env.NODE_ENV !== 'production' && children.length > 1) {
       warn(
         '<transition> can only be used on a single element. Use ' +
-        '<transition-group> for lists.'
+        '<transition-group> for lists.',
+        this.$parent
+      )
+    }
+
+    const mode = this.mode
+
+    // warn invalid mode
+    if (process.env.NODE_ENV !== 'production' &&
+        mode && mode !== 'in-out' && mode !== 'out-in') {
+      warn(
+        'invalid <transition> mode: ' + mode,
+        this.$parent
       )
     }
 
@@ -63,11 +93,10 @@ export default {
     const child = getRealChild(rawChild)
     child.key = child.key || `__v${child.tag + this._uid}__`
     const data = (child.data || (child.data = {})).transition = extractTransitionData(this)
-
-    // handle transition mode
-    const mode = this.mode
     const oldRawChild = this._vnode
     const oldChild = getRealChild(oldRawChild)
+
+    // handle transition mode
     if (mode && oldChild && oldChild.data && oldChild.key !== child.key) {
       const oldData = oldChild.data.transition
       if (mode === 'out-in') {
@@ -91,8 +120,6 @@ export default {
         mergeVNodeHook(oldData, 'leaveCancelled', () => {
           delayedLeave = noop
         })
-      } else if (process.env.NODE_ENV !== 'production') {
-        warn('invalid <transition> mode: ' + mode)
       }
     }
 

+ 6 - 3
src/platforms/web/runtime/modules/transition.js

@@ -185,15 +185,17 @@ export function leave (vnode: VNodeWithData, rm: Function) {
 function resolveTransition (def?: string | Object): ?Object {
   if (!def) {
     return
-  } else if (typeof def === 'object') {
+  }
+  /* istanbul ignore else */
+  if (typeof def === 'object') {
     const res = {}
     if (def.css !== false) {
       extend(res, autoCssTransition(def.name || 'v'))
     }
     extend(res, def)
     return res
-  } else {
-    return autoCssTransition(typeof def === 'string' ? def : 'v')
+  } else if (typeof def === 'string') {
+    return autoCssTransition(def)
   }
 }
 
@@ -225,6 +227,7 @@ export default inBrowser ? {
     }
   },
   remove (vnode: VNode, rm: Function) {
+    /* istanbul ignore else */
     if (!vnode.data.show) {
       leave(vnode, rm)
     } else {

+ 80 - 0
test/unit/features/transition/transition-mode.spec.js

@@ -142,6 +142,72 @@ if (!isIE9) {
       }).then(done)
     })
 
+    it('dynamic components, in-out with leaveCancell', done => {
+      let next
+      const vm = new Vue({
+        template: `<div>
+          <transition name="test" mode="in-out" @after-enter="afterEnter">
+            <component :is="view" class="test"></component>
+          </transition>
+        </div>`,
+        data: { view: 'one' },
+        components,
+        methods: {
+          afterEnter () {
+            next()
+          }
+        }
+      }).$mount(el)
+      expect(vm.$el.textContent).toBe('one')
+      vm.view = 'two'
+      waitForUpdate(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test">one</div>' +
+          '<div class="test test-enter test-enter-active">two</div>'
+        )
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test">one</div>' +
+          '<div class="test test-enter-active">two</div>'
+        )
+        // switch again before enter finishes,
+        // this cancels both enter and leave.
+        vm.view = 'one'
+      }).then(() => {
+        // 1. the pending leaving "one" should be removed instantly.
+        // 2. the entering "two" should be placed into its final state instantly.
+        // 3. a new "one" is created and entering
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test">two</div>' +
+          '<div class="test test-enter test-enter-active">one</div>'
+        )
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test">two</div>' +
+          '<div class="test test-enter-active">one</div>'
+        )
+      }).thenWaitFor(_next => { next = _next }).then(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test">two</div>' +
+          '<div class="test">one</div>'
+        )
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test test-leave test-leave-active">two</div>' +
+          '<div class="test">one</div>'
+        )
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test test-leave-active">two</div>' +
+          '<div class="test">one</div>'
+        )
+      }).thenWaitFor(duration + 10).then(() => {
+        expect(vm.$el.innerHTML).toBe(
+          '<div class="test">one</div>'
+        )
+      }).then(done).then(done)
+    })
+
     it('normal elements with different keys, simultaneous', done => {
       const vm = new Vue({
         template: `<div>
@@ -263,5 +329,19 @@ if (!isIE9) {
         )
       }).then(done)
     })
+
+    it('warn invaid mode', () => {
+      new Vue({
+        template: '<transition mode="foo"><div>123</div></transition>'
+      }).$mount()
+      expect('invalid <transition> mode: foo').toHaveBeenWarned()
+    })
+
+    it('warn usage on non element/component', () => {
+      new Vue({
+        template: '<transition mode="foo">foo</transition>'
+      }).$mount()
+      expect('<transition> can only be used on elements or components, not text nodes.').toHaveBeenWarned()
+    })
   })
 }