handlebars 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/lib/handlebars/context.rb +3 -5
  2. data/lib/handlebars/version.rb +1 -1
  3. data/spec/handlebars_spec.rb +3 -3
  4. data/vendor/handlebars/.gitignore +3 -1
  5. data/vendor/handlebars/.jshintrc +2 -0
  6. data/vendor/handlebars/.npmignore +0 -1
  7. data/vendor/handlebars/.rspec +1 -0
  8. data/vendor/handlebars/Gemfile +1 -1
  9. data/vendor/handlebars/LICENSE +0 -1
  10. data/vendor/handlebars/README.markdown +8 -6
  11. data/vendor/handlebars/Rakefile +14 -21
  12. data/vendor/handlebars/bench/benchwarmer.js +1 -1
  13. data/vendor/handlebars/bench/handlebars.js +43 -34
  14. data/vendor/handlebars/bin/handlebars +56 -2
  15. data/vendor/handlebars/dist/handlebars.js +2201 -0
  16. data/vendor/handlebars/dist/handlebars.runtime.js +321 -0
  17. data/vendor/handlebars/lib/handlebars/base.js +68 -15
  18. data/vendor/handlebars/lib/handlebars/compiler/ast.js +50 -20
  19. data/vendor/handlebars/lib/handlebars/compiler/base.js +7 -13
  20. data/vendor/handlebars/lib/handlebars/compiler/compiler.js +758 -299
  21. data/vendor/handlebars/lib/handlebars/compiler/printer.js +24 -30
  22. data/vendor/handlebars/lib/handlebars/runtime.js +23 -3
  23. data/vendor/handlebars/lib/handlebars/utils.js +9 -10
  24. data/vendor/handlebars/package.json +15 -5
  25. data/vendor/handlebars/spec/acceptance_spec.rb +5 -5
  26. data/vendor/handlebars/spec/parser_spec.rb +201 -32
  27. data/vendor/handlebars/spec/qunit_spec.js +462 -159
  28. data/vendor/handlebars/spec/spec_helper.rb +7 -7
  29. data/vendor/handlebars/spec/tokenizer_spec.rb +55 -8
  30. data/vendor/handlebars/src/handlebars.l +14 -3
  31. data/vendor/handlebars/src/handlebars.yy +15 -5
  32. data/vendor/handlebars/src/parser-prefix.js +1 -0
  33. data/vendor/handlebars/src/parser-suffix.js +4 -0
  34. metadata +8 -3
@@ -1,22 +1,52 @@
1
- module("basic context");
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
- Handlebars.registerHelper('helperMissing', function(helper, context) {
4
- if(helper === "link_to") {
5
- return new Handlebars.SafeString("<a>" + context + "</a>");
6
- }
7
- });
31
+ suite("basic context");
8
32
 
9
- var shouldCompileTo = function(string, hashOrArray, expected, message) {
33
+ function shouldCompileTo(string, hashOrArray, expected, message) {
10
34
  shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
11
- };
12
- var shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
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
- result = template.apply(this, ary);
31
- equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
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 exception) {
41
- caught = true;
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
- shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
71
- "num1: 42, num2: 0");
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: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
97
143
  "by default expressions should be escaped");
98
144
 
145
+ shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;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
- shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
135
- "Goodbye beautiful world!", "Literal paths can be used");
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("--- TODO --- bad idea nested paths", function() {
139
- return;
140
- var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
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
- module("inverted sections");
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
- module("blocks");
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("helper with complex lookup", function() {
226
- var string = "{{#goodbyes}}{{{link ../prefix}}}{{/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(fn) {
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, fn) {
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, fn) {
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(fn) { return fn({text: "GOODBYE"}); }}});
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(fn) { return "<form>" + fn(this) + "</form>" } }});
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(fn) {
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, fn) { return "<form>" + fn(context) + "</form>" }}});
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, fn) { return "<form>" + fn(context) + "</form>" }}});
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(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>" },
331
- form: function(context, fn) { return "<form>" + fn(context) + "</form>" }
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&#x27;s here</p>", "the context of an inverse is the parent of the block");
378
464
  });
379
465
 
380
- module("helpers hash");
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, the explicit hash wins", function() {
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
- module("partials");
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 literal paths", function() {
459
- var string = "Dudes: {{> [dude]}}";
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, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
557
+ shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
463
558
  });
464
559
 
465
- module("String literal parameters");
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
- module("multiple parameters");
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, fn) {
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
- module("safestring");
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
- module("helperMissing");
622
+ suite("helperMissing");
527
623
 
528
624
  test("if a context is not found, helperMissing is used", function() {
529
- var string = "{{hello}} {{link_to world}}"
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
- shouldCompileTo(string, context, "Hello <a>world</a>")
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
- module("knownHelpers");
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
- module("blockHelperMissing");
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
- module("built-in helpers", {
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 = "&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!";
761
+ var expected2 = "2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. 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(fn) {
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(fn) {
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(fn, inverse) {
729
- return fn.data.accessData + " " + fn({exclaim: "?"});
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(fn) {
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(fn) {
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(fn) {
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(fn) {
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
- var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
896
- var result = template(context, {helpers: helpers});
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
- var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
931
- var result = template({}, {helpers: helpers});
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, options) {
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
- module("Regressions")
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
+ });