Переглянути джерело

big rewrite for block-handling

Evan You 11 роки тому
батько
коміт
056a0376f2

+ 1 - 1
examples/commits/app.js

@@ -1,5 +1,5 @@
 var apiURL = 'https://api.github.com/repos/yyx990803/vue/commits?per_page=3&sha='
-var isPhantom = true//navigator.userAgent.indexOf('PhantomJS') > -1
+var isPhantom = navigator.userAgent.indexOf('PhantomJS') > -1
 
 /**
  * Test mocks

+ 7 - 5
examples/todomvc/index.html

@@ -79,13 +79,15 @@
 
         <!-- testing/benchmark only -->
         <script>
-            if (navigator.userAgent.indexOf('PhantomJS') > -1) {
+            var isPhantom = navigator.userAgent.indexOf('PhantomJS') > -1
+            if (isPhantom) {
                 localStorage.clear()
+            } else {
+                var now = window.performance && window.performance.now
+                    ? function () { return window.performance.now() }
+                    : Date.now
+                var metrics = { beforeLoad: now() }
             }
-            var now = window.performance && window.performance.now
-                ? function () { return window.performance.now() }
-                : Date.now
-            var metrics = { beforeLoad: now() }
         </script>
         <!-- end testing/bench -->
 

+ 2 - 0
examples/todomvc/js/perf.js

@@ -1,4 +1,6 @@
 setTimeout(function () {
+
+    if (window.isPhantom) return
     
     // Initial load & render metrics
 

+ 2 - 2
src/compile/compile.js

@@ -381,8 +381,8 @@ function makeParamsLinkFn (params, options) {
 
 var terminalDirectives = [
   'repeat',
-  'component',
-  'if'
+  'if',
+  'component'
 ]
 
 function skip () {}

+ 7 - 4
src/compile/transclude.js

@@ -19,11 +19,14 @@ module.exports = function transclude (el, options) {
   if (el.tagName === 'TEMPLATE') {
     el = templateParser.parse(el)
   }
-  if (options.template) {
-    return transcludeTemplate(el, options)
-  } else {
-    return el
+  if (options && options.template) {
+    el = transcludeTemplate(el, options)
+  }
+  if (el instanceof DocumentFragment) {
+    _.prepend(document.createComment('v-start'), el)
+    el.appendChild(document.createComment('v-end'))
   }
+  return el
 }
 
 /**

+ 1 - 1
src/directive.js

@@ -158,7 +158,7 @@ p._teardown = function () {
     if (watcher && watcher.active) {
       watcher.removeCb(this._update)
       if (!watcher.active) {
-        this.vm._watchers[this.expression] = null
+        this.vm._watchers[this.raw] = null
       }
     }
     this._bound = false

+ 3 - 59
src/directives/component.js

@@ -1,5 +1,4 @@
 var _ = require('../util')
-var Watcher = require('../watcher')
 var templateParser = require('../parse/template')
 
 module.exports = {
@@ -7,19 +6,13 @@ module.exports = {
   isLiteral: true,
 
   /**
-   * Setup. Need to check a few possible permutations:
+   * Setup. Two possible usages:
    *
-   * - literal:
+   * - static:
    *   v-component="comp"
    *
    * - dynamic:
    *   v-component="{{currentView}}"
-   *
-   * - conditional:
-   *   v-component="comp" v-if="abc"
-   *
-   * - dynamic + conditional:
-   *   v-component="{{currentView}}" v-if="abc"
    */
 
   bind: function () {
@@ -27,8 +20,6 @@ module.exports = {
       // create a ref anchor
       this.ref = document.createComment('v-component')
       _.replace(this.el, this.ref)
-      // check v-if conditionals
-      this.checkIf()
       // check keep-alive options
       this.checkKeepAlive()
       // if static, build right now.
@@ -44,46 +35,6 @@ module.exports = {
     }
   },
 
-  /**
-   * Check if v-component is being used together with v-if.
-   * If yes, we created a watcher for the v-if value and
-   * react to its value change in `this.ifCallback`.
-   */
-
-  checkIf: function () {
-    var condition = _.attr(this.el, 'if')
-    if (condition !== null) {
-      var self = this
-      this.ifWatcher = new Watcher(
-        this.vm,
-        condition,
-        function (value) {
-          self.toggleIf(value)
-        }
-      )
-      this.active = this.ifWatcher.value
-    } else {
-      this.active = true
-    }
-  },
-
-  /**
-   * Callback when v-if value changes.
-   * Marks the active flag.
-   *
-   * @param {*} value
-   */
-
-  toggleIf: function (value) {
-    if (value) {
-      this.active = true
-      this.build()
-    } else {
-      this.active = false
-      this.unbuild(true)
-    }
-  },
-
   /**
    * Check if the "keep-alive" flag is present.
    * If yes, instead of destroying the active vm when
@@ -119,9 +70,6 @@ module.exports = {
    */
 
   build: function () {
-    if (!this.active) {
-      return
-    }
     if (this.keepAlive) {
       var vm = this.cache[this.ctorId]
       if (vm) {
@@ -178,16 +126,12 @@ module.exports = {
   /**
    * Unbind.
    * Make sure keepAlive is set to false so that the
-   * instance is always destroyed. Teardown v-if watcher
-   * if present.
+   * instance is always destroyed.
    */
 
   unbind: function () {
     this.keepAlive = false
     this.unbuild()
-    if (this.ifWatcher) {
-      this.ifWatcher.teardown()
-    }
   }
 
 }

+ 16 - 27
src/directives/if.js

@@ -8,12 +8,16 @@ module.exports = {
   bind: function () {
     var el = this.el
     if (!el.__vue__) {
-      this.ref = document.createComment('v-if')
-      _.replace(el, this.ref)
-      this.isBlock = el.tagName === 'TEMPLATE'
-      this.template = this.isBlock
-        ? templateParser.parse(el, true)
-        : el
+      this.start = document.createComment('v-if-start')
+      this.end = document.createComment('v-if-end')
+      _.replace(el, this.end)
+      _.before(this.start, this.end)
+      if (el.tagName === 'TEMPLATE') {
+        this.template = templateParser.parse(el, true)
+      } else {
+        this.template = document.createDocumentFragment()
+        this.template.appendChild(el)
+      }
       // compile the nested partial
       this.linker = compile(
         this.template,
@@ -40,28 +44,13 @@ module.exports = {
 
   insert: function () {
     var vm = this.vm
-    var el = templateParser.clone(this.template)
-    var ref = this.ref
-    var decompile = this.linker(vm, el)
-    if (this.isBlock) {
-      var blockStart = el.firstChild
-      this.decompile = function () {
-        decompile()
-        var node = blockStart
-        var next
-        while (node !== ref) {
-          next = node.nextSibling
-          transition.remove(node, vm)
-          node = next
-        }
-      }
-    } else {
-      this.decompile = function () {
-        decompile()
-        transition.remove(el, vm)
-      }
+    var frag = templateParser.clone(this.template)
+    var decompile = this.linker(vm, frag)
+    this.decompile = function () {
+      decompile()
+      transition.blockRemove(this.start, this.end, vm)
     }
-    transition.before(el, ref, vm)
+    transition.blockAppend(frag, this.end, vm)
   },
 
   teardown: function () {

+ 15 - 28
src/directives/partial.js

@@ -1,5 +1,6 @@
 var _ = require('../util')
 var templateParser = require('../parse/template')
+var transition = require('../transition')
 
 module.exports = {
 
@@ -7,16 +8,20 @@ module.exports = {
 
   bind: function () {
     var el = this.el
+    this.start = document.createComment('v-partial-start')
+    this.end = document.createComment('v-partial-end')
     if (el.nodeType !== 8) {
       el.innerHTML = ''
     }
-    if (el.tagName === 'TEMPLATE') {
-      this.el = document.createComment('v-partial')
-      _.replace(el, this.el)
+    if (el.tagName === 'TEMPLATE' || el.nodeType === 8) {
+      _.replace(el, this.end)
+    } else {
+      el.appendChild(this.end)
     }
+    _.before(this.start, this.end)
     if (!this._isDynamicLiteral) {
       this.compile(this.expression)
-    } 
+    }
   },
 
   update: function (id) {
@@ -30,32 +35,14 @@ module.exports = {
     if (!partial) {
       return
     }
-    partial = templateParser.parse(partial, true)
-    var el = this.el
     var vm = this.vm
-    var decompile = vm.$compile(partial)
-    if (el.nodeType === 8) {
-      // comment ref node means inline partial
-      var blockStart = partial.firstChild
-      this.decompile = function () {
-        decompile()
-        var node = blockStart
-        var next
-        while (node !== el) {
-          next = node.nextSibling
-          _.remove(node)
-          node = next
-        }
-      }
-      _.before(partial, el)
-    } else {
-      // just append to container
-      this.decompile = function () {
-        decompile()
-        el.innerHTML = ''
-      }
-      el.appendChild(partial)
+    var frag = templateParser.parse(partial, true)
+    var decompile = vm.$compile(frag)
+    this.decompile = function () {
+      decompile()
+      transition.blockRemove(this.start, this.end, vm)
     }
+    transition.blockAppend(frag, this.end, vm)
   },
 
   teardown: function () {

+ 6 - 2
src/directives/repeat.js

@@ -95,14 +95,18 @@ module.exports = {
 
   checkComponent: function () {
     var id = _.attr(this.el, 'component')
+    var options = this.vm.$options
     if (!id) {
       this.Ctor = _.Vue // default constructor
       this.inherit = true // inline repeats should inherit
-      this._linker = compile(this.template, this.vm.$options)
+      // important: transclude with no options, just
+      // to ensure block start and block end
+      this.template = transclude(this.template)
+      this._linker = compile(this.template, options)
     } else {
       var tokens = textParser.parse(id)
       if (!tokens) { // static component
-        var Ctor = this.Ctor = this.vm.$options.components[id]
+        var Ctor = this.Ctor = options.components[id]
         _.assertAsset(Ctor, 'component', id)
         if (Ctor) {
           // merge an empty object with owner vm as parent

+ 17 - 0
src/transition/index.js

@@ -62,6 +62,23 @@ exports.removeThenAppend = function (el, target, vm, cb) {
   }, vm, cb)
 }
 
+exports.blockAppend = function (block, target, vm) {
+  var nodes = _.toArray(block.childNodes)
+  for (var i = 0, l = nodes.length; i < l; i++) {
+    exports.before(nodes[i], target, vm)
+  }
+}
+
+exports.blockRemove = function (start, end, vm) {
+  var node = start.nextSibling
+  var next
+  while (node !== end) {
+    next = node.nextSibling
+    exports.remove(node, vm)
+    node = next
+  }
+}
+
 /**
  * Apply transitions with an operation callback.
  *

+ 28 - 28
test/unit/specs/api/dom_spec.js

@@ -40,11 +40,11 @@ if (_.inBrowser) {
 
       it('block instance', function () {
         vm2.$appendTo(parent, spy)
-        expect(parent.childNodes.length).toBe(4)
+        expect(parent.childNodes.length).toBe(6)
         expect(parent.childNodes[2]).toBe(vm2.$el)
-        expect(parent.childNodes[2].tagName).toBe('P')
-        expect(parent.childNodes[3].tagName).toBe('SPAN')
-        expect(parent.childNodes[3]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[3].tagName).toBe('P')
+        expect(parent.childNodes[4].tagName).toBe('SPAN')
+        expect(parent.childNodes[5]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(1)
       })
 
@@ -65,19 +65,19 @@ if (_.inBrowser) {
 
       it('block instance', function () {
         vm2.$prependTo(parent, spy)
-        expect(parent.childNodes.length).toBe(4)
+        expect(parent.childNodes.length).toBe(6)
         expect(parent.childNodes[0]).toBe(vm2.$el)
-        expect(parent.childNodes[0].tagName).toBe('P')
-        expect(parent.childNodes[1].tagName).toBe('SPAN')
-        expect(parent.childNodes[1]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[1].tagName).toBe('P')
+        expect(parent.childNodes[2].tagName).toBe('SPAN')
+        expect(parent.childNodes[3]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(1)
         // empty
         vm2.$prependTo(empty, spy)
-        expect(empty.childNodes.length).toBe(2)
+        expect(empty.childNodes.length).toBe(4)
         expect(empty.childNodes[0]).toBe(vm2.$el)
-        expect(empty.childNodes[0].tagName).toBe('P')
-        expect(empty.childNodes[1].tagName).toBe('SPAN')
-        expect(empty.childNodes[1]).toBe(vm2._blockEnd)
+        expect(empty.childNodes[1].tagName).toBe('P')
+        expect(empty.childNodes[2].tagName).toBe('SPAN')
+        expect(empty.childNodes[3]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(2)
       })
 
@@ -94,11 +94,11 @@ if (_.inBrowser) {
 
       it('block instance', function () {
         vm2.$before(sibling, spy)
-        expect(parent.childNodes.length).toBe(4)
+        expect(parent.childNodes.length).toBe(6)
         expect(parent.childNodes[1]).toBe(vm2.$el)
-        expect(parent.childNodes[1].tagName).toBe('P')
-        expect(parent.childNodes[2].tagName).toBe('SPAN')
-        expect(parent.childNodes[2]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[2].tagName).toBe('P')
+        expect(parent.childNodes[3].tagName).toBe('SPAN')
+        expect(parent.childNodes[4]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(1)
       })
 
@@ -122,21 +122,21 @@ if (_.inBrowser) {
 
       it('block instance', function () {
         vm2.$after(target, spy)
-        expect(parent.childNodes.length).toBe(4)
+        expect(parent.childNodes.length).toBe(6)
         expect(parent.childNodes[1]).toBe(vm2.$el)
-        expect(parent.childNodes[1].tagName).toBe('P')
-        expect(parent.childNodes[2].tagName).toBe('SPAN')
-        expect(parent.childNodes[2]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[2].tagName).toBe('P')
+        expect(parent.childNodes[3].tagName).toBe('SPAN')
+        expect(parent.childNodes[4]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(1)
       })
 
       it('block instance no next sibling', function () {
         vm2.$after(sibling, spy)
-        expect(parent.childNodes.length).toBe(4)
+        expect(parent.childNodes.length).toBe(6)
         expect(parent.childNodes[2]).toBe(vm2.$el)
-        expect(parent.childNodes[2].tagName).toBe('P')
-        expect(parent.childNodes[3].tagName).toBe('SPAN')
-        expect(parent.childNodes[3]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[3].tagName).toBe('P')
+        expect(parent.childNodes[4].tagName).toBe('SPAN')
+        expect(parent.childNodes[5]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(1)
       })
 
@@ -157,11 +157,11 @@ if (_.inBrowser) {
 
       it('block instance', function () {
         vm2.$before(sibling)
-        expect(parent.childNodes.length).toBe(4)
+        expect(parent.childNodes.length).toBe(6)
         expect(parent.childNodes[1]).toBe(vm2.$el)
-        expect(parent.childNodes[1].tagName).toBe('P')
-        expect(parent.childNodes[2].tagName).toBe('SPAN')
-        expect(parent.childNodes[2]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[2].tagName).toBe('P')
+        expect(parent.childNodes[3].tagName).toBe('SPAN')
+        expect(parent.childNodes[4]).toBe(vm2._blockEnd)
         vm2.$remove(spy)
         expect(parent.childNodes.length).toBe(2)
         expect(parent.childNodes[0]).toBe(target)

+ 3 - 3
test/unit/specs/api/lifecycle_spec.js

@@ -98,7 +98,7 @@ if (_.inBrowser) {
         vm.$mount(frag)
         expect(vm.$el).toBe(vm._blockStart)
         expect(vm._blockFragment).toBe(frag)
-        expect(vm.$el.textContent).toBe('frag')
+        expect(vm.$el.nextSibling.textContent).toBe('frag')
       })
 
       it('replace fragment', function () {
@@ -109,9 +109,9 @@ if (_.inBrowser) {
           template: '<div>{{test}}</div><div>{{test}}</div>'
         })
         vm.$mount(el)
-        expect(vm.$el).not.toBe(el)
-        expect(vm.$el.textContent).toBe('hi!')
+        expect(vm.$el.nextSibling).not.toBe(el)
         expect(vm.$el.nextSibling.textContent).toBe('hi!')
+        expect(vm.$el.nextSibling.nextSibling.textContent).toBe('hi!')
         expect(document.body.contains(el)).toBe(false)
         expect(document.body.lastChild).toBe(vm._blockEnd)
         vm.$remove()

+ 11 - 5
test/unit/specs/compile/transclude_spec.js

@@ -46,6 +46,10 @@ if (_.inBrowser) {
       frag.appendChild(el)
       var res = transclude(frag, options)
       expect(res).toBe(frag)
+      expect(res.childNodes.length).toBe(3)
+      expect(res.childNodes[0].nodeType).toBe(8)
+      expect(res.childNodes[1]).toBe(el)
+      expect(res.childNodes[2].nodeType).toBe(8)
     })
 
     it('template element', function () {
@@ -53,8 +57,10 @@ if (_.inBrowser) {
       tpl.innerHTML = '<div>123</div>'
       var res = transclude(tpl, options)
       expect(res instanceof DocumentFragment).toBe(true)
-      expect(res.childNodes.length).toBe(1)
-      expect(res.childNodes[0].textContent).toBe('123')
+      expect(res.childNodes.length).toBe(3)
+      expect(res.childNodes[0].nodeType).toBe(8)
+      expect(res.childNodes[1].textContent).toBe('123')
+      expect(res.childNodes[2].nodeType).toBe(8)
     })
 
     it('content transclusion', function () {
@@ -89,9 +95,9 @@ if (_.inBrowser) {
       options.template = '<div></div><content select="p"></content><content select="span"></content>'
       options.replace = true
       var res = transclude(el, options)
-      expect(res.firstChild.tagName).toBe('DIV')
-      expect(res.childNodes[1].tagName).toBe('P')
-      expect(res.childNodes[2].tagName).toBe('SPAN')
+      expect(res.childNodes[1].tagName).toBe('DIV')
+      expect(res.childNodes[2].tagName).toBe('P')
+      expect(res.childNodes[3].tagName).toBe('SPAN')
     })
 
   })

+ 1 - 77
test/unit/specs/directives/component_spec.js

@@ -57,7 +57,7 @@ if (_.inBrowser) {
           }
         }
       })
-      expect(el.innerHTML).toBe('<p>123</p><p>234</p><!--v-component-->')
+      expect(el.innerHTML).toBe('<!--v-start--><p>123</p><p>234</p><!--v-end--><!--v-component-->')
     })
 
     it('dynamic', function (done) {
@@ -90,82 +90,6 @@ if (_.inBrowser) {
       })
     })
 
-    it('static v-if', function (done) {
-      var vm = new Vue({
-        el: el,
-        data: { ok: false },
-        template: '<div v-component="test" v-if="ok"></div>',
-        components: {
-          test: {
-            data: function () {
-              return { a: 123 }
-            },
-            template: '{{a}}'
-          }
-        }
-      })
-      expect(el.innerHTML).toBe('<!--v-component-->')
-      expect(vm._children).toBeNull()
-      vm.ok = true
-      _.nextTick(function () {
-        expect(el.innerHTML).toBe('<div>123</div><!--v-component-->')
-        expect(vm._children.length).toBe(1)
-        vm.ok = false
-        _.nextTick(function () {
-          expect(el.innerHTML).toBe('<!--v-component-->')
-          expect(vm._children.length).toBe(0)
-          done()
-        })
-      })
-    })
-
-    it('dynamic v-if', function (done) {
-      var vm = new Vue({
-        el: el,
-        data: {
-          ok: false,
-          view: 'a'
-        },
-        template: '<div v-component="{{view}}" v-if="ok"></div>',
-        components: {
-          a: {
-            template: 'AAA'
-          },
-          b: {
-            template: 'BBB'
-          }
-        }
-      })
-      expect(el.innerHTML).toBe('<!--v-component-->')
-      expect(vm._children).toBeNull()
-      // toggle if with lazy instantiation
-      vm.ok = true
-      _.nextTick(function () {
-        expect(el.innerHTML).toBe('<div>AAA</div><!--v-component-->')
-        expect(vm._children.length).toBe(1)
-        // switch view when if=true
-        vm.view = 'b'
-        _.nextTick(function () {
-          expect(el.innerHTML).toBe('<div>BBB</div><!--v-component-->')
-          expect(vm._children.length).toBe(1)
-          // toggle if when already instantiated
-          vm.ok = false
-          _.nextTick(function () {
-            expect(el.innerHTML).toBe('<!--v-component-->')
-            expect(vm._children.length).toBe(0)
-            // toggle if and switch view at the same time
-            vm.view = 'a'
-            vm.ok = true
-            _.nextTick(function () {
-              expect(el.innerHTML).toBe('<div>AAA</div><!--v-component-->')
-              expect(vm._children.length).toBe(1)
-              done()
-            })
-          })
-        })
-      })
-    })
-
     it('keep-alive', function (done) {
       var spyA = jasmine.createSpy()
       var spyB = jasmine.createSpy()

+ 87 - 7
test/unit/specs/directives/if_spec.js

@@ -10,6 +10,10 @@ if (_.inBrowser) {
       spyOn(_, 'warn')
     })
 
+    function wrap (content) {
+      return '<!--v-if-start-->' + content + '<!--v-if-end-->'
+    }
+
     it('normal', function (done) {
       var vm = new Vue({
         el: el,
@@ -23,19 +27,19 @@ if (_.inBrowser) {
         }
       })
       // lazy instantitation
-      expect(el.innerHTML).toBe('<!--v-if-->')
+      expect(el.innerHTML).toBe(wrap(''))
       expect(vm._children).toBeNull()
       vm.test = true
       _.nextTick(function () {
-        expect(el.innerHTML).toBe('<div><div>A</div><!--v-component--></div><!--v-if-->')
+        expect(el.innerHTML).toBe(wrap('<div><div>A</div><!--v-component--></div>'))
         expect(vm._children.length).toBe(1)
         vm.test = false
         _.nextTick(function () {
-          expect(el.innerHTML).toBe('<!--v-if-->')
+          expect(el.innerHTML).toBe(wrap(''))
           expect(vm._children.length).toBe(0)
           vm.test = true
           _.nextTick(function () {
-            expect(el.innerHTML).toBe('<div><div>A</div><!--v-component--></div><!--v-if-->')
+            expect(el.innerHTML).toBe(wrap('<div><div>A</div><!--v-component--></div>'))
             expect(vm._children.length).toBe(1)
             var child = vm._children[0]
             vm.$destroy()
@@ -53,18 +57,94 @@ if (_.inBrowser) {
         template: '<template v-if="test"><p>{{a}}</p><p>{{b}}</p></template>'
       })
       // lazy instantitation
-      expect(el.innerHTML).toBe('<!--v-if-->')
+      expect(el.innerHTML).toBe(wrap(''))
       vm.test = true
       _.nextTick(function () {
-        expect(el.innerHTML).toBe('<p>A</p><p>B</p><!--v-if-->')
+        expect(el.innerHTML).toBe(wrap('<p>A</p><p>B</p>'))
         vm.test = false
         _.nextTick(function () {
-          expect(el.innerHTML).toBe('<!--v-if-->')
+          expect(el.innerHTML).toBe(wrap(''))
           done()
         })
       })
     })
 
+    it('v-if + v-component', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: { ok: false },
+        template: '<div v-component="test" v-if="ok"></div>',
+        components: {
+          test: {
+            data: function () {
+              return { a: 123 }
+            },
+            template: '{{a}}'
+          }
+        }
+      })
+      expect(el.innerHTML).toBe(wrap(''))
+      expect(vm._children).toBeNull()
+      vm.ok = true
+      _.nextTick(function () {
+        expect(el.innerHTML).toBe(wrap('<div>123</div><!--v-component-->'))
+        expect(vm._children.length).toBe(1)
+        vm.ok = false
+        _.nextTick(function () {
+          expect(el.innerHTML).toBe(wrap(''))
+          expect(vm._children.length).toBe(0)
+          done()
+        })
+      })
+    })
+
+    it('v-if + dynamic component', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          ok: false,
+          view: 'a'
+        },
+        template: '<div v-component="{{view}}" v-if="ok"></div>',
+        components: {
+          a: {
+            template: 'AAA'
+          },
+          b: {
+            template: 'BBB'
+          }
+        }
+      })
+      expect(el.innerHTML).toBe(wrap(''))
+      expect(vm._children).toBeNull()
+      // toggle if with lazy instantiation
+      vm.ok = true
+      _.nextTick(function () {
+        expect(el.innerHTML).toBe(wrap('<div>AAA</div><!--v-component-->'))
+        expect(vm._children.length).toBe(1)
+        // switch view when if=true
+        vm.view = 'b'
+        _.nextTick(function () {
+          expect(el.innerHTML).toBe(wrap('<div>BBB</div><!--v-component-->'))
+          expect(vm._children.length).toBe(1)
+          // toggle if when already instantiated
+          vm.ok = false
+          _.nextTick(function () {
+            expect(el.innerHTML).toBe(wrap(''))
+            expect(vm._children.length).toBe(0)
+            // toggle if and switch view at the same time
+            vm.view = 'a'
+            vm.ok = true
+            _.nextTick(function () {
+              expect(el.innerHTML).toBe(wrap('<div>AAA</div><!--v-component-->'))
+              expect(vm._children.length).toBe(1)
+              done()
+            })
+          })
+        })
+      })
+    })
+
     it('invalid warn', function () {
       el.setAttribute('v-if', 'test')
       var vm = new Vue({

+ 12 - 8
test/unit/specs/directives/partial_spec.js

@@ -10,6 +10,10 @@ if (_.inBrowser) {
       spyOn(_, 'warn')
     })
 
+    function wrap (content) {
+      return '<!--v-partial-start-->' + content + '<!--v-partial-end-->'
+    }
+
     it('element', function () {
       var vm = new Vue({
         el: el,
@@ -22,7 +26,7 @@ if (_.inBrowser) {
           b: 'B'
         }
       })
-      expect(el.innerHTML).toBe('<div><p>A</p><p>B</p></div>')
+      expect(el.innerHTML).toBe('<div>' + wrap('<p>A</p><p>B</p>') + '</div>')
     })
 
     it('inline', function () {
@@ -37,7 +41,7 @@ if (_.inBrowser) {
           b: 'B'
         }
       })
-      expect(el.innerHTML).toBe('<div><p>A</p><p>B</p><!--v-partial--></div>')
+      expect(el.innerHTML).toBe('<div>' + wrap('<p>A</p><p>B</p>') + '</div>')
     })
 
     it('not found', function () {
@@ -45,7 +49,7 @@ if (_.inBrowser) {
         el: el,
         template: '<div>{{>test}}</div>'
       })
-      expect(el.innerHTML).toBe('<div><!--v-partial--></div>')
+      expect(el.innerHTML).toBe('<div>' + wrap('') + '</div>')
     })
 
     it('dynamic partial', function (done) {
@@ -69,16 +73,16 @@ if (_.inBrowser) {
           }
         }
       })
-      expect(el.firstChild.innerHTML).toBe('hello')
+      expect(el.firstChild.innerHTML).toBe(wrap('hello'))
       expect(vm._directives.length).toBe(2)
       vm.partial = 'p2'
       _.nextTick(function () {
-        expect(el.firstChild.innerHTML).toBe('<div>123</div><!--v-component-->')
+        expect(el.firstChild.innerHTML).toBe(wrap('<div>123</div><!--v-component-->'))
         expect(vm._directives.length).toBe(2)
         expect(vm._children.length).toBe(1)
         vm.partial = 'p1'
         _.nextTick(function () {
-          expect(el.firstChild.innerHTML).toBe('hello')
+          expect(el.firstChild.innerHTML).toBe(wrap('hello'))
           expect(vm._directives.length).toBe(2)
           expect(vm._children.length).toBe(0)
           done()
@@ -99,10 +103,10 @@ if (_.inBrowser) {
           p2: '<span>1</span><a>2</a>'
         }
       })
-      expect(el.innerHTML).toBe('a b c<!--v-partial-->')
+      expect(el.innerHTML).toBe(wrap('a b c'))
       vm.test = 'p2'
       _.nextTick(function () {
-        expect(el.innerHTML).toBe('<span>1</span><a>2</a><!--v-partial-->')
+        expect(el.innerHTML).toBe(wrap('<span>1</span><a>2</a>'))
         done()
       })
     })

+ 1 - 1
test/unit/specs/directives/repeat_spec.js

@@ -220,7 +220,7 @@ if (_.inBrowser) {
         }
       })
       var markup = vm.list.map(function (item) {
-        return '<p>' + item.a + '</p><p>' + (item.a + 1) + '</p>'
+        return '<!--v-start--><p>' + item.a + '</p><p>' + (item.a + 1) + '</p><!--v-end-->'
       }).join('')
       expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
     })