handlebars 0.0.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +1 -1
  2. data/.gitmodules +3 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +1 -1
  5. data/README.mdown +44 -0
  6. data/Rakefile +3 -0
  7. data/handlebars.gemspec +19 -13
  8. data/lib/handlebars.rb +4 -3
  9. data/lib/handlebars/context.rb +37 -0
  10. data/lib/handlebars/version.rb +1 -1
  11. data/spec/handlebars_spec.rb +40 -0
  12. data/spike.rb +17 -0
  13. data/vendor/handlebars/.gitignore +6 -0
  14. data/vendor/handlebars/.jshintrc +50 -0
  15. data/vendor/handlebars/.npmignore +11 -0
  16. data/vendor/handlebars/Gemfile +5 -0
  17. data/vendor/handlebars/LICENSE +20 -0
  18. data/vendor/handlebars/README.markdown +315 -0
  19. data/vendor/handlebars/Rakefile +116 -0
  20. data/vendor/handlebars/bench/benchwarmer.js +149 -0
  21. data/vendor/handlebars/bench/handlebars.js +163 -0
  22. data/vendor/handlebars/bin/handlebars +139 -0
  23. data/vendor/handlebars/lib/handlebars.js +14 -0
  24. data/vendor/handlebars/lib/handlebars/base.js +101 -0
  25. data/vendor/handlebars/lib/handlebars/compiler/ast.js +103 -0
  26. data/vendor/handlebars/lib/handlebars/compiler/base.js +27 -0
  27. data/vendor/handlebars/lib/handlebars/compiler/compiler.js +808 -0
  28. data/vendor/handlebars/lib/handlebars/compiler/index.js +7 -0
  29. data/vendor/handlebars/lib/handlebars/compiler/printer.js +137 -0
  30. data/vendor/handlebars/lib/handlebars/compiler/visitor.js +13 -0
  31. data/vendor/handlebars/lib/handlebars/runtime.js +68 -0
  32. data/vendor/handlebars/lib/handlebars/utils.js +68 -0
  33. data/vendor/handlebars/package.json +25 -0
  34. data/vendor/handlebars/spec/acceptance_spec.rb +101 -0
  35. data/vendor/handlebars/spec/parser_spec.rb +264 -0
  36. data/vendor/handlebars/spec/qunit_spec.js +1067 -0
  37. data/vendor/handlebars/spec/spec_helper.rb +157 -0
  38. data/vendor/handlebars/spec/tokenizer_spec.rb +254 -0
  39. data/vendor/handlebars/src/handlebars.l +42 -0
  40. data/vendor/handlebars/src/handlebars.yy +99 -0
  41. metadata +93 -77
  42. data/README.md +0 -39
  43. data/lib/handlebars/generator.rb +0 -4
  44. data/lib/handlebars/parser.rb +0 -240
  45. data/spec/generator_spec.rb +0 -5
  46. data/spec/parser_spec.rb +0 -163
  47. data/spec/spec_helper.rb +0 -17
@@ -0,0 +1,1067 @@
1
+ module("basic context");
2
+
3
+ Handlebars.registerHelper('helperMissing', function(helper, context) {
4
+ if(helper === "link_to") {
5
+ return new Handlebars.SafeString("<a>" + context + "</a>");
6
+ }
7
+ });
8
+
9
+ var shouldCompileTo = function(string, hashOrArray, expected, message) {
10
+ shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
11
+ };
12
+ var shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
13
+ var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
14
+ if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
15
+ helpers = hashOrArray[1];
16
+
17
+ if(helpers) {
18
+ for(var prop in Handlebars.helpers) {
19
+ helpers[prop] = Handlebars.helpers[prop];
20
+ }
21
+ }
22
+
23
+ ary = [];
24
+ ary.push(hashOrArray[0]);
25
+ ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
26
+ } else {
27
+ ary = [hashOrArray];
28
+ }
29
+
30
+ result = template.apply(this, ary);
31
+ equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
32
+ };
33
+
34
+ var shouldThrow = function(fn, exception, message) {
35
+ var caught = false;
36
+ try {
37
+ fn();
38
+ }
39
+ catch (e) {
40
+ if (e instanceof exception) {
41
+ caught = true;
42
+ }
43
+ }
44
+
45
+ ok(caught, message || null);
46
+ }
47
+
48
+
49
+ test("compiling with a basic context", function() {
50
+ shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
51
+ "It works if all the required keys are provided");
52
+ });
53
+
54
+ test("comments", function() {
55
+ shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
56
+ {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
57
+ "comments are ignored");
58
+ });
59
+
60
+ test("boolean", function() {
61
+ var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
62
+ shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
63
+ "booleans show the contents when true");
64
+
65
+ shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
66
+ "booleans do not show the contents when false");
67
+ });
68
+
69
+ test("zeros", function() {
70
+ shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
71
+ "num1: 42, num2: 0");
72
+ shouldCompileTo("num: {{.}}", 0, "num: 0");
73
+ shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
74
+ });
75
+
76
+ test("newlines", function() {
77
+ shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
78
+ shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
79
+ });
80
+
81
+ test("escaping text", function() {
82
+ shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
83
+ shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored");
84
+ shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes");
85
+ shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes");
86
+ shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript");
87
+ });
88
+
89
+ test("escaping expressions", function() {
90
+ shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
91
+ "expressions with 3 handlebars aren't escaped");
92
+
93
+ shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
94
+ "expressions with {{& handlebars aren't escaped");
95
+
96
+ shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
97
+ "by default expressions should be escaped");
98
+
99
+ });
100
+
101
+ test("functions returning safestrings shouldn't be escaped", function() {
102
+ var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
103
+ shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
104
+ "functions returning safestrings aren't escaped");
105
+ });
106
+
107
+ test("functions", function() {
108
+ shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
109
+ "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");
117
+ });
118
+
119
+ test("paths with hyphens", function() {
120
+ shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
121
+ });
122
+
123
+ test("nested paths", function() {
124
+ shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
125
+ "Goodbye beautiful world!", "Nested paths access nested objects");
126
+ });
127
+
128
+ test("nested paths with empty string value", function() {
129
+ shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
130
+ "Goodbye world!", "Nested paths access nested objects with empty string");
131
+ });
132
+
133
+ test("literal paths", function() {
134
+ shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
135
+ "Goodbye beautiful world!", "Literal paths can be used");
136
+ });
137
+
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");
148
+ });
149
+
150
+ test("that current context path ({{.}}) doesn't hit helpers", function() {
151
+ shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
152
+ });
153
+
154
+ test("complex but empty paths", function() {
155
+ shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
156
+ shouldCompileTo("{{person/name}}", {person: {}}, "");
157
+ });
158
+
159
+ test("this keyword in paths", function() {
160
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
161
+ var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
162
+ shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
163
+ "This keyword in paths evaluates to current context");
164
+
165
+ string = "{{#hellos}}{{this/text}}{{/hellos}}"
166
+ hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
167
+ shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
168
+ });
169
+
170
+ module("inverted sections");
171
+
172
+ test("inverted sections with unset value", function() {
173
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
174
+ var hash = {};
175
+ shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set.");
176
+ });
177
+
178
+ test("inverted section with false value", function() {
179
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
180
+ var hash = {goodbyes: false};
181
+ shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
182
+ });
183
+
184
+ test("inverted section with empty set", function() {
185
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
186
+ var hash = {goodbyes: []};
187
+ shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
188
+ });
189
+
190
+ module("blocks");
191
+
192
+ test("array", function() {
193
+ var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"
194
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
195
+ shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
196
+ "Arrays iterate over the contents when not empty");
197
+
198
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
199
+ "Arrays ignore the contents when empty");
200
+
201
+ });
202
+
203
+ test("empty block", function() {
204
+ var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"
205
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
206
+ shouldCompileTo(string, hash, "cruel world!",
207
+ "Arrays iterate over the contents when not empty");
208
+
209
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
210
+ "Arrays ignore the contents when empty");
211
+ });
212
+
213
+ test("nested iteration", function() {
214
+
215
+ });
216
+
217
+ test("block with complex lookup", function() {
218
+ var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}"
219
+ var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
220
+
221
+ shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
222
+ "Templates can access variables in contexts up the stack with relative path syntax");
223
+ });
224
+
225
+ test("helper with complex lookup", function() {
226
+ var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"
227
+ var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
228
+ var helpers = {link: function(prefix) {
229
+ return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>"
230
+ }};
231
+ shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>")
232
+ });
233
+
234
+ test("helper block with complex lookup expression", function() {
235
+ var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"
236
+ var hash = {name: "Alan"};
237
+ var helpers = {goodbyes: function(fn) {
238
+ var out = "";
239
+ var byes = ["Goodbye", "goodbye", "GOODBYE"];
240
+ for (var i = 0,j = byes.length; i < j; i++) {
241
+ out += byes[i] + " " + fn(this) + "! ";
242
+ }
243
+ return out;
244
+ }};
245
+ shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! ");
246
+ });
247
+
248
+ test("helper with complex lookup and nested template", function() {
249
+ var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
250
+ 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>";
253
+ }};
254
+ shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
255
+ });
256
+
257
+ test("helper with complex lookup and nested template in VM+Compiler", function() {
258
+ var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
259
+ 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>";
262
+ }};
263
+ shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
264
+ });
265
+
266
+ test("block with deep nested complex lookup", function() {
267
+ var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
268
+ var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
269
+
270
+ shouldCompileTo(string, hash, "Goodbye cruel OMG!");
271
+ });
272
+
273
+ test("block helper", function() {
274
+ var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
275
+ var template = CompilerContext.compile(string);
276
+
277
+ result = template({world: "world"}, { helpers: {goodbyes: function(fn) { return fn({text: "GOODBYE"}); }}});
278
+ equal(result, "GOODBYE! cruel world!", "Block helper executed");
279
+ });
280
+
281
+ test("block helper staying in the same context", function() {
282
+ var string = "{{#form}}<p>{{name}}</p>{{/form}}"
283
+ var template = CompilerContext.compile(string);
284
+
285
+ result = template({name: "Yehuda"}, {helpers: {form: function(fn) { return "<form>" + fn(this) + "</form>" } }});
286
+ equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
287
+ });
288
+
289
+ test("block helper should have context in this", function() {
290
+ 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>';
293
+ };
294
+ var data = { "people": [
295
+ { "name": "Alan", "id": 1 },
296
+ { "name": "Yehuda", "id": 2 }
297
+ ]};
298
+
299
+ shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
300
+ });
301
+
302
+ test("block helper for undefined value", function() {
303
+ shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
304
+ });
305
+
306
+ test("block helper passing a new context", function() {
307
+ var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}"
308
+ var template = CompilerContext.compile(string);
309
+
310
+ result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, fn) { return "<form>" + fn(context) + "</form>" }}});
311
+ equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
312
+ });
313
+
314
+ test("block helper passing a complex path context", function() {
315
+ var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}"
316
+ var template = CompilerContext.compile(string);
317
+
318
+ result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, fn) { return "<form>" + fn(context) + "</form>" }}});
319
+ equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
320
+ });
321
+
322
+ test("nested block helpers", function() {
323
+ var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}"
324
+ var template = CompilerContext.compile(string);
325
+
326
+ result = template({
327
+ yehuda: {name: "Yehuda" }
328
+ }, {
329
+ helpers: {
330
+ link: function(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>" },
331
+ form: function(context, fn) { return "<form>" + fn(context) + "</form>" }
332
+ }
333
+ });
334
+ equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
335
+ });
336
+
337
+ test("block inverted sections", function() {
338
+ shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
339
+ "No people");
340
+ });
341
+
342
+ test("block inverted sections with empty arrays", function() {
343
+ shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
344
+ "No people");
345
+ });
346
+
347
+ test("block helper inverted sections", function() {
348
+ var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"
349
+ var list = function(context, options) {
350
+ if (context.length > 0) {
351
+ var out = "<ul>";
352
+ for(var i = 0,j=context.length; i < j; i++) {
353
+ out += "<li>";
354
+ out += options.fn(context[i]);
355
+ out += "</li>";
356
+ }
357
+ out += "</ul>";
358
+ return out;
359
+ } else {
360
+ return "<p>" + options.inverse(this) + "</p>";
361
+ }
362
+ };
363
+
364
+ var hash = {people: [{name: "Alan"}, {name: "Yehuda"}]};
365
+ var empty = {people: []};
366
+ var rootMessage = {
367
+ people: [],
368
+ message: "Nobody's here"
369
+ }
370
+
371
+ var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
372
+
373
+ // the meaning here may be kind of hard to catch, but list.not is always called,
374
+ // so we should see the output of both
375
+ shouldCompileTo(string, [hash, { list: list }], "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
376
+ shouldCompileTo(string, [empty, { list: list }], "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
377
+ shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block");
378
+ });
379
+
380
+ module("helpers hash");
381
+
382
+ test("providing a helpers hash", function() {
383
+ shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!",
384
+ "helpers hash is available");
385
+
386
+ shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}],
387
+ "Goodbye cruel world!", "helpers hash is available inside other blocks");
388
+ });
389
+
390
+ test("in cases of conflict, the explicit hash wins", function() {
391
+
392
+ });
393
+
394
+ test("the helpers hash is available is nested contexts", function() {
395
+
396
+ });
397
+
398
+ module("partials");
399
+
400
+ test("basic partials", function() {
401
+ var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
402
+ var partial = "{{name}} ({{url}}) ";
403
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
404
+ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
405
+ "Basic partials output based on current context.");
406
+ });
407
+
408
+ test("partials with context", function() {
409
+ var string = "Dudes: {{>dude dudes}}";
410
+ var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
411
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
412
+ shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
413
+ "Partials can be passed a context");
414
+ });
415
+
416
+ test("partial in a partial", function() {
417
+ var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
418
+ var dude = "{{name}} {{> url}} ";
419
+ var url = "<a href='{{url}}'>{{url}}</a>";
420
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
421
+ shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "Partials are rendered inside of other partials");
422
+ });
423
+
424
+ test("rendering undefined partial throws an exception", function() {
425
+ shouldThrow(function() {
426
+ var template = CompilerContext.compile("{{> whatever}}");
427
+ template();
428
+ }, Handlebars.Exception, "Should throw exception");
429
+ });
430
+
431
+ test("rendering template partial in vm mode throws an exception", function() {
432
+ shouldThrow(function() {
433
+ 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
+ template();
438
+ }, Handlebars.Exception, "Should throw exception");
439
+ });
440
+
441
+ test("rendering function partial in vm mode", function() {
442
+ var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
443
+ var partial = function(context) {
444
+ return context.name + ' (' + context.url + ') ';
445
+ };
446
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
447
+ shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
448
+ "Function partials output based in VM.");
449
+ });
450
+
451
+ test("GH-14: a partial preceding a selector", function() {
452
+ var string = "Dudes: {{>dude}} {{another_dude}}";
453
+ var dude = "{{name}}";
454
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
455
+ shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
456
+ });
457
+
458
+ test("Partials with literal paths", function() {
459
+ var string = "Dudes: {{> [dude]}}";
460
+ var dude = "{{name}}";
461
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
462
+ shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
463
+ });
464
+
465
+ module("String literal parameters");
466
+
467
+ test("simple literals work", function() {
468
+ var string = 'Message: {{hello "world" 12 true false}}';
469
+ var hash = {};
470
+ var helpers = {hello: function(param, times, bool1, bool2) {
471
+ if(typeof times !== 'number') { times = "NaN"; }
472
+ if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
473
+ if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
474
+ return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
475
+ }}
476
+ shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
477
+ });
478
+
479
+ test("using a quote in the middle of a parameter raises an error", function() {
480
+ shouldThrow(function() {
481
+ var string = 'Message: {{hello wo"rld"}}';
482
+ CompilerContext.compile(string);
483
+ }, Error, "should throw exception");
484
+ });
485
+
486
+ test("escaping a String is possible", function(){
487
+ var string = 'Message: {{{hello "\\"world\\""}}}';
488
+ var hash = {}
489
+ var helpers = {hello: function(param) { return "Hello " + param; }}
490
+ shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
491
+ });
492
+
493
+ test("it works with ' marks", function() {
494
+ var string = 'Message: {{{hello "Alan\'s world"}}}';
495
+ var hash = {}
496
+ var helpers = {hello: function(param) { return "Hello " + param; }}
497
+ shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
498
+ });
499
+
500
+ module("multiple parameters");
501
+
502
+ test("simple multi-params work", function() {
503
+ var string = 'Message: {{goodbye cruel world}}';
504
+ var hash = {cruel: "cruel", world: "world"}
505
+ var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}
506
+ shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
507
+ });
508
+
509
+ test("block multi-params work", function() {
510
+ 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
+ }}
515
+ shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
516
+ })
517
+
518
+ module("safestring");
519
+
520
+ test("constructing a safestring from a string and checking its type", function() {
521
+ var safe = new Handlebars.SafeString("testing 1, 2, 3");
522
+ ok(safe instanceof Handlebars.SafeString, "SafeString is an instance of Handlebars.SafeString");
523
+ equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
524
+ });
525
+
526
+ module("helperMissing");
527
+
528
+ test("if a context is not found, helperMissing is used", function() {
529
+ var string = "{{hello}} {{link_to world}}"
530
+ var context = { hello: "Hello", world: "world" };
531
+
532
+ shouldCompileTo(string, context, "Hello <a>world</a>")
533
+ });
534
+
535
+ module("knownHelpers");
536
+
537
+ test("Known helper should render helper", function() {
538
+ var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}})
539
+
540
+ var result = template({}, {helpers: {hello: function() { return "foo"; }}});
541
+ equal(result, "foo", "'foo' should === '" + result);
542
+ });
543
+
544
+ 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})
546
+
547
+ var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
548
+ equal(result, "undefined", "'undefined' should === '" + result);
549
+ });
550
+ test("Builtin helpers available in knownHelpers only mode", function() {
551
+ var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true})
552
+
553
+ var result = template({});
554
+ equal(result, "bar", "'bar' should === '" + result);
555
+ });
556
+ test("Field lookup works in knownHelpers only mode", function() {
557
+ var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true})
558
+
559
+ var result = template({foo: 'bar'});
560
+ equal(result, "bar", "'bar' should === '" + result);
561
+ });
562
+ test("Conditional blocks work in knownHelpers only mode", function() {
563
+ var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true})
564
+
565
+ var result = template({foo: 'baz'});
566
+ equal(result, "bar", "'bar' should === '" + result);
567
+ });
568
+ test("Invert blocks work in knownHelpers only mode", function() {
569
+ var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true})
570
+
571
+ var result = template({foo: false});
572
+ equal(result, "bar", "'bar' should === '" + result);
573
+ });
574
+
575
+ module("blockHelperMissing");
576
+
577
+ test("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
578
+ var string = "{{#truthy}}yep{{/truthy}}";
579
+ var data = { truthy: function() { return true; } };
580
+ shouldCompileTo(string, data, "yep");
581
+ });
582
+
583
+ var teardown;
584
+ module("built-in helpers", {
585
+ setup: function(){ teardown = null; },
586
+ teardown: function(){ if (teardown) { teardown(); } }
587
+ });
588
+
589
+ test("with", function() {
590
+ var string = "{{#with person}}{{first}} {{last}}{{/with}}";
591
+ shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
592
+ });
593
+
594
+ test("if", function() {
595
+ var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
596
+ shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
597
+ "if with boolean argument shows the contents when true");
598
+ shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
599
+ "if with string argument shows the contents");
600
+ shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
601
+ "if with boolean argument does not show the contents when false");
602
+ shouldCompileTo(string, {world: "world"}, "cruel world!",
603
+ "if with undefined does not show the contents");
604
+ shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
605
+ "if with non-empty array shows the contents");
606
+ shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
607
+ "if with empty array does not show the contents");
608
+ });
609
+
610
+ test("if with function argument", function() {
611
+ var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
612
+ shouldCompileTo(string, {goodbye: function() {return true}, world: "world"}, "GOODBYE cruel world!",
613
+ "if with function shows the contents when function returns true");
614
+ shouldCompileTo(string, {goodbye: function() {return this.world}, world: "world"}, "GOODBYE cruel world!",
615
+ "if with function shows the contents when function returns string");
616
+ shouldCompileTo(string, {goodbye: function() {return false}, world: "world"}, "cruel world!",
617
+ "if with function does not show the contents when returns false");
618
+ shouldCompileTo(string, {goodbye: function() {return this.foo}, world: "world"}, "cruel world!",
619
+ "if with function does not show the contents when returns undefined");
620
+ });
621
+
622
+ test("each", function() {
623
+ var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"
624
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
625
+ shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
626
+ "each with array argument iterates over the contents when not empty");
627
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
628
+ "each with array argument ignores the contents when empty");
629
+ });
630
+
631
+ test("log", function() {
632
+ var string = "{{log blah}}"
633
+ var hash = { blah: "whee" };
634
+
635
+ var logArg;
636
+ var originalLog = Handlebars.log;
637
+ Handlebars.log = function(arg){ logArg = arg; }
638
+ teardown = function(){ Handlebars.log = originalLog; }
639
+
640
+ shouldCompileTo(string, hash, "", "log should not display");
641
+ equals("whee", logArg, "should call log with 'whee'");
642
+ });
643
+
644
+ test("overriding property lookup", function() {
645
+
646
+ });
647
+
648
+
649
+ test("passing in data to a compiled function that expects data - works with helpers", function() {
650
+ var template = CompilerContext.compile("{{hello}}", {data: true});
651
+
652
+ var helpers = {
653
+ hello: function(options) {
654
+ return options.data.adjective + " " + this.noun;
655
+ }
656
+ };
657
+
658
+ var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
659
+ equals("happy cat", result, "Data output by helper");
660
+ });
661
+
662
+ test("passing in data to a compiled function that expects data - works with helpers in partials", function() {
663
+ var template = CompilerContext.compile("{{>my_partial}}", {data: true});
664
+
665
+ var partials = {
666
+ my_partial: CompilerContext.compile("{{hello}}", {data: true})
667
+ };
668
+
669
+ var helpers = {
670
+ hello: function(options) {
671
+ return options.data.adjective + " " + this.noun;
672
+ }
673
+ };
674
+
675
+ var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}});
676
+ equals("happy cat", result, "Data output by helper inside partial");
677
+ });
678
+
679
+ test("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
680
+ var template = CompilerContext.compile("{{hello world}}", {data: true});
681
+
682
+ var helpers = {
683
+ hello: function(noun, options) {
684
+ return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
685
+ }
686
+ };
687
+
688
+ var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
689
+ equals("happy world!", result, "Data output by helper");
690
+ });
691
+
692
+ test("passing in data to a compiled function that expects data - works with block helpers", function() {
693
+ var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
694
+
695
+ var helpers = {
696
+ hello: function(fn) {
697
+ return fn(this);
698
+ },
699
+ world: function(options) {
700
+ return options.data.adjective + " world" + (this.exclaim ? "!" : "");
701
+ }
702
+ };
703
+
704
+ var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
705
+ equals("happy world!", result, "Data output by helper");
706
+ });
707
+
708
+ test("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
709
+ var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
710
+
711
+ var helpers = {
712
+ hello: function(fn) {
713
+ return fn({exclaim: "?"});
714
+ },
715
+ world: function(thing, options) {
716
+ return options.data.adjective + " " + thing + (this.exclaim || "");
717
+ }
718
+ };
719
+
720
+ var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
721
+ equals("happy world?", result, "Data output by helper");
722
+ });
723
+
724
+ test("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
725
+ var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
726
+
727
+ var helpers = {
728
+ hello: function(fn, inverse) {
729
+ return fn.data.accessData + " " + fn({exclaim: "?"});
730
+ },
731
+ world: function(thing, options) {
732
+ return options.data.adjective + " " + thing + (this.exclaim || "");
733
+ }
734
+ };
735
+
736
+ var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
737
+ equals("#win happy world?", result, "Data output by helper");
738
+ });
739
+
740
+ test("you can override inherited data when invoking a helper", function() {
741
+ var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
742
+
743
+ var helpers = {
744
+ hello: function(fn) {
745
+ return fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
746
+ },
747
+ world: function(thing, options) {
748
+ return options.data.adjective + " " + thing + (this.exclaim || "");
749
+ }
750
+ };
751
+
752
+ var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
753
+ equals("sad world?", result, "Overriden data output by helper");
754
+ });
755
+
756
+
757
+ test("you can override inherited data when invoking a helper with depth", function() {
758
+ var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
759
+
760
+ var helpers = {
761
+ hello: function(fn) {
762
+ return fn({exclaim: "?"}, { data: {adjective: "sad"} });
763
+ },
764
+ world: function(thing, options) {
765
+ return options.data.adjective + " " + thing + (this.exclaim || "");
766
+ }
767
+ };
768
+
769
+ var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
770
+ equals("sad world?", result, "Overriden data output by helper");
771
+ });
772
+
773
+ test("helpers take precedence over same-named context properties", function() {
774
+ var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");
775
+
776
+ var helpers = {
777
+ goodbye: function() {
778
+ return this.goodbye.toUpperCase();
779
+ }
780
+ };
781
+
782
+ var context = {
783
+ cruel: function(world) {
784
+ return "cruel " + world.toUpperCase();
785
+ },
786
+
787
+ goodbye: "goodbye",
788
+ world: "world"
789
+ };
790
+
791
+ var result = template(context, {helpers: helpers});
792
+ equals(result, "GOODBYE cruel WORLD", "Helper executed");
793
+ });
794
+
795
+ test("helpers take precedence over same-named context properties", function() {
796
+ var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
797
+
798
+ var helpers = {
799
+ goodbye: function(fn) {
800
+ return this.goodbye.toUpperCase() + fn(this);
801
+ }
802
+ };
803
+
804
+ var context = {
805
+ cruel: function(world) {
806
+ return "cruel " + world.toUpperCase();
807
+ },
808
+
809
+ goodbye: "goodbye",
810
+ world: "world"
811
+ };
812
+
813
+ var result = template(context, {helpers: helpers});
814
+ equals(result, "GOODBYE cruel WORLD", "Helper executed");
815
+ });
816
+
817
+ test("Scoped names take precedence over helpers", function() {
818
+ var template = CompilerContext.compile("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}");
819
+
820
+ var helpers = {
821
+ goodbye: function() {
822
+ return this.goodbye.toUpperCase();
823
+ }
824
+ };
825
+
826
+ var context = {
827
+ cruel: function(world) {
828
+ return "cruel " + world.toUpperCase();
829
+ },
830
+
831
+ goodbye: "goodbye",
832
+ world: "world"
833
+ };
834
+
835
+ var result = template(context, {helpers: helpers});
836
+ equals(result, "goodbye cruel WORLD cruel GOODBYE", "Helper not executed");
837
+ });
838
+
839
+ test("Scoped names take precedence over block helpers", function() {
840
+ var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
841
+
842
+ var helpers = {
843
+ goodbye: function(fn) {
844
+ return this.goodbye.toUpperCase() + fn(this);
845
+ }
846
+ };
847
+
848
+ var context = {
849
+ cruel: function(world) {
850
+ return "cruel " + world.toUpperCase();
851
+ },
852
+
853
+ goodbye: "goodbye",
854
+ world: "world"
855
+ };
856
+
857
+ var result = template(context, {helpers: helpers});
858
+ equals(result, "GOODBYE cruel WORLD goodbye", "Helper executed");
859
+ });
860
+
861
+ test("helpers can take an optional hash", function() {
862
+ var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}');
863
+
864
+ var helpers = {
865
+ goodbye: function(options) {
866
+ return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES";
867
+ }
868
+ };
869
+
870
+ var context = {};
871
+
872
+ var result = template(context, {helpers: helpers});
873
+ equals(result, "GOODBYE CRUEL WORLD 12 TIMES", "Helper output hash");
874
+ });
875
+
876
+ test("helpers can take an optional hash with booleans", function() {
877
+ var helpers = {
878
+ goodbye: function(options) {
879
+ if (options.hash.print === true) {
880
+ return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
881
+ } else if (options.hash.print === false) {
882
+ return "NOT PRINTING";
883
+ } else {
884
+ return "THIS SHOULD NOT HAPPEN";
885
+ }
886
+ }
887
+ };
888
+
889
+ var context = {};
890
+
891
+ var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}');
892
+ var result = template(context, {helpers: helpers});
893
+ equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
894
+
895
+ var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
896
+ var result = template(context, {helpers: helpers});
897
+ equals(result, "NOT PRINTING", "Boolean helper parameter honored");
898
+ });
899
+
900
+ test("block helpers can take an optional hash", function() {
901
+ var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}');
902
+
903
+ var helpers = {
904
+ goodbye: function(options) {
905
+ return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
906
+ }
907
+ };
908
+
909
+ var result = template({}, {helpers: helpers});
910
+ equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
911
+ });
912
+
913
+ test("block helpers can take an optional hash with booleans", function() {
914
+ var helpers = {
915
+ goodbye: function(options) {
916
+ if (options.hash.print === true) {
917
+ return "GOODBYE " + options.hash.cruel + " " + options.fn(this);
918
+ } else if (options.hash.print === false) {
919
+ return "NOT PRINTING";
920
+ } else {
921
+ return "THIS SHOULD NOT HAPPEN";
922
+ }
923
+ }
924
+ };
925
+
926
+ var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}');
927
+ var result = template({}, {helpers: helpers});
928
+ equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
929
+
930
+ var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
931
+ var result = template({}, {helpers: helpers});
932
+ equals(result, "NOT PRINTING", "Boolean hash parameter honored");
933
+ });
934
+
935
+
936
+ test("arguments to helpers can be retrieved from options hash in string form", function() {
937
+ var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
938
+
939
+ var helpers = {
940
+ wycats: function(passiveVoice, noun, options) {
941
+ return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
942
+ }
943
+ };
944
+
945
+ var result = template({}, {helpers: helpers});
946
+
947
+ equals(result, "HELP ME MY BOSS is.a slave.driver", "String parameters output");
948
+ });
949
+
950
+ test("when using block form, arguments to helpers can be retrieved from options hash in string form", function() {
951
+ var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
952
+
953
+ var helpers = {
954
+ wycats: function(passiveVoice, noun, options) {
955
+ return "HELP ME MY BOSS " + passiveVoice + ' ' +
956
+ noun + ': ' + options.fn(this);
957
+ }
958
+ };
959
+
960
+ var result = template({}, {helpers: helpers});
961
+
962
+ equals(result, "HELP ME MY BOSS is.a slave.driver: help :(", "String parameters output");
963
+ });
964
+
965
+ test("when inside a block in String mode, .. passes the appropriate context in the options hash", function() {
966
+ var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
967
+
968
+ var helpers = {
969
+ tomdale: function(desire, noun, options) {
970
+ return "STOP ME FROM READING HACKER NEWS I " +
971
+ options.contexts[0][desire] + " " + noun;
972
+ },
973
+
974
+ "with": function(context, options) {
975
+ return options.fn(options.contexts[0][context]);
976
+ }
977
+ };
978
+
979
+ var result = template({
980
+ dale: {},
981
+
982
+ need: 'need-a'
983
+ }, {helpers: helpers});
984
+
985
+ equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
986
+ });
987
+
988
+ test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
989
+ var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
990
+
991
+ var helpers = {
992
+ tomdale: function(desire, noun, options) {
993
+ return "STOP ME FROM READING HACKER NEWS I " +
994
+ options.contexts[0][desire] + " " + noun + " " +
995
+ options.fn(this);
996
+ },
997
+
998
+ "with": function(context, options) {
999
+ return options.fn(options.contexts[0][context]);
1000
+ }
1001
+ };
1002
+
1003
+ var result = template({
1004
+ dale: {},
1005
+
1006
+ need: 'need-a'
1007
+ }, {helpers: helpers});
1008
+
1009
+ equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
1010
+ });
1011
+
1012
+ module("Regressions")
1013
+
1014
+ test("GH-94: Cannot read property of undefined", function() {
1015
+ var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
1016
+ var string = "{{#books}}{{title}}{{author.name}}{{/books}}";
1017
+ shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes",
1018
+ "Renders without an undefined property error");
1019
+ });
1020
+
1021
+ test("GH-150: Inverted sections print when they shouldn't", function() {
1022
+ var string = "{{^set}}not set{{/set}} :: {{#set}}set{{/set}}";
1023
+
1024
+ shouldCompileTo(string, {}, "not set :: ", "inverted sections run when property isn't present in context");
1025
+ shouldCompileTo(string, {set: undefined}, "not set :: ", "inverted sections run when property is undefined");
1026
+ shouldCompileTo(string, {set: false}, "not set :: ", "inverted sections run when property is false");
1027
+ shouldCompileTo(string, {set: true}, " :: set", "inverted sections don't run when property is true");
1028
+ });
1029
+
1030
+ test("Mustache man page", function() {
1031
+ var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}"
1032
+ var data = {
1033
+ "name": "Chris",
1034
+ "value": 10000,
1035
+ "taxed_value": 10000 - (10000 * 0.4),
1036
+ "in_ca": true
1037
+ }
1038
+
1039
+ shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
1040
+ });
1041
+
1042
+ test("GH-158: Using array index twice, breaks the template", function() {
1043
+ var string = "{{arr.[0]}}, {{arr.[1]}}";
1044
+ var data = { "arr": [1,2] };
1045
+
1046
+ shouldCompileTo(string, data, "1, 2", "it works as expected");
1047
+ });
1048
+
1049
+ test("bug reported by @fat where lambdas weren't being properly resolved", function() {
1050
+ var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}";
1051
+ var data = {
1052
+ thing: function() {
1053
+ return "blah";
1054
+ },
1055
+ things: [
1056
+ {className: "one", word: "@fat"},
1057
+ {className: "two", word: "@dhg"},
1058
+ {className: "three", word:"@sayrer"}
1059
+ ],
1060
+ hasThings: function() {
1061
+ return true;
1062
+ }
1063
+ };
1064
+
1065
+ 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
+ shouldCompileTo(string, data, output);
1067
+ });