Browse Source

expressions

- add functional test case for sd-on expression handlers
- invalid inline expressions: the error will be captured and warning thrown
- add unit test case for invalid inline expressions
- utils.warn() now always gets thrown by default
- add `silent` config option to suppress warnings
Evan You 12 years ago
parent
commit
30784b5756

+ 2 - 4
src/compiler.js

@@ -425,8 +425,8 @@ CompilerProto.createBinding = function (key, isExp, isFn) {
         // a complex expression binding
         // we need to generate an anonymous computed property for it
         var result = ExpParser.parse(key)
-        if (result) {
-            log('  created anonymous binding: ' + key)
+        if (result.getter) {
+            log('  created expression binding: ' + key)
             binding.value = isFn
                 ? result.getter
                 : { $get: result.getter }
@@ -443,8 +443,6 @@ CompilerProto.createBinding = function (key, isExp, isFn) {
                     }
                 }
             }
-        } else {
-            utils.warn('  invalid expression: ' + key)
         }
     } else {
         log('  created binding: ' + key)

+ 1 - 0
src/config.js

@@ -2,6 +2,7 @@ module.exports = {
 
     prefix      : 'sd',
     debug       : false,
+    silent      : false,
     attrs       : {}
     
 }

+ 20 - 3
src/exp-parser.js

@@ -1,3 +1,5 @@
+var utils = require('./utils')
+
 // Variable extraction scooped from https://github.com/RubyLouvre/avalon
 
 var KEYWORDS =
@@ -44,6 +46,22 @@ function getPaths (code, vars) {
     return code.match(pathRE)
 }
 
+/**
+ *  Create a function from a string...
+ *  this looks like evil magic but since all variables are limited
+ *  to the VM's scope it's actually properly sandboxed
+ */
+function makeGetter (exp, raw) {
+    /* jshint evil: true */
+    var fn
+    try {
+        fn = new Function(exp)
+    } catch (e) {
+        utils.warn('Invalid expression: ' + raw)
+    }
+    return fn
+}
+
 module.exports = {
 
     /**
@@ -52,12 +70,11 @@ module.exports = {
      *  created as bindings.
      */
     parse: function (exp) {
-        /* jshint evil: true */
         // extract variable names
         var vars = getVariables(exp)
         if (!vars.length) {
             return {
-                getter: new Function('return ' + exp)
+                getter: makeGetter('return ' + exp, exp)
             }
         }
         var args = [],
@@ -79,7 +96,7 @@ module.exports = {
         }
         args = 'var ' + args.join(',') + ';return ' + exp
         return {
-            getter: new Function(args),
+            getter: makeGetter(args, exp),
             paths: getPaths(exp, Object.keys(hash))
         }
     }

+ 2 - 2
src/utils.js

@@ -161,10 +161,10 @@ var utils = module.exports = {
     },
     
     /**
-     *  warn for debugging
+     *  warnings, thrown in all cases
      */
     warn: function() {
-        if (config.debug && console) {
+        if (!config.silent && console) {
             console.warn(join.call(arguments, ' '))
         }
     }

+ 2 - 0
test/functional/fixtures/expression.html

@@ -10,12 +10,14 @@
         <div id="normal">
             <p sd-text="one + ' ' + two.three + '!'"></p>
             <input id="one" sd-model="one" name="one"> <input id="two" sd-model="two.three" name="two">
+            <button sd-on="click: this.one = 'clicked'">click</button>
         </div>
         <div id="lazy">
             <p sd-text="one + ' ' + two.three + '!'"></p>
             <form id="form">
                 <input sd-model="one" name="three"> <input sd-model="two.three" name="four">
             </form>
+            <button sd-on="click: two.three = 'clicked'">click</button>
         </div>
         <script>
             var normal = new Seed({

+ 11 - 1
test/functional/specs/expression.js

@@ -1,6 +1,6 @@
 /* global normal */
 
-casper.test.begin('Expression', 12, function (test) {
+casper.test.begin('Expression', 16, function (test) {
     
     casper
     .start('./fixtures/expression.html', function () {
@@ -37,6 +37,16 @@ casper.test.begin('Expression', 12, function (test) {
         this.sendKeys('#one', 'Bye')
         test.assertSelectorHasText('#normal p', 'Bye Casper!')
 
+        // sd-on with expression
+        this.click('#normal button')
+        test.assertField('one', 'clicked')
+        test.assertSelectorHasText('#normal p', 'clicked Casper!')
+
+        // sd-on with expression
+        this.click('#lazy button')
+        test.assertField('four', 'clicked')
+        test.assertSelectorHasText('#lazy p', 'three clicked!')
+
     })
     .run(function () {
         test.done()

+ 2 - 0
test/unit/runner.html

@@ -20,6 +20,8 @@
 			var Seed = require('seed'),
 				assert = chai.assert
 
+            Seed.config({silent:true})
+
 			function mock (id, html, attrs) {
 				var el = document.createElement('div')
 				el.id = id

+ 24 - 0
test/unit/specs/exp-parser.js

@@ -52,6 +52,12 @@ describe('UNIT: Expression Parser', function () {
                 }
             },
             expectedValue: 'write tests : nope'
+        },
+        {
+            // expression with no scope variables
+            exp: "'a' + 'b'",
+            vm: {},
+            expectedValue: 'ab'
         }
     ]
 
@@ -69,6 +75,7 @@ describe('UNIT: Expression Parser', function () {
             vm.$get = function (key) { return this[key] }
 
             it('should get correct args', function () {
+                if (!vars.length) return
                 assert.strictEqual(result.paths.length, vars.length)
                 for (var i = 0; i < vars.length; i++) {
                     assert.strictEqual(vars[i], result.paths[i])
@@ -83,4 +90,21 @@ describe('UNIT: Expression Parser', function () {
         })
     }
 
+    // extra case for invalid expressions
+    describe('invalid expression', function () {
+        
+        it('should capture the error and warn', function () {
+            var utils = require('seed/src/utils'),
+                oldWarn = utils.warn,
+                warned = false
+            utils.warn = function () {
+                warned = true
+            }
+            ExpParser.parse('a + "fsef')
+            assert.ok(warned)
+            utils.warn = oldWarn
+        })
+
+    })
+
 })