handlebars 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/handlebars/context.rb +3 -5
- data/lib/handlebars/version.rb +1 -1
- data/spec/handlebars_spec.rb +3 -3
- data/vendor/handlebars/.gitignore +3 -1
- data/vendor/handlebars/.jshintrc +2 -0
- data/vendor/handlebars/.npmignore +0 -1
- data/vendor/handlebars/.rspec +1 -0
- data/vendor/handlebars/Gemfile +1 -1
- data/vendor/handlebars/LICENSE +0 -1
- data/vendor/handlebars/README.markdown +8 -6
- data/vendor/handlebars/Rakefile +14 -21
- data/vendor/handlebars/bench/benchwarmer.js +1 -1
- data/vendor/handlebars/bench/handlebars.js +43 -34
- data/vendor/handlebars/bin/handlebars +56 -2
- data/vendor/handlebars/dist/handlebars.js +2201 -0
- data/vendor/handlebars/dist/handlebars.runtime.js +321 -0
- data/vendor/handlebars/lib/handlebars/base.js +68 -15
- data/vendor/handlebars/lib/handlebars/compiler/ast.js +50 -20
- data/vendor/handlebars/lib/handlebars/compiler/base.js +7 -13
- data/vendor/handlebars/lib/handlebars/compiler/compiler.js +758 -299
- data/vendor/handlebars/lib/handlebars/compiler/printer.js +24 -30
- data/vendor/handlebars/lib/handlebars/runtime.js +23 -3
- data/vendor/handlebars/lib/handlebars/utils.js +9 -10
- data/vendor/handlebars/package.json +15 -5
- data/vendor/handlebars/spec/acceptance_spec.rb +5 -5
- data/vendor/handlebars/spec/parser_spec.rb +201 -32
- data/vendor/handlebars/spec/qunit_spec.js +462 -159
- data/vendor/handlebars/spec/spec_helper.rb +7 -7
- data/vendor/handlebars/spec/tokenizer_spec.rb +55 -8
- data/vendor/handlebars/src/handlebars.l +14 -3
- data/vendor/handlebars/src/handlebars.yy +15 -5
- data/vendor/handlebars/src/parser-prefix.js +1 -0
- data/vendor/handlebars/src/parser-suffix.js +4 -0
- metadata +8 -3
@@ -1,22 +1,52 @@
|
|
1
|
-
|
1
|
+
var Handlebars;
|
2
|
+
if (!Handlebars) {
|
3
|
+
// Setup for Node package testing
|
4
|
+
Handlebars = require('../lib/handlebars');
|
5
|
+
|
6
|
+
var assert = require("assert"),
|
7
|
+
|
8
|
+
equal = assert.equal,
|
9
|
+
equals = assert.equal,
|
10
|
+
ok = assert.ok;
|
11
|
+
|
12
|
+
// Note that this doesn't have the same context separation as the rspec test.
|
13
|
+
// Both should be run for full acceptance of the two libary modes.
|
14
|
+
var CompilerContext = {
|
15
|
+
compile: function(template, options) {
|
16
|
+
var templateSpec = Handlebars.precompile(template, options);
|
17
|
+
return Handlebars.template(eval('(' + templateSpec + ')'));
|
18
|
+
},
|
19
|
+
compileWithPartial: function(template, options) {
|
20
|
+
return Handlebars.compile(template, options);
|
21
|
+
}
|
22
|
+
};
|
23
|
+
} else {
|
24
|
+
var _equal = equal;
|
25
|
+
equals = equal = function(a, b, msg) {
|
26
|
+
// Allow exec with missing message params
|
27
|
+
_equal(a, b, msg || '');
|
28
|
+
};
|
29
|
+
}
|
2
30
|
|
3
|
-
|
4
|
-
if(helper === "link_to") {
|
5
|
-
return new Handlebars.SafeString("<a>" + context + "</a>");
|
6
|
-
}
|
7
|
-
});
|
31
|
+
suite("basic context");
|
8
32
|
|
9
|
-
|
33
|
+
function shouldCompileTo(string, hashOrArray, expected, message) {
|
10
34
|
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
11
|
-
}
|
12
|
-
|
35
|
+
}
|
36
|
+
|
37
|
+
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
38
|
+
var result = compileWithPartials(string, hashOrArray, partials);
|
39
|
+
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
|
40
|
+
}
|
41
|
+
|
42
|
+
function compileWithPartials(string, hashOrArray, partials) {
|
13
43
|
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
|
14
44
|
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
15
|
-
helpers = hashOrArray[1];
|
45
|
+
var helpers = hashOrArray[1];
|
16
46
|
|
17
47
|
if(helpers) {
|
18
48
|
for(var prop in Handlebars.helpers) {
|
19
|
-
helpers[prop] = Handlebars.helpers[prop];
|
49
|
+
helpers[prop] = helpers[prop] || Handlebars.helpers[prop];
|
20
50
|
}
|
21
51
|
}
|
22
52
|
|
@@ -27,24 +57,40 @@ var shouldCompileToWithPartials = function(string, hashOrArray, partials, expect
|
|
27
57
|
ary = [hashOrArray];
|
28
58
|
}
|
29
59
|
|
30
|
-
|
31
|
-
|
32
|
-
|
60
|
+
return template.apply(this, ary);
|
61
|
+
}
|
62
|
+
|
63
|
+
function shouldThrow(fn, exception, message) {
|
64
|
+
var caught = false,
|
65
|
+
exType, exMessage;
|
66
|
+
|
67
|
+
if (exception instanceof Array) {
|
68
|
+
exType = exception[0];
|
69
|
+
exMessage = exception[1];
|
70
|
+
} else if (typeof exception === 'string') {
|
71
|
+
exType = Error;
|
72
|
+
exMessage = exception;
|
73
|
+
} else {
|
74
|
+
exType = exception;
|
75
|
+
}
|
33
76
|
|
34
|
-
var shouldThrow = function(fn, exception, message) {
|
35
|
-
var caught = false;
|
36
77
|
try {
|
37
78
|
fn();
|
38
79
|
}
|
39
80
|
catch (e) {
|
40
|
-
if (e instanceof
|
41
|
-
|
81
|
+
if (e instanceof exType) {
|
82
|
+
if (!exMessage || e.message === exMessage) {
|
83
|
+
caught = true;
|
84
|
+
}
|
42
85
|
}
|
43
86
|
}
|
44
87
|
|
45
88
|
ok(caught, message || null);
|
46
89
|
}
|
47
90
|
|
91
|
+
test("most basic", function() {
|
92
|
+
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
|
93
|
+
});
|
48
94
|
|
49
95
|
test("compiling with a basic context", function() {
|
50
96
|
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
@@ -67,8 +113,8 @@ test("boolean", function() {
|
|
67
113
|
});
|
68
114
|
|
69
115
|
test("zeros", function() {
|
70
|
-
|
71
|
-
|
116
|
+
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
|
117
|
+
"num1: 42, num2: 0");
|
72
118
|
shouldCompileTo("num: {{.}}", 0, "num: 0");
|
73
119
|
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
|
74
120
|
});
|
@@ -96,6 +142,8 @@ test("escaping expressions", function() {
|
|
96
142
|
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
97
143
|
"by default expressions should be escaped");
|
98
144
|
|
145
|
+
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;',
|
146
|
+
"escaping should properly handle amperstands");
|
99
147
|
});
|
100
148
|
|
101
149
|
test("functions returning safestrings shouldn't be escaped", function() {
|
@@ -107,17 +155,14 @@ test("functions returning safestrings shouldn't be escaped", function() {
|
|
107
155
|
test("functions", function() {
|
108
156
|
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
109
157
|
"functions are called and render their output");
|
110
|
-
});
|
111
|
-
|
112
|
-
test("functions with context argument", function() {
|
113
|
-
shouldCompileTo("{{awesome frank}}",
|
114
|
-
{awesome: function(context) { return context; },
|
115
|
-
frank: "Frank"},
|
116
|
-
"Frank", "functions are called with context arguments");
|
158
|
+
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
|
159
|
+
"functions are bound to the context");
|
117
160
|
});
|
118
161
|
|
119
162
|
test("paths with hyphens", function() {
|
120
163
|
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
164
|
+
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
165
|
+
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
121
166
|
});
|
122
167
|
|
123
168
|
test("nested paths", function() {
|
@@ -131,20 +176,15 @@ test("nested paths with empty string value", function() {
|
|
131
176
|
});
|
132
177
|
|
133
178
|
test("literal paths", function() {
|
134
|
-
|
135
|
-
|
179
|
+
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
|
180
|
+
"Goodbye beautiful world!", "Literal paths can be used");
|
181
|
+
shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
|
182
|
+
"Goodbye beautiful world!", "Literal paths can be used");
|
136
183
|
});
|
137
184
|
|
138
|
-
test(
|
139
|
-
|
140
|
-
|
141
|
-
shouldThrow(function() {
|
142
|
-
CompilerContext.compile("{{#goodbyes}}{{../name/../name}}{{/goodbyes}}")(hash);
|
143
|
-
}, Handlebars.Exception,
|
144
|
-
"Cannot jump (..) into previous context after moving into a context.");
|
145
|
-
|
146
|
-
var string = "{{#goodbyes}}{{.././world}} {{/goodbyes}}";
|
147
|
-
shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths");
|
185
|
+
test('literal references', function() {
|
186
|
+
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
|
187
|
+
"Goodbye beautiful world!", "Literal paths can be used");
|
148
188
|
});
|
149
189
|
|
150
190
|
test("that current context path ({{.}}) doesn't hit helpers", function() {
|
@@ -162,12 +202,40 @@ test("this keyword in paths", function() {
|
|
162
202
|
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
|
163
203
|
"This keyword in paths evaluates to current context");
|
164
204
|
|
165
|
-
string = "{{#hellos}}{{this/text}}{{/hellos}}"
|
205
|
+
string = "{{#hellos}}{{this/text}}{{/hellos}}";
|
166
206
|
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
167
207
|
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
|
168
208
|
});
|
169
209
|
|
170
|
-
|
210
|
+
test("this keyword nested inside path", function() {
|
211
|
+
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
|
212
|
+
shouldThrow(function() {
|
213
|
+
CompilerContext.compile(string);
|
214
|
+
}, Error, "Should throw exception");
|
215
|
+
});
|
216
|
+
|
217
|
+
test("this keyword in helpers", function() {
|
218
|
+
var helpers = {foo: function(value) {
|
219
|
+
return 'bar ' + value;
|
220
|
+
}};
|
221
|
+
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
|
222
|
+
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
223
|
+
shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE",
|
224
|
+
"This keyword in paths evaluates to current context");
|
225
|
+
|
226
|
+
string = "{{#hellos}}{{foo this/text}}{{/hellos}}";
|
227
|
+
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
228
|
+
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
|
229
|
+
});
|
230
|
+
|
231
|
+
test("this keyword nested inside helpers param", function() {
|
232
|
+
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
|
233
|
+
shouldThrow(function() {
|
234
|
+
CompilerContext.compile(string);
|
235
|
+
}, Error, "Should throw exception");
|
236
|
+
});
|
237
|
+
|
238
|
+
suite("inverted sections");
|
171
239
|
|
172
240
|
test("inverted sections with unset value", function() {
|
173
241
|
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
@@ -187,10 +255,10 @@ test("inverted section with empty set", function() {
|
|
187
255
|
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
|
188
256
|
});
|
189
257
|
|
190
|
-
|
258
|
+
suite("blocks");
|
191
259
|
|
192
260
|
test("array", function() {
|
193
|
-
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"
|
261
|
+
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
194
262
|
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
195
263
|
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
196
264
|
"Arrays iterate over the contents when not empty");
|
@@ -200,8 +268,18 @@ test("array", function() {
|
|
200
268
|
|
201
269
|
});
|
202
270
|
|
271
|
+
test("array with @index", function() {
|
272
|
+
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
|
273
|
+
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
274
|
+
|
275
|
+
var template = CompilerContext.compile(string);
|
276
|
+
var result = template(hash);
|
277
|
+
|
278
|
+
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
279
|
+
});
|
280
|
+
|
203
281
|
test("empty block", function() {
|
204
|
-
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"
|
282
|
+
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
205
283
|
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
206
284
|
shouldCompileTo(string, hash, "cruel world!",
|
207
285
|
"Arrays iterate over the contents when not empty");
|
@@ -215,30 +293,38 @@ test("nested iteration", function() {
|
|
215
293
|
});
|
216
294
|
|
217
295
|
test("block with complex lookup", function() {
|
218
|
-
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"
|
296
|
+
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
|
219
297
|
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
|
220
298
|
|
221
299
|
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
|
222
300
|
"Templates can access variables in contexts up the stack with relative path syntax");
|
223
301
|
});
|
224
302
|
|
225
|
-
test("
|
226
|
-
var string = "{{#goodbyes}}{{{
|
303
|
+
test("block with complex lookup using nested context", function() {
|
304
|
+
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
|
305
|
+
|
306
|
+
shouldThrow(function() {
|
307
|
+
CompilerContext.compile(string);
|
308
|
+
}, Error, "Should throw exception");
|
309
|
+
});
|
310
|
+
|
311
|
+
test("helper with complex lookup$", function() {
|
312
|
+
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
|
227
313
|
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
228
314
|
var helpers = {link: function(prefix) {
|
229
|
-
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>"
|
315
|
+
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
|
230
316
|
}};
|
231
|
-
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>")
|
317
|
+
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
|
232
318
|
});
|
233
319
|
|
234
320
|
test("helper block with complex lookup expression", function() {
|
235
|
-
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"
|
321
|
+
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
|
236
322
|
var hash = {name: "Alan"};
|
237
|
-
var helpers = {goodbyes: function(
|
323
|
+
var helpers = {goodbyes: function(options) {
|
238
324
|
var out = "";
|
239
325
|
var byes = ["Goodbye", "goodbye", "GOODBYE"];
|
240
326
|
for (var i = 0,j = byes.length; i < j; i++) {
|
241
|
-
out += byes[i] + " " + fn(this) + "! ";
|
327
|
+
out += byes[i] + " " + options.fn(this) + "! ";
|
242
328
|
}
|
243
329
|
return out;
|
244
330
|
}};
|
@@ -248,8 +334,8 @@ test("helper block with complex lookup expression", function() {
|
|
248
334
|
test("helper with complex lookup and nested template", function() {
|
249
335
|
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
250
336
|
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
251
|
-
var helpers = {link: function (prefix,
|
252
|
-
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>";
|
337
|
+
var helpers = {link: function (prefix, options) {
|
338
|
+
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
|
253
339
|
}};
|
254
340
|
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
|
255
341
|
});
|
@@ -257,8 +343,8 @@ test("helper with complex lookup and nested template", function() {
|
|
257
343
|
test("helper with complex lookup and nested template in VM+Compiler", function() {
|
258
344
|
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
259
345
|
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
260
|
-
var helpers = {link: function (prefix,
|
261
|
-
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>";
|
346
|
+
var helpers = {link: function (prefix, options) {
|
347
|
+
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
|
262
348
|
}};
|
263
349
|
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
|
264
350
|
});
|
@@ -274,22 +360,22 @@ test("block helper", function() {
|
|
274
360
|
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
275
361
|
var template = CompilerContext.compile(string);
|
276
362
|
|
277
|
-
result = template({world: "world"}, { helpers: {goodbyes: function(
|
363
|
+
var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}});
|
278
364
|
equal(result, "GOODBYE! cruel world!", "Block helper executed");
|
279
365
|
});
|
280
366
|
|
281
367
|
test("block helper staying in the same context", function() {
|
282
|
-
var string = "{{#form}}<p>{{name}}</p>{{/form}}"
|
368
|
+
var string = "{{#form}}<p>{{name}}</p>{{/form}}";
|
283
369
|
var template = CompilerContext.compile(string);
|
284
370
|
|
285
|
-
result = template({name: "Yehuda"}, {helpers: {form: function(
|
371
|
+
var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
|
286
372
|
equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
|
287
373
|
});
|
288
374
|
|
289
375
|
test("block helper should have context in this", function() {
|
290
376
|
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
291
|
-
var link = function(
|
292
|
-
return '<a href="/people/' + this.id + '">' + fn(this) + '</a>';
|
377
|
+
var link = function(options) {
|
378
|
+
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
293
379
|
};
|
294
380
|
var data = { "people": [
|
295
381
|
{ "name": "Alan", "id": 1 },
|
@@ -304,31 +390,31 @@ test("block helper for undefined value", function() {
|
|
304
390
|
});
|
305
391
|
|
306
392
|
test("block helper passing a new context", function() {
|
307
|
-
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}"
|
393
|
+
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}";
|
308
394
|
var template = CompilerContext.compile(string);
|
309
395
|
|
310
|
-
result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context,
|
396
|
+
var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
|
311
397
|
equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
|
312
398
|
});
|
313
399
|
|
314
400
|
test("block helper passing a complex path context", function() {
|
315
|
-
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}"
|
401
|
+
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
|
316
402
|
var template = CompilerContext.compile(string);
|
317
403
|
|
318
|
-
result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context,
|
404
|
+
var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
|
319
405
|
equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
|
320
406
|
});
|
321
407
|
|
322
408
|
test("nested block helpers", function() {
|
323
|
-
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}"
|
409
|
+
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
|
324
410
|
var template = CompilerContext.compile(string);
|
325
411
|
|
326
|
-
result = template({
|
412
|
+
var result = template({
|
327
413
|
yehuda: {name: "Yehuda" }
|
328
414
|
}, {
|
329
415
|
helpers: {
|
330
|
-
link: function(
|
331
|
-
form: function(context,
|
416
|
+
link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
|
417
|
+
form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
|
332
418
|
}
|
333
419
|
});
|
334
420
|
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
|
@@ -345,7 +431,7 @@ test("block inverted sections with empty arrays", function() {
|
|
345
431
|
});
|
346
432
|
|
347
433
|
test("block helper inverted sections", function() {
|
348
|
-
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"
|
434
|
+
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
|
349
435
|
var list = function(context, options) {
|
350
436
|
if (context.length > 0) {
|
351
437
|
var out = "<ul>";
|
@@ -366,7 +452,7 @@ test("block helper inverted sections", function() {
|
|
366
452
|
var rootMessage = {
|
367
453
|
people: [],
|
368
454
|
message: "Nobody's here"
|
369
|
-
}
|
455
|
+
};
|
370
456
|
|
371
457
|
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
|
372
458
|
|
@@ -377,25 +463,30 @@ test("block helper inverted sections", function() {
|
|
377
463
|
shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody's here</p>", "the context of an inverse is the parent of the block");
|
378
464
|
});
|
379
465
|
|
380
|
-
|
466
|
+
suite("helpers hash");
|
381
467
|
|
382
468
|
test("providing a helpers hash", function() {
|
383
|
-
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!",
|
469
|
+
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
|
384
470
|
"helpers hash is available");
|
385
471
|
|
386
|
-
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}],
|
472
|
+
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
|
387
473
|
"Goodbye cruel world!", "helpers hash is available inside other blocks");
|
388
474
|
});
|
389
475
|
|
390
|
-
test("in cases of conflict,
|
391
|
-
|
476
|
+
test("in cases of conflict, helpers win", function() {
|
477
|
+
shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
478
|
+
"helpers hash has precedence escaped expansion");
|
479
|
+
shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
480
|
+
"helpers hash has precedence simple expansion");
|
392
481
|
});
|
393
482
|
|
394
483
|
test("the helpers hash is available is nested contexts", function() {
|
395
|
-
|
484
|
+
shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
|
485
|
+
[{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
|
486
|
+
"helpers hash is available in nested contexts.");
|
396
487
|
});
|
397
488
|
|
398
|
-
|
489
|
+
suite("partials");
|
399
490
|
|
400
491
|
test("basic partials", function() {
|
401
492
|
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
@@ -425,17 +516,14 @@ test("rendering undefined partial throws an exception", function() {
|
|
425
516
|
shouldThrow(function() {
|
426
517
|
var template = CompilerContext.compile("{{> whatever}}");
|
427
518
|
template();
|
428
|
-
}, Handlebars.Exception, "Should throw exception");
|
519
|
+
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
|
429
520
|
});
|
430
521
|
|
431
522
|
test("rendering template partial in vm mode throws an exception", function() {
|
432
523
|
shouldThrow(function() {
|
433
524
|
var template = CompilerContext.compile("{{> whatever}}");
|
434
|
-
var string = "Dudes: {{>dude}} {{another_dude}}";
|
435
|
-
var dude = "{{name}}";
|
436
|
-
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
437
525
|
template();
|
438
|
-
}, Handlebars.Exception, "Should throw exception");
|
526
|
+
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
|
439
527
|
});
|
440
528
|
|
441
529
|
test("rendering function partial in vm mode", function() {
|
@@ -455,14 +543,22 @@ test("GH-14: a partial preceding a selector", function() {
|
|
455
543
|
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
|
456
544
|
});
|
457
545
|
|
458
|
-
test("Partials with
|
459
|
-
var string = "Dudes: {{>
|
546
|
+
test("Partials with slash paths", function() {
|
547
|
+
var string = "Dudes: {{> shared/dude}}";
|
548
|
+
var dude = "{{name}}";
|
549
|
+
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
550
|
+
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
551
|
+
});
|
552
|
+
|
553
|
+
test("Partials with integer path", function() {
|
554
|
+
var string = "Dudes: {{> 404}}";
|
460
555
|
var dude = "{{name}}";
|
461
556
|
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
462
|
-
shouldCompileToWithPartials(string, [hash, {}, {
|
557
|
+
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
463
558
|
});
|
464
559
|
|
465
|
-
|
560
|
+
|
561
|
+
suite("String literal parameters");
|
466
562
|
|
467
563
|
test("simple literals work", function() {
|
468
564
|
var string = 'Message: {{hello "world" 12 true false}}';
|
@@ -472,7 +568,7 @@ test("simple literals work", function() {
|
|
472
568
|
if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
|
473
569
|
if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
|
474
570
|
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
|
475
|
-
}}
|
571
|
+
}};
|
476
572
|
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
|
477
573
|
});
|
478
574
|
|
@@ -485,37 +581,37 @@ test("using a quote in the middle of a parameter raises an error", function() {
|
|
485
581
|
|
486
582
|
test("escaping a String is possible", function(){
|
487
583
|
var string = 'Message: {{{hello "\\"world\\""}}}';
|
488
|
-
var hash = {}
|
489
|
-
var helpers = {hello: function(param) { return "Hello " + param; }}
|
584
|
+
var hash = {};
|
585
|
+
var helpers = {hello: function(param) { return "Hello " + param; }};
|
490
586
|
shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
|
491
587
|
});
|
492
588
|
|
493
589
|
test("it works with ' marks", function() {
|
494
590
|
var string = 'Message: {{{hello "Alan\'s world"}}}';
|
495
|
-
var hash = {}
|
496
|
-
var helpers = {hello: function(param) { return "Hello " + param; }}
|
591
|
+
var hash = {};
|
592
|
+
var helpers = {hello: function(param) { return "Hello " + param; }};
|
497
593
|
shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
|
498
594
|
});
|
499
595
|
|
500
|
-
|
596
|
+
suite("multiple parameters");
|
501
597
|
|
502
598
|
test("simple multi-params work", function() {
|
503
599
|
var string = 'Message: {{goodbye cruel world}}';
|
504
|
-
var hash = {cruel: "cruel", world: "world"}
|
505
|
-
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}
|
600
|
+
var hash = {cruel: "cruel", world: "world"};
|
601
|
+
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }};
|
506
602
|
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
|
507
603
|
});
|
508
604
|
|
509
605
|
test("block multi-params work", function() {
|
510
606
|
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
|
511
|
-
var hash = {cruel: "cruel", world: "world"}
|
512
|
-
var helpers = {goodbye: function(cruel, world,
|
513
|
-
return fn({greeting: "Goodbye", adj: cruel, noun: world});
|
514
|
-
}}
|
607
|
+
var hash = {cruel: "cruel", world: "world"};
|
608
|
+
var helpers = {goodbye: function(cruel, world, options) {
|
609
|
+
return options.fn({greeting: "Goodbye", adj: cruel, noun: world});
|
610
|
+
}};
|
515
611
|
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
|
516
|
-
})
|
612
|
+
});
|
517
613
|
|
518
|
-
|
614
|
+
suite("safestring");
|
519
615
|
|
520
616
|
test("constructing a safestring from a string and checking its type", function() {
|
521
617
|
var safe = new Handlebars.SafeString("testing 1, 2, 3");
|
@@ -523,65 +619,90 @@ test("constructing a safestring from a string and checking its type", function()
|
|
523
619
|
equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
|
524
620
|
});
|
525
621
|
|
526
|
-
|
622
|
+
suite("helperMissing");
|
527
623
|
|
528
624
|
test("if a context is not found, helperMissing is used", function() {
|
529
|
-
|
625
|
+
shouldThrow(function() {
|
626
|
+
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
|
627
|
+
template({});
|
628
|
+
}, [Error, "Could not find property 'link_to'"], "Should throw exception");
|
629
|
+
});
|
630
|
+
|
631
|
+
test("if a context is not found, custom helperMissing is used", function() {
|
632
|
+
var string = "{{hello}} {{link_to world}}";
|
530
633
|
var context = { hello: "Hello", world: "world" };
|
531
634
|
|
532
|
-
|
635
|
+
var helpers = {
|
636
|
+
helperMissing: function(helper, context) {
|
637
|
+
if(helper === "link_to") {
|
638
|
+
return new Handlebars.SafeString("<a>" + context + "</a>");
|
639
|
+
}
|
640
|
+
}
|
641
|
+
};
|
642
|
+
|
643
|
+
shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
|
533
644
|
});
|
534
645
|
|
535
|
-
|
646
|
+
suite("knownHelpers");
|
536
647
|
|
537
648
|
test("Known helper should render helper", function() {
|
538
|
-
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}})
|
649
|
+
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}});
|
539
650
|
|
540
651
|
var result = template({}, {helpers: {hello: function() { return "foo"; }}});
|
541
652
|
equal(result, "foo", "'foo' should === '" + result);
|
542
653
|
});
|
543
654
|
|
544
655
|
test("Unknown helper in knownHelpers only mode should be passed as undefined", function() {
|
545
|
-
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true})
|
656
|
+
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
|
546
657
|
|
547
658
|
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
|
548
659
|
equal(result, "undefined", "'undefined' should === '" + result);
|
549
660
|
});
|
550
661
|
test("Builtin helpers available in knownHelpers only mode", function() {
|
551
|
-
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true})
|
662
|
+
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true});
|
552
663
|
|
553
664
|
var result = template({});
|
554
665
|
equal(result, "bar", "'bar' should === '" + result);
|
555
666
|
});
|
556
667
|
test("Field lookup works in knownHelpers only mode", function() {
|
557
|
-
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true})
|
668
|
+
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
558
669
|
|
559
670
|
var result = template({foo: 'bar'});
|
560
671
|
equal(result, "bar", "'bar' should === '" + result);
|
561
672
|
});
|
562
673
|
test("Conditional blocks work in knownHelpers only mode", function() {
|
563
|
-
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true})
|
674
|
+
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true});
|
564
675
|
|
565
676
|
var result = template({foo: 'baz'});
|
566
677
|
equal(result, "bar", "'bar' should === '" + result);
|
567
678
|
});
|
568
679
|
test("Invert blocks work in knownHelpers only mode", function() {
|
569
|
-
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true})
|
680
|
+
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true});
|
570
681
|
|
571
682
|
var result = template({foo: false});
|
572
683
|
equal(result, "bar", "'bar' should === '" + result);
|
573
684
|
});
|
685
|
+
test("Functions are bound to the context in knownHelpers only mode", function() {
|
686
|
+
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
687
|
+
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
|
688
|
+
equal(result, "bar", "'bar' should === '" + result);
|
689
|
+
});
|
574
690
|
|
575
|
-
|
691
|
+
suite("blockHelperMissing");
|
576
692
|
|
577
693
|
test("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
|
578
694
|
var string = "{{#truthy}}yep{{/truthy}}";
|
579
695
|
var data = { truthy: function() { return true; } };
|
580
696
|
shouldCompileTo(string, data, "yep");
|
581
697
|
});
|
698
|
+
test("lambdas resolved by blockHelperMissing are bound to the context", function() {
|
699
|
+
var string = "{{#truthy}}yep{{/truthy}}";
|
700
|
+
var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
|
701
|
+
shouldCompileTo(string, boundData, "");
|
702
|
+
});
|
582
703
|
|
583
704
|
var teardown;
|
584
|
-
|
705
|
+
suite("built-in helpers", {
|
585
706
|
setup: function(){ teardown = null; },
|
586
707
|
teardown: function(){ if (teardown) { teardown(); } }
|
587
708
|
});
|
@@ -609,18 +730,18 @@ test("if", function() {
|
|
609
730
|
|
610
731
|
test("if with function argument", function() {
|
611
732
|
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
612
|
-
shouldCompileTo(string, {goodbye: function() {return true}, world: "world"}, "GOODBYE cruel world!",
|
733
|
+
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
|
613
734
|
"if with function shows the contents when function returns true");
|
614
|
-
shouldCompileTo(string, {goodbye: function() {return this.world}, world: "world"}, "GOODBYE cruel world!",
|
735
|
+
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
|
615
736
|
"if with function shows the contents when function returns string");
|
616
|
-
shouldCompileTo(string, {goodbye: function() {return false}, world: "world"}, "cruel world!",
|
737
|
+
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
|
617
738
|
"if with function does not show the contents when returns false");
|
618
|
-
shouldCompileTo(string, {goodbye: function() {return this.foo}, world: "world"}, "cruel world!",
|
739
|
+
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
|
619
740
|
"if with function does not show the contents when returns undefined");
|
620
741
|
});
|
621
742
|
|
622
743
|
test("each", function() {
|
623
|
-
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"
|
744
|
+
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
624
745
|
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
625
746
|
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
626
747
|
"each with array argument iterates over the contents when not empty");
|
@@ -628,16 +749,60 @@ test("each", function() {
|
|
628
749
|
"each with array argument ignores the contents when empty");
|
629
750
|
});
|
630
751
|
|
752
|
+
test("each with an object and @key", function() {
|
753
|
+
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
754
|
+
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
|
755
|
+
|
756
|
+
// Object property iteration order is undefined according to ECMA spec,
|
757
|
+
// so we need to check both possible orders
|
758
|
+
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
759
|
+
var actual = compileWithPartials(string, hash);
|
760
|
+
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
761
|
+
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
762
|
+
|
763
|
+
ok(actual === expected1 || actual === expected2, "each with object argument iterates over the contents when not empty");
|
764
|
+
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
765
|
+
"each with object argument ignores the contents when empty");
|
766
|
+
});
|
767
|
+
|
768
|
+
test("each with @index", function() {
|
769
|
+
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
770
|
+
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
771
|
+
|
772
|
+
var template = CompilerContext.compile(string);
|
773
|
+
var result = template(hash);
|
774
|
+
|
775
|
+
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
776
|
+
});
|
777
|
+
|
778
|
+
test("data passed to helpers", function() {
|
779
|
+
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
|
780
|
+
var hash = {letters: ['a', 'b', 'c']};
|
781
|
+
|
782
|
+
var template = CompilerContext.compile(string);
|
783
|
+
var result = template(hash, {
|
784
|
+
data: {
|
785
|
+
exclaim: '!'
|
786
|
+
}
|
787
|
+
});
|
788
|
+
equal(result, 'a!b!c!', 'should output data');
|
789
|
+
});
|
790
|
+
|
791
|
+
Handlebars.registerHelper('detectDataInsideEach', function(options) {
|
792
|
+
return options.data && options.data.exclaim;
|
793
|
+
});
|
794
|
+
|
631
795
|
test("log", function() {
|
632
|
-
var string = "{{log blah}}"
|
796
|
+
var string = "{{log blah}}";
|
633
797
|
var hash = { blah: "whee" };
|
634
798
|
|
635
|
-
var logArg;
|
799
|
+
var levelArg, logArg;
|
636
800
|
var originalLog = Handlebars.log;
|
637
|
-
Handlebars.log = function(arg){ logArg = arg; }
|
638
|
-
teardown = function(){ Handlebars.log = originalLog; }
|
801
|
+
Handlebars.log = function(level, arg){ levelArg = level, logArg = arg; };
|
802
|
+
teardown = function(){ Handlebars.log = originalLog; };
|
639
803
|
|
640
804
|
shouldCompileTo(string, hash, "", "log should not display");
|
805
|
+
equals(1, levelArg, "should call log with 1");
|
641
806
|
equals("whee", logArg, "should call log with 'whee'");
|
642
807
|
});
|
643
808
|
|
@@ -659,6 +824,71 @@ test("passing in data to a compiled function that expects data - works with help
|
|
659
824
|
equals("happy cat", result, "Data output by helper");
|
660
825
|
});
|
661
826
|
|
827
|
+
test("data can be looked up via @foo", function() {
|
828
|
+
var template = CompilerContext.compile("{{@hello}}");
|
829
|
+
var result = template({}, { data: { hello: "hello" } });
|
830
|
+
equals("hello", result, "@foo retrieves template data");
|
831
|
+
});
|
832
|
+
|
833
|
+
var objectCreate = Handlebars.createFrame;
|
834
|
+
|
835
|
+
test("deep @foo triggers automatic top-level data", function() {
|
836
|
+
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
|
837
|
+
|
838
|
+
var helpers = objectCreate(Handlebars.helpers);
|
839
|
+
|
840
|
+
helpers.let = function(options) {
|
841
|
+
var frame = Handlebars.createFrame(options.data);
|
842
|
+
|
843
|
+
for (var prop in options.hash) {
|
844
|
+
frame[prop] = options.hash[prop];
|
845
|
+
}
|
846
|
+
return options.fn(this, { data: frame });
|
847
|
+
};
|
848
|
+
|
849
|
+
var result = template({ foo: true }, { helpers: helpers });
|
850
|
+
equals("Hello world", result, "Automatic data was triggered");
|
851
|
+
});
|
852
|
+
|
853
|
+
test("parameter data can be looked up via @foo", function() {
|
854
|
+
var template = CompilerContext.compile("{{hello @world}}");
|
855
|
+
var helpers = {
|
856
|
+
hello: function(noun) {
|
857
|
+
return "Hello " + noun;
|
858
|
+
}
|
859
|
+
};
|
860
|
+
|
861
|
+
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
862
|
+
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
863
|
+
});
|
864
|
+
|
865
|
+
test("hash values can be looked up via @foo", function() {
|
866
|
+
var template = CompilerContext.compile("{{hello noun=@world}}");
|
867
|
+
var helpers = {
|
868
|
+
hello: function(options) {
|
869
|
+
return "Hello " + options.hash.noun;
|
870
|
+
}
|
871
|
+
};
|
872
|
+
|
873
|
+
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
874
|
+
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
875
|
+
});
|
876
|
+
|
877
|
+
test("data is inherited downstream", function() {
|
878
|
+
var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true });
|
879
|
+
var helpers = {
|
880
|
+
let: function(options) {
|
881
|
+
for (var prop in options.hash) {
|
882
|
+
options.data[prop] = options.hash[prop];
|
883
|
+
}
|
884
|
+
return options.fn(this);
|
885
|
+
}
|
886
|
+
};
|
887
|
+
|
888
|
+
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
|
889
|
+
equals("hello world", result, "data variables are inherited downstream");
|
890
|
+
});
|
891
|
+
|
662
892
|
test("passing in data to a compiled function that expects data - works with helpers in partials", function() {
|
663
893
|
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
|
664
894
|
|
@@ -693,8 +923,8 @@ test("passing in data to a compiled function that expects data - works with bloc
|
|
693
923
|
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
|
694
924
|
|
695
925
|
var helpers = {
|
696
|
-
hello: function(
|
697
|
-
return fn(this);
|
926
|
+
hello: function(options) {
|
927
|
+
return options.fn(this);
|
698
928
|
},
|
699
929
|
world: function(options) {
|
700
930
|
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
|
@@ -709,8 +939,8 @@ test("passing in data to a compiled function that expects data - works with bloc
|
|
709
939
|
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
710
940
|
|
711
941
|
var helpers = {
|
712
|
-
hello: function(
|
713
|
-
return fn({exclaim: "?"});
|
942
|
+
hello: function(options) {
|
943
|
+
return options.fn({exclaim: "?"});
|
714
944
|
},
|
715
945
|
world: function(thing, options) {
|
716
946
|
return options.data.adjective + " " + thing + (this.exclaim || "");
|
@@ -725,8 +955,8 @@ test("passing in data to a compiled function that expects data - data is passed
|
|
725
955
|
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
726
956
|
|
727
957
|
var helpers = {
|
728
|
-
hello: function(
|
729
|
-
return
|
958
|
+
hello: function(options) {
|
959
|
+
return options.data.accessData + " " + options.fn({exclaim: "?"});
|
730
960
|
},
|
731
961
|
world: function(thing, options) {
|
732
962
|
return options.data.adjective + " " + thing + (this.exclaim || "");
|
@@ -741,8 +971,8 @@ test("you can override inherited data when invoking a helper", function() {
|
|
741
971
|
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
|
742
972
|
|
743
973
|
var helpers = {
|
744
|
-
hello: function(
|
745
|
-
return fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
|
974
|
+
hello: function(options) {
|
975
|
+
return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
|
746
976
|
},
|
747
977
|
world: function(thing, options) {
|
748
978
|
return options.data.adjective + " " + thing + (this.exclaim || "");
|
@@ -758,8 +988,8 @@ test("you can override inherited data when invoking a helper with depth", functi
|
|
758
988
|
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
759
989
|
|
760
990
|
var helpers = {
|
761
|
-
hello: function(
|
762
|
-
return fn({exclaim: "?"}, { data: {adjective: "sad"} });
|
991
|
+
hello: function(options) {
|
992
|
+
return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
|
763
993
|
},
|
764
994
|
world: function(thing, options) {
|
765
995
|
return options.data.adjective + " " + thing + (this.exclaim || "");
|
@@ -776,14 +1006,14 @@ test("helpers take precedence over same-named context properties", function() {
|
|
776
1006
|
var helpers = {
|
777
1007
|
goodbye: function() {
|
778
1008
|
return this.goodbye.toUpperCase();
|
779
|
-
}
|
780
|
-
};
|
1009
|
+
},
|
781
1010
|
|
782
|
-
var context = {
|
783
1011
|
cruel: function(world) {
|
784
1012
|
return "cruel " + world.toUpperCase();
|
785
|
-
}
|
1013
|
+
}
|
1014
|
+
};
|
786
1015
|
|
1016
|
+
var context = {
|
787
1017
|
goodbye: "goodbye",
|
788
1018
|
world: "world"
|
789
1019
|
};
|
@@ -792,20 +1022,20 @@ test("helpers take precedence over same-named context properties", function() {
|
|
792
1022
|
equals(result, "GOODBYE cruel WORLD", "Helper executed");
|
793
1023
|
});
|
794
1024
|
|
795
|
-
test("helpers take precedence over same-named context properties", function() {
|
1025
|
+
test("helpers take precedence over same-named context properties$", function() {
|
796
1026
|
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
|
797
1027
|
|
798
1028
|
var helpers = {
|
799
|
-
goodbye: function(
|
800
|
-
return this.goodbye.toUpperCase() + fn(this);
|
801
|
-
}
|
802
|
-
};
|
1029
|
+
goodbye: function(options) {
|
1030
|
+
return this.goodbye.toUpperCase() + options.fn(this);
|
1031
|
+
},
|
803
1032
|
|
804
|
-
var context = {
|
805
1033
|
cruel: function(world) {
|
806
1034
|
return "cruel " + world.toUpperCase();
|
807
|
-
}
|
1035
|
+
}
|
1036
|
+
};
|
808
1037
|
|
1038
|
+
var context = {
|
809
1039
|
goodbye: "goodbye",
|
810
1040
|
world: "world"
|
811
1041
|
};
|
@@ -820,14 +1050,14 @@ test("Scoped names take precedence over helpers", function() {
|
|
820
1050
|
var helpers = {
|
821
1051
|
goodbye: function() {
|
822
1052
|
return this.goodbye.toUpperCase();
|
823
|
-
}
|
824
|
-
};
|
1053
|
+
},
|
825
1054
|
|
826
|
-
var context = {
|
827
1055
|
cruel: function(world) {
|
828
1056
|
return "cruel " + world.toUpperCase();
|
829
1057
|
},
|
1058
|
+
};
|
830
1059
|
|
1060
|
+
var context = {
|
831
1061
|
goodbye: "goodbye",
|
832
1062
|
world: "world"
|
833
1063
|
};
|
@@ -840,16 +1070,16 @@ test("Scoped names take precedence over block helpers", function() {
|
|
840
1070
|
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
|
841
1071
|
|
842
1072
|
var helpers = {
|
843
|
-
goodbye: function(
|
844
|
-
return this.goodbye.toUpperCase() + fn(this);
|
845
|
-
}
|
846
|
-
};
|
1073
|
+
goodbye: function(options) {
|
1074
|
+
return this.goodbye.toUpperCase() + options.fn(this);
|
1075
|
+
},
|
847
1076
|
|
848
|
-
var context = {
|
849
1077
|
cruel: function(world) {
|
850
1078
|
return "cruel " + world.toUpperCase();
|
851
1079
|
},
|
1080
|
+
};
|
852
1081
|
|
1082
|
+
var context = {
|
853
1083
|
goodbye: "goodbye",
|
854
1084
|
world: "world"
|
855
1085
|
};
|
@@ -892,8 +1122,8 @@ test("helpers can take an optional hash with booleans", function() {
|
|
892
1122
|
var result = template(context, {helpers: helpers});
|
893
1123
|
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
|
894
1124
|
|
895
|
-
|
896
|
-
|
1125
|
+
template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
|
1126
|
+
result = template(context, {helpers: helpers});
|
897
1127
|
equals(result, "NOT PRINTING", "Boolean helper parameter honored");
|
898
1128
|
});
|
899
1129
|
|
@@ -910,6 +1140,19 @@ test("block helpers can take an optional hash", function() {
|
|
910
1140
|
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
911
1141
|
});
|
912
1142
|
|
1143
|
+
test("block helpers can take an optional hash with single quoted stings", function() {
|
1144
|
+
var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}");
|
1145
|
+
|
1146
|
+
var helpers = {
|
1147
|
+
goodbye: function(options) {
|
1148
|
+
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
|
1149
|
+
}
|
1150
|
+
};
|
1151
|
+
|
1152
|
+
var result = template({}, {helpers: helpers});
|
1153
|
+
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
1154
|
+
});
|
1155
|
+
|
913
1156
|
test("block helpers can take an optional hash with booleans", function() {
|
914
1157
|
var helpers = {
|
915
1158
|
goodbye: function(options) {
|
@@ -927,8 +1170,8 @@ test("block helpers can take an optional hash with booleans", function() {
|
|
927
1170
|
var result = template({}, {helpers: helpers});
|
928
1171
|
equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
|
929
1172
|
|
930
|
-
|
931
|
-
|
1173
|
+
template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
|
1174
|
+
result = template({}, {helpers: helpers});
|
932
1175
|
equals(result, "NOT PRINTING", "Boolean hash parameter honored");
|
933
1176
|
});
|
934
1177
|
|
@@ -937,7 +1180,7 @@ test("arguments to helpers can be retrieved from options hash in string form", f
|
|
937
1180
|
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
|
938
1181
|
|
939
1182
|
var helpers = {
|
940
|
-
wycats: function(passiveVoice, noun
|
1183
|
+
wycats: function(passiveVoice, noun) {
|
941
1184
|
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
|
942
1185
|
}
|
943
1186
|
};
|
@@ -985,6 +1228,48 @@ test("when inside a block in String mode, .. passes the appropriate context in t
|
|
985
1228
|
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
|
986
1229
|
});
|
987
1230
|
|
1231
|
+
test("in string mode, information about the types is passed along", function() {
|
1232
|
+
var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true });
|
1233
|
+
|
1234
|
+
var helpers = {
|
1235
|
+
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
1236
|
+
equal(options.types[0], 'STRING', "the string type is passed");
|
1237
|
+
equal(options.types[1], 'ID', "the expression type is passed");
|
1238
|
+
equal(options.types[2], 'BOOLEAN', "the expression type is passed");
|
1239
|
+
equal(desire, "need", "the string form is passed for strings");
|
1240
|
+
equal(noun, "dad.joke", "the string form is passed for expressions");
|
1241
|
+
equal(trueBool, true, "raw booleans are passed through");
|
1242
|
+
equal(falseBool, false, "raw booleans are passed through");
|
1243
|
+
return "Helper called";
|
1244
|
+
}
|
1245
|
+
};
|
1246
|
+
|
1247
|
+
var result = template({}, { helpers: helpers });
|
1248
|
+
equal(result, "Helper called");
|
1249
|
+
});
|
1250
|
+
|
1251
|
+
test("in string mode, hash parameters get type information", function() {
|
1252
|
+
var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true });
|
1253
|
+
|
1254
|
+
var helpers = {
|
1255
|
+
tomdale: function(exclamation, options) {
|
1256
|
+
equal(exclamation, "he.says");
|
1257
|
+
equal(options.types[0], "ID");
|
1258
|
+
|
1259
|
+
equal(options.hashTypes.desire, "STRING");
|
1260
|
+
equal(options.hashTypes.noun, "ID");
|
1261
|
+
equal(options.hashTypes.bool, "BOOLEAN");
|
1262
|
+
equal(options.hash.desire, "need");
|
1263
|
+
equal(options.hash.noun, "dad.joke");
|
1264
|
+
equal(options.hash.bool, true);
|
1265
|
+
return "Helper called";
|
1266
|
+
}
|
1267
|
+
};
|
1268
|
+
|
1269
|
+
var result = template({}, { helpers: helpers });
|
1270
|
+
equal(result, "Helper called");
|
1271
|
+
});
|
1272
|
+
|
988
1273
|
test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
|
989
1274
|
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
|
990
1275
|
|
@@ -1009,7 +1294,7 @@ test("when inside a block in String mode, .. passes the appropriate context in t
|
|
1009
1294
|
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
|
1010
1295
|
});
|
1011
1296
|
|
1012
|
-
|
1297
|
+
suite("Regressions");
|
1013
1298
|
|
1014
1299
|
test("GH-94: Cannot read property of undefined", function() {
|
1015
1300
|
var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
|
@@ -1028,13 +1313,13 @@ test("GH-150: Inverted sections print when they shouldn't", function() {
|
|
1028
1313
|
});
|
1029
1314
|
|
1030
1315
|
test("Mustache man page", function() {
|
1031
|
-
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}"
|
1316
|
+
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}";
|
1032
1317
|
var data = {
|
1033
1318
|
"name": "Chris",
|
1034
1319
|
"value": 10000,
|
1035
1320
|
"taxed_value": 10000 - (10000 * 0.4),
|
1036
1321
|
"in_ca": true
|
1037
|
-
}
|
1322
|
+
};
|
1038
1323
|
|
1039
1324
|
shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
|
1040
1325
|
});
|
@@ -1065,3 +1350,21 @@ test("bug reported by @fat where lambdas weren't being properly resolved", funct
|
|
1065
1350
|
var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
|
1066
1351
|
shouldCompileTo(string, data, output);
|
1067
1352
|
});
|
1353
|
+
|
1354
|
+
test("Passing falsy values to Handlebars.compile throws an error", function() {
|
1355
|
+
shouldThrow(function() {
|
1356
|
+
CompilerContext.compile(null);
|
1357
|
+
}, "You must pass a string or Handlebars AST to Handlebars.compile. You passed null");
|
1358
|
+
});
|
1359
|
+
|
1360
|
+
test('GH-408: Multiple loops fail', function() {
|
1361
|
+
var context = [
|
1362
|
+
{ name: "John Doe", location: { city: "Chicago" } },
|
1363
|
+
{ name: "Jane Doe", location: { city: "New York"} }
|
1364
|
+
];
|
1365
|
+
|
1366
|
+
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
|
1367
|
+
|
1368
|
+
var result = template(context);
|
1369
|
+
equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times');
|
1370
|
+
});
|