Compare commits

..

37 Commits

Author SHA1 Message Date
kpdecker f5289a35ec 1.0.6 2012-07-23 15:40:07 -05:00
kpdecker fc99d90337 Fix loading under node 2012-07-23 13:48:27 -05:00
kpdecker eb509a4b8b Execute mocha test from npm test 2012-07-23 13:47:52 -05:00
kpdecker 09ac8ac24b Make jshint happy(er) 2012-07-23 13:45:53 -05:00
kpdecker adb8486e5f Use suite rather than module for module decl 2012-07-23 13:44:45 -05:00
kpdecker 1e151e2030 Ignore sublime projects 2012-07-23 13:42:38 -05:00
kpdecker fe7d16de91 1.0.6beta 2012-07-23 10:36:13 -05:00
tomhuda 8be16c64f9 Add a .rspec 2012-07-12 10:14:21 -07:00
tomhuda f79af6bfa3 Merge branch 'master' of github.com:wycats/handlebars.js 2012-07-12 10:07:10 -07:00
Yehuda Katz efb1e25690 Update for change in TRR API 2012-07-12 08:15:10 -07:00
Yehuda Katz 36b242a710 Document lookupData 2012-07-05 23:10:39 -07:00
Yehuda Katz 659be5a690 Reduce duplication 2012-07-05 23:08:57 -07:00
Yehuda Katz 46c04fa71e Make deep @data trigger the data option 2012-07-05 23:05:55 -07:00
Yehuda Katz 5e8be14d78 Add support for @index in #each 2012-07-05 22:43:50 -07:00
Yehuda Katz 72e05d623c Add support for @data variables 2012-07-05 22:43:05 -07:00
Yehuda Katz ff1547ea04 Merge pull request #262 from naholyr/patch-1
FIX global leak
2012-06-25 07:40:52 -07:00
Nicolas Chambrier 7a393a972b FIX global leak 2012-06-25 17:38:27 +03:00
tomhuda 92b6c1401a Bump version to RC1 2012-05-28 20:05:55 -07:00
tomhuda 0afc8b58d2 Clean up parser tests and AST printer 2012-05-28 19:06:26 -07:00
tomhuda 1082ec2414 Fix a bug where registers were shared 2012-05-28 19:06:08 -07:00
tomhuda 727eb26cb6 Remove unneeded code and add docs
There were a few operations that are no longer
needed, so remove them. Also document all
operations.
2012-05-28 17:35:21 -07:00
tomhuda 175c6fae0f More cleanup
* Make block and inverse use the main helper path
* Eliminate separate inverse AST node
2012-05-28 17:00:41 -07:00
tomhuda 3486b530be Disambiguate more ahead of time 2012-05-28 14:19:48 -07:00
tomhuda bc5efc1767 Remove unneeded binary opcode stuff
It turns out that we don't need to store the
opcodes in a compact way, so don't bother.
2012-05-28 13:28:51 -07:00
Yehuda Katz 90adaa3cc8 Continue work on cleaning up helpers 2012-05-28 13:03:22 -07:00
Yehuda Katz 8786a6c6e2 Start doing earlier work on helpers 2012-05-28 00:15:52 -07:00
Yehuda Katz 246325085f Simple spec for isolation analysis 2012-05-26 22:17:31 -07:00
Yehuda Katz 68c76c9b2e Clean up the compiler a bit 2012-05-26 22:17:22 -07:00
tomhuda facefe8fed Several improvements to compiled output:
* Eliminate legacy support for an options hash
  that doubles as a function. This prevented us
  from building the hash as a literal, and added
  a bunch of code weight
* Create a new "stack literal" construct, that
  allows an opcode to push a literal expression
  onto the stack. This will not allocate a new
  stack variable, and when popped, will simply
  return the literal expression as a String.
2012-05-26 14:51:41 -07:00
tomhuda 8e70e3b58b Don't assume execution in the top context 2012-05-26 14:47:32 -07:00
Yehuda Katz 730c412925 Merge pull request #218 from SlexAxton/master
Adding using a 'class' variable name as a 'known issue'
2012-05-06 20:53:52 -07:00
Peter Wagenet 2418feaf19 Wrap base Handlebars in a closure to prevent var leakage - fixes #205 2012-05-04 15:27:50 -07:00
Alex Sexton c2591f428b Adding warning in about using 'class' as a variable name. 2012-04-11 21:46:28 -05:00
Peter Wagenet 98b3327c75 Merge pull request #202 from neomorphic/master
vm -> runtime change in README
2012-03-06 07:17:58 -08:00
Jody Clements 992fdf2071 Update README to reflect vm -> runtime change
Looks like one handlebars.vm.js was missed in the README file during the
change to handlebars.runtime.js
2012-03-06 08:32:01 -05:00
tomhuda 24e04bad94 JSHint the library and fix a few bugs 2012-02-17 16:51:35 -08:00
Peter Wagenet e6079ca5e2 Merge pull request #177 from kpdecker/master
Rev npm package to 1.0.5beta
2012-02-09 09:56:22 -08:00
17 changed files with 982 additions and 511 deletions
+2
View File
@@ -4,3 +4,5 @@ vendor
.DS_Store
lib/handlebars/compiler/parser.js
node_modules
*.sublime-project
*.sublime-workspace
+52
View File
@@ -0,0 +1,52 @@
{
"predef": [
"console",
"Ember",
"DS",
"Handlebars",
"Metamorph",
"ember_assert",
"ember_warn",
"ember_deprecate",
"ember_deprecateFunc",
"require",
"suite",
"equal",
"equals",
"test",
"testBoth",
"raises",
"deepEqual",
"start",
"stop",
"ok",
"strictEqual",
"module"
],
"node" : true,
"es5" : true,
"browser" : true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false
}
+1
View File
@@ -0,0 +1 @@
-cfs
+9 -9
View File
@@ -4,15 +4,15 @@ GEM
diff-lcs (1.1.3)
libv8 (3.3.10.4)
rake (0.9.2.2)
rspec (2.7.0)
rspec-core (~> 2.7.0)
rspec-expectations (~> 2.7.0)
rspec-mocks (~> 2.7.0)
rspec-core (2.7.1)
rspec-expectations (2.7.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.7.0)
therubyracer (0.9.9)
rspec (2.11.0)
rspec-core (~> 2.11.0)
rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.11.0)
rspec-core (2.11.0)
rspec-expectations (2.11.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.1)
therubyracer (0.10.1)
libv8 (~> 3.3.10)
PLATFORMS
+2 -1
View File
@@ -239,7 +239,7 @@ method and the resulting object may be as normal.
### Optimizations
- Rather than using the full _handlebars.js_ library, implementations that
do not need to compile templates at runtime may include _handlebars.vm.js_
do not need to compile templates at runtime may include _handlebars.runtime.js_
whose min+gzip size is approximately 1k.
- If a helper is known to exist in the target environment they may be defined
using the `--known name` argument may be used to optimize accesses to these
@@ -283,6 +283,7 @@ template(context, {helpers: helpers, partials: partials, data: data})
Known Issues
------------
* Handlebars.js can be cryptic when there's an error while rendering.
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
Handlebars in the Wild
-----------------
+25 -5
View File
@@ -1,7 +1,11 @@
// BEGIN(BROWSER)
var Handlebars = {};
Handlebars.VERSION = "1.0.beta.5";
/*jshint eqnull:true*/
this.Handlebars = {};
(function(Handlebars) {
Handlebars.VERSION = "1.0.rc.1";
Handlebars.helpers = {};
Handlebars.partials = {};
@@ -52,13 +56,27 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
}
});
Handlebars.K = function() {};
Handlebars.createFrame = Object.create || function(object) {
Handlebars.K.prototype = object;
var obj = new Handlebars.K();
Handlebars.K.prototype = null;
return obj;
};
Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var ret = "";
var ret = "", data;
if (options.data) {
data = Handlebars.createFrame(options.data);
}
if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
}
} else {
ret = inverse(this);
@@ -93,7 +111,9 @@ Handlebars.registerHelper('log', function(context) {
Handlebars.log(context);
});
}(this.Handlebars));
// END(BROWSER)
module.exports = Handlebars;
module.exports = this.Handlebars;
+32 -12
View File
@@ -11,12 +11,26 @@ var Handlebars = require('./base');
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
};
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
this.type = "mustache";
this.id = params[0];
this.params = params.slice(1);
this.hash = hash;
this.escaped = !unescaped;
this.hash = hash;
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
var eligibleHelper = this.eligibleHelper = id.isSimple;
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
this.isHelper = eligibleHelper && (params.length || hash);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
};
Handlebars.AST.PartialNode = function(id, context) {
@@ -34,18 +48,16 @@ var Handlebars = require('./base');
}
};
Handlebars.AST.BlockNode = function(mustache, program, close) {
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
};
this.inverse = inverse;
Handlebars.AST.InverseNode = function(mustache, program, close) {
verifyMatch(mustache.id, close);
this.type = "inverse";
this.mustache = mustache;
this.program = program;
if (this.inverse && !this.program) {
this.isInverse = true;
}
};
Handlebars.AST.ContentNode = function(string) {
@@ -75,7 +87,15 @@ var Handlebars = require('./base');
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
this.isSimple = (dig.length === 1) && (depth === 0);
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
};
Handlebars.AST.DataNode = function(id) {
this.type = "DATA";
this.id = id;
};
Handlebars.AST.StringNode = function(string) {
File diff suppressed because it is too large Load Diff
+17 -27
View File
@@ -18,31 +18,17 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
};
Handlebars.PrintVisitor.prototype.program = function(program) {
var out = this.pad("PROGRAM:"),
var out = "",
statements = program.statements,
inverse = program.inverse,
i, l;
this.padding++;
for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]);
}
this.padding--;
if(inverse) {
out = out + this.pad("{{^}}");
this.padding++;
for(i=0, l=inverse.statements.length; i<l; i++) {
out = out + this.accept(inverse.statements[i]);
}
}
this.padding--;
return out;
};
@@ -52,25 +38,25 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
out = out + this.pad("BLOCK:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.inverse = function(block) {
var out = "";
out = out + this.pad("INVERSE:");
if (block.program) {
out = out + this.pad("PROGRAM:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
out = out + this.pad("{{^}}");
this.padding++;
out = out + this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
}
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
var params = mustache.params, paramStrings = [], hash;
@@ -125,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
}
};
Handlebars.PrintVisitor.prototype.DATA = function(data) {
return "@" + data.id;
};
Handlebars.PrintVisitor.prototype.content = function(content) {
return this.pad("CONTENT[ '" + content.string + "' ]");
};
+9 -3
View File
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
"version": "1.0.5beta",
"version": "1.0.6",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars mustache template html"
@@ -17,9 +17,15 @@
"optimist": "~0.3",
"uglify-js": "~1.2"
},
"devDependencies": {},
"devDependencies": {
"mocha": "*"
},
"main": "lib/handlebars.js",
"bin": {
"handlebars": "bin/handlebars"
}
},
"scripts": {
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
},
"optionalDependencies": {}
}
+5 -5
View File
@@ -39,11 +39,11 @@ Module.new do
end
end
js_context["p"] = proc do |str|
js_context["p"] = proc do |this, str|
p str
end
js_context["ok"] = proc do |ok, message|
js_context["ok"] = proc do |this, ok, message|
js_context["$$RSPEC1$$"] = ok
result = js_context.eval("!!$$RSPEC1$$")
@@ -58,7 +58,7 @@ Module.new do
assert result, message
end
js_context["equals"] = proc do |first, second, message|
js_context["equals"] = proc do |this, first, second, message|
js_context["$$RSPEC1$$"] = first
js_context["$$RSPEC2$$"] = second
@@ -77,11 +77,11 @@ Module.new do
js_context["equal"] = js_context["equals"]
js_context["module"] = proc do |name|
js_context["suite"] = proc do |this, name|
test_context.module(name)
end
js_context["test"] = proc do |name, function|
js_context["test"] = proc do |this, name, function|
test_context.test(name, function)
end
+45 -31
View File
@@ -7,13 +7,11 @@ describe "Parser" do
@compiles = true
end
def program(&block)
def root(&block)
ASTBuilder.build do
program do
instance_eval(&block)
end
end
end
def ast_for(string)
ast = handlebars.parse(string)
@@ -112,115 +110,131 @@ describe "Parser" do
"ID:#{id}"
end
def data(id)
"@#{id}"
end
def path(*parts)
"PATH:#{parts.join("/")}"
end
end
it "parses simple mustaches" do
ast_for("{{foo}}").should == program { mustache id("foo") }
ast_for("{{foo}}").should == root { mustache id("foo") }
end
it "parses simple mustaches with data" do
ast_for("{{@foo}}").should == root { mustache data("foo") }
end
it "parses mustaches with paths" do
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
end
it "parses mustaches with this/foo" do
ast_for("{{this/foo}}").should == program { mustache id("foo") }
ast_for("{{this/foo}}").should == root { mustache id("foo") }
end
it "parses mustaches with - in a path" do
ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") }
ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") }
end
it "parses mustaches with parameters" do
ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] }
ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] }
end
it "parses mustaches with hash arguments" do
ast_for("{{foo bar=baz}}").should == program do
ast_for("{{foo bar=baz}}").should == root do
mustache id("foo"), [], hash(["bar", id("baz")])
end
ast_for("{{foo bar=1}}").should == program do
ast_for("{{foo bar=1}}").should == root do
mustache id("foo"), [], hash(["bar", integer("1")])
end
ast_for("{{foo bar=true}}").should == program do
ast_for("{{foo bar=true}}").should == root do
mustache id("foo"), [], hash(["bar", boolean("true")])
end
ast_for("{{foo bar=false}}").should == program do
ast_for("{{foo bar=false}}").should == root do
mustache id("foo"), [], hash(["bar", boolean("false")])
end
ast_for("{{foo bar=baz bat=bam}}").should == program do
ast_for("{{foo bar=@baz}}").should == root do
mustache id("foo"), [], hash(["bar", data("baz")])
end
ast_for("{{foo bar=baz bat=bam}}").should == root do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
end
ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do
ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
end
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
end
end
it "parses mustaches with string parameters" do
ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] }
ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] }
end
it "parses mustaches with INTEGER parameters" do
ast_for("{{foo 1}}").should == program { mustache id("foo"), [integer("1")] }
ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] }
end
it "parses mustaches with BOOLEAN parameters" do
ast_for("{{foo true}}").should == program { mustache id("foo"), [boolean("true")] }
ast_for("{{foo false}}").should == program { mustache id("foo"), [boolean("false")] }
ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] }
ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
end
it "parses mutaches with DATA parameters" do
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
end
it "parses contents followed by a mustache" do
ast_for("foo bar {{baz}}").should == program do
ast_for("foo bar {{baz}}").should == root do
content "foo bar "
mustache id("baz")
end
end
it "parses a partial" do
ast_for("{{> foo }}").should == program { partial id("foo") }
ast_for("{{> foo }}").should == root { partial id("foo") }
end
it "parses a partial with context" do
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") }
end
it "parses a comment" do
ast_for("{{! this is a comment }}").should == program do
ast_for("{{! this is a comment }}").should == root do
comment " this is a comment "
end
end
it "parses a multi-line comment" do
ast_for("{{!\nthis is a multi-line comment\n}}").should == program do
ast_for("{{!\nthis is a multi-line comment\n}}").should == root do
multiline_comment "this is a multi-line comment"
end
end
it "parses an inverse section" do
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do
block do
mustache id("foo")
@@ -236,11 +250,11 @@ describe "Parser" do
end
it "parses a standalone inverse section" do
ast_for("{{^foo}}bar{{/foo}}").should == program do
inverted_block do
ast_for("{{^foo}}bar{{/foo}}").should == root do
block do
mustache id("foo")
program do
inverse do
content "bar"
end
end
+217 -125
View File
@@ -1,4 +1,28 @@
module("basic context");
var Handlebars;
if (!Handlebars) {
// Setup for Node package testing
Handlebars = require('../lib/handlebars');
var assert = require("assert"),
equal = assert.equal,
equals = assert.equal,
ok = assert.ok;
// Note that this doesn't have the same context separation as the rspec test.
// Both should be run for full acceptance of the two libary modes.
var CompilerContext = {
compile: function(template, options) {
var templateSpec = Handlebars.precompile(template, options);
return Handlebars.template(eval('(' + templateSpec + ')'));
},
compileWithPartial: function(template, options) {
return Handlebars.compile(template, options);
}
};
}
suite("basic context");
Handlebars.registerHelper('helperMissing', function(helper, context) {
if(helper === "link_to") {
@@ -6,13 +30,13 @@ Handlebars.registerHelper('helperMissing', function(helper, context) {
}
});
var shouldCompileTo = function(string, hashOrArray, expected, message) {
function shouldCompileTo(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
};
var shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
}
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
helpers = hashOrArray[1];
var helpers = hashOrArray[1];
if(helpers) {
for(var prop in Handlebars.helpers) {
@@ -27,11 +51,11 @@ var shouldCompileToWithPartials = function(string, hashOrArray, partials, expect
ary = [hashOrArray];
}
result = template.apply(this, ary);
var result = template.apply(this, ary);
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
};
}
var shouldThrow = function(fn, exception, message) {
function shouldThrow(fn, exception, message) {
var caught = false;
try {
fn();
@@ -45,6 +69,9 @@ var shouldThrow = function(fn, exception, message) {
ok(caught, message || null);
}
test("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
});
test("compiling with a basic context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
@@ -109,13 +136,6 @@ test("functions", function() {
"functions are called and render their output");
});
test("functions with context argument", function() {
shouldCompileTo("{{awesome frank}}",
{awesome: function(context) { return context; },
frank: "Frank"},
"Frank", "functions are called with context arguments");
});
test("paths with hyphens", function() {
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
});
@@ -162,12 +182,12 @@ test("this keyword in paths", function() {
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
"This keyword in paths evaluates to current context");
string = "{{#hellos}}{{this/text}}{{/hellos}}"
string = "{{#hellos}}{{this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
});
module("inverted sections");
suite("inverted sections");
test("inverted sections with unset value", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
@@ -187,10 +207,10 @@ test("inverted section with empty set", function() {
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
});
module("blocks");
suite("blocks");
test("array", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"Arrays iterate over the contents when not empty");
@@ -201,7 +221,7 @@ test("array", function() {
});
test("empty block", function() {
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "cruel world!",
"Arrays iterate over the contents when not empty");
@@ -215,30 +235,30 @@ test("nested iteration", function() {
});
test("block with complex lookup", function() {
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
"Templates can access variables in contexts up the stack with relative path syntax");
});
test("helper with complex lookup", function() {
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"
test("helper with complex lookup$", function() {
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function(prefix) {
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>"
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
}};
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>")
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
});
test("helper block with complex lookup expression", function() {
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
var hash = {name: "Alan"};
var helpers = {goodbyes: function(fn) {
var helpers = {goodbyes: function(options) {
var out = "";
var byes = ["Goodbye", "goodbye", "GOODBYE"];
for (var i = 0,j = byes.length; i < j; i++) {
out += byes[i] + " " + fn(this) + "! ";
out += byes[i] + " " + options.fn(this) + "! ";
}
return out;
}};
@@ -248,8 +268,8 @@ test("helper block with complex lookup expression", function() {
test("helper with complex lookup and nested template", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function (prefix, fn) {
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>";
var helpers = {link: function (prefix, options) {
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}};
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
});
@@ -257,8 +277,8 @@ test("helper with complex lookup and nested template", function() {
test("helper with complex lookup and nested template in VM+Compiler", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function (prefix, fn) {
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>";
var helpers = {link: function (prefix, options) {
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}};
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
});
@@ -274,22 +294,22 @@ test("block helper", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
var template = CompilerContext.compile(string);
result = template({world: "world"}, { helpers: {goodbyes: function(fn) { return fn({text: "GOODBYE"}); }}});
var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}});
equal(result, "GOODBYE! cruel world!", "Block helper executed");
});
test("block helper staying in the same context", function() {
var string = "{{#form}}<p>{{name}}</p>{{/form}}"
var string = "{{#form}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string);
result = template({name: "Yehuda"}, {helpers: {form: function(fn) { return "<form>" + fn(this) + "</form>" } }});
var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
});
test("block helper should have context in this", function() {
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
var link = function(fn) {
return '<a href="/people/' + this.id + '">' + fn(this) + '</a>';
var link = function(options) {
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
};
var data = { "people": [
{ "name": "Alan", "id": 1 },
@@ -304,31 +324,31 @@ test("block helper for undefined value", function() {
});
test("block helper passing a new context", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}"
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string);
result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, fn) { return "<form>" + fn(context) + "</form>" }}});
var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
});
test("block helper passing a complex path context", function() {
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}"
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string);
result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, fn) { return "<form>" + fn(context) + "</form>" }}});
var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
});
test("nested block helpers", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}"
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
var template = CompilerContext.compile(string);
result = template({
var result = template({
yehuda: {name: "Yehuda" }
}, {
helpers: {
link: function(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>" },
form: function(context, fn) { return "<form>" + fn(context) + "</form>" }
link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
}
});
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
@@ -345,7 +365,7 @@ test("block inverted sections with empty arrays", function() {
});
test("block helper inverted sections", function() {
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
var list = function(context, options) {
if (context.length > 0) {
var out = "<ul>";
@@ -366,7 +386,7 @@ test("block helper inverted sections", function() {
var rootMessage = {
people: [],
message: "Nobody's here"
}
};
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
@@ -377,13 +397,13 @@ test("block helper inverted sections", function() {
shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block");
});
module("helpers hash");
suite("helpers hash");
test("providing a helpers hash", function() {
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!",
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
"helpers hash is available");
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}],
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
"Goodbye cruel world!", "helpers hash is available inside other blocks");
});
@@ -395,7 +415,7 @@ test("the helpers hash is available is nested contexts", function() {
});
module("partials");
suite("partials");
test("basic partials", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
@@ -431,9 +451,6 @@ test("rendering undefined partial throws an exception", function() {
test("rendering template partial in vm mode throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
var string = "Dudes: {{>dude}} {{another_dude}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
template();
}, Handlebars.Exception, "Should throw exception");
});
@@ -462,7 +479,7 @@ test("Partials with literal paths", function() {
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
module("String literal parameters");
suite("String literal parameters");
test("simple literals work", function() {
var string = 'Message: {{hello "world" 12 true false}}';
@@ -472,7 +489,7 @@ test("simple literals work", function() {
if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
}}
}};
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
});
@@ -485,37 +502,37 @@ test("using a quote in the middle of a parameter raises an error", function() {
test("escaping a String is possible", function(){
var string = 'Message: {{{hello "\\"world\\""}}}';
var hash = {}
var helpers = {hello: function(param) { return "Hello " + param; }}
var hash = {};
var helpers = {hello: function(param) { return "Hello " + param; }};
shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
});
test("it works with ' marks", function() {
var string = 'Message: {{{hello "Alan\'s world"}}}';
var hash = {}
var helpers = {hello: function(param) { return "Hello " + param; }}
var hash = {};
var helpers = {hello: function(param) { return "Hello " + param; }};
shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
});
module("multiple parameters");
suite("multiple parameters");
test("simple multi-params work", function() {
var string = 'Message: {{goodbye cruel world}}';
var hash = {cruel: "cruel", world: "world"}
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}
var hash = {cruel: "cruel", world: "world"};
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }};
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
});
test("block multi-params work", function() {
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
var hash = {cruel: "cruel", world: "world"}
var helpers = {goodbye: function(cruel, world, fn) {
return fn({greeting: "Goodbye", adj: cruel, noun: world});
}}
var hash = {cruel: "cruel", world: "world"};
var helpers = {goodbye: function(cruel, world, options) {
return options.fn({greeting: "Goodbye", adj: cruel, noun: world});
}};
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
})
});
module("safestring");
suite("safestring");
test("constructing a safestring from a string and checking its type", function() {
var safe = new Handlebars.SafeString("testing 1, 2, 3");
@@ -523,56 +540,56 @@ test("constructing a safestring from a string and checking its type", function()
equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
});
module("helperMissing");
suite("helperMissing");
test("if a context is not found, helperMissing is used", function() {
var string = "{{hello}} {{link_to world}}"
var string = "{{hello}} {{link_to world}}";
var context = { hello: "Hello", world: "world" };
shouldCompileTo(string, context, "Hello <a>world</a>")
shouldCompileTo(string, context, "Hello <a>world</a>");
});
module("knownHelpers");
suite("knownHelpers");
test("Known helper should render helper", function() {
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}})
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}});
var result = template({}, {helpers: {hello: function() { return "foo"; }}});
equal(result, "foo", "'foo' should === '" + result);
});
test("Unknown helper in knownHelpers only mode should be passed as undefined", function() {
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true})
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
equal(result, "undefined", "'undefined' should === '" + result);
});
test("Builtin helpers available in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true})
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true});
var result = template({});
equal(result, "bar", "'bar' should === '" + result);
});
test("Field lookup works in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true})
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
var result = template({foo: 'bar'});
equal(result, "bar", "'bar' should === '" + result);
});
test("Conditional blocks work in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true})
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true});
var result = template({foo: 'baz'});
equal(result, "bar", "'bar' should === '" + result);
});
test("Invert blocks work in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true})
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true});
var result = template({foo: false});
equal(result, "bar", "'bar' should === '" + result);
});
module("blockHelperMissing");
suite("blockHelperMissing");
test("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
var string = "{{#truthy}}yep{{/truthy}}";
@@ -581,7 +598,7 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi
});
var teardown;
module("built-in helpers", {
suite("built-in helpers", {
setup: function(){ teardown = null; },
teardown: function(){ if (teardown) { teardown(); } }
});
@@ -609,18 +626,18 @@ test("if", function() {
test("if with function argument", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: function() {return true}, world: "world"}, "GOODBYE cruel world!",
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns true");
shouldCompileTo(string, {goodbye: function() {return this.world}, world: "world"}, "GOODBYE cruel world!",
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns string");
shouldCompileTo(string, {goodbye: function() {return false}, world: "world"}, "cruel world!",
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns false");
shouldCompileTo(string, {goodbye: function() {return this.foo}, world: "world"}, "cruel world!",
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns undefined");
});
test("each", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array argument iterates over the contents when not empty");
@@ -628,14 +645,24 @@ test("each", function() {
"each with array argument ignores the contents when empty");
});
test("each with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
test("log", function() {
var string = "{{log blah}}"
var string = "{{log blah}}";
var hash = { blah: "whee" };
var logArg;
var originalLog = Handlebars.log;
Handlebars.log = function(arg){ logArg = arg; }
teardown = function(){ Handlebars.log = originalLog; }
Handlebars.log = function(arg){ logArg = arg; };
teardown = function(){ Handlebars.log = originalLog; };
shouldCompileTo(string, hash, "", "log should not display");
equals("whee", logArg, "should call log with 'whee'");
@@ -659,6 +686,71 @@ test("passing in data to a compiled function that expects data - works with help
equals("happy cat", result, "Data output by helper");
});
test("data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{@hello}}");
var result = template({}, { data: { hello: "hello" } });
equals("hello", result, "@foo retrieves template data");
});
var objectCreate = Handlebars.createFrame;
test("deep @foo triggers automatic top-level data", function() {
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
var helpers = objectCreate(Handlebars.helpers);
helpers.let = function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
frame[prop] = options.hash[prop];
}
return options.fn(this, { data: frame });
};
var result = template({ foo: true }, { helpers: helpers });
equals("Hello world", result, "Automatic data was triggered");
});
test("parameter data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello @world}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
test("hash values can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello noun=@world}}");
var helpers = {
hello: function(options) {
return "Hello " + options.hash.noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
test("data is inherited downstream", function() {
var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true });
var helpers = {
let: function(options) {
for (var prop in options.hash) {
options.data[prop] = options.hash[prop];
}
return options.fn(this);
}
};
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
equals("hello world", result, "data variables are inherited downstream");
});
test("passing in data to a compiled function that expects data - works with helpers in partials", function() {
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
@@ -693,8 +785,8 @@ test("passing in data to a compiled function that expects data - works with bloc
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn(this);
hello: function(options) {
return options.fn(this);
},
world: function(options) {
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
@@ -709,8 +801,8 @@ test("passing in data to a compiled function that expects data - works with bloc
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn({exclaim: "?"});
hello: function(options) {
return options.fn({exclaim: "?"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -725,8 +817,8 @@ test("passing in data to a compiled function that expects data - data is passed
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn, inverse) {
return fn.data.accessData + " " + fn({exclaim: "?"});
hello: function(options) {
return options.data.accessData + " " + options.fn({exclaim: "?"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -741,8 +833,8 @@ test("you can override inherited data when invoking a helper", function() {
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
hello: function(options) {
return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -758,8 +850,8 @@ test("you can override inherited data when invoking a helper with depth", functi
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn({exclaim: "?"}, { data: {adjective: "sad"} });
hello: function(options) {
return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
@@ -776,14 +868,14 @@ test("helpers take precedence over same-named context properties", function() {
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
}
};
var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
goodbye: "goodbye",
world: "world"
};
@@ -792,20 +884,20 @@ test("helpers take precedence over same-named context properties", function() {
equals(result, "GOODBYE cruel WORLD", "Helper executed");
});
test("helpers take precedence over same-named context properties", function() {
test("helpers take precedence over same-named context properties$", function() {
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
var helpers = {
goodbye: function(fn) {
return this.goodbye.toUpperCase() + fn(this);
goodbye: function(options) {
return this.goodbye.toUpperCase() + options.fn(this);
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
}
};
var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
goodbye: "goodbye",
world: "world"
};
@@ -820,14 +912,14 @@ test("Scoped names take precedence over helpers", function() {
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
}
};
},
var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
};
var context = {
goodbye: "goodbye",
world: "world"
};
@@ -840,16 +932,16 @@ test("Scoped names take precedence over block helpers", function() {
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
var helpers = {
goodbye: function(fn) {
return this.goodbye.toUpperCase() + fn(this);
}
};
goodbye: function(options) {
return this.goodbye.toUpperCase() + options.fn(this);
},
var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
};
var context = {
goodbye: "goodbye",
world: "world"
};
@@ -892,8 +984,8 @@ test("helpers can take an optional hash with booleans", function() {
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
var result = template(context, {helpers: helpers});
template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
result = template(context, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean helper parameter honored");
});
@@ -927,8 +1019,8 @@ test("block helpers can take an optional hash with booleans", function() {
var result = template({}, {helpers: helpers});
equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
var result = template({}, {helpers: helpers});
template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
result = template({}, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean hash parameter honored");
});
@@ -1009,7 +1101,7 @@ test("when inside a block in String mode, .. passes the appropriate context in t
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
});
module("Regressions")
suite("Regressions");
test("GH-94: Cannot read property of undefined", function() {
var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
@@ -1028,13 +1120,13 @@ test("GH-150: Inverted sections print when they shouldn't", function() {
});
test("Mustache man page", function() {
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}"
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}";
var data = {
"name": "Chris",
"value": 10000,
"taxed_value": 10000 - (10000 * 0.4),
"in_ca": true
}
};
shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
});
+7 -7
View File
@@ -47,15 +47,15 @@ module Handlebars
def self.load_helpers(context)
context["exports"] = nil
context["p"] = proc do |val|
context["p"] = proc do |this, val|
p val if ENV["DEBUG_JS"]
end
context["puts"] = proc do |val|
context["puts"] = proc do |this, val|
puts val if ENV["DEBUG_JS"]
end
context["puts_node"] = proc do |val|
context["puts_node"] = proc do |this, val|
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
puts
end
@@ -82,12 +82,12 @@ module Handlebars
context["CompilerContext"] = {}
CompilerContext = context["CompilerContext"]
CompilerContext["compile"] = proc do |*args|
CompilerContext["compile"] = proc do |this, *args|
template, options = args[0], args[1] || nil
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
end
CompilerContext["compileWithPartial"] = proc do |*args|
CompilerContext["compileWithPartial"] = proc do |this, *args|
template, options = args[0], args[1] || nil
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
end
@@ -108,7 +108,7 @@ module Handlebars
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str|
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level
@@ -133,7 +133,7 @@ module Handlebars
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str|
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level
+14
View File
@@ -244,6 +244,20 @@ describe "Tokenizer" do
result[2].should be_token("ID", "omg")
end
it "tokenizes special @ identifiers" do
result = tokenize("{{ @foo }}")
result.should match_tokens %w( OPEN DATA CLOSE )
result[1].should be_token("DATA", "foo")
result = tokenize("{{ foo @bar }}")
result.should match_tokens %w( OPEN ID DATA CLOSE )
result[2].should be_token("DATA", "bar")
result = tokenize("{{ foo bar=@baz }}")
result.should match_tokens %w( OPEN ID ID EQUALS DATA CLOSE )
result[4].should be_token("DATA", "baz")
end
it "does not time out in a mustache with a single } followed by EOF" do
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
end
+1
View File
@@ -31,6 +31,7 @@
<mu>"}}}" { this.popState(); return 'CLOSE'; }
<mu>"}}" { this.popState(); return 'CLOSE'; }
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
<mu>"true"/[}\s] { return 'BOOLEAN'; }
<mu>"false"/[}\s] { return 'BOOLEAN'; }
<mu>[0-9]+/[}\s] { return 'INTEGER'; }
+5 -2
View File
@@ -18,8 +18,8 @@ statements
;
statement
: openInverse program closeBlock { $$ = new yy.InverseNode($1, $2, $3); }
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $3); }
: openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
| mustache { $$ = $1; }
| partial { $$ = $1; }
| CONTENT { $$ = new yy.ContentNode($1); }
@@ -58,6 +58,7 @@ inMustache
| path params { $$ = [[$1].concat($2), null]; }
| path hash { $$ = [[$1], $2]; }
| path { $$ = [[$1], null]; }
| DATA { $$ = [[new yy.DataNode($1)], null]; }
;
params
@@ -70,6 +71,7 @@ param
| STRING { $$ = new yy.StringNode($1); }
| INTEGER { $$ = new yy.IntegerNode($1); }
| BOOLEAN { $$ = new yy.BooleanNode($1); }
| DATA { $$ = new yy.DataNode($1); }
;
hash
@@ -86,6 +88,7 @@ hashSegment
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
;
path