Просмотр исходного кода

tests for oneway and onetime props

Evan You 11 лет назад
Родитель
Сommit
47d151b561
3 измененных файлов с 140 добавлено и 40 удалено
  1. 19 17
      src/compiler/compile.js
  2. 10 22
      test/unit/specs/compiler/compile_spec.js
  3. 111 1
      test/unit/specs/directives/prop_spec.js

+ 19 - 17
src/compiler/compile.js

@@ -401,13 +401,13 @@ function makeChildLinkFn (linkFns) {
 
 var dataAttrRE = /^data-/
 var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
-var literalValueRE = /^true|false|\d+$/
+var literalValueRE = /^(true|false|\d+)$/
 var identRE = require('../parsers/path').identRE
 
 function compileProps (el, attrs, propNames) {
   var props = []
   var i = propNames.length
-  var name, value, path, prop, settable, single
+  var name, value, path, prop, settable, literal, single
   while (i--) {
     name = propNames[i]
     // props could contain dashes, which will be
@@ -449,22 +449,24 @@ function compileProps (el, attrs, propNames) {
         prop.parentPath = textParser.tokensToExp(tokens)
         // check prop binding type.
         single = tokens.length === 1
-        settable =
-          settablePathRE.test(prop.parentPath) &&
-          !literalValueRE.test(prop.parentPath)
+        literal = literalValueRE.test(prop.parentPath)
         // one time: {{* prop}}
         prop.oneTime =
-          !single ||
-          !settable ||
-          tokens[0].oneTime
-        // one way down: {{> prop}}
-        prop.oneWayDown =
-          single &&
-          tokens[0].oneWay === 62 // >
-        // one way up: {{< prop}}
-        prop.oneWayUp =
-          tokens[0].oneWay === 60 && // <
-          settable
+          literal ||
+          (single && tokens[0].oneTime)
+        // check one-way bindings
+        if (!prop.oneTime) {
+          settable = !literal && settablePathRE.test(prop.parentPath)
+          // one way down: {{> prop}}
+          prop.oneWayDown =
+            !settable ||
+            (single && tokens[0].oneWay === 60) // <
+          // one way up: {{< prop}}
+          prop.oneWayUp =
+            single &&
+            settable &&
+            tokens[0].oneWay === 62 // >
+        }
       }
       props.push(prop)
     }
@@ -488,7 +490,7 @@ function makePropsLinkFn (props) {
       path = prop.path
       if (prop.dynamic) {
         if (vm.$parent) {
-          if (prop.onetime) {
+          if (prop.oneTime) {
             // one time binding
             vm.$set(path, vm.$parent.$get(prop.parentPath))
           } else {

+ 10 - 22
test/unit/specs/compiler/compile_spec.js

@@ -23,7 +23,7 @@ if (_.inBrowser) {
             _teardown: directiveTeardown
           })
         },
-        $set: jasmine.createSpy(),
+        $set: jasmine.createSpy('vm.$set'),
         $eval: function (value) {
           return data[value]
         },
@@ -167,14 +167,14 @@ if (_.inBrowser) {
       el.setAttribute('some-other-attr', '2')
       el.setAttribute('multiple-attrs', 'a {{b}} c')
       el.setAttribute('onetime', '{{*a}}')
-      el.setAttribute('oneway-up', '{{<a}}')
-      el.setAttribute('oneway-down', '{{>a}}')
+      el.setAttribute('oneway-up', '{{>a}}')
+      el.setAttribute('oneway-down', '{{<a}}')
       el.setAttribute('with-filter', '{{a | filter}}')
       el.setAttribute('boolean-literal', '{{true}}')
       transclude(el, options)
       compiler.compileAndLinkRoot(vm, el, options)
       // should skip literals and one-time bindings
-      expect(vm._bindDir.calls.count()).toBe(7)
+      expect(vm._bindDir.calls.count()).toBe(5)
       // data-some-attr
       var args = vm._bindDir.calls.argsFor(0)
       expect(args[0]).toBe('prop')
@@ -189,16 +189,8 @@ if (_.inBrowser) {
       expect(args[2].path).toBe('multipleAttrs')
       expect(args[2].parentPath).toBe('"a "+(b)+" c"')
       expect(args[3]).toBe(def)
-      // one time
-      args = vm._bindDir.calls.argsFor(2)
-      expect(args[0]).toBe('prop')
-      expect(args[1]).toBe(null)
-      expect(args[2].path).toBe('onetime')
-      expect(args[2].oneTime).toBe(true)
-      expect(args[2].parentPath).toBe('a')
-      expect(args[3]).toBe(def)
       // one way up
-      args = vm._bindDir.calls.argsFor(3)
+      args = vm._bindDir.calls.argsFor(2)
       expect(args[0]).toBe('prop')
       expect(args[1]).toBe(null)
       expect(args[2].path).toBe('onewayUp')
@@ -207,7 +199,7 @@ if (_.inBrowser) {
       expect(args[2].parentPath).toBe('a')
       expect(args[3]).toBe(def)
       // one way down
-      args = vm._bindDir.calls.argsFor(4)
+      args = vm._bindDir.calls.argsFor(3)
       expect(args[0]).toBe('prop')
       expect(args[1]).toBe(null)
       expect(args[2].path).toBe('onewayDown')
@@ -216,25 +208,21 @@ if (_.inBrowser) {
       expect(args[2].parentPath).toBe('a')
       expect(args[3]).toBe(def)
       // with-filter
-      args = vm._bindDir.calls.argsFor(5)
+      args = vm._bindDir.calls.argsFor(4)
       expect(args[0]).toBe('prop')
       expect(args[1]).toBe(null)
       expect(args[2].path).toBe('withFilter')
       expect(args[2].parentPath).toBe('this._applyFilters(a,null,[{"name":"filter"}],false)')
       expect(args[3]).toBe(def)
-      // boolean-literal
-      args = vm._bindDir.calls.argsFor(6)
-      expect(args[0]).toBe('prop')
-      expect(args[1]).toBe(null)
-      expect(args[2].path).toBe('booleanLiteral')
-      expect(args[2].parentPath).toBe('true')
-      expect(args[2].oneTime).toBe(true)
       // camelCase should've warn
       expect(hasWarned(_, 'using camelCase')).toBe(true)
       // literal and one time should've called vm.$set
       // and numbers should be casted
+      expect(vm.$set.calls.count()).toBe(4)
       expect(vm.$set).toHaveBeenCalledWith('a', 1)
       expect(vm.$set).toHaveBeenCalledWith('someOtherAttr', 2)
+      expect(vm.$set).toHaveBeenCalledWith('onetime', 'from parent: a')
+      expect(vm.$set).toHaveBeenCalledWith('booleanLiteral', 'from parent: true')
     })
 
     it('props on root instance', function () {

+ 111 - 1
test/unit/specs/directives/prop_spec.js

@@ -82,6 +82,116 @@ if (_.inBrowser) {
       })
     })
 
+    it('auto one-way binding for non-settable parent path', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          b: 'B'
+        },
+        template: '<test b="{{b + \'B\'}}" v-ref="child"></test>',
+        components: {
+          test: {
+            props: ['b'],
+            template: '{{b}}'
+          }
+        }
+      })
+      expect(el.innerHTML).toBe('<test>BB</test>')
+      vm.b = 'BB'
+      _.nextTick(function () {
+        expect(el.innerHTML).toBe('<test>BBB</test>')
+        vm.$.child.b = 'hahaha'
+        _.nextTick(function () {
+          expect(vm.b).toBe('BB')
+          expect(el.innerHTML).toBe('<test>hahaha</test>')
+          done()
+        })
+      })
+    })
+
+    it('explicit one time binding', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          b: 'B'
+        },
+        template: '<test b="{{*b}}" v-ref="child"></test>',
+        components: {
+          test: {
+            props: ['b'],
+            template: '{{b}}'
+          }
+        }
+      })
+      expect(el.innerHTML).toBe('<test>B</test>')
+      vm.b = 'BB'
+      _.nextTick(function () {
+        expect(el.innerHTML).toBe('<test>B</test>')
+        done()
+      })
+    })
+
+    it('one way down binding', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          b: 'B'
+        },
+        template: '<test b="{{<b}}" v-ref="child"></test>',
+        components: {
+          test: {
+            props: ['b'],
+            template: '{{b}}'
+          }
+        }
+      })
+      expect(el.innerHTML).toBe('<test>B</test>')
+      vm.b = 'BB'
+      _.nextTick(function () {
+        expect(el.innerHTML).toBe('<test>BB</test>')
+        vm.$.child.b = 'BBB'
+        _.nextTick(function () {
+          expect(el.innerHTML).toBe('<test>BBB</test>')
+          expect(vm.b).toBe('BB')
+          done()
+        })
+      })
+    })
+
+    it('one way up binding', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          b: null
+        },
+        template: '<test b="{{>b}}" v-ref="child"></test>',
+        components: {
+          test: {
+            props: ['b'],
+            template: '{{b}}',
+            data: function () {
+              return {
+                b: 'B'
+              }
+            }
+          }
+        }
+      })
+      expect(el.innerHTML).toBe('<test>B</test>')
+      // initial set
+      expect(vm.b).toBe('B')
+      vm.b = 'BB'
+      _.nextTick(function () {
+        expect(el.innerHTML).toBe('<test>B</test>')
+        vm.$.child.b = 'BBB'
+        _.nextTick(function () {
+          expect(el.innerHTML).toBe('<test>BBB</test>')
+          expect(vm.b).toBe('BBB')
+          done()
+        })
+      })
+    })
+
     it('warn invalid keys', function () {
       var vm = new Vue({
         el: el,
@@ -142,4 +252,4 @@ if (_.inBrowser) {
     })
 
   })
-}
+}