Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Refactor acceptance test #426

Closed
wants to merge 3 commits into from

2 participants

@leshill

No description provided.

@kpdecker
Collaborator

I know we chatted about this a bit but I don't remember all of the details. What was the goal of these changes again?

@leshill

Hi @kpdecker,

Simple: use QUnit to run the acceptance (JS-only) test.

@kpdecker
Collaborator

But what are the advantages to having qunit tests only? Remove the dependency on both ruby and node for the developer tools?

One of the advantages of having the split layer here is that the ruby-based acceptance test allow for testing the behavior in a manner that is both representative of the client mode with the full compiler and with only the runtime. This is important for us to test as the node-environment is not the only one out there.

Unless we can get this sort of coverage within the node-based tests (might be possible with child contexts, etc) I don't think that it's good to drop the acceptance tests from the ruby tests and frankly I don't want to drop the ruby tests until we can get coverage for all of the test cases that are implemented in the ruby environment, including the tokenizer and parser tests.

I'm investigating testling a bit for better client-level testing but I don't think that this is something that is quite ready yet as some of the tests seem to fail due to environmental issues rather than library issues.
https://ci.testling.com/kpdecker/handlebars.js

@leshill

Hi @kpdecker,

The primary goal is to use QUnit, and not what the Ruby integration spec is providing (QUnit-like). It has been a while, but the suite running in Ruby was not working as expected for new specs (not pushed). There were multiple levels of problems.

However, the specs worked as expected when run under npm QUnit or the browser. On the other hand, the tokenizer and parser Ruby tests work well and as expected (they are written in Ruby).

Absolutely agree that there is an advantage to running the client mode spec in isolation. That might be better served by independent suites (files) and probably should be done regardless of how the integration tests are run.

@kpdecker
Collaborator

@leshill I have noticed a few issues myself. Mostly revolving around trying to apply changes to the Handlebars object that is accessible to the tests but these not being available when attempting to run some of the test behaviors.

73f2016 resolved most of these issues for me. Does that help with the tests that were failing for yourself?

@kpdecker
Collaborator

@leshill I think I'm going to close this rather than letting it sit around.

I'm all for trying to unify the testing environment under node (but @wycats might have opinions there :) ), I just don't think that we should do it part way. I.e. I'd like to move all of the tests over including the parser tests, etc and still have the same level of coverage in terms of runtime available vs. not.

@kpdecker kpdecker closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 149 additions and 301 deletions.
  1. +6 −3 Rakefile
  2. +0 −101 spec/acceptance_spec.rb
  3. +143 −197 spec/qunit_spec.js
View
9 Rakefile
@@ -26,8 +26,8 @@ end
task :compile => "lib/handlebars/compiler/parser.js"
-desc "run the spec suite"
-task :spec => [:release] do
+desc "run the rspec suite"
+task :rspec => [:release] do
rc = system "rspec -cfs spec"
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
@@ -38,7 +38,10 @@ task :npm_test => [:release] do
fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
-task :default => [:compile, :spec, :npm_test]
+desc "run both rspec and npm test suites"
+task :spec => [:rspec, :npm_test]
+
+task :default => [:compile, :spec]
def remove_exports(string)
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
View
101 spec/acceptance_spec.rb
@@ -1,101 +0,0 @@
-require "spec_helper"
-
-class TestContext
- class TestModule
- attr_reader :name, :tests
-
- def initialize(name)
- @name = name
- @tests = []
- end
- end
-
- attr_reader :modules
-
- def initialize
- @modules = []
- end
-
- def module(name)
- @modules << TestModule.new(name)
- end
-
- def test(name, function)
- @modules.last.tests << [name, function]
- end
-end
-
-test_context = TestContext.new
-js_context = Handlebars::Spec::CONTEXT
-
-Module.new do
- extend Test::Unit::Assertions
-
- def self.js_backtrace(context)
- begin
- context.eval("throw")
- rescue V8::JSError => e
- return e.backtrace(:javascript)
- end
- end
-
- js_context["p"] = proc do |this, str|
- p str
- end
-
- js_context["ok"] = proc do |this, ok, message|
- js_context["$$RSPEC1$$"] = ok
-
- result = js_context.eval("!!$$RSPEC1$$")
-
- message ||= "#{ok} was not truthy"
-
- unless result
- backtrace = js_backtrace(js_context)
- message << "\n#{backtrace.join("\n")}"
- end
-
- assert result, message
- end
-
- js_context["equals"] = proc do |this, first, second, message|
- js_context["$$RSPEC1$$"] = first
- js_context["$$RSPEC2$$"] = second
-
- result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
-
- additional_message = "#{first.inspect} did not == #{second.inspect}"
- message = message ? "#{message} (#{additional_message})" : additional_message
-
- unless result
- backtrace = js_backtrace(js_context)
- message << "\n#{backtrace.join("\n")}"
- end
-
- assert result, message
- end
-
- js_context["equal"] = js_context["equals"]
-
- js_context["suite"] = proc do |this, name|
- test_context.module(name)
- end
-
- js_context["test"] = proc do |this, name, function|
- test_context.test(name, function)
- end
-
- local = Regexp.escape(File.expand_path(Dir.pwd))
- qunit_spec = File.expand_path("../qunit_spec.js", __FILE__)
- js_context.load(qunit_spec.sub(/^#{local}\//, ''))
-end
-
-test_context.modules.each do |mod|
- describe mod.name do
- mod.tests.each do |name, function|
- it name do
- function.call
- end
- end
- end
-end
View
340 spec/qunit_spec.js
@@ -1,34 +1,27 @@
-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);
- }
- };
-} else {
- var _equal = equal;
- equals = equal = function(a, b, msg) {
- // Allow exec with missing message params
- _equal(a, b, msg || '');
- };
-}
+var Handlebars = require('../lib/handlebars');
+
+var CompilerContext = Handlebars.createFrame({
+ 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");
+var assert = require("assert"),
+equal = assert.equal,
+equals = assert.equal,
+ok = assert.ok;
+
+function cloneHelpers(helpers) {
+ var copy = Handlebars.createFrame(Handlebars.helpers);
+ for (var helper in helpers) {
+ copy[helper] = helpers[helper]
+ }
+ return copy;
+}
function shouldCompileTo(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
@@ -62,7 +55,7 @@ function compileWithPartials(string, hashOrArray, partials) {
function shouldThrow(fn, exception, message) {
var caught = false,
- exType, exMessage;
+ exType, exMessage;
if (exception instanceof Array) {
exType = exception[0];
@@ -88,40 +81,35 @@ function shouldThrow(fn, exception, message) {
ok(caught, message || null);
}
+suite("basic context");
+
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!",
- "It works if all the required keys are provided");
+ shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", "It works if all the required keys are provided");
});
test("comments", function() {
- shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
- {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
- "comments are ignored");
+ shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!", "comments are ignored");
});
test("boolean", function() {
var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
- shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
- "booleans show the contents when true");
-
- shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
- "booleans do not show the contents when false");
+ shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", "booleans show the contents when true");
+ shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", "booleans do not show the contents when false");
});
test("zeros", function() {
- shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
- "num1: 42, num2: 0");
- shouldCompileTo("num: {{.}}", 0, "num: 0");
- shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
+ shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0}, "num1: 42, num2: 0");
+ shouldCompileTo("num: {{.}}", 0, "num: 0");
+ shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
});
test("newlines", function() {
- shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
- shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
+ shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
+ shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
});
test("escaping text", function() {
@@ -133,30 +121,21 @@ test("escaping text", function() {
});
test("escaping expressions", function() {
- shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
- "expressions with 3 handlebars aren't escaped");
-
- shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
- "expressions with {{& handlebars aren't escaped");
-
- shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
- "by default expressions should be escaped");
-
- shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
- "escaping should properly handle amperstands");
+ shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>', "expressions with 3 handlebars aren't escaped");
+ shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>', "expressions with {{& handlebars aren't escaped");
+ shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;', "by default expressions should be escaped");
+ shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;', "escaping should properly handle amperstands");
});
test("functions returning safestrings shouldn't be escaped", function() {
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
- "functions returning safestrings aren't escaped");
+ "functions returning safestrings aren't escaped");
});
test("functions", function() {
- shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
- "functions are called and render their output");
- shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
- "functions are bound to the context");
+ shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome", "functions are called and render their output");
+ shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome", "functions are bound to the context");
});
test("paths with hyphens", function() {
@@ -166,29 +145,24 @@ test("paths with hyphens", function() {
});
test("nested paths", function() {
- shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
- "Goodbye beautiful world!", "Nested paths access nested objects");
+ shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}}, "Goodbye beautiful world!", "Nested paths access nested objects");
});
test("nested paths with empty string value", function() {
- shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
- "Goodbye world!", "Nested paths access nested objects with empty string");
+ shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}}, "Goodbye world!", "Nested paths access nested objects with empty string");
});
test("literal paths", function() {
- shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
- "Goodbye beautiful world!", "Literal paths can be used");
- shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
- "Goodbye beautiful world!", "Literal paths can be used");
+ shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}}, "Goodbye beautiful world!", "Literal paths can be used");
+ shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}}, "Goodbye beautiful world!", "Literal paths can be used");
});
test('literal references', function() {
- shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
- "Goodbye beautiful world!", "Literal paths can be used");
+ shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"}, "Goodbye beautiful world!", "Literal paths can be used");
});
test("that current context path ({{.}}) doesn't hit helpers", function() {
- shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
+ shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
});
test("complex but empty paths", function() {
@@ -199,8 +173,7 @@ test("complex but empty paths", function() {
test("this keyword in paths", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
- shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
- "This keyword in paths evaluates to current context");
+ shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE", "This keyword in paths evaluates to current context");
string = "{{#hellos}}{{this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
@@ -210,18 +183,17 @@ test("this keyword in paths", function() {
test("this keyword nested inside path", function() {
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
shouldThrow(function() {
- CompilerContext.compile(string);
- }, Error, "Should throw exception");
+ CompilerContext.compile(string);
+ }, Error, "Should throw exception");
});
test("this keyword in helpers", function() {
var helpers = {foo: function(value) {
- return 'bar ' + value;
+ return 'bar ' + value;
}};
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
- shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE",
- "This keyword in paths evaluates to current context");
+ shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE", "This keyword in paths evaluates to current context");
string = "{{#hellos}}{{foo this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
@@ -231,8 +203,8 @@ test("this keyword in helpers", function() {
test("this keyword nested inside helpers param", function() {
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
shouldThrow(function() {
- CompilerContext.compile(string);
- }, Error, "Should throw exception");
+ CompilerContext.compile(string);
+ }, Error, "Should throw exception");
});
suite("inverted sections");
@@ -260,11 +232,9 @@ suite("blocks");
test("array", function() {
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");
+ shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", "Arrays iterate over the contents when not empty");
- shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
- "Arrays ignore the contents when empty");
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", "Arrays ignore the contents when empty");
});
@@ -281,31 +251,26 @@ test("array with @index", function() {
test("empty block", function() {
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");
+ shouldCompileTo(string, hash, "cruel world!", "Arrays iterate over the contents when not empty");
- shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
- "Arrays ignore the contents when empty");
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", "Arrays ignore the contents when empty");
});
-test("nested iteration", function() {
-
-});
+test("nested iteration");
test("block with complex lookup", function() {
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");
+ 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("block with complex lookup using nested context", function() {
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
shouldThrow(function() {
- CompilerContext.compile(string);
- }, Error, "Should throw exception");
+ CompilerContext.compile(string);
+ }, Error, "Should throw exception");
});
test("helper with complex lookup$", function() {
@@ -321,11 +286,11 @@ test("helper block with complex lookup expression", function() {
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
var hash = {name: "Alan"};
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] + " " + options.fn(this) + "! ";
- }
+ var out = "";
+ var byes = ["Goodbye", "goodbye", "GOODBYE"];
+ for (var i = 0,j = byes.length; i < j; i++) {
+ out += byes[i] + " " + options.fn(this) + "! ";
+ }
return out;
}};
shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! ");
@@ -335,7 +300,7 @@ 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, options) {
- return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
+ return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}};
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
});
@@ -344,7 +309,7 @@ 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, options) {
- return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
+ return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}};
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
});
@@ -386,7 +351,7 @@ test("block helper should have context in this", function() {
});
test("block helper for undefined value", function() {
- shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
+ shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
});
test("block helper passing a new context", function() {
@@ -422,12 +387,12 @@ test("nested block helpers", function() {
test("block inverted sections", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
- "No people");
+ "No people");
});
test("block inverted sections with empty arrays", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
- "No people");
+ "No people");
});
test("block helper inverted sections", function() {
@@ -466,24 +431,18 @@ test("block helper inverted sections", function() {
suite("helpers hash");
test("providing a helpers hash", function() {
- shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
- "helpers hash is available");
+ 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: function() { return "world"; }}],
- "Goodbye cruel world!", "helpers hash is available inside other blocks");
+ shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}], "Goodbye cruel world!", "helpers hash is available inside other blocks");
});
test("in cases of conflict, helpers win", function() {
- shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
- "helpers hash has precedence escaped expansion");
- shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
- "helpers hash has precedence simple expansion");
+ shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers", "helpers hash has precedence escaped expansion");
+ shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers", "helpers hash has precedence simple expansion");
});
test("the helpers hash is available is nested contexts", function() {
- shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
- [{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
- "helpers hash is available in nested contexts.");
+ shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}", [{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper", "helpers hash is available in nested contexts.");
});
suite("partials");
@@ -492,16 +451,14 @@ test("basic partials", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
var partial = "{{name}} ({{url}}) ";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
- shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
- "Basic partials output based on current context.");
+ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", "Basic partials output based on current context.");
});
test("partials with context", function() {
var string = "Dudes: {{>dude dudes}}";
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
- shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
- "Partials can be passed a context");
+ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", "Partials can be passed a context");
});
test("partial in a partial", function() {
@@ -514,16 +471,16 @@ test("partial in a partial", function() {
test("rendering undefined partial throws an exception", function() {
shouldThrow(function() {
- var template = CompilerContext.compile("{{> whatever}}");
- template();
- }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
+ var template = CompilerContext.compile("{{> whatever}}");
+ template();
+ }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
});
test("rendering template partial in vm mode throws an exception", function() {
shouldThrow(function() {
- var template = CompilerContext.compile("{{> whatever}}");
- template();
- }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
+ var template = CompilerContext.compile("{{> whatever}}");
+ template();
+ }, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
});
test("rendering function partial in vm mode", function() {
@@ -537,27 +494,26 @@ test("rendering function partial in vm mode", function() {
});
test("GH-14: a partial preceding a selector", function() {
- var string = "Dudes: {{>dude}} {{another_dude}}";
- var dude = "{{name}}";
- var hash = {name:"Jeepers", another_dude:"Creepers"};
- shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
+ var string = "Dudes: {{>dude}} {{another_dude}}";
+ var dude = "{{name}}";
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
+ shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
});
test("Partials with slash paths", function() {
- var string = "Dudes: {{> shared/dude}}";
- var dude = "{{name}}";
- var hash = {name:"Jeepers", another_dude:"Creepers"};
+ var string = "Dudes: {{> shared/dude}}";
+ var dude = "{{name}}";
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
test("Partials with integer path", function() {
- var string = "Dudes: {{> 404}}";
- var dude = "{{name}}";
- var hash = {name:"Jeepers", another_dude:"Creepers"};
+ var string = "Dudes: {{> 404}}";
+ var dude = "{{name}}";
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
-
suite("String literal parameters");
test("simple literals work", function() {
@@ -571,6 +527,7 @@ test("simple literals work", function() {
}};
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
});
+
test("negative number literals work", function() {
var string = 'Message: {{hello -12}}';
var hash = {};
@@ -639,9 +596,9 @@ suite("helperMissing");
test("if a context is not found, helperMissing is used", function() {
shouldThrow(function() {
- var template = CompilerContext.compile("{{hello}} {{link_to world}}");
- template({});
- }, [Error, "Could not find property 'link_to'"], "Should throw exception");
+ var template = CompilerContext.compile("{{hello}} {{link_to world}}");
+ template({});
+ }, [Error, "Could not find property 'link_to'"], "Should throw exception");
});
test("if a context is not found, custom helperMissing is used", function() {
@@ -674,30 +631,35 @@ test("Unknown helper in knownHelpers only mode should be passed as undefined", f
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 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 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 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 result = template({foo: false});
equal(result, "bar", "'bar' should === '" + result);
});
+
test("Functions are bound to the context in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
@@ -717,11 +679,12 @@ test("lambdas resolved by blockHelperMissing are bound to the context", function
shouldCompileTo(string, boundData, "");
});
+
+suite("built-in helpers");
+
var teardown;
-suite("built-in helpers", {
- setup: function(){ teardown = null; },
- teardown: function(){ if (teardown) { teardown(); } }
-});
+beforeEach(function() { teardown = null; });
+afterEach(function() { if (teardown) { teardown(); }});
test("with", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
@@ -730,39 +693,27 @@ test("with", function() {
test("if", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
- shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
- "if with boolean argument shows the contents when true");
- shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
- "if with string argument shows the contents");
- shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
- "if with boolean argument does not show the contents when false");
- shouldCompileTo(string, {world: "world"}, "cruel world!",
- "if with undefined does not show the contents");
- shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
- "if with non-empty array shows the contents");
- shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
- "if with empty array does not show the contents");
+ shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!", "if with boolean argument shows the contents when true");
+ shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!", "if with string argument shows the contents");
+ shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!", "if with boolean argument does not show the contents when false");
+ shouldCompileTo(string, {world: "world"}, "cruel world!", "if with undefined does not show the contents");
+ shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!", "if with non-empty array shows the contents");
+ shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!", "if with empty array does not show the contents");
});
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!",
- "if with function shows the contents when function returns true");
- 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!",
- "if with function does not show the contents when returns false");
- shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
- "if with function does not show the contents when returns undefined");
+ 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!", "if with function shows the contents when function returns string");
+ 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!", "if with function does not show the contents when returns undefined");
});
test("each", function() {
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");
- shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
- "each with array argument ignores the contents when empty");
+ shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!", "each with array argument iterates over the contents when not empty");
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!", "each with array argument ignores the contents when empty");
});
test("each with an object and @key", function() {
@@ -796,7 +747,12 @@ test("data passed to helpers", function() {
var hash = {letters: ['a', 'b', 'c']};
var template = CompilerContext.compile(string);
+ var helpers = cloneHelpers({
+ detectDataInsideEach: function(options) { return options.data && options.data.exclaim; }
+ });
+
var result = template(hash, {
+ helpers: helpers,
data: {
exclaim: '!'
}
@@ -804,10 +760,6 @@ test("data passed to helpers", function() {
equal(result, 'a!b!c!', 'should output data');
});
-Handlebars.registerHelper('detectDataInsideEach', function(options) {
- return options.data && options.data.exclaim;
-});
-
test("log", function() {
var string = "{{log blah}}";
var hash = { blah: "whee" };
@@ -822,10 +774,7 @@ test("log", function() {
equals("whee", logArg, "should call log with 'whee'");
});
-test("overriding property lookup", function() {
-
-});
-
+test("overriding property lookup");
test("passing in data to a compiled function that expects data - works with helpers", function() {
var template = CompilerContext.compile("{{hello}}", {data: true});
@@ -846,21 +795,19 @@ test("data can be looked up via @foo", function() {
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);
+ var helpers = cloneHelpers({
+ let: function(options) {
+ var frame = Handlebars.createFrame(options.data);
- for (var prop in options.hash) {
- frame[prop] = options.hash[prop];
+ for (var prop in options.hash) {
+ frame[prop] = options.hash[prop];
+ }
+ return options.fn(this, { data: frame });
}
- return options.fn(this, { data: frame });
- };
+ });
var result = template({ foo: true }, { helpers: helpers });
equals("Hello world", result, "Automatic data was triggered");
@@ -1212,7 +1159,7 @@ test("when using block form, arguments to helpers can be retrieved from options
var helpers = {
wycats: function(passiveVoice, noun, options) {
return "HELP ME MY BOSS " + passiveVoice + ' ' +
- noun + ': ' + options.fn(this);
+ noun + ': ' + options.fn(this);
}
};
@@ -1227,7 +1174,7 @@ test("when inside a block in String mode, .. passes the appropriate context in t
var helpers = {
tomdale: function(desire, noun, options) {
return "STOP ME FROM READING HACKER NEWS I " +
- options.contexts[0][desire] + " " + noun;
+ options.contexts[0][desire] + " " + noun;
},
"with": function(context, options) {
@@ -1292,8 +1239,8 @@ test("when inside a block in String mode, .. passes the appropriate context in t
var helpers = {
tomdale: function(desire, noun, options) {
return "STOP ME FROM READING HACKER NEWS I " +
- options.contexts[0][desire] + " " + noun + " " +
- options.fn(this);
+ options.contexts[0][desire] + " " + noun + " " +
+ options.fn(this);
},
"with": function(context, options) {
@@ -1313,10 +1260,9 @@ test("when inside a block in String mode, .. passes the appropriate context in t
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"}]};
- var string = "{{#books}}{{title}}{{author.name}}{{/books}}";
- shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes",
- "Renders without an undefined property error");
+ var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
+ var string = "{{#books}}{{title}}{{author.name}}{{/books}}";
+ shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes", "Renders without an undefined property error");
});
test("GH-150: Inverted sections print when they shouldn't", function() {
Something went wrong with that request. Please try again.