hbs_plus 0.1.3

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.
@@ -0,0 +1,836 @@
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
+ var template = Handlebars.compile(string), ary;
11
+ if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
12
+ helpers = hashOrArray[1];
13
+
14
+ if(helpers) {
15
+ for(var prop in Handlebars.helpers) {
16
+ helpers[prop] = Handlebars.helpers[prop];
17
+ }
18
+ }
19
+
20
+ ary = [];
21
+ ary.push(hashOrArray[0]);
22
+ ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
23
+ } else {
24
+ ary = [hashOrArray];
25
+ }
26
+
27
+ result = template.apply(this, ary);
28
+ equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
29
+ };
30
+
31
+ var shouldThrow = function(fn, exception, message) {
32
+ var caught = false;
33
+ try {
34
+ fn();
35
+ }
36
+ catch (e) {
37
+ if (e instanceof exception) {
38
+ caught = true;
39
+ }
40
+ }
41
+
42
+ ok(caught, message || null);
43
+ }
44
+
45
+
46
+ test("compiling with a basic context", function() {
47
+ shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
48
+ "It works if all the required keys are provided");
49
+ });
50
+
51
+ test("comments", function() {
52
+ shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
53
+ {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
54
+ "comments are ignored");
55
+ });
56
+
57
+ test("boolean", function() {
58
+ var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
59
+ shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
60
+ "booleans show the contents when true");
61
+
62
+ shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
63
+ "booleans do not show the contents when false");
64
+ });
65
+
66
+ test("zeros", function() {
67
+ shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
68
+ "num1: 42, num2: 0");
69
+ shouldCompileTo("num: {{.}}", 0, "num: 0");
70
+ shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
71
+ });
72
+
73
+ test("newlines", function() {
74
+ shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
75
+ shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
76
+ });
77
+
78
+ test("escaping text", function() {
79
+ shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
80
+ shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored");
81
+ shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes");
82
+ shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes");
83
+ shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript");
84
+ });
85
+
86
+ test("escaping expressions", function() {
87
+ shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
88
+ "expressions with 3 handlebars aren't escaped");
89
+
90
+ shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
91
+ "expressions with {{& handlebars aren't escaped");
92
+
93
+ shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
94
+ "by default expressions should be escaped");
95
+
96
+ });
97
+
98
+ test("functions returning safestrings shouldn't be escaped", function() {
99
+ var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
100
+ shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
101
+ "functions returning safestrings aren't escaped");
102
+ });
103
+
104
+ test("functions", function() {
105
+ shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
106
+ "functions are called and render their output");
107
+ });
108
+
109
+ test("functions with context argument", function() {
110
+ shouldCompileTo("{{awesome frank}}",
111
+ {awesome: function(context) { return context; },
112
+ frank: "Frank"},
113
+ "Frank", "functions are called with context arguments");
114
+ });
115
+
116
+ test("paths with hyphens", function() {
117
+ shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
118
+ });
119
+
120
+ test("nested paths", function() {
121
+ shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
122
+ "Goodbye beautiful world!", "Nested paths access nested objects");
123
+ });
124
+
125
+ test("nested paths with empty string value", function() {
126
+ shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
127
+ "Goodbye world!", "Nested paths access nested objects with empty string");
128
+ });
129
+
130
+ test("--- TODO --- bad idea nested paths", function() {
131
+ return;
132
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
133
+ shouldThrow(function() {
134
+ Handlebars.compile("{{#goodbyes}}{{../name/../name}}{{/goodbyes}}")(hash);
135
+ }, Handlebars.Exception,
136
+ "Cannot jump (..) into previous context after moving into a context.");
137
+
138
+ var string = "{{#goodbyes}}{{.././world}} {{/goodbyes}}";
139
+ shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths");
140
+ });
141
+
142
+ test("that current context path ({{.}}) doesn't hit helpers", function() {
143
+ shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
144
+ });
145
+
146
+ test("complex but empty paths", function() {
147
+ shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
148
+ shouldCompileTo("{{person/name}}", {person: {}}, "");
149
+ });
150
+
151
+ test("this keyword in paths", function() {
152
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
153
+ var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
154
+ shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
155
+ "This keyword in paths evaluates to current context");
156
+
157
+ string = "{{#hellos}}{{this/text}}{{/hellos}}"
158
+ hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
159
+ shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
160
+ });
161
+
162
+ module("inverted sections");
163
+
164
+ test("inverted sections with unset value", function() {
165
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
166
+ var hash = {};
167
+ shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set.");
168
+ });
169
+
170
+ test("inverted section with false value", function() {
171
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
172
+ var hash = {goodbyes: false};
173
+ shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
174
+ });
175
+
176
+ test("inverted section with empty set", function() {
177
+ var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
178
+ var hash = {goodbyes: []};
179
+ shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
180
+ });
181
+
182
+ module("blocks");
183
+
184
+ test("array", function() {
185
+ var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!"
186
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
187
+ shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
188
+ "Arrays iterate over the contents when not empty");
189
+
190
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
191
+ "Arrays ignore the contents when empty");
192
+
193
+ });
194
+
195
+ test("empty block", function() {
196
+ var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!"
197
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
198
+ shouldCompileTo(string, hash, "cruel world!",
199
+ "Arrays iterate over the contents when not empty");
200
+
201
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
202
+ "Arrays ignore the contents when empty");
203
+ });
204
+
205
+ test("incorrectly matched blocks", function() {
206
+ var string = "{{#goodbyes}}{{/hellos}}";
207
+
208
+ shouldThrow(function() {
209
+ Handlebars.compile(string);
210
+ }, Handlebars.Exception, "Incorrectly matched blocks return an exception at compile time.");
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
+ shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>")
255
+ });
256
+
257
+ test("block with deep nested complex lookup", function() {
258
+ var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
259
+ var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
260
+
261
+ shouldCompileTo(string, hash, "Goodbye cruel OMG!");
262
+ });
263
+
264
+ test("block helper", function() {
265
+ var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
266
+ var template = Handlebars.compile(string);
267
+
268
+ result = template({goodbyes: function(fn) { return fn({text: "GOODBYE"}); }, world: "world"});
269
+ equal(result, "GOODBYE! cruel world!");
270
+ });
271
+
272
+ test("block helper staying in the same context", function() {
273
+ var string = "{{#form}}<p>{{name}}</p>{{/form}}"
274
+ var template = Handlebars.compile(string);
275
+
276
+ result = template({form: function(fn) { return "<form>" + fn(this) + "</form>" }, name: "Yehuda"});
277
+ equal(result, "<form><p>Yehuda</p></form>");
278
+ });
279
+
280
+ test("block helper should have context in this", function() {
281
+ var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
282
+ var link = function(fn) {
283
+ return '<a href="/people/' + this.id + '">' + fn(this) + '</a>';
284
+ };
285
+ var data = { "people": [
286
+ { "name": "Alan", "id": 1 },
287
+ { "name": "Yehuda", "id": 2 }
288
+ ]};
289
+
290
+ shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
291
+ });
292
+
293
+ test("block helper for undefined value", function() {
294
+ shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
295
+ });
296
+
297
+ test("block helper passing a new context", function() {
298
+ var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}"
299
+ var template = Handlebars.compile(string);
300
+
301
+ result = template({form: function(context, fn) { return "<form>" + fn(context) + "</form>" }, yehuda: {name: "Yehuda"}});
302
+ equal(result, "<form><p>Yehuda</p></form>");
303
+ });
304
+
305
+ test("block helper passing a complex path context", function() {
306
+ var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}"
307
+ var template = Handlebars.compile(string);
308
+
309
+ result = template({form: function(context, fn) { return "<form>" + fn(context) + "</form>" }, yehuda: {name: "Yehuda", cat: {name: "Harold"}}});
310
+ equal(result, "<form><p>Harold</p></form>");
311
+ });
312
+
313
+ test("nested block helpers", function() {
314
+ var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}"
315
+ var template = Handlebars.compile(string);
316
+
317
+ result = template({
318
+ form: function(context, fn) { return "<form>" + fn(context) + "</form>" },
319
+ yehuda: {name: "Yehuda",
320
+ link: function(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>"; }
321
+ }
322
+ });
323
+ equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>");
324
+ });
325
+
326
+ test("block inverted sections", function() {
327
+ shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
328
+ "No people");
329
+ });
330
+
331
+ test("block inverted sections with empty arrays", function() {
332
+ shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
333
+ "No people");
334
+ });
335
+
336
+ test("block helper inverted sections", function() {
337
+ var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"
338
+ var list = function(context, options) {
339
+ if (context.length > 0) {
340
+ var out = "<ul>";
341
+ for(var i = 0,j=context.length; i < j; i++) {
342
+ out += "<li>";
343
+ out += options.fn(context[i]);
344
+ out += "</li>";
345
+ }
346
+ out += "</ul>";
347
+ return out;
348
+ } else {
349
+ return "<p>" + options.inverse(this) + "</p>";
350
+ }
351
+ };
352
+
353
+ var hash = {list: list, people: [{name: "Alan"}, {name: "Yehuda"}]};
354
+ var empty = {list: list, people: []};
355
+ var rootMessage = {
356
+ list: function(context, options) { if(context.length === 0) { return "<p>" + options.inverse(this) + "</p>"; } },
357
+ people: [],
358
+ message: "Nobody's here"
359
+ }
360
+
361
+ var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
362
+
363
+ // the meaning here may be kind of hard to catch, but list.not is always called,
364
+ // so we should see the output of both
365
+ shouldCompileTo(string, hash, "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
366
+ shouldCompileTo(string, empty, "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
367
+ shouldCompileTo(messageString, rootMessage, "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block");
368
+ });
369
+
370
+ module("helpers hash");
371
+
372
+ test("providing a helpers hash", function() {
373
+ shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!",
374
+ "helpers hash is available");
375
+
376
+ shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}],
377
+ "Goodbye cruel world!", "helpers hash is available inside other blocks");
378
+ });
379
+
380
+ test("in cases of conflict, the explicit hash wins", function() {
381
+
382
+ });
383
+
384
+ test("the helpers hash is available is nested contexts", function() {
385
+
386
+ });
387
+
388
+ module("partials");
389
+
390
+ test("basic partials", function() {
391
+ var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
392
+ var partial = "{{name}} ({{url}}) ";
393
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
394
+ shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
395
+ "Basic partials output based on current context.");
396
+ });
397
+
398
+ test("partials with context", function() {
399
+ var string = "Dudes: {{>dude dudes}}";
400
+ var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
401
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
402
+ shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
403
+ "Partials can be passed a context");
404
+ });
405
+
406
+ test("partial in a partial", function() {
407
+ var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
408
+ var dude = "{{name}} {{> url}} ";
409
+ var url = "<a href='{{url}}'>{{url}}</a>";
410
+ var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
411
+ shouldCompileTo(string, [hash, {}, {dude: dude, url: url}], "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "Partials are rendered inside of other partials");
412
+ });
413
+
414
+ test("rendering undefined partial throws an exception", function() {
415
+ shouldThrow(function() {
416
+ var template = Handlebars.compile("{{> whatever}}");
417
+ template();
418
+ }, Handlebars.Exception, "Should throw exception");
419
+ });
420
+
421
+ test("GH-14: a partial preceding a selector", function() {
422
+ var string = "Dudes: {{>dude}} {{another_dude}}";
423
+ var dude = "{{name}}";
424
+ var hash = {name:"Jeepers", another_dude:"Creepers"};
425
+ shouldCompileTo(string, [hash, {}, {dude:dude}], "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
426
+ });
427
+
428
+ module("String literal parameters");
429
+
430
+ test("simple literals work", function() {
431
+ var string = 'Message: {{hello "world" 12 true false}}';
432
+ var hash = {};
433
+ var helpers = {hello: function(param, times, bool1, bool2) {
434
+ if(typeof times !== 'number') { times = "NaN"; }
435
+ if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
436
+ if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
437
+ return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
438
+ }}
439
+ shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
440
+ });
441
+
442
+ test("using a quote in the middle of a parameter raises an error", function() {
443
+ shouldThrow(function() {
444
+ var string = 'Message: {{hello wo"rld"}}';
445
+ Handlebars.compile(string);
446
+ }, Error, "should throw exception");
447
+ });
448
+
449
+ test("escaping a String is possible", function(){
450
+ var string = 'Message: {{{hello "\\"world\\""}}}';
451
+ var hash = {}
452
+ var helpers = {hello: function(param) { return "Hello " + param; }}
453
+ shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
454
+ });
455
+
456
+ test("it works with ' marks", function() {
457
+ var string = 'Message: {{{hello "Alan\'s world"}}}';
458
+ var hash = {}
459
+ var helpers = {hello: function(param) { return "Hello " + param; }}
460
+ shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
461
+ });
462
+
463
+ module("multiple parameters");
464
+
465
+ test("simple multi-params work", function() {
466
+ var string = 'Message: {{goodbye cruel world}}';
467
+ var hash = {cruel: "cruel", world: "world"}
468
+ var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}
469
+ shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
470
+ });
471
+
472
+ test("block multi-params work", function() {
473
+ var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
474
+ var hash = {cruel: "cruel", world: "world"}
475
+ var helpers = {goodbye: function(cruel, world, fn) {
476
+ return fn({greeting: "Goodbye", adj: cruel, noun: world});
477
+ }}
478
+ shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
479
+ })
480
+
481
+ module("safestring");
482
+
483
+ test("constructing a safestring from a string and checking its type", function() {
484
+ var safe = new Handlebars.SafeString("testing 1, 2, 3");
485
+ ok(safe instanceof Handlebars.SafeString, "SafeString is an instance of Handlebars.SafeString");
486
+ equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
487
+ });
488
+
489
+ module("helperMissing");
490
+
491
+ test("if a context is not found, helperMissing is used", function() {
492
+ var string = "{{hello}} {{link_to world}}"
493
+ var context = { hello: "Hello", world: "world" };
494
+
495
+ shouldCompileTo(string, context, "Hello <a>world</a>")
496
+ });
497
+
498
+ module("built-in helpers");
499
+
500
+ test("with", function() {
501
+ var string = "{{#with person}}{{first}} {{last}}{{/with}}";
502
+ shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
503
+ });
504
+
505
+ test("if", function() {
506
+ var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
507
+ shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
508
+ "if with boolean argument shows the contents when true");
509
+ shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
510
+ "if with string argument shows the contents");
511
+ shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
512
+ "if with boolean argument does not show the contents when false");
513
+ shouldCompileTo(string, {world: "world"}, "cruel world!",
514
+ "if with undefined does not show the contents");
515
+ shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
516
+ "if with non-empty array shows the contents");
517
+ shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
518
+ "if with empty array does not show the contents");
519
+ });
520
+
521
+ test("each", function() {
522
+ var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!"
523
+ var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
524
+ shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
525
+ "each with array argument iterates over the contents when not empty");
526
+ shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
527
+ "each with array argument ignores the contents when empty");
528
+ });
529
+
530
+ test("overriding property lookup", function() {
531
+
532
+ });
533
+
534
+
535
+ test("passing in data to a compiled function that expects data - works with helpers", function() {
536
+ var template = Handlebars.compile("{{hello}}", {data: true});
537
+
538
+ var helpers = {
539
+ hello: function(options) {
540
+ return options.data.adjective + " " + this.noun;
541
+ }
542
+ };
543
+
544
+ var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
545
+ equals("happy cat", result);
546
+ });
547
+
548
+ test("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
549
+ var template = Handlebars.compile("{{hello world}}", {data: true});
550
+
551
+ var helpers = {
552
+ hello: function(noun, options) {
553
+ return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
554
+ }
555
+ };
556
+
557
+ var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
558
+ equals("happy world!", result);
559
+ });
560
+
561
+ test("passing in data to a compiled function that expects data - works with block helpers", function() {
562
+ var template = Handlebars.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
563
+
564
+ var helpers = {
565
+ hello: function(fn) {
566
+ return fn(this);
567
+ },
568
+ world: function(options) {
569
+ return options.data.adjective + " world" + (this.exclaim ? "!" : "");
570
+ }
571
+ };
572
+
573
+ var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
574
+ equals("happy world!", result);
575
+ });
576
+
577
+ test("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
578
+ var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
579
+
580
+ var helpers = {
581
+ hello: function(fn) {
582
+ return fn({exclaim: "?"});
583
+ },
584
+ world: function(thing, options) {
585
+ return options.data.adjective + " " + thing + (this.exclaim || "");
586
+ }
587
+ };
588
+
589
+ var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
590
+ equals("happy world?", result);
591
+ });
592
+
593
+ test("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
594
+ var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
595
+
596
+ var helpers = {
597
+ hello: function(fn, inverse) {
598
+ return fn.data.accessData + " " + fn({exclaim: "?"});
599
+ },
600
+ world: function(thing, options) {
601
+ return options.data.adjective + " " + thing + (this.exclaim || "");
602
+ }
603
+ };
604
+
605
+ var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
606
+ equals("#win happy world?", result);
607
+ });
608
+
609
+ test("you can override inherited data when invoking a helper", function() {
610
+ var template = Handlebars.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
611
+
612
+ var helpers = {
613
+ hello: function(fn) {
614
+ return fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
615
+ },
616
+ world: function(thing, options) {
617
+ return options.data.adjective + " " + thing + (this.exclaim || "");
618
+ }
619
+ };
620
+
621
+ var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
622
+ equals("sad world?", result);
623
+ });
624
+
625
+
626
+ test("you can override inherited data when invoking a helper with depth", function() {
627
+ var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
628
+
629
+ var helpers = {
630
+ hello: function(fn) {
631
+ return fn({exclaim: "?"}, { data: {adjective: "sad"} });
632
+ },
633
+ world: function(thing, options) {
634
+ return options.data.adjective + " " + thing + (this.exclaim || "");
635
+ }
636
+ };
637
+
638
+ var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
639
+ equals("sad world?", result);
640
+ });
641
+
642
+ test("helpers take precedence over same-named context properties", function() {
643
+ var template = Handlebars.compile("{{goodbye}} {{cruel world}}");
644
+
645
+ var helpers = {
646
+ goodbye: function() {
647
+ return this.goodbye.toUpperCase();
648
+ }
649
+ };
650
+
651
+ var context = {
652
+ cruel: function(world) {
653
+ return "cruel " + world.toUpperCase();
654
+ },
655
+
656
+ goodbye: "goodbye",
657
+ world: "world"
658
+ };
659
+
660
+ var result = template(context, {helpers: helpers});
661
+ equals(result, "GOODBYE cruel WORLD");
662
+ });
663
+
664
+ test("helpers take precedence over same-named context properties", function() {
665
+ var template = Handlebars.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
666
+
667
+ var helpers = {
668
+ goodbye: function(fn) {
669
+ return this.goodbye.toUpperCase() + fn(this);
670
+ }
671
+ };
672
+
673
+ var context = {
674
+ cruel: function(world) {
675
+ return "cruel " + world.toUpperCase();
676
+ },
677
+
678
+ goodbye: "goodbye",
679
+ world: "world"
680
+ };
681
+
682
+ var result = template(context, {helpers: helpers});
683
+ equals(result, "GOODBYE cruel WORLD");
684
+ });
685
+
686
+ test("helpers can take an optional hash", function() {
687
+ var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}');
688
+
689
+ var helpers = {
690
+ goodbye: function(options) {
691
+ return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES";
692
+ }
693
+ };
694
+
695
+ var context = {};
696
+
697
+ var result = template(context, {helpers: helpers});
698
+ equals(result, "GOODBYE CRUEL WORLD 12 TIMES");
699
+ });
700
+
701
+ test("helpers can take an optional hash with booleans", function() {
702
+ var helpers = {
703
+ goodbye: function(options) {
704
+ if (options.hash.print === true) {
705
+ return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
706
+ } else if (options.hash.print === false) {
707
+ return "NOT PRINTING";
708
+ } else {
709
+ return "THIS SHOULD NOT HAPPEN";
710
+ }
711
+ }
712
+ };
713
+
714
+ var context = {};
715
+
716
+ var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}');
717
+ var result = template(context, {helpers: helpers});
718
+ equals(result, "GOODBYE CRUEL WORLD");
719
+
720
+ var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
721
+ var result = template(context, {helpers: helpers});
722
+ equals(result, "NOT PRINTING");
723
+ });
724
+
725
+ test("block helpers can take an optional hash", function() {
726
+ var template = Handlebars.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}');
727
+
728
+ var helpers = {
729
+ goodbye: function(options) {
730
+ return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
731
+ }
732
+ };
733
+
734
+ var result = template({}, {helpers: helpers});
735
+ equals(result, "GOODBYE CRUEL world 12 TIMES");
736
+ });
737
+
738
+ test("block helpers can take an optional hash with booleans", function() {
739
+ var helpers = {
740
+ goodbye: function(options) {
741
+ if (options.hash.print === true) {
742
+ return "GOODBYE " + options.hash.cruel + " " + options.fn(this);
743
+ } else if (options.hash.print === false) {
744
+ return "NOT PRINTING";
745
+ } else {
746
+ return "THIS SHOULD NOT HAPPEN";
747
+ }
748
+ }
749
+ };
750
+
751
+ var template = Handlebars.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}');
752
+ var result = template({}, {helpers: helpers});
753
+ equals(result, "GOODBYE CRUEL world");
754
+
755
+ var template = Handlebars.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
756
+ var result = template({}, {helpers: helpers});
757
+ equals(result, "NOT PRINTING");
758
+ });
759
+
760
+
761
+ test("arguments to helpers can be retrieved from options hash in string form", function() {
762
+ var template = Handlebars.compile('{{wycats is.a slave.driver}}', {stringParams: true});
763
+
764
+ var helpers = {
765
+ wycats: function(passiveVoice, noun, options) {
766
+ return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
767
+ }
768
+ };
769
+
770
+ var result = template({}, {helpers: helpers});
771
+
772
+ equals(result, "HELP ME MY BOSS is.a slave.driver");
773
+ });
774
+
775
+ test("when using block form, arguments to helpers can be retrieved from options hash in string form", function() {
776
+ var template = Handlebars.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
777
+
778
+ var helpers = {
779
+ wycats: function(passiveVoice, noun, options) {
780
+ return "HELP ME MY BOSS " + passiveVoice + ' ' +
781
+ noun + ': ' + options.fn(this);
782
+ }
783
+ };
784
+
785
+ var result = template({}, {helpers: helpers});
786
+
787
+ equals(result, "HELP ME MY BOSS is.a slave.driver: help :(");
788
+ });
789
+
790
+ test("when inside a block in String mode, .. passes the appropriate context in the options hash", function() {
791
+ var template = Handlebars.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
792
+
793
+ var helpers = {
794
+ tomdale: function(desire, noun, options) {
795
+ return "STOP ME FROM READING HACKER NEWS I " +
796
+ options.contexts[0][desire] + " " + noun;
797
+ },
798
+
799
+ "with": function(context, options) {
800
+ return options.fn(options.contexts[0][context]);
801
+ }
802
+ };
803
+
804
+ var result = template({
805
+ dale: {},
806
+
807
+ need: 'need-a'
808
+ }, {helpers: helpers});
809
+
810
+ equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke");
811
+ });
812
+
813
+ test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
814
+ var template = Handlebars.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
815
+
816
+ var helpers = {
817
+ tomdale: function(desire, noun, options) {
818
+ return "STOP ME FROM READING HACKER NEWS I " +
819
+ options.contexts[0][desire] + " " + noun + " " +
820
+ options.fn(this);
821
+ },
822
+
823
+ "with": function(context, options) {
824
+ return options.fn(options.contexts[0][context]);
825
+ }
826
+ };
827
+
828
+ var result = template({
829
+ dale: {},
830
+
831
+ need: 'need-a'
832
+ }, {helpers: helpers});
833
+
834
+ equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot");
835
+ });
836
+