浏览代码

simplify block instance logic, more tests for v-repeat

Evan You 11 年之前
父节点
当前提交
2e6dcf06bc

+ 1 - 18
src/compile/transclude.js

@@ -19,29 +19,13 @@ module.exports = function transclude (el, options) {
   if (el.tagName === 'TEMPLATE') {
     el = templateParser.parse(el)
   }
-  if (el instanceof DocumentFragment) {
-    return transcludeBlock(el)
-  } else if (options.template) {
+  if (options.template) {
     return transcludeTemplate(el, options)
   } else {
     return el
   }
 }
 
-/**
- * Mark a block fragment that manages a group of nodes
- * instead of one element. The group is denoted by
- * a starting node and an ending node.
- *
- * @param {DocumentFragment} frag
- */
-
-function transcludeBlock (frag) {
-  _.prepend(document.createComment('v-block-start'), frag)
-  frag.appendChild(document.createComment('v-block-end'))
-  return frag
-}
-
 /**
  * Process the template option.
  * If the replace option is true this will swap the $el.
@@ -60,7 +44,6 @@ function transcludeTemplate (el, options) {
     collectRawContent(el)
     if (options.replace) {
       if (frag.childNodes.length > 1) {
-        transcludeBlock(frag)
         transcludeContent(_.toArray(frag.childNodes))
         return frag
       } else {

+ 13 - 26
src/directives/repeat.js

@@ -30,6 +30,10 @@ module.exports = {
     // setup ref node
     this.ref = document.createComment('v-repeat')
     _.replace(this.el, this.ref)
+    // check if this is a block repeat
+    this.template = this.el.tagName === 'TEMPLATE'
+      ? templateParser.parse(this.el, true)
+      : this.el
     // check other directives that need to be handled
     // at v-repeat level
     this.checkIf()
@@ -38,10 +42,6 @@ module.exports = {
     this.checkComponent()
     // cache for primitive value instances
     this.cache = Object.create(null)
-    // check if this is a block repeat
-    if (this.el.tagName === 'TEMPLATE') {
-      this.el = templateParser.parse(this.el)
-    }
   },
 
   /**
@@ -100,7 +100,7 @@ module.exports = {
     if (!id) {
       this.Ctor = _.Vue // default constructor
       this.inherit = true // inline repeats should inherit
-      this._linker = compile(this.el, this.vm.$options)
+      this._linker = compile(this.template, this.vm.$options)
     } else {
       var tokens = textParser.parse(id)
       if (!tokens) { // static component
@@ -114,19 +114,13 @@ module.exports = {
             {},
             { $parent: this.vm }
           )
-          this.el = transclude(this.el, merged)
-          this._linker = compile(this.el, merged)
+          this.template = transclude(this.template, merged)
+          this._linker = compile(this.template, merged)
         }
-      } else if (tokens.length === 1) {
-        // to be resolved later
-        this.CtorExp = tokens[0].value
       } else {
-        _.warn(
-          'Invalid attribute binding: "' +
-           'component="' + id + '"' +
-          '\nDon\'t mix binding tags with plain text ' +
-          'in attribute bindings.'
-        )
+        // to be resolved later
+        var ctorExp = textParser.tokensToExp(tokens)
+        this.ctorGetter = expParser.parse(ctorExp).get
       }
     }
   },
@@ -139,13 +133,7 @@ module.exports = {
    */
 
   update: function (data) {
-    if (data && !_.isArray(data)) {
-      _.warn(
-        'Invalid value for v-repeat:' + data +
-        '\nExpects Object or Array.'
-      )
-      return
-    }
+    if (!data) return
     this.converted = data && data._converted
     this.vms = this.diff(data || [], this.vms)
     // update v-ref
@@ -288,7 +276,7 @@ module.exports = {
     // resolve constructor
     var Ctor = this.Ctor || this.resolveCtor(data)
     var vm = this.vm.$addChild({
-      el: this.el.cloneNode(true),
+      el: this.template.cloneNode(true),
       _linker: this._linker,
       _meta: meta,
       data: data,
@@ -309,14 +297,13 @@ module.exports = {
    */
 
   resolveCtor: function (data) {
-    var getter = expParser.parse(this.CtorExp).get
     var context = Object.create(this.vm)
     for (var key in data) {
       // use _.define to avoid accidentally
       // overwriting scope properties
       _.define(context, key, data[key])
     }
-    var id = getter(context)
+    var id = this.ctorGetter.call(context, context)
     var Ctor = this.vm.$options.components[id]
     _.assertAsset(Ctor, 'component', id)
     return Ctor

+ 1 - 1
src/parse/text.js

@@ -130,7 +130,7 @@ exports.tokensToExp = function (tokens, vm) {
 
 function formatToken (token, vm) {
   return token.tag
-    ? token.oneTime
+    ? vm && token.oneTime
       ? '"' + vm.$get(token.value) + '"'
       : token.value
     : '"' + token.value + '"'

+ 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(6)
+        expect(parent.childNodes.length).toBe(4)
         expect(parent.childNodes[2]).toBe(vm2.$el)
-        expect(parent.childNodes[3].tagName).toBe('P')
-        expect(parent.childNodes[4].tagName).toBe('SPAN')
-        expect(parent.lastChild).toBe(vm2._blockEnd)
+        expect(parent.childNodes[2].tagName).toBe('P')
+        expect(parent.childNodes[3].tagName).toBe('SPAN')
+        expect(parent.childNodes[3]).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(6)
+        expect(parent.childNodes.length).toBe(4)
         expect(parent.childNodes[0]).toBe(vm2.$el)
-        expect(parent.childNodes[1].tagName).toBe('P')
-        expect(parent.childNodes[2].tagName).toBe('SPAN')
-        expect(parent.childNodes[3]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[0].tagName).toBe('P')
+        expect(parent.childNodes[1].tagName).toBe('SPAN')
+        expect(parent.childNodes[1]).toBe(vm2._blockEnd)
         expect(spy.calls.count()).toBe(1)
         // empty
         vm2.$prependTo(empty, spy)
-        expect(empty.childNodes.length).toBe(4)
+        expect(empty.childNodes.length).toBe(2)
         expect(empty.childNodes[0]).toBe(vm2.$el)
-        expect(empty.childNodes[1].tagName).toBe('P')
-        expect(empty.childNodes[2].tagName).toBe('SPAN')
-        expect(empty.childNodes[3]).toBe(vm2._blockEnd)
+        expect(empty.childNodes[0].tagName).toBe('P')
+        expect(empty.childNodes[1].tagName).toBe('SPAN')
+        expect(empty.childNodes[1]).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(6)
+        expect(parent.childNodes.length).toBe(4)
         expect(parent.childNodes[1]).toBe(vm2.$el)
-        expect(parent.childNodes[2].tagName).toBe('P')
-        expect(parent.childNodes[3].tagName).toBe('SPAN')
-        expect(parent.childNodes[4]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[1].tagName).toBe('P')
+        expect(parent.childNodes[2].tagName).toBe('SPAN')
+        expect(parent.childNodes[2]).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(6)
+        expect(parent.childNodes.length).toBe(4)
         expect(parent.childNodes[1]).toBe(vm2.$el)
-        expect(parent.childNodes[2].tagName).toBe('P')
-        expect(parent.childNodes[3].tagName).toBe('SPAN')
-        expect(parent.childNodes[4]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[1].tagName).toBe('P')
+        expect(parent.childNodes[2].tagName).toBe('SPAN')
+        expect(parent.childNodes[2]).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(6)
+        expect(parent.childNodes.length).toBe(4)
         expect(parent.childNodes[2]).toBe(vm2.$el)
-        expect(parent.childNodes[3].tagName).toBe('P')
-        expect(parent.childNodes[4].tagName).toBe('SPAN')
-        expect(parent.childNodes[5]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[2].tagName).toBe('P')
+        expect(parent.childNodes[3].tagName).toBe('SPAN')
+        expect(parent.childNodes[3]).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(6)
+        expect(parent.childNodes.length).toBe(4)
         expect(parent.childNodes[1]).toBe(vm2.$el)
-        expect(parent.childNodes[2].tagName).toBe('P')
-        expect(parent.childNodes[3].tagName).toBe('SPAN')
-        expect(parent.childNodes[4]).toBe(vm2._blockEnd)
+        expect(parent.childNodes[1].tagName).toBe('P')
+        expect(parent.childNodes[2].tagName).toBe('SPAN')
+        expect(parent.childNodes[2]).toBe(vm2._blockEnd)
         vm2.$remove(spy)
         expect(parent.childNodes.length).toBe(2)
         expect(parent.childNodes[0]).toBe(target)

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

@@ -85,7 +85,7 @@ if (_.inBrowser) {
         vm.$mount(frag)
         expect(vm.$el).toBe(vm._blockStart)
         expect(vm._blockFragment).toBe(frag)
-        expect(vm.$el.nextSibling.textContent).toBe('frag')
+        expect(vm.$el.textContent).toBe('frag')
       })
 
       it('replace fragment', function () {
@@ -97,8 +97,8 @@ if (_.inBrowser) {
         })
         vm.$mount(el)
         expect(vm.$el).not.toBe(el)
+        expect(vm.$el.textContent).toBe('hi!')
         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()

+ 3 - 8
test/unit/specs/compile/transclude_spec.js

@@ -46,20 +46,15 @@ if (_.inBrowser) {
       frag.appendChild(el)
       var res = transclude(frag, options)
       expect(res).toBe(frag)
-      expect(res.childNodes.length).toBe(3)
-      expect(res.firstChild.nodeType).toBe(8)
-      expect(res.lastChild.nodeType).toBe(8)
-      expect(res.childNodes[1]).toBe(el)
     })
 
     it('template element', function () {
       var tpl = document.createElement('template')
       tpl.innerHTML = '<div>123</div>'
       var res = transclude(tpl, options)
-      expect(res.childNodes.length).toBe(3)
-      expect(res.firstChild.nodeType).toBe(8)
-      expect(res.lastChild.nodeType).toBe(8)
-      expect(res.childNodes[1].textContent).toBe('123')
+      expect(res instanceof DocumentFragment).toBe(true)
+      expect(res.childNodes.length).toBe(1)
+      expect(res.childNodes[0].textContent).toBe('123')
     })
 
     it('content transclusion', function () {

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

@@ -57,7 +57,7 @@ if (_.inBrowser) {
           }
         }
       })
-      expect(el.innerHTML).toBe('<!--v-block-start--><p>123</p><p>234</p><!--v-block-end--><!--v-component-->')
+      expect(el.innerHTML).toBe('<p>123</p><p>234</p><!--v-component-->')
     })
 
     it('dynamic', function (done) {

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

@@ -51,7 +51,7 @@ if (_.inBrowser) {
       expect(vm._children).toBeNull()
       vm.test = true
       _.nextTick(function () {
-        expect(el.innerHTML).toBe('<!--v-block-start--><p>A</p><p>B</p><!--v-block-end--><!--v-if-->')
+        expect(el.innerHTML).toBe('<p>A</p><p>B</p><!--v-if-->')
         expect(vm._children.length).toBe(1)
         vm.test = false
         _.nextTick(function () {

+ 70 - 3
test/unit/specs/directives/repeat_spec.js

@@ -128,7 +128,67 @@ if (_.inBrowser) {
     })
 
     it('nested repeats', function () {
-      // body...
+      var vm = new Vue({
+        el: el,
+        data: {
+          items: [
+            { items: [{a:1}, {a:2}], a: 1 },
+            { items: [{a:3}, {a:4}], a: 2 }
+          ]
+        },
+        template: '<div v-repeat="items">' +
+            '<p v-repeat="items">{{$index}} {{a}} {{$parent.$index}} {{$parent.a}}</p>' +
+          '</div>'
+      })
+      expect(el.innerHTML).toBe(
+        '<div><p>0 1 0 1</p><p>1 2 0 1</p><!--v-repeat--></div>' +
+        '<div><p>0 3 1 2</p><p>1 4 1 2</p><!--v-repeat--></div>' +
+        '<!--v-repeat-->'
+      )
+    })
+
+    it('dynamic component type based on instance data', function () {
+      var vm = new Vue({
+        el: el,
+        template: '<div v-repeat="list" v-component="view-{{type}}"></div>',
+        data: {
+          list: [
+            { type: 'a' },
+            { type: 'b' },
+            { type: 'c' }
+          ]
+        },
+        components: {
+          'view-a': {
+            template: 'AAA'
+          },
+          'view-b': {
+            template: 'BBB'
+          },
+          'view-c': {
+            template: 'CCC'
+          }
+        }
+      })
+      expect(el.innerHTML).toBe('<div>AAA</div><div>BBB</div><div>CCC</div><!--v-repeat-->')
+    })
+
+    it('block repeat', function () {
+      var vm = new Vue({
+        el: el,
+        template: '<template v-repeat="list"><p>{{a}}</p><p>{{a + 1}}</p></template>',
+        data: {
+          list: [
+            { a: 1 },
+            { a: 2 },
+            { a: 3 }
+          ]
+        }
+      })
+      var markup = vm.list.map(function (item) {
+        return '<p>' + item.a + '</p><p>' + (item.a + 1) + '</p>'
+      }).join('')
+      expect(el.innerHTML).toBe(markup + '<!--v-repeat-->')
     })
 
     it('array filters', function () {
@@ -139,8 +199,15 @@ if (_.inBrowser) {
       // body...
     })
 
-    it('warn invalid type', function () {
-      // body...
+    it('warn invalid data type', function () {
+      var vm = new Vue({
+        el: el,
+        template: '<div v-repeat="items"></div>',
+        data: {
+          items: 1234
+        }
+      })
+      expect(_.warn).toHaveBeenCalled()
     })
 
   })