diff --git a/hsp/compiler/jsgenerator/processors.js b/hsp/compiler/jsgenerator/processors.js index 9560e80..617dffe 100644 --- a/hsp/compiler/jsgenerator/processors.js +++ b/hsp/compiler/jsgenerator/processors.js @@ -388,6 +388,11 @@ function formatExpression (expression, firstIndex, walker) { var generatedPath = [], pathItem; generatedPath.push(rootRef); + if (exprType === 2 || exprType === 4) { + // literal references that don't actually need the root path name + // but we still put it in the expression to support better runtime error descriptions + generatedPath.push('"' + path[0].slice(1) + '"'); + } for (var i = 1; i < pathLength; i++) { pathItem = path[i]; if ((typeof pathItem) === "string") { diff --git a/hsp/rt/exphandler.js b/hsp/rt/exphandler.js index e654736..f0cf8ac 100644 --- a/hsp/rt/exphandler.js +++ b/hsp/rt/exphandler.js @@ -17,6 +17,16 @@ var klass = require("../klass"), log = require("./log"), json = require("../json"); +/** + * Return the core part of a function code + * @param {Function} fn the function to retrieve the code from + */ +function trimFunctionCode(fn) { + var s=fn.toString(); + // trim beginning and end of the function code + return s.replace(/(^[^\{]+\{\s*return\s*\(?)|(\)?\;?\s*\}\s*$)/ig,""); +} + var ExpHandler = klass({ /** * Expression handler Used by all node to access the expressions linked to their properties Note: the same @@ -28,9 +38,9 @@ var ExpHandler = klass({ * Possible expression types are: * 0: unbound data ref - e.g. {e1:[0,1,"item_key"]} * 1: bound data ref - e.g. {e1:[1,2,"person","name"]} - * 2: literal data ref - e.g. {e1:[2,2,person,"name"]} + * 2: literal data ref - e.g. {e1:[2,2,person,"person","name"]} * 3: function call - e.g. {e1:[3,2,"ctl","deleteItem",1,2,1,0]} - * 4: function call literal- e.g. {e1:[4,1,myfunc,1,2,1,0]} + * 4: function call literal- e.g. {e1:[4,1,myfunc,"myfunc",1,2,1,0]} * 5: literal value - e.g. {e1:[5,"some value"]} * 6: function expression - e.g. {e1:[6,function(a0,a1){return a0+a1;},2,3]} * 7: dynamic data reference - e.g. {e1:[7,2,function(i,a0,a1) {return [a0,a1][i];},2,3]} @@ -43,6 +53,7 @@ var ExpHandler = klass({ // initialize the exps map to support a fast accessor function var v, etype, exp = null; // onm=object name for (var key in edef) { + exp=null; v = edef[key]; if (v.constructor === Array) { etype = v[0]; @@ -60,14 +71,13 @@ var ExpHandler = klass({ exp = new FuncExpr(v, this); } else if (etype === 7) { exp = new DynRefExpr(v, this); - } else { - log.warning("Unsupported expression type: " + etype); } - if (exp) - this.exps[key] = exp; + } + if (exp) { + this.exps[key] = exp; } else { - // check other types of variables - e.g. callback - log.warning("Unsupported expression definition: " + v); + // we should only get here while implementing new expressions in the compiler + log.error("Unsupported expression (compilation error): " + v); } } }, @@ -146,6 +156,14 @@ var LiteralExpr = klass({ */ getObservablePairs : function (eh, vscope) { return null; + }, + + /** + * Return a string representing the expression as code - e.g. "person.name" + */ + toCode:function() { + var v=this.value; + return (typeof(v)==="string")? '"'+v+'"' : ""+v; } }); @@ -162,14 +180,21 @@ var DataRefExpr = klass({ * @param {ExpHandler} eh the associated expression handler */ $constructor : function (desc,eh) { - var etype = desc[0], pl = desc[1], isLiteral = (etype === 2 || etype === 4), root, path = [], ppl; // pl: path - // length + var etype = desc[0], + pl = desc[1], // path length + isLiteral = (etype === 2 || etype === 4), + root, + path = [], + ppl; // property path length + if (!isLiteral) { // e.g. {e1:[0,1,"item_key"]} >> this is a scope variable root = "scope"; path = desc.slice(2, pl + 2); ppl = pl; } else { + this.rootRef=desc[3]; + desc.splice(3,1); // remove root name root = desc[2]; path = desc.slice(3, pl + 2); ppl = pl - 1; @@ -215,13 +240,17 @@ var DataRefExpr = klass({ * used by input elements to push DOM value changes in the data model */ setValue : function (vscope, value) { + var err=false; if (this.isLiteral && this.ppLength <= 0) { - log.warning("[DataRefExpr.setValue] Global literal values cannot be updated from the DOM - please use object referenes"); + err=true; } else { - var v = this.isLiteral ? this.root : vscope[this.root], ppl = this.ppLength, goahead = true; + var v = this.isLiteral ? this.root : vscope[this.root], ppl = this.ppLength; if (ppl < 1) { return; // this case should not occur } + if (!v) { + err=true; + } if (ppl===1) { if (!this.isLiteral) { v=ExpHandler.getScopeOwner(this.path[0], vscope); @@ -230,15 +259,18 @@ var DataRefExpr = klass({ for (var i = 0; ppl - 1 > i; i++) { v = v[this.path[i]]; if (v === undefined || v===null) { - goahead = false; + err=true; break; } } } - if (goahead) { + if (!err) { json.set(v, this.path[ppl - 1], value); } } + if (err) { + log.warning("Reference cannot be resolved for 2-way data-binding: "+this.toCode()); + } }, /** @@ -285,8 +317,21 @@ var DataRefExpr = klass({ r.push([v,null]); } return r; - } + }, + /** + * Return a string representing the expression as code - e.g. "person.name" + */ + toCode:function() { + var r=[]; + if (this.rootRef) { + r.push(this.rootRef); + } + if (this.path.length) { + r.push(this.path.join(".")); + } + return r.join("."); + } }); /** @@ -306,6 +351,7 @@ var FuncRefExpr = klass({ var etype = desc[0]; // call parent constructor DataRefExpr.$constructor.call(this, desc, eh); + this.eh=eh; this.bound = (etype === 3); // literal data ref are considered unbound var argIdx = desc[1] + 2; if (desc.length > argIdx) { @@ -365,18 +411,13 @@ var FuncRefExpr = klass({ executeCb : function (evt, eh, vscope) { var v = this.getFuncRef(vscope, null); - var fn, info=""; + var fn; if (!v) { - if (this.path && this.path.length>0) { - info=this.path[0]; - } - return log.error("[function expression] Invalid reference: "+info); + return log.error("Invalid function reference in expression: "+this.toCode()); } else { fn = v.fn; - if (!fn || !fn.apply || fn.apply.constructor !== Function) { - info=this.path? this.path.join('.') : ""; - return log.error("[function expression] Object is not a function or has no apply() method: "+info); + return log.error("Object is not a function or has no apply() method: "+this.toCode(true)); } } @@ -427,17 +468,44 @@ var FuncRefExpr = klass({ var sz=r.length, lastRefOwner=r[sz-1][0], lastRef=lastRefOwner[r[sz-1][1]]; - if (lastRef.constructor === Function) { - // lastRef is a function - so we observe all properties of its context - if (sz>1) { - r.push([lastRefOwner,null]); + if (lastRef) { + if (lastRef.constructor === Function) { + // lastRef is a function - so we observe all properties of its context + if (sz>1) { + r.push([lastRefOwner,null]); + } + } else { + // lastRef is an object with an apply method - so we observe all properties of this object + r.push([lastRef,null]); } - } else { - // lastRef is an object with an apply method - so we observe all properties of this object - r.push([lastRef,null]); } } return r; + }, + + /** + * Return a string representing the expression as code - e.g. "person.getDetails()" + * @param {Boolean} refOnly true if only the function reference code should be returned - default: false + */ + toCode:function(refOnly) { + var s=DataRefExpr.toCode.call(this); + if (refOnly===true) { + return s; + } + var args=this.args, ac=[], v; + if (args) { + for (var i = 0, sz = args.length; sz > i; i += 2) { + v=args[i + 1]; + if (args[i] === 0) { + // this is a literal argument + ac.push(typeof(v)==="string"? '"'+v+'"' : v); + } else { + // this is an expression; + ac.push(this.eh.getExpr(v).toCode()); + } + } + } + return s+"("+ac.join(",")+")"; } }); @@ -496,6 +564,22 @@ var FuncExpr = klass({ getObservablePairs : function (eh, vscope) { // Observable pairs are returned by the sub-expressions associated to the function arguments return null; + }, + + /** + * Return a string representing the expression as code - e.g. "person.name" + */ + toCode:function() { + var s=trimFunctionCode(this.fn); + if (this.args) { + var sz=this.args.length, v; + for (var i=sz-1;i>-1;i--) { + // replace each argument by its code + v=this.eh.getExpr(this.args[i]).toCode(); + s=s.replace(new RegExp("a"+i),v); + } + } + return s; } }); @@ -560,6 +644,9 @@ var DynRefExpr = klass({ return defvalue; } } + if (v===null || v===undefined) { + break; + } } if (v && this.observeTarget) { op.push([v,null]); @@ -598,7 +685,8 @@ var DynRefExpr = klass({ var pfragments=this.getFragments(vscope); if (pfragments.length<2) { - log.error("[DynRefExpr.setValue] Invalid expression: "+this.fn.toString()); + // we should only get there in case of wrong compilation + log.error("[DynRefExpr] Invalid expression (compilation error): "+this.fn.toString()); } else { var v,fragment,sz=pfragments.length; for (var i = 0; sz-1>i; i++) { @@ -629,5 +717,54 @@ var DynRefExpr = klass({ // get Value also updates the opairs array this.getValue(vscope,eh,""); return this.opairs; + }, + + /** + * Return a string representing the expression as code - e.g. "person.name" + */ + toCode:function() { + // this.fn looks like function(i,a0) {return [a0,(1 + 2),"blah"][i];} + // for {person.foo[1+2].blah} + var s=trimFunctionCode(this.fn); + // remove extra array characters to transform [a0,(1 + 2),"blah"][i] + // into a0,(1 + 2),"blah" + s=s.replace(/(^\s*\[)|(\s*\]\[i\]\s*$)/ig,""); + var fragments=s.split(","), sz=fragments.length, fr, idx, res=[]; + for (var i=0;sz>i;i++) { + fr=fragments[i]; + // check fragment type + if (fr.match(/^a(\d+)$/)) { + // fragment refers to another expression + idx=parseInt(RegExp.$1,10); + s=this.eh.getExpr(this.args[idx]).toCode(); + if (i===0) { + // first expression + res.push(s); + } else { + res.push("["+s+"]"); + } + } else { + // fragment is a literal or literal expression + if (fr.match(/^\((.*)\)$/)) { + // fragment is a literal expression - e.g. (1 + a2) + s=RegExp.$1; // trim parenthesis + while(s.match(/a(\d+)/)) { + // replace aXXX arguments with their expression values + // will also replace aXXX in strings - but this rare edge case should be acceptable + // for error reporting + idx=parseInt(RegExp.$1,10); + s=s.replace(new RegExp("a"+idx), this.eh.getExpr(this.args[idx]).toCode() || ""); + } + res.push("["+s+"]"); + } else if (fr.match(/^\"(.*)\"$/)) { + // fragment is a string + res.push("."+RegExp.$1); + } else { + res.push("["+fr+"]"); + } + } + } + + return res.join(""); } }); diff --git a/test/compiler/samples/component4.txt b/test/compiler/samples/component4.txt index 7a6df63..4cae55a 100644 --- a/test/compiler/samples/component4.txt +++ b/test/compiler/samples/component4.txt @@ -16,6 +16,6 @@ test=[ n.elt("input",{e1:[1,2,"d","value"]},{"type":"text","value":["",1]},0), n.cpt([_lib,"lib","nbrfield"],{ e1:[1,2,"d","value"], - e2:[4,1,_notifyReset,0,123] + e2:[4,1,_notifyReset,"notifyReset",0,123] },{"value":["",1],"min":"-10","max":"10"},{"reset":2}) ] diff --git a/test/compiler/samples/component7.txt b/test/compiler/samples/component7.txt index de9b788..8e725d5 100644 --- a/test/compiler/samples/component7.txt +++ b/test/compiler/samples/component7.txt @@ -12,5 +12,9 @@ ##### Template Code: test=[ - n.cpt([_lib,"lib","nbrfield"],{e1:[6,function(a0,a1) {return {value:a0,min:10,max:"10",reset:a1};},2,3],e2:[1,2,"d","value"],e3:[4,1,_notifyReset,0,123]},{"objliteral":["",1]},0) + n.cpt([_lib,"lib","nbrfield"],{ + e1:[6,function(a0,a1) {return {value:a0,min:10,max:"10",reset:a1};},2,3], + e2:[1,2,"d","value"], + e3:[4,1,_notifyReset,"notifyReset",0,123] + },{"objliteral":["",1]},0) ] diff --git a/test/compiler/samples/evthandler1.txt b/test/compiler/samples/evthandler1.txt index 0015e2a..319ce33 100644 --- a/test/compiler/samples/evthandler1.txt +++ b/test/compiler/samples/evthandler1.txt @@ -110,7 +110,7 @@ test=[ {"click":1} ), n.elt( "img", - {e1:[4,1,_doClick,0,"blah",1,2],e2:[0,1,"event"]}, + {e1:[4,1,_doClick,"doClick",0,"blah",1,2],e2:[0,1,"event"]}, 0, {"click":1} ) diff --git a/test/compiler/samples/evthandler2.txt b/test/compiler/samples/evthandler2.txt index eb1871b..37d6479 100644 --- a/test/compiler/samples/evthandler2.txt +++ b/test/compiler/samples/evthandler2.txt @@ -12,7 +12,7 @@ ##### Template code test=[ n.elt( "img", - {e1:[4,1,_doClick,0,1]}, + {e1:[4,1,_doClick,"doClick",0,1]}, 0, {"click":1} ) diff --git a/test/compiler/samples/foreach4.txt b/test/compiler/samples/foreach4.txt index 0ae478d..ee4e23a 100644 --- a/test/compiler/samples/foreach4.txt +++ b/test/compiler/samples/foreach4.txt @@ -16,7 +16,7 @@ ##### Template Code test=[ n.$foreach ( - {e1:[2,1,_items]}, + {e1:[2,1,_items,"items"]}, "itm_key", "itm", 0, diff --git a/test/compiler/samples/jsexpression11.txt b/test/compiler/samples/jsexpression11.txt index 9e464f5..c41fbfb 100644 --- a/test/compiler/samples/jsexpression11.txt +++ b/test/compiler/samples/jsexpression11.txt @@ -16,7 +16,7 @@ hello=[ e2:[1,1,"person"], e3:[1,2,"person","name"], e4:[7,3,function(i,a0,a1) {return [a0,(a1 + 1),"foo"][i];},2,3], - e5:[2,2,_x,"y"]}, + e5:[2,2,_x,"x","y"]}, ["",1] ) ] diff --git a/test/compiler/samples/jsexpression13.txt b/test/compiler/samples/jsexpression13.txt index 52c7b5d..1812d27 100644 --- a/test/compiler/samples/jsexpression13.txt +++ b/test/compiler/samples/jsexpression13.txt @@ -89,7 +89,7 @@ ##### Template Code test=[ n.$text({ - e1:[4,1,_orderBy,1,2,0,"firstName"], + e1:[4,1,_orderBy,"orderBy",1,2,0,"firstName"], e2:[1,2,"person","list"] },["",1]) ] diff --git a/test/compiler/samples/jsexpression14.txt b/test/compiler/samples/jsexpression14.txt index 396758d..a959d06 100644 --- a/test/compiler/samples/jsexpression14.txt +++ b/test/compiler/samples/jsexpression14.txt @@ -85,7 +85,7 @@ ##### Template Code test=[ n.$text({ - e1:[4,1,_orderBy,1,2,0,"firstName"], + e1:[4,1,_orderBy,"orderBy",1,2,0,"firstName"], e2:[1,2,"person","list"] },["",1]) ] diff --git a/test/compiler/samples/jsexpression15.txt b/test/compiler/samples/jsexpression15.txt index 3676fb9..500aa3c 100644 --- a/test/compiler/samples/jsexpression15.txt +++ b/test/compiler/samples/jsexpression15.txt @@ -145,8 +145,8 @@ ##### Template Code test=[ n.elt("div",{ - e1:[4,1,_foo,1,2], - e2:[4,1,_bar,1,3,1,5], + e1:[4,1,_foo,"foo",1,2], + e2:[4,1,_bar,"bar",1,3,1,5], e3:[6,function(a0) {return (a0 + "!");},4], e4:[1,2,"person","name"], e5:[6,function() {return (1 + 2);}] diff --git a/test/compiler/samples/jsexpression16.txt b/test/compiler/samples/jsexpression16.txt index ecd9455..64c9532 100644 --- a/test/compiler/samples/jsexpression16.txt +++ b/test/compiler/samples/jsexpression16.txt @@ -145,8 +145,8 @@ ##### Template Code test=[ n.elt("div",{ - e1:[4,1,_foo,1,2], - e2:[4,1,_bar,1,3,1,5], + e1:[4,1,_foo,"foo",1,2], + e2:[4,1,_bar,"bar",1,3,1,5], e3:[6,function(a0) {return (a0 + "!");},4], e4:[1,2,"person","name"], e5:[6,function() {return (1 + 2);}] diff --git a/test/compiler/samples/jsexpression17.txt b/test/compiler/samples/jsexpression17.txt index 2e4bbab..977fdeb 100644 --- a/test/compiler/samples/jsexpression17.txt +++ b/test/compiler/samples/jsexpression17.txt @@ -14,7 +14,7 @@ ##### Template Code test=[ n.$if({ - e1:[4,1,_acceptEmpty,1,2], + e1:[4,1,_acceptEmpty,"acceptEmpty",1,2], e2:[1,2,"person","name"] },1,[n.$text(0,["hello "])]) ] diff --git a/test/compiler/samples/jsexpression18.txt b/test/compiler/samples/jsexpression18.txt index 102ce8c..5bbc25f 100644 --- a/test/compiler/samples/jsexpression18.txt +++ b/test/compiler/samples/jsexpression18.txt @@ -15,7 +15,7 @@ test=[ n.$foreach( { - e1:[4,1,_orderBy,1,2,0,"name"], + e1:[4,1,_orderBy,"orderBy",1,2,0,"name"], e2:[1,2,"person","list"] }, "p_key", diff --git a/test/compiler/samples/jsexpression6.txt b/test/compiler/samples/jsexpression6.txt index 2376bb1..f4ce4a3 100644 --- a/test/compiler/samples/jsexpression6.txt +++ b/test/compiler/samples/jsexpression6.txt @@ -78,8 +78,8 @@ ##### Template Code test=[ n.$text({ - e1:[4,1,_content1], - e2:[4,1,_content2,0,"First Name",1,3], + e1:[4,1,_content1,"content1"], + e2:[4,1,_content2,"content2",0,"First Name",1,3], e3:[1,2,"person","firstName"] },["Before ",1," ",2," After"]) ] diff --git a/test/compiler/samples/jsexpression7.txt b/test/compiler/samples/jsexpression7.txt index 1ecc9fe..0ff1330 100644 --- a/test/compiler/samples/jsexpression7.txt +++ b/test/compiler/samples/jsexpression7.txt @@ -13,5 +13,5 @@ ##### Template Code test=[ - n.$text({e1:[4,1,_content1]},["",1]) + n.$text({e1:[4,1,_content1,"content1"]},["",1]) ] diff --git a/test/compiler/samples/let2.txt b/test/compiler/samples/let2.txt index ebf8e61..46947ba 100644 --- a/test/compiler/samples/let2.txt +++ b/test/compiler/samples/let2.txt @@ -23,7 +23,7 @@ test=[ n.$text(0,[" "]), n.let({ e1:[1,2,"value","nbr"], - e2:[2,1,_blah] + e2:[2,1,_blah,"blah"] },['aVarName',1,'anotherName',2] ) ], diff --git a/test/compiler/samples/let3.txt b/test/compiler/samples/let3.txt index c904510..1e3bcd3 100644 --- a/test/compiler/samples/let3.txt +++ b/test/compiler/samples/let3.txt @@ -19,7 +19,7 @@ test=[ n.$text(0,[" "]), n.let({ e1:[6,function(a0) {return new a0();},2], - e2:[2,1,_Foo], + e2:[2,1,_Foo,"Foo"], e3:[6,function(a0) {return new a0();},4], e4:[1,2,"foo","Bar"] },['aVarName',1,'anotherName',3]) diff --git a/test/compiler/samples/let4.txt b/test/compiler/samples/let4.txt index ea1a33b..4d3cc98 100644 --- a/test/compiler/samples/let4.txt +++ b/test/compiler/samples/let4.txt @@ -17,8 +17,8 @@ test=[ "div",0,{"class":"foo"},0,[ n.let({ e1:[6,function(a0,a1) {return new a0("a",123,a1);},2,3], - e2:[2,1,_Foo], - e3:[4,1,_blah,1,4], + e2:[2,1,_Foo,"Foo"], + e3:[4,1,_blah,"blah",1,4], e4:[6,function(a0) {return (1 + a0);},5], e5:[1,2,"foo","value"] },['val',1]) diff --git a/test/compiler/samples/text7.txt b/test/compiler/samples/text7.txt index 47c0ac8..01164d7 100644 --- a/test/compiler/samples/text7.txt +++ b/test/compiler/samples/text7.txt @@ -12,5 +12,5 @@ var person = {name : "World"}; ##### Template Code hello=[ - n.$text({e1:[2,2,_person,"name"]},["Hello ",1,"!"]) + n.$text({e1:[2,2,_person,"person","name"]},["Hello ",1,"!"]) ] diff --git a/test/compiler/samples/text8.txt b/test/compiler/samples/text8.txt index 98a430c..430a672 100644 --- a/test/compiler/samples/text8.txt +++ b/test/compiler/samples/text8.txt @@ -13,5 +13,5 @@ var nullValue = "null"; ##### Template Code main=[ - n.$text({e1:[2,1,_nullValue]},["before-",1,"-after"]) + n.$text({e1:[2,1,_nullValue,"nullValue"]},["before-",1,"-after"]) ] diff --git a/test/rt/exprcode.spec.hsp b/test/rt/exprcode.spec.hsp new file mode 100644 index 0000000..b7a9df2 --- /dev/null +++ b/test/rt/exprcode.spec.hsp @@ -0,0 +1,126 @@ +/* + * Copyright 2014 Amadeus s.a.s. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var ht=require("hsp/utils/hashtester"); + + +describe("Expression code generation", function () { + var h; + + beforeEach(function () { + h=ht.newTestContext(); + }); + + afterEach(function () { + h.$dispose(); + }); + + it("validates LiteralExpr", function() { + {template test()} + {123}{"hello"}{false} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("123"); + expect(exps["e2"].toCode()).to.equal("\"hello\""); + expect(exps["e3"].toCode()).to.equal("false"); + }); + + it("validates DataRefExpr with variables in scope", function() { + {template test(msg,person)} + {msg}{person.name} + {/template} + + var exps=test("hello",{name:"Bart"}).childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("msg"); + expect(exps["e2"].toCode()).to.equal("person.name"); + }); + + it("validates DataRefExpr with variables out of scope", function() { + {template test()} + {msg}{person.name} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("msg"); + expect(exps["e2"].toCode()).to.equal("person.name"); + }); + + it("validates FuncRefExpr with variables in scope", function() { + {template test(foo,person)} + {foo()}{foo.bar(123+"hello",person.name,"blah")} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("foo()"); + expect(exps["e2"].toCode()).to.equal("foo.bar(123 + \"hello\",person.name,\"blah\")"); + expect(h.logs().length).to.equal(2); + expect(h.logs(0).message).to.equal("Invalid function reference in expression: foo()"); + expect(h.logs(1).message).to.equal("Invalid function reference in expression: foo.bar(123 + \"hello\",person.name,\"blah\")"); + h.logs.clear(); + }); + + it("validates FuncRefExpr with variables out of scope", function() { + {template test()} + {foo()}{foo.bar(123+"hello",person.name,"blah")} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("foo()"); + expect(exps["e2"].toCode()).to.equal("foo.bar(123 + \"hello\",person.name,\"blah\")"); + expect(h.logs().length).to.equal(2); + expect(h.logs(0).message).to.equal("Invalid function reference in expression: foo()"); + expect(h.logs(1).message).to.equal("Invalid function reference in expression: foo.bar(123 + \"hello\",person.name,\"blah\")"); + h.logs.clear(); + }); + + it("validates FuncExpr", function() { + {template test(person)} + {123 + 345 * 2}{person.name + 123 * "hello"} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("123 + (345 * 2)"); + expect(exps["e2"].toCode()).to.equal("person.name + (123 * \"hello\")"); + }); + + it("validates DynRefExpr 1", function() { + {template test(person)} + {person["name"]}{person.foo[1+2].blah} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("person.name"); + expect(exps["e2"].toCode()).to.equal("person.foo[1 + 2].blah"); + }); + + it("validates DynRefExpr 2", function() { + {template test(person)} + {person[person.key][12]} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("person[person.key][12]"); + }); + + it("validates DynRefExpr 3", function() { + {template test(person)} + {person.foo[1+person.idx*person.age][123].blah["hello"]} + {/template} + + var exps=test().childNodes[0].eh.exps; + expect(exps["e1"].toCode()).to.equal("person.foo[1 + (person.idx * person.age)][123].blah.hello"); + }); +}); diff --git a/test/rt/fnexpressions.spec.hsp b/test/rt/fnexpressions.spec.hsp index 24eee23..bf16a1b 100644 --- a/test/rt/fnexpressions.spec.hsp +++ b/test/rt/fnexpressions.spec.hsp @@ -261,7 +261,7 @@ describe("Function expressions", function () { test7(d).render(h.container); expect(h.logs().length).to.equal(1); - expect(h.logs()[0].message).to.equal("[function expression] Object is not a function or has no apply() method: modifier"); + expect(h.logs()[0].message).to.equal("Object is not a function or has no apply() method: modifier"); h.logs.clear(); h.$dispose(); diff --git a/test/rt/index.html b/test/rt/index.html index c21061e..1db6a96 100644 --- a/test/rt/index.html +++ b/test/rt/index.html @@ -64,6 +64,8 @@ require("test/rt/elt.spec.hsp"); require("test/rt/evthandler.spec.hsp"); require("test/rt/exprbinding.spec.hsp"); + require("test/rt/exprcode.spec.hsp"); + require("test/rt/rterrors.spec.hsp"); require("test/rt/fnexpressions.spec.hsp"); require("test/rt/foreach.spec.hsp"); require("test/rt/if.spec.hsp"); diff --git a/test/rt/rterrors.spec.hsp b/test/rt/rterrors.spec.hsp new file mode 100644 index 0000000..e0220b8 --- /dev/null +++ b/test/rt/rterrors.spec.hsp @@ -0,0 +1,79 @@ +/* + * Copyright 2014 Amadeus s.a.s. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var ht=require("hsp/utils/hashtester"); + + +describe("Runtime errors", function () { + var h; + + beforeEach(function () { + h=ht.newTestContext(); + }); + + afterEach(function () { + h.$dispose(); + }); + + it("should raise an error when global references are used for 2-way binding 1", function() { + {template test()} + + {/template} + + test().render(h.container); + h("input").type("bar"); + expect(h.logs().length).to.equal(1); + expect(h.logs(0).message).to.equal("Reference cannot be resolved for 2-way data-binding: foo"); + h.logs.clear(); + }); + + it("should raise an error when global references are used for 2-way binding 2", function() { + {template test()} + + {/template} + + test().render(h.container); + h("input").type("bar"); + expect(h.logs().length).to.equal(1); + expect(h.logs(0).message).to.equal("Reference cannot be resolved for 2-way data-binding: foo.bar"); + h.logs.clear(); + }); + + it("should raise an error when global references are used for 2-way binding 3", function() { + var foo={}; + + {template test()} + + {/template} + + test().render(h.container); + h("input").type("bar"); + expect(h.logs().length).to.equal(1); + expect(h.logs(0).message).to.equal("Reference cannot be resolved for 2-way data-binding: foo.bar.blah"); + h.logs.clear(); + }); + + it("should raise an error when an invalid callback is used", function() { + {template test()} + click me + {/template} + + test().render(h.container); + h("a").click(); + expect(h.logs().length).to.equal(1); + expect(h.logs(0).message).to.equal("Invalid function reference in expression: foo()"); + h.logs.clear(); + }); +});