handlebars 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ });