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();
+ });
+});