Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
6056 efrain 1
/*global QUnit*/
2
(function(undefined) {
3
"use strict";
4
 
5
var global = (0, eval)('this'), // jshint ignore:line
6
 
7
	isIE8 = global.attachEvent && !global.addEventListener,
8
	isBrowser = !!global.document,
9
 
10
	$ = global.jsrender || global.jQuery; // On Node.js with QUnit, jsrender is added as namespace, to global
11
 
12
if (!isBrowser) {
13
	global.document = {};
14
}
15
 
16
function compileTmpl(template) {
17
	try {
18
		return typeof $.templates(template).fn === "function" ? "compiled" : "failed compile";
19
	} catch(e) {
20
		return e.message;
21
	}
22
}
23
 
24
function sort(array) {
25
	var ret = "";
26
	if (this.tagCtx.props.reverse) {
27
		// Render in reverse order
28
		if (arguments.length > 1) {
29
			for (i = arguments.length; i; i--) {
30
				ret += sort.call(this, arguments[ i - 1 ]);
31
			}
32
		} else {
33
			for (var i = array.length; i; i--) {
34
				ret += this.tagCtx.render(array[ i - 1 ]);
35
			}
36
		}
37
	} else {
38
		// Render in original order
39
		ret += this.tmpl.render(array);
40
	}
41
	return ret;
42
}
43
 
44
var person = {name: "Jo"},
45
	people = [{name: "Jo"}, {name: "Bill"}],
46
	towns = [{name: "Seattle"}, {name: "Paris"}, {name: "Delhi"}];
47
 
48
var tmplString = "A_{{:name}}_B";
49
$.views.tags({sort: sort});
50
 
51
QUnit.module("tagParser");
52
QUnit.test("{{if}} {{else}}", function(assert) {
53
	assert.equal(compileTmpl("A_{{if true}}{{/if}}_B"), "compiled", "Empty if block: {{if}}{{/if}}");
54
	assert.equal(compileTmpl("A_{{if true}}yes{{/if}}_B"), "compiled", "{{if}}...{{/if}}");
55
	assert.equal(compileTmpl("A_{{if true/}}yes{{/if}}_B"), "Syntax error\nUnmatched or missing {{/if}}, in template:\nA_{{if true/}}yes{{/if}}_B", "unmatched or missing tag error");
56
	assert.equal($.templates("<span id='x'></span> a'b\"c\\").render(), "<span id=\'x\'></span> a\'b\"c\\", "Correct escaping of quotes and backslash");
57
});
58
 
59
QUnit.test("syntax errors", function(assert) {
60
	assert.equal(compileTmpl("{^{*:foo}}"), "Syntax error\n{^{*:foo}}", "Syntax error for {^{* ...}}");
61
	assert.equal(compileTmpl("{{:foo/}}"), "Syntax error\n{{:foo/}}", "Syntax error for {{: ... /}}");
62
	assert.equal(compileTmpl("{{:foo:}}"), "Syntax error\n{{:foo:}}", "Syntax error for {{: ... :}}");
63
	assert.equal(compileTmpl("{^{:foo:}}"), "Syntax error\n{^{:foo:}}", "Syntax error for {^{: ... :}}");
64
	assert.equal(compileTmpl("{{mytag foo :}}"), "Syntax error\n{{mytag foo :}}", "Syntax error for {{mytag ... :}}");
65
	assert.equal(compileTmpl("{^{mytag foo :}}"), "Syntax error\n{^{mytag foo :}}", "Syntax error for {^{mytag ... :}}");
66
	assert.equal(compileTmpl("{{if foo?bar:baz}}{{/if}}"), "compiled", "No syntax error for {{tag foo?bar:baz}}");
67
	assert.equal(compileTmpl("{{for [1,2]/}}"), "Syntax error\n[1,2]", "Syntax error for {{for [1,2]}} - top-level array");
68
	assert.equal(compileTmpl("{{:constructor()}}"), "Syntax error\nconstructor", "Syntax error for {{: ...constructor ...}}");
69
	assert.equal(compileTmpl("{{for #tmpl.constructor()}}"), "Syntax error\n#tmpl.constructor", "Syntax error for {{for ...constructor ...}}");
70
 
71
$.views.settings.debugMode(true);
72
	assert.equal($.templates('{{:#data["constructor"]["constructor"]("alert(0);")()}}').render(), "{Error: Syntax error\n}", 'Syntax error 1 for ["constructor]"');
73
	assert.equal($.templates('{{:valueOf["constructor"]("alert(1);")()}}').render(1), "{Error: Syntax error\n}", 'Syntax error 2 for ["constructor]"');
74
	assert.equal($.templates('{{:valueOf["const"+"ructor"]("alert(2);")()}}').render(1), "{Error: Syntax error\n}", 'Syntax error 3 for ["constructor]"');
75
	assert.equal($.templates('{{if true ~c=toString["con" + foo + "or"]}}{{:convert=~c("alert(3);")}}{{/if}}').render({foo: "struct"}), "{Error: Syntax error\n}", 'Syntax error 1 for indirect ["constructor]"');
76
	assert.equal($.templates('{{if true ~tmp="constructo"}}{{if true ~tmp2="r"}}{{:toString[~tmp + ~tmp2]}}{{/if}}{{/if}}').render(1), "{Error: Syntax error\n}", 'Syntax error 2 for indirect ["constructor]"');
77
$.views.settings.debugMode(false);
78
});
79
 
80
QUnit.module("{{if}}");
81
QUnit.test("{{if}}", function(assert) {
82
	assert.equal($.templates("A_{{if true}}yes{{/if}}_B").render(), "A_yes_B", "{{if a}}: a");
83
	assert.equal($.templates("A_{{if false}}yes{{/if}}_B").render(), "A__B", "{{if a}}: !a");
84
	assert.equal($.templates("A_{{if true}}{{/if}}_B").render(), "A__B", "{{if a}}: empty: a");
85
	assert.equal($.templates("A_{{if false}}{{/if}}_B").render(), "A__B", "{{if a}}: empty: !a");
86
});
87
 
88
QUnit.test("{{if}} {{else}}", function(assert) {
89
	assert.equal($.templates("A_{{if true}}yes{{else}}no{{/if}}_B").render(), "A_yes_B", "{{if a}} {{else}}: a");
90
	assert.equal($.templates("A_{{if false}}yes{{else}}no{{/if}}_B").render(), "A_no_B", "{{if a}} {{else}}: !a");
91
	assert.equal($.templates("A_{{if true}}yes{{else true}}or{{else}}no{{/if}}_B").render(), "A_yes_B", "{{if a}} {{else b}} {{else}}: a");
92
	assert.equal($.templates("A_{{if false}}yes{{else true}}or{{else}}no{{/if}}_B").render(), "A_or_B", "{{if a}} {{else b}} {{else}}: b");
93
	assert.equal($.templates("A_{{if false}}yes{{else false}}or{{else}}no{{/if}}_B").render(), "A_no_B", "{{if a}} {{else b}} {{else}}: !a!b");
94
	assert.equal($.templates("A_{{if undefined}}yes{{else true}}or{{else}}no{{/if}}_B").render({}), "A_or_B", "{{if undefined}} {{else b}} {{else}}: !a!b");
95
	assert.equal($.templates("A_{{if false}}yes{{else undefined}}or{{else}}no{{/if}}_B").render({}), "A_no_B", "{{if a}} {{else undefined}} {{else}}: !a!b");
96
	assert.equal($.templates("A_{{if false}}<div title='yes'{{else}}<div title='no'{{/if}}>x</div>_B").render(), "A_<div title='no'>x</div>_B", "{{if}} and {{else}} work across HTML tags");
97
	assert.equal($.templates("A_<div title='{{if true}}yes'{{else}}no'{{/if}}>x</div>_B").render(), "A_<div title='yes'>x</div>_B", "{{if}} and {{else}} work across quoted strings");
98
});
99
 
100
QUnit.test("{{if}} {{else}} external templates", function(assert) {
101
	assert.equal($.templates("A_{{if true tmpl='yes<br/>'/}}_B").render(), "A_yes<br/>_B", "{{if a tmpl=foo/}}: a");
102
	assert.equal($.templates("A_{{if false tmpl='yes<br/>'}}{{else false tmpl='or<br/>'}}{{else tmpl='no<br/>'}}{{/if}}_B").render(), "A_no<br/>_B", "{{if a tmpl=foo}}{{else b tmpl=bar}}{{else tmpl=baz}}: !a!b");
103
});
104
 
105
QUnit.module("{{:}}");
106
QUnit.test("convert", function(assert) {
107
	assert.equal($.templates("{{>#data}}").render("<br/>'\"&"), "&lt;br/&gt;&#39;&#34;&amp;", "default html converter");
108
	assert.equal($.templates("{{html:#data}}").render("<br/>'\"&"), "&lt;br/&gt;&#39;&#34;&amp;", "html converter");
109
	assert.equal($.templates("{{:#data}}").render("<br/>'\"&"), "<br/>'\"&", "no convert");
110
 
111
	function loc(data) {
112
		switch (data) {case "desktop": return "bureau";}
113
	}
114
	$.views.converters("loc", loc);
115
	assert.equal($.templates("{{loc:#data}}:{{loc:'desktop'}}").render("desktop"), "bureau:bureau", '$.views.converters("loc", locFunction);... {{loc:#data}}');
116
});
117
 
118
QUnit.test("paths", function(assert) {
119
	assert.equal($.templates("{{:a}}").render({a: "aVal"}), "aVal", "a");
120
	assert.equal($.templates("{{:a.b}}").render({a: {b: "bVal"}}), "bVal", "a.b");
121
	assert.equal($.templates("{{:a.b.c}}").render({a: {b: {c: "cVal"}}}), "cVal", "a.b.c");
122
	assert.equal($.templates("{{:a.name}}").render({a: {name: "aName"}}), "aName", "a.name");
123
	assert.equal($.templates("{{:a['name']}}").render({a: {name: "aName"}}), "aName", "a['name']");
124
	assert.equal($.templates("{{:a['x - _*!']}}").render({a: {"x - _*!": "aName"}}), "aName", "a['x - _*!']");
125
	assert.equal($.templates("{{:#data['x - _*!']}}").render({"x - _*!": "aName"}), "aName", "#data['x - _*!']");
126
	assert.equal($.templates('{{:a["x - _*!"]}}').render({a: {"x - _*!": "aName"}}), "aName", 'a["x - _*!"]');
127
	assert.equal($.templates("{{:a.b[1].d}}").render({a: {b: [0, {d: "dVal"}]}}), "dVal", "a.b[1].d");
128
	assert.equal($.templates("{{:a.b[1].d}}").render({a: {b: {1:{d: "dVal"}}}}), "dVal", "a.b[1].d");
129
	assert.equal($.templates("{{:a.b[~incr(1-1)].d}}").render({a: {b: {1:{d: "dVal"}}}}, {incr:function(val) {return val + 1;}}), "dVal", "a.b[~incr(1-1)].d");
130
	assert.equal($.templates("{{:a.b.c.d}}").render({a: {b: {c:{d: "dVal"}}}}), "dVal", "a.b.c.d");
131
	assert.equal($.templates("{{:a[0]}}").render({a: [ "bVal" ]}), "bVal", "a[0]");
132
	assert.equal($.templates("{{:a.b[1][0].msg}}").render({a: {b: [22,[{msg: " yes - that's right. "}]]}}), " yes - that's right. ", "a.b[1][0].msg");
133
	assert.equal($.templates("{{:#data.a}}").render({a: "aVal"}), "aVal", "#data.a");
134
	assert.equal($.templates("{{:#view.data.a}}").render({a: "aVal"}), "aVal", "#view.data.a");
135
	assert.equal($.templates("{{:#index === 0}}").render([{a: "aVal"}]), "true", "#index");
136
});
137
 
138
QUnit.test("types", function(assert) {
139
	assert.equal($.templates("{{:'abc'}}").render(), "abc", "'abc'");
140
	assert.equal($.templates('{{:"abc"}}').render(), "abc", '"abc"');
141
	assert.equal($.templates("{{:true}}").render(), "true", "true");
142
	assert.equal($.templates("{{:false}}").render(), "false", "false");
143
	assert.equal($.templates("{{:null}}").render(), "", 'null -> ""');
144
	assert.equal($.templates("{{:199}}").render(), "199", "199");
145
	assert.equal($.templates("{{: 199.9}}").render(), "199.9", "| 199.9 |");
146
	assert.equal($.templates("{{:-33.33}}").render(), "-33.33", "-33.33");
147
	assert.equal($.templates("{{: -33.33}}").render(), "-33.33", "| -33.33 |");
148
	assert.equal($.templates("{{:-33.33 - 2.2}}").render(), "-35.53", "-33.33 - 2.2");
149
	assert.equal($.templates("{{:notdefined}}").render({}), "", "notdefined");
150
	assert.equal($.templates("{{:}}").render("aString"), "aString", "{{:}} returns current data item");
151
	assert.equal($.templates("{{:x=22}}").render("aString"), "aString", "{{:x=...}} returns current data item");
152
	assert.equal($.templates("{{html:x=22}}").render("aString"), "aString", "{{html:x=...}} returns current data item");
153
	assert.equal($.templates("{{>x=22}}").render("aString"), "aString", "{{>x=...}} returns current data item");
154
	assert.equal($.templates("{{:'abc('}}").render(), "abc(", "'abc(': final paren in string is rendered correctly"); // https://github.com/BorisMoore/jsviews/issues/300
155
	assert.equal($.templates('{{:"abc("}}').render(), "abc(", '"abc(": final paren in string is rendered correctly');
156
	assert.equal($.templates("{{:(('(abc('))}}").render(), "(abc(", "(('(abc('))");
157
	assert.equal($.templates('{{:((")abc)"))}}').render(), ")abc)", '((")abc)"))');
158
});
159
 
160
QUnit.test("Fallbacks for missing or undefined paths:\nusing {{:some.path onError = 'fallback'}}, etc.", function(assert) {
161
	var message;
162
	try {
163
		$.templates("{{:a.missing.willThrow.path}}").render({a:1});
164
	} catch(e) {
165
		message = e.message;
166
	}
167
	assert.ok(!!message,
168
		"{{:a.missing.willThrow.path}} throws: " + message);
169
 
170
	assert.equal($.templates("{{:a.missing.willThrow.path onError='Missing Object'}}").render({a:1}), "Missing Object",
171
		'{{:a.missing.willThrow.path onError="Missing Object"}} renders "Missing Object"');
172
	assert.equal($.templates('{{:a.missing.willThrow.path onError=""}}').render({a:1}), "",
173
		'{{:a.missing.willThrow.path onError=""}} renders ""');
174
	assert.equal($.templates("{{:a.missing.willThrow.path onError=null}}").render({a:1}), "",
175
		'{{:a.missing.willThrow.path onError=null}} renders ""');
176
	assert.equal($.templates("{{>a.missing.willThrow.path onError='Missing Object'}}").render({a:1}), "Missing Object",
177
		'{{>a.missing.willThrow.path onError="Missing Object"}} renders "Missing Object"');
178
	assert.equal($.templates('{{>a.missing.willThrow.path onError=""}}').render({a:1}), "",
179
		'{{>a.missing.willThrow.path onError=""}} renders ""');
180
	assert.equal($.templates("{{>a.missing.willThrow.path onError=defaultVal}}").render(
181
		{
182
			a:1,
183
			defaultVal: "defaultFromData"
184
		}), "defaultFromData",
185
		'{{>a.missing.willThrow.path onError=defaultVal}} renders "defaultFromData"');
186
 
187
	assert.equal($.templates("{{>a.missing.willThrow.path onError=~myOnErrorFunction}}").render({a:1}, {
188
		myOnErrorFunction: function(e, view) {
189
			return "Override onError using a callback: " + view.ctx.helperValue + e.message;
190
		},
191
		helperValue: "hlp"
192
	}).slice(0, 38), "Override onError using a callback: hlp",
193
		'{{>a.missing.willThrow.path onError=~myOnErrorFunction}}" >' +
194
		'\nProviding a function "onError=~myOnErrorFunction" calls the function as onError callback');
195
 
196
	assert.equal($.templates("{{>a.missing.willThrow.path onError=myOnErrorDataMethod}}").render(
197
		{
198
			a: "dataValue",
199
			myOnErrorDataMethod: function(e) {
200
				var data = this;
201
				return "Override onError using a callback data method: " + data.a;
202
			}
203
		}), "Override onError using a callback data method: dataValue",
204
		'{{>a.missing.willThrow.path onError=myOnErrorDataMethod}}" >' +
205
		'\nProviding a function "onError=myOnErrorDataMethod" calls the function as onError callback');
206
 
207
	assert.equal($.templates("1: {{>a.missing.willThrow.path onError=defaultVal}}" +
208
		" 2: {{:a.missing.willThrow.path onError='Missing Object'}}" +
209
		" 3: {{:a.missing.willThrow.path onError=''}}" +
210
		" 4: {{:a.missing.willThrow.path onError=null}}" +
211
		" 5: {{:a onError='missing'}}" +
212
		" 6: {{:a.undefined onError='missing'}}" +
213
		" 7: {{:a.missing.willThrow onError=myCb}} end").render(
214
		{
215
			a:"aVal",
216
			defaultVal: "defaultFromData",
217
			myCb: function(e, view) {
218
				return "myCallback: " + this.a;
219
			}
220
		}), "1: defaultFromData 2: Missing Object 3:  4:  5: aVal 6:  7: myCallback: aVal end",
221
		'multiple onError fallbacks in same template - correctly concatenated into output');
222
 
223
	assert.equal($.templates({
224
		markup: "{{withfallback:a.notdefined fallback='fallback for undefined'}}",
225
		converters: {
226
			withfallback: function(val) {
227
				return val || this.tagCtx.props.fallback;
228
			}
229
		}
230
	}).render({a:"yes"}), "fallback for undefined",
231
		'{{withfallback:a.notdefined fallback="fallback for undefined"}}' +
232
		'\nusing converter to get fallback value for undefined properties');
233
 
234
	assert.equal($.templates({
235
		markup: "1: {{withfallback:a.missing.y onError='Missing object' fallback='undefined prop'}}" +
236
			" 2: {{withfallback:a.undefined onError='Missing object' fallback='undefined prop'}}",
237
		converters: {
238
			withfallback: function(val) {
239
				return val || this.tagCtx.props.fallback;
240
			}
241
		}
242
	}).render({a:"yes"}), "1: Missing object 2: undefined prop",
243
		'both fallback for undefined and onError for missing on same tags');
244
 
245
	assert.equal($.templates({
246
		markup: "1: {{>a.missing.willThrow.path onError=defaultVal}}" +
247
		" 2: {{:a.missing.willThrow.path onError='Missing Object'}}" +
248
		" 3: {{:a.missing.willThrow.path onError=''}}" +
249
		" 4: {{:a onError='missing'}}" +
250
		" 5: {{:a.undefined onError='missing'}}" +
251
		" 6: {{:a.missing.willThrow onError=myCb}}" +
252
		" 7: {{withfallback:a.undefined fallback='undefined prop'}} end",
253
		converters: {
254
			withfallback: function(val) {
255
				return val || this.tagCtx.props.fallback;
256
			}
257
		}
258
	}).render(
259
		{
260
		a:"aVal",
261
		defaultVal: "defaultFromData",
262
		myCb: function(e, view) {
263
			return "myCallback: " + this.a;
264
		}
265
	}), "1: defaultFromData 2: Missing Object 3:  4: aVal 5:  6: myCallback: aVal 7: undefined prop end",
266
	'multiple onError fallbacks or undefined property fallbacks in same template - correctly concatenated into output');
267
 
268
	try {
269
		message = "";
270
		$.templates({
271
			markup: "1: {{>a.missing.willThrow.path onError=defaultVal}}" +
272
			" 2: {{:a.missing.willThrow.path onError='Missing Object'}}" +
273
			" 3: {{:a.missing.willThrow.path onError=''}}" +
274
			" 4: {{:a onError='missing'}}" +
275
			" 5: {{:a.missing.willThrow.foo}}" +
276
			" 6: {{:a.undefined onError='missing'}}" +
277
			" 7: {{:a.missing.willThrow onError=myCb}}" +
278
			" 8: {{withfallback:a.undefined fallback='undefined prop'}} end",
279
			converters: {
280
				withfallback: function(val) {
281
					return val || this.tagCtx.props.fallback;
282
				}
283
			}
284
		}).render({
285
			a:"aVal",
286
			defaultVal: "defaultFromData",
287
			myCb: function(e, view) {
288
				return "myCallback: " + this.a;
289
			}
290
		});
291
	} catch(e) {
292
		message = e.message;
293
	}
294
 
295
	assert.ok(!!message,
296
		'onError/fallback converter and regular thrown error message in same template: throws:\n"' + message + '"');
297
 
298
	assert.equal($.templates("{{for missing.willThrow.path onError='Missing Object'}}yes{{/for}}").render({a:1}), "Missing Object",
299
		'{{for missing.willThrow.path onError="Missing Object"}} -> "Missing Object"');
300
 
301
	assert.equal($.templates("{{for true missing.willThrow.path onError='Missing Object'}}yes{{/for}}").render({a:1}), "Missing Object",
302
		'{{for true missing.willThrow.path onError="Missing Object"}} -> "Missing Object"');
303
 
304
	assert.equal($.templates("{{for true foo=missing.willThrow.path onError='Missing Object'}}yes{{/for}}").render({a:1}), "Missing Object",
305
		'{{for ... foo=missing.willThrow.path onError="Missing Object"}} -> "Missing Object"');
306
 
307
	assert.equal($.templates("{{for true ~foo=missing.willThrow.path onError='Missing Object'}}yes{{/for}}").render({a:1}), "Missing Object",
308
		'{{for ... ~foo=missing.willThrow.path onError="Missing Object"}} -> "Missing Object"');
309
 
310
	assert.equal($.templates({
311
			markup: "{{mytag foo='a'/}} {{mytag foo=missing.willThrow.path onError='Missing Object'/}} {{mytag foo='c' bar=missing.willThrow.path onError='Missing Object'/}} {{mytag foo='c' missing.willThrow.path onError='Missing Object'/}} {{mytag foo='b'/}}",
312
			tags: {
313
				mytag: {template: "MyTag: {{:~tagCtx.props.foo}} end"}
314
			}
315
		}).render({a:1}), "MyTag: a end Missing Object Missing Object Missing Object MyTag: b end",
316
		'onError=... for custom tags: e.g. {{mytag foo=missing.willThrow.path onError="Missing Object"/}}');
317
 
318
	assert.equal($.templates({
319
		markup: "1: {{for a.missing.willThrow.path onError=defaultVal}}yes{{/for}}" +
320
		" 2: {{if a.missing.willThrow.path onError='Missing Object'}}yes{{/if}}" +
321
		" 3: {{include a.missing.willThrow.path onError=''/}}" +
322
		" 4: {{if a onError='missing'}}yes{{/if}}" +
323
		" 5: {{for a.undefined onError='missing'}}yes{{/for}}" +
324
		" 6: {{if a.missing.willThrow onError=myCb}}yes{{/if}}" +
325
		" 7: {{withfallback:a.undefined fallback='undefined prop'}} end" +
326
		" 8: {{mytag foo=missing.willThrow.path onError='Missing Object'/}}",
327
		converters: {
328
			withfallback: function(val) {
329
				return val || this.tagCtx.props.fallback;
330
			}
331
		},
332
		tags: {
333
			mytag: {template: "MyTag: {{:~tagCtx.props.foo}} end"}
334
		}
335
	}).render(
336
		{
337
		a:"aVal",
338
		defaultVal: "defaultFromData",
339
		myCb: function(e, view) {
340
			return "myCallback: " + this.a;
341
		}
342
	}), "1: defaultFromData 2: Missing Object 3:  4: yes 5:  6: myCallback: aVal 7: undefined prop end 8: Missing Object",
343
	'multiple onError fallbacks or undefined property fallbacks in same template - correctly concatenated into output');
344
 
345
	try {
346
		message = "";
347
		$.templates({
348
			markup: "1: {{for a.missing.willThrow.path onError=defaultVal}}yes{{/for}}" +
349
			" 2: {{if a.missing.willThrow.path onError='Missing Object'}}yes{{/if}}" +
350
			" 3: {{include a.missing.willThrow.path onError=''/}}" +
351
			" 4: {{if a onError='missing'}}yes{{/if}}" +
352
			" 5: {{for missing.willThrow.foo}}yes{{/for}}" +
353
			" 6: {{for a.undefined onError='missing'}}yes{{/for}}" +
354
			" 7: {{if a.missing.willThrow onError=myCb}}yes{{/if}}" +
355
			" 8: {{withfallback:a.undefined fallback='undefined prop'}} end",
356
			converters: {
357
				withfallback: function(val) {
358
					return val || this.tagCtx.props.fallback;
359
				}
360
			}
361
		}).render({
362
			a:"aVal",
363
			defaultVal: "defaultFromData",
364
			myCb: function(e, view) {
365
				return "myCallback: " + this.a;
366
			}
367
		});
368
	} catch(e) {
369
		message = e.message;
370
	}
371
 
372
	assert.ok(!!message,
373
		'onError/fallback converter and regular thrown error message in same template: throws: \n"' + message + '"');
374
 
375
	$.views.settings.debugMode(true);
376
	assert.equal($.templates({
377
		markup: "1: {{for a.missing.willThrow.path onError=defaultVal}}yes{{/for}}" +
378
		" 2: {{if a.missing.willThrow.path onError='Missing Object'}}yes{{/if}}" +
379
		" 3: {{include a.missing.willThrow.path onError=''/}}" +
380
		" 4: {{if a onError='missing'}}yes{{/if}}" +
381
		" 5: {{for missing.willThrow.foo}}yes{{/for}}" +
382
		" 6: {{for a.undefined onError='missing'}}yes{{/for}}" +
383
		" 7: {{if a.missing.willThrow onError=myCb}}yes{{/if}}" +
384
		" 8: {{withfallback:a.undefined fallback='undefined prop'}} end",
385
		converters: {
386
			withfallback: function(val) {
387
				return val || this.tagCtx.props.fallback;
388
			}
389
		}
390
	}).render(
391
		{
392
		a:"aVal",
393
		defaultVal: "defaultFromData",
394
		myCb: function(e, view) {
395
			return "myCallback: " + this.a;
396
		}
397
	}).slice(0, 21), "1: defaultFromData 2:",
398
	'In debug mode, onError/fallback converter and regular thrown error message in same template:' +
399
	'\override errors and regular thrown error each render for the corrresponding tag');
400
	$.views.settings.debugMode(false);
401
 
402
});
403
 
404
QUnit.test("comparisons", function(assert) {
405
	assert.equal($.templates("{{:1<2}}").render(), "true", "1<2");
406
	assert.equal($.templates("{{:2<1}}").render(), "false", "2<1");
407
	assert.equal($.templates("{{:5===5}}").render(), "true", "5===5");
408
	assert.equal($.templates("{{:0==''}}").render(), "true", "0==''");
409
	assert.equal($.templates("{{:'ab'=='ab'}}").render(), "true", "'ab'=='ab'");
410
	assert.equal($.templates("{{:2>1}}").render(), "true", "2>1");
411
	assert.equal($.templates("{{:2 == 2}}").render(), "true", "2 == 2");
412
	assert.equal($.templates("{{:2<=2}}").render(), "true", "2<=2");
413
	assert.equal($.templates("{{:'ab'<'ac'}}").render(), "true", "'ab'<'ac'");
414
	assert.equal($.templates("{{:3>=3}}").render(), "true", "3 =3");
415
	assert.equal($.templates("{{:3>=2}}").render(), "true", "3>=2");
416
	assert.equal($.templates("{{:3>=4}}").render(), "false", "3>=4");
417
	assert.equal($.templates("{{:3 !== 2}}").render(), "true", "3 !== 2");
418
	assert.equal($.templates("{{:3 != 2}}").render(), "true", "3 != 2");
419
	assert.equal($.templates("{{:0 !== null}}").render(), "true", "0 !== null");
420
	assert.equal($.templates("{{:(3 >= 4)}}").render(), "false", "3>=4");
421
	assert.equal($.templates("{{:3 >= 4}}").render(), "false", "3>=4");
422
	assert.equal($.templates("{{:(3>=4)}}").render(), "false", "3>=4");
423
	assert.equal($.templates("{{:(3 < 4)}}").render(), "true", "3>=4");
424
	assert.equal($.templates("{{:3 < 4}}").render(), "true", "3>=4");
425
	assert.equal($.templates("{{:(3<4)}}").render(), "true", "3>=4");
426
	assert.equal($.templates("{{:0 != null}}").render(), "true", "0 != null");
427
});
428
 
429
QUnit.test("array access", function(assert) {
430
	assert.equal($.templates("{{:a[1]}}").render({a: ["a0","a1"]}), "a1", "a[1]");
431
	assert.equal($.templates("{{:a[1+1]+5}}").render({a: [11,22,33]}), "38", "a[1+1]+5)");
432
	assert.equal($.templates("{{:a[~incr(1)]+5}}").render({a: [11,22,33]}, {incr:function(val) {return val + 1;}}), "38", "a[~incr(1)]+5");
433
	assert.equal($.templates("{{:true && (a[0] || 'default')}}").render({a: [0,22,33]}, {incr:function(val) {return val + 1;}}), "default", "true && (a[0] || 'default')");
434
});
435
 
436
QUnit.test("context", function(assert) {
437
	assert.equal($.templates("{{:~val}}").render(1, {val: "myvalue"}), "myvalue", "~val");
438
	function format(value, upper) {
439
		return value[upper ? "toUpperCase" : "toLowerCase"]();
440
	}
441
	assert.equal($.templates("{{:~format(name) + ~format(name, true)}}").render(person, {format: format}), "joJO",
442
		"render(data, {format: formatFn}); ... {{:~format(name, true)}}");
443
	assert.equal($.templates("{{for people[0]}}{{:~format(~type) + ~format(name, true)}}{{/for}}").render({people: people}, {format: format, type: "PascalCase"}), "pascalcaseJO",
444
		"render(data, {format: formatFn}); ... {{:~format(name, true)}}");
445
	assert.equal($.templates("{{for people ~twn=town}}{{:name}} lives in {{:~format(~twn, true)}}. {{/for}}").render({people: people, town:"Redmond"}, {format: format}),
446
		"Jo lives in REDMOND. Bill lives in REDMOND. ",
447
		"Passing in context to nested templates: {{for people ~twn=town}}");
448
	assert.equal($.templates("{{if true}}{{for people}}{{:~root.people[0].name}}{{/for}}{{/if}}").render({people: people}), "JoJo",
449
		"{{:~root}} returns the top-level data");
450
});
451
 
452
QUnit.test("values", function(assert) {
453
	assert.equal($.templates("{{:a}}").render({a: 0}), "0", '{{:0}} returns "0"');
454
	assert.equal($.templates("{{:a}}").render({}), "", "{{:undefined}} returns empty string");
455
	assert.equal($.templates("{{:a}}").render({a: ""}), "", "{{:''}} returns empty string");
456
	assert.equal($.templates("{{:a}}").render({a: null}), "", "{{:null}} returns empty string");
457
});
458
 
459
QUnit.test("expressions", function(assert) {
460
	assert.equal(compileTmpl("{{:a++}}"), "Syntax error\na++", "a++");
461
	assert.equal(compileTmpl("{{:(a,b)}}"), "Syntax error\n(a,b)", "(a,b)");
462
	assert.equal($.templates("{{: a+2}}").render({a: 2, b: false}), "4", "a+2");
463
	assert.equal($.templates("{{: b?'yes':'no'}}").render({a: 2, b: false}), "no", "b?'yes':'no'");
464
	assert.equal($.templates("{{:(a||-1) + (b||-1)}}").render({a: 2, b: 0}), "1", "a||-1");
465
	assert.equal($.templates("{{:3*b()*!a*4/3}}").render({a: false, b: function() {return 3;}}), "12", "3*b()*!a*4/3");
466
	assert.equal($.templates("{{:a%b}}").render({a: 30, b: 16}), "14", "a%b");
467
	assert.equal($.templates("A_{{if v1 && v2 && v3 && v4}}no{{else !v1 && v2 || v3 && v4}}yes{{/if}}_B").render({v1:true,v2:false,v3:2,v4:"foo"}), "A_yes_B", "x && y || z");
468
	assert.equal($.templates("{{:!true}}").render({}), "false", "!true");
469
	assert.equal($.templates("{{if !true}}yes{{else}}no{{/if}}").render({}), "no", "{{if !true}}...");
470
	assert.equal($.templates("{{:!false}}").render({}), "true", "!false");
471
	assert.equal($.templates("{{if !false}}yes{{else}}no{{/if}}").render({}), "yes", "{{if !false}}...");
472
	assert.equal($.templates("{{:!!true}}").render({}), "true", "!!true");
473
	assert.equal($.templates("{{if !!true}}yes{{else}}no{{/if}}").render({}), "yes", "{{if !!true}}...");
474
	assert.equal($.templates("{{:!(true)}}").render({}), "false", "!(true)");
475
	assert.equal($.templates("{{:!true === false}}").render({}), "true", "!true === false");
476
	assert.equal($.templates("{{:false === !true}}").render({}), "true", "false === !true");
477
	assert.equal($.templates("{{:false === !null}}").render({}), "false", "false === !null");
478
});
479
 
480
QUnit.module("{{for}}");
481
QUnit.test("{{for}}", function(assert) {
482
	$.templates({
483
		forTmpl: "header_{{for people}}{{:name}}{{/for}}_footer",
484
		templateForArray: "header_{{for #data}}{{:name}}{{/for}}_footer",
485
		pageTmpl: '{{for [people] tmpl="templateForArray"/}}',
486
		simpleFor: "a{{for people}}Content{{:#data}}|{{/for}}b",
487
		forPrimitiveDataTypes: "a{{for people}}|{{:#data}}{{/for}}b",
488
		testTmpl: "xxx{{:name}} {{:~foo}}"
489
	});
490
 
491
	assert.equal($.render.forTmpl({people: people}), "header_JoBill_footer", '{{for people}}...{{/for}}');
492
	assert.equal($.render.templateForArray([people]), "header_JoBill_footer", 'Can render a template against an array, as a "layout template", by wrapping array in an array');
493
	assert.equal($.render.pageTmpl({people: people}), "header_JoBill_footer", '{{for [people] tmpl="templateForArray"/}}');
494
	assert.equal($.templates("{{for}}xxx{{:name}} {{:~foo}}{{/for}}").render({name: "Jeff"}, {foo:"fooVal"}), "xxxJeff fooVal", "no parameter - renders once with parent #data context: {{for}}");
495
	assert.equal($.templates("{{for tmpl='testTmpl'/}}").render({name: "Jeff"}, {foo:"fooVal"}), "xxxJeff fooVal", ": {{for tmpl=.../}} no parameter - equivalent to {{include tmpl=.../}} - renders once with parent #data context");
496
	assert.equal($.templates("{{include tmpl='testTmpl'/}}").render({name: "Jeff"}, {foo:"fooVal"}), "xxxJeff fooVal", "{{include tmpl=.../}} with tmpl parameter - renders once with parent #data context. Equivalent to {{for tmpl=.../}}");
497
	assert.equal($.templates("{{for missingProperty}}xxx{{:#data===~undefined}}{{/for}}").render({}), "", "missingProperty - renders empty string");
498
	assert.equal($.templates("{{for null}}xxx{{:#data===null}}{{/for}}").render(), "xxxtrue", "null - renders once with #data null: {{for null}}");
499
	assert.equal($.templates("{{for false}}xxx{{:#data}}{{/for}}").render(), "xxxfalse", "false - renders once with #data false: {{for false}}");
500
	assert.equal($.templates("{{for 0}}xxx{{:#data}}{{/for}}").render(), "xxx0", "0 - renders once with #data false: {{for 0}}");
501
	assert.equal($.templates("{{for ''}}xxx{{:#data===''}}{{/for}}").render(), "xxxtrue", "'' - renders once with #data false: {{for ''}}");
502
	assert.equal($.templates("{{for #data}}{{:name}}{{/for}}").render(people), "JoBill", "If #data is an array, {{for #data}} iterates");
503
 
504
	assert.equal($.render.simpleFor({people:[]}), "ab", 'Empty array renders empty string');
505
	assert.equal($.render.simpleFor({people:["", false, null, undefined, 1]}), "aContent|Contentfalse|Content|Content|Content1|b", 'Empty string, false, null or undefined members of array are also rendered');
506
	assert.equal($.render.simpleFor({people:null}), "aContent|b", 'null is rendered once with #data null');
507
	assert.equal($.render.simpleFor({}), "ab", 'if #data is undefined, renders empty string');
508
	assert.equal($.render.forPrimitiveDataTypes({people:[0, 1, "abc", "", ,null ,true ,false]}), "a|0|1|abc||||true|falseb", 'Primitive types render correctly, even if falsey');
509
});
510
QUnit.test("{{for start end sort filter reverse}}", function(assert) {
511
	// =============================== Arrange ===============================
512
	function level(aField, bField) {
513
		return aField > bField ? 1 : aField < bField ? -1 : 0;
514
	}
515
 
516
	var oddValue = function(item, index, items) { return item%2; };
517
	var oddIndex = function(item, index, items) { return index%2; };
518
	var sortAgeName = function(a, b) {
519
		return level(a.details.role.toLowerCase(), b.details.role.toLowerCase()) // First level sort: by role
520
			|| (this.props.reverseAge ? level(b.details.age, a.details.age) : level(a.details.age, b.details.age)) // 2nd level sort: sort by age, or reverse sort by age
521
			|| level(a.name.toLowerCase(), b.name.toLowerCase()); // 3rd level sort: sort by name
522
	};
523
	var underLimit = function(item, index, items) {
524
		return item.details.age < this.props.limit;
525
	};
526
 
527
	// ................................ Assert ..................................
528
 
529
	assert.equal($.templates("{{for start=0 end=10}}{{:}} {{/for}}").render(), "0 1 2 3 4 5 6 7 8 9 ", "{{for start=0 end=10}}: Auto-create array");
530
	assert.equal($.templates("{{for start=5 end=9 reverse=1}}{{:}} {{/for}}").render(), "8 7 6 5 ", "{{for start=5 end=9 reverse=1}}: Auto-create array");
531
	assert.equal($.templates("{{for start=8 end=4 step=-1}}{{:}} {{/for}}").render(), "8 7 6 5 ", "{{for start=8 end=4 step=-1}}: Auto-create array");
532
	assert.equal($.templates("{{for start=8 end=4  step=-1 reverse=true}}{{:}} {{/for}}").render(), "5 6 7 8 ", "{{for start=8 end=4 step=-1 reverse=true}}: Auto-create array, with reverse");
533
	assert.equal($.templates("{{for start=20 end='10' step=-2}}{{:}} {{/for}}").render(), "20 18 16 14 12 ", "{{for start=20 end='10' step=-2}}: Auto-create array");
534
	assert.equal($.templates("{{for start=20 end='10' step=2}}{{:}} {{/for}}").render(), "", "{{for start=20 end='10' step=2}}: Auto-create array (outputs nothing)");
535
	assert.equal($.templates("{{for start=2 end=-1.5 step=-.5}}{{:}} {{/for}}").render(), "2 1.5 1 0.5 0 -0.5 -1 ", "{{for start=0 end='10' step=-1}}: Auto-create array");
536
	assert.equal($.templates("{{for start=2}}{{:}} {{/for}}").render(), "", "{{for start=2}}: (outputs nothing)");
537
	assert.equal($.templates("{{for end=4}}{{:}} {{/for}}").render(), "0 1 2 3 ", "{{for end=4}}: (start defaults to 0)");
538
	assert.equal($.templates("{{for start=8 end=4  step=-1 reverse=true sort=true filter=~oddIndex}}{{:}} {{/for}}").render({}, {oddIndex: oddIndex}), "5 6 7 8 ", "{{for start=8 end=4 step=-1 reverse=true sort=true}}: Auto-create array, sort and filter not supported with auto-create arrays - do nothing");
539
 
540
	// =============================== Arrange ===============================
541
 
542
	var myarray = [1, 9, 2, 8, 3, 7, 4, 6, 5];
543
 
544
	assert.equal($.templates("{{for #data }}{{:}} {{/for}}").render(myarray, true), "1 9 2 8 3 7 4 6 5 ", "{{for #data}}");
545
	assert.equal($.templates("{{for #data sort=true}}{{:}} {{/for}}").render(myarray, true), "1 2 3 4 5 6 7 8 9 ", "{{for #data sort=true}}");
546
	assert.equal($.templates("{{for myarray reverse=true}}{{:}} {{/for}}").render({myarray: myarray}), "5 6 4 7 3 8 2 9 1 ", "{{for myarray reverse=true}}");
547
	assert.equal($.templates("{{for myarray start=1 end=-1}}{{:}} {{/for}}").render({myarray: myarray}), "9 2 8 3 7 4 6 ", "{{for myarray start=1 end=-1}}");
548
	assert.equal($.templates("{{for myarray start=1}}{{:}} {{/for}}").render({myarray: myarray}), "9 2 8 3 7 4 6 5 ", "{{for myarray start=1}}");
549
	assert.equal($.templates("{{for myarray end=-1}}{{:}} {{/for}}").render({myarray: myarray}), "1 9 2 8 3 7 4 6 ", "{{for myarray end=-1}}");
550
	assert.equal($.templates("{{for myarray sort=true}}{{:}} {{/for}}").render({myarray: myarray}), "1 2 3 4 5 6 7 8 9 ", "{{for myarray sort=true}}");
551
	assert.equal($.templates("{{for myarray sort=true reverse=true}}{{:}} {{/for}}").render({myarray: myarray}), "9 8 7 6 5 4 3 2 1 ", "{{for myarray sort=true reverse=true}}");
552
 
553
if (!isIE8) { // IE8 does not support filter. Need to add polyfill on sites that want this support
554
	assert.equal($.templates("{{for myarray filter=~oddValue}}{{:}} {{/for}}").render({myarray: myarray}, {oddValue: oddValue}), "1 9 3 7 5 ", "{{for myarray filter=~oddValue}}");
555
	assert.equal($.templates("{{for myarray filter=~oddIndex}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "9 8 7 6 ", "{{for myarray filter=~oddIndex}}");
556
	assert.equal($.templates("{{for myarray sort=true filter=~oddValue}}{{:}} {{/for}}").render({myarray: myarray}, {oddValue: oddValue}), "1 3 5 7 9 ", "{{for myarray sort=true filter=~oddValue}}");
557
	assert.equal($.templates("{{for myarray sort=true filter=~oddIndex}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "2 4 6 8 ", "{{for myarray sort=true filter=~oddIndex}}");
558
	assert.equal($.templates("{{for myarray sort=true filter=~oddIndex start=1 end=3}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "4 6 ", "{{for myarray sort=true filter=~oddIndex start=1 end=3}}");
559
	assert.equal($.templates("{{for myarray sort=true filter=~oddIndex start=-3 end=-1}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "4 6 ", "{{for myarray sort=true filter=~oddIndex start=-3 end=-1}} Negative start or end count from the end");
560
	assert.equal($.templates("{{for myarray sort=true filter=~oddIndex start=3 end=3}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "", "{{for myarray sort=true filter=~oddIndex start=3 end=3}} (outputs nothing)");
561
}
562
	assert.equal($.templates("{{for myarray step=2 start=1}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "9 8 7 6 ", "{{for myarray step=2 start=1}}");
563
	assert.equal($.templates("{{for myarray sort=true step=2 start=1}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "2 4 6 8 ", "{{for myarray sort=true step=2 start=1}}");
564
	assert.equal($.templates("{{for myarray sort=true step=2 start=3 end=6}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "4 6 ", "{{for myarray sort=true step=2 start=3 end=6}}");
565
	assert.equal($.templates("{{for myarray sort=true step=2 start=-6 end=-3}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "4 6 ", "{{for myarray sort=true step=2 start=-6 end=-3}} Negative start or end count from the end");
566
	assert.equal($.templates("{{for myarray sort=true step=2 start=3 end=3}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "", "{{for myarray sort=true step=2 start=3 end=3}} (outputs nothing)");
567
	assert.equal($.templates("{{for myarray step=3.5}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "1 8 4 ", "{{for myarray step=3.5}} - equivalent to step=3");
568
	assert.equal($.templates("{{for myarray step=-2}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "1 9 2 8 3 7 4 6 5 ", "{{for myarray step=-2}} equivalent to no step");
569
	assert.equal($.templates("{{for myarray step=1}}{{:}} {{/for}}").render({myarray: myarray}, {oddIndex: oddIndex}), "1 9 2 8 3 7 4 6 5 ", "{{for myarray step=1}} equivalent to no step");
570
	// =============================== Arrange ===============================
571
 
572
	var mypeople = [
573
		{name: "Jo", details: {age: 22}},
574
		{name: "Bob", details: {age: 2}},
575
		{name: "Emma", details: {age: 12}},
576
		{name: "Jeff", details: {age: 13.5}},
577
		{name: "Julia", details: {age: 0.6}},
578
		{name: "Xavier", details: {age: 0}}
579
	];
580
 
581
	// ................................ Assert ..................................
582
 
583
	assert.equal($.templates("{{for mypeople sort='name'}}{{:name}}: age {{:details.age}} - {{/for}}").render({mypeople: mypeople}), "Bob: age 2 - Emma: age 12 - Jeff: age 13.5 - Jo: age 22 - Julia: age 0.6 - Xavier: age 0 - ",
584
		"{{for mypeople  sort='name'}}");
585
	assert.equal($.templates("{{for mypeople sort='details.age'}}{{:name}}: age {{:details.age}} - {{/for}}").render({mypeople: mypeople}), "Xavier: age 0 - Julia: age 0.6 - Bob: age 2 - Emma: age 12 - Jeff: age 13.5 - Jo: age 22 - ",
586
		"{{for mypeople  sort='details.age'}}");
587
 
588
if (!isIE8) { // IE8 does not support filter. Need to add polyfill on sites that want this support
589
	assert.equal($.templates("{{for mypeople sort='details.age' reverse=true filter=~underLimit limit=20}}{{:name}}: age {{:details.age}} - {{/for}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Jeff: age 13.5 - Emma: age 12 - Bob: age 2 - Julia: age 0.6 - Xavier: age 0 - ",
590
		"{{for mypeople  sort='details.age' reverse=true filter=~underLimit...}}");
591
	assert.equal($.templates("{{for mypeople sort='details.age' reverse=true filter=~underLimit limit=20 start=1 end=-1}}{{:name}}: age {{:details.age}} - {{/for}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Emma: age 12 - Bob: age 2 - Julia: age 0.6 - ",
592
		"{{for mypeople  sort='details.age' reverse=true filter=~underLimit... start=1 end=-1}}");
593
	assert.equal($.templates("{{for mypeople sort='details.age' reverse=true filter=~underLimit limit=20 start=1 end=-1}}{{:name}}: age {{:details.age}} - {{/for}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Emma: age 12 - Bob: age 2 - Julia: age 0.6 - ",
594
		"{{for mypeople  sort='details.age' reverse=true filter=~underLimit... start=1 end=-1}}");
595
}
596
	// =============================== Arrange ===============================
597
 
598
	var mypeople2 = [
599
		{name: "Bill", details: {age: 22, role: "Lead"}},
600
		{name: "Anne", details: {age: 32, role: "Assistant"}},
601
		{name: "Emma", details: {age: 19.1, role: "Team member"}},
602
		{name: "Jeff", details: {age: 33.5, role: "Lead"}},
603
		{name: "Xavier", details: {age: 32, role: "Team member"}},
604
		{name: "Julia", details: {age: 18, role: "Assistant"}},
605
		{name: "Bill", details: {age: 32, role: "Team member"}}
606
	];
607
 
608
	// ................................ Assert ..................................
609
 
610
	assert.equal($.templates("{{for mypeople sort=~sortAgeName}}{{:name}}: ({{:details.role}}) age {{:details.age}} -{{/for}}").render({mypeople: mypeople2}, {sortAgeName: sortAgeName}),
611
		"Julia: (Assistant) age 18 -Anne: (Assistant) age 32 -Bill: (Lead) age 22 -Jeff: (Lead) age 33.5 -Emma: (Team member) age 19.1 -Bill: (Team member) age 32 -Xavier: (Team member) age 32 -",
612
		"{{for mypeople sort=~sortAgeName}}: custom sort function");
613
 
614
	// ................................ Assert ..................................
615
 
616
	assert.equal($.templates("{{for mypeople sort=~sortAgeName reverseAge=true}}{{:name}}: ({{:details.role}}) age {{:details.age}} -{{/for}}").render({mypeople: mypeople2}, {sortAgeName: sortAgeName}),
617
		"Anne: (Assistant) age 32 -Julia: (Assistant) age 18 -Jeff: (Lead) age 33.5 -Bill: (Lead) age 22 -Bill: (Team member) age 32 -Xavier: (Team member) age 32 -Emma: (Team member) age 19.1 -",
618
		"{{for mypeople sort=~sortAgeName}}: custom sort function - this pointer is tagCtx");
619
 
620
	// ................................ Assert ..................................
621
 
622
	assert.equal($.templates("{{for start=0 end=0}}{{else mypeople sort=~sortAgeName reverseAge=true}}{{:name}}: ({{:details.role}}) age {{:details.age}} -{{/for}}").render({mypeople: mypeople2}, {sortAgeName: sortAgeName}),
623
		"Anne: (Assistant) age 32 -Julia: (Assistant) age 18 -Jeff: (Lead) age 33.5 -Bill: (Lead) age 22 -Bill: (Team member) age 32 -Xavier: (Team member) age 32 -Emma: (Team member) age 19.1 -",
624
		"{{for start=0 end=0}}{{else mypeople sort=~sortAgeName}}: custom sort function - this pointer is tagCtx (else block)");
625
 
626
	// =============================== Arrange ===============================
627
 
628
	$.views.tags("for2", {
629
		baseTag: "for"
630
	});
631
 
632
	// ................................ Assert ..................................
633
 
634
if (!isIE8) { // IE8 does not support filter. Need to add polyfill on sites that want this support
635
	assert.equal($.templates("{{for2 mypeople sort='details.age' reverse=true filter=~underLimit limit=20 start=1 end=-1}}{{:name}}: age {{:details.age}} - {{/for2}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Emma: age 12 - Bob: age 2 - Julia: age 0.6 - ",
636
		"{{for2 mypeople  sort='details.age' reverse=true filter=~underLimit... start=1 end=-1}} Derived tag");
637
}
638
});
639
 
640
QUnit.module("{{props}}");
641
QUnit.test("{{props}}", function(assert) {
642
	$.templates({
643
		propsTmpl: "header_{{props person}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}_footer",
644
		propsTmplObjectArray: "header_{{props people}}Key: {{:key}} - Prop: {{for prop}}{{:name}} {{/for}}{{/props}}_footer",
645
		propsTmplPrimitivesArray: "header_{{props people}}Key: {{:key}} - Prop: {{for prop}}{{:name}} {{/for}}{{/props}}_footer",
646
		templatePropsArray: "header_{{props #data}}Key: {{:key}} - Prop: {{for prop}}{{:name}} {{/for}}{{/props}}_footer",
647
		propTmpl: "Key: {{:key}} - Prop: {{:prop}}",
648
		pageTmpl: '{{props person tmpl="propTmpl"/}}',
649
		simpleProps: "a{{props people}}Content{{:#data}}|{{/props}}b",
650
		propsPrimitiveDataTypes: "a{{props people}}|{{:#data}}{{/props}}b",
651
		testTmpl: "xxx{{:name}} {{:~foo}}"
652
	});
653
 
654
	assert.equal($.render.propsTmpl({person: people[0]}), "header_Key: name - Prop: Jo| _footer", '{{props person}}...{{/props}} for an object iterates over properties');
655
	assert.equal($.render.propsTmplObjectArray({people: people}), "header_Key: 0 - Prop: Jo Key: 1 - Prop: Bill _footer", '{{props people}}...{{/props}} for an array iterates over the array - with index as key and object a prop');
656
	assert.equal($.render.templatePropsArray([people]), "header_Key: 0 - Prop: Jo Key: 1 - Prop: Bill _footer", 'Can render a template against an array, as a "layout template", by wrapping array in an array');
657
	assert.equal($.render.pageTmpl({person: people[0]}), "Key: name - Prop: Jo", '{{props person tmpl="propTmpl"/}}');
658
	assert.equal($.templates("{{props}}{{:key}} {{:prop}}{{/props}}").render({name: "Jeff"}), "name Jeff", "no parameter - defaults to current data item");
659
	assert.equal($.templates("{{props foo}}xxx{{:key}} {{:prop}} {{:~foo}}{{/props}}").render({name: "Jeff"}), "", "undefined arg - renders nothing");
660
	assert.equal($.templates("{{props tmpl='propTmpl'/}}").render({name: "Jeff"}), "Key: name - Prop: Jeff", ": {{props tmpl=.../}} no parameter - defaults to current data item");
661
 
662
	assert.equal($.templates("{{props null}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}").render(), "", "null - renders nothing");
663
	assert.equal($.templates("{{props false}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}").render(), "", "false - renders nothing");
664
	assert.equal($.templates("{{props 0}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}").render(), "", "0 - renders nothing");
665
	assert.equal($.templates("{{props 'abc'}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}").render(), "", "'abc' - renders nothing");
666
	assert.equal($.templates("{{props ''}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}").render(), "", "'' - renders nothing");
667
	assert.equal($.templates("{{props #data}}Key: {{:key}} - Prop: {{:prop}}| {{/props}}").render(people),
668
	"Key: name - Prop: Jo| Key: name - Prop: Bill| ",
669
	"If #data is an array, {{props #data}} iterates");
670
 
671
	assert.equal($.render.propsTmpl({person:{}}), "header__footer", 'Empty object renders empty string');
672
	assert.equal($.render.propsTmpl({person:{zero: 0, one: 1, str: "abc", emptyStr: "", nullVal: null , trueVal: true , falseVal: false}}),
673
	"header_Key: zero - Prop: 0| Key: one - Prop: 1| Key: str - Prop: abc| Key: emptyStr - Prop: | Key: nullVal - Prop: | Key: trueVal - Prop: true| Key: falseVal - Prop: false| _footer",
674
	'Primitive types render correctly, even if falsey');
675
});
676
 
677
QUnit.test("{{props start end sort filter reverse}}", function(assert) {
678
	// =============================== Arrange ===============================
679
		function level(aField, bField) {
680
			return aField > bField ? 1 : aField < bField ? -1 : 0;
681
		}
682
 
683
	var oddValue = function(item, index, items) { return item.prop%2; };
684
	var oddIndex = function(item, index, items) { return index%2; };
685
	var sortAgeName = function(a, b) {
686
		return level(a.prop.details.role.toLowerCase(), b.prop.details.role.toLowerCase()) // First level sort: by role
687
			|| (this.props.reverseAge ? level(b.prop.details.age, a.prop.details.age) : level(a.prop.details.age, b.prop.details.age)) // 2nd level sort: sort by age, or reverse sort by age
688
			|| level(a.prop.name.toLowerCase(), b.prop.name.toLowerCase()); // 3rd level sort: sort by name
689
	};
690
 
691
	var underLimit = function(item, index, items) {
692
		return item.prop.details.age < this.props.limit;
693
	};
694
 
695
	var myobject = {a: 1, b: 9, c: 2, d:8, A:3, B:7, C:4, D:6, e:5};
696
 
697
	assert.equal($.templates("{{props myobject}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}), "a 1 - b 9 - c 2 - d 8 - A 3 - B 7 - C 4 - D 6 - e 5 - ", "{{props myobject}} (original order)");
698
	assert.equal($.templates("{{props #data sort='prop'}}{{:key}} {{:prop}} - {{/props}}").render(myobject, true), "a 1 - c 2 - A 3 - C 4 - e 5 - D 6 - B 7 - d 8 - b 9 - ", "{{props #data sort='prop'}}");
699
	assert.equal($.templates("{{props #data sort='key'}}{{:key}} {{:prop}} - {{/props}}").render(myobject, true), 	"a 1 - A 3 - b 9 - B 7 - c 2 - C 4 - d 8 - D 6 - e 5 - ", "{{props #data sort='key'}}");
700
	assert.equal($.templates("{{props #data sort='prop' reverse=true}}{{:key}} {{:prop}} - {{/props}}").render(myobject, true), "b 9 - d 8 - B 7 - D 6 - e 5 - C 4 - A 3 - c 2 - a 1 - ", "{{props #data sort='prop' reverse=true}}");
701
	assert.equal($.templates("{{props #data sort='key' reverse=true}}{{:key}} {{:prop}} - {{/props}}").render(myobject, true), "e 5 - d 8 - D 6 - c 2 - C 4 - b 9 - B 7 - a 1 - A 3 - ", "{{props #data sort='key' reverse=true}}");
702
	assert.equal($.templates("{{props myobject reverse=true}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}), "e 5 - D 6 - C 4 - B 7 - A 3 - d 8 - c 2 - b 9 - a 1 - ", "{{props myobject reverse=true}}");
703
	assert.equal($.templates("{{props myobject sort='key' reverse=true}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}), "e 5 - d 8 - D 6 - c 2 - C 4 - b 9 - B 7 - a 1 - A 3 - ", "{{props myobject sort='key' reverse=true}}");
704
	assert.equal($.templates("{{props myobject start=1 end=-1}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "b 9 - c 2 - d 8 - A 3 - B 7 - C 4 - D 6 - ", "{{props myobject start=1 end=-1}}");
705
	assert.equal($.templates("{{props myobject start=1}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "b 9 - c 2 - d 8 - A 3 - B 7 - C 4 - D 6 - e 5 - ", "{{props myobject start=1}}");
706
	assert.equal($.templates("{{props myobject end=-1}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}),  "a 1 - b 9 - c 2 - d 8 - A 3 - B 7 - C 4 - D 6 - ", "{{props myobject end=-1}}");
707
 
708
if (!isIE8) { // IE8 does not support filter. Need to add polyfill on sites that want this support
709
	assert.equal($.templates("{{props myobject filter=~oddValue}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddValue: oddValue}), "a 1 - b 9 - A 3 - B 7 - e 5 - ", "{{props myobject filter=~oddValue}}");
710
	assert.equal($.templates("{{props myobject filter=~oddIndex}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "b 9 - d 8 - B 7 - D 6 - ", "{{props myobject filter=~oddIndex}}");
711
	assert.equal($.templates("{{props myobject sort='prop' filter=~oddValue}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddValue: oddValue}), "a 1 - A 3 - e 5 - B 7 - b 9 - ", "{{props myobject sort='prop' filter=~oddValue}}");
712
	assert.equal($.templates("{{props myobject sort='prop' filter=~oddIndex}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "c 2 - C 4 - D 6 - d 8 - ", "{{props myobject sort='prop' filter=~oddIndex}}");
713
	assert.equal($.templates("{{props myobject sort='prop' filter=~oddIndex start=1 end=3}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "C 4 - D 6 - ", "{{props myobject sort='prop' filter=~oddIndex start=1 end=3}}");
714
	assert.equal($.templates("{{props myobject sort='prop' filter=~oddIndex start=-3 end=-1}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "C 4 - D 6 - ", "{{props myobject sort='prop' filter=~oddIndex start=-3 end=-1}} Negative start or end count from the end");
715
	assert.equal($.templates("{{props myobject sort='prop' filter=~oddIndex start=3 end=3}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "", "{{props myobject sort='key' filter=~oddIndex start=3 end=3}} (outputs nothing)");
716
}
717
	assert.equal($.templates("{{props myobject step=2 start=1}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "b 9 - d 8 - B 7 - D 6 - ", "{{props myobject step=2 start=1}}");
718
	assert.equal($.templates("{{props myobject sort='prop' step=2 start=1}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "c 2 - C 4 - D 6 - d 8 - ", "{{props myobject sort='prop' step=2 start=1}}");
719
	assert.equal($.templates("{{props myobject sort='prop' step=2 start=3 end=6}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "C 4 - D 6 - ", "{{props myobject sort='prop' step=2 start=3 end=6}}");
720
	assert.equal($.templates("{{props myobject sort='prop' step=2 start=-6 end=-3}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "C 4 - D 6 - ", "{{props myobject sort='prop' step=2 start=-6 end=-3}} Negative start or end count from the end");
721
	assert.equal($.templates("{{props myobject sort='prop' step=2 start=3 end=3}}{{:key}} {{:prop}} - {{/props}}").render({myobject: myobject}, {oddIndex: oddIndex}), "", "{{props myobject sort='key' step=2 start=3 end=3}} (outputs nothing)");
722
	// =============================== Arrange ===============================
723
 
724
	var mypeople = {
725
		p1: {name: "Jo", details: {age: 22}},
726
		p2: {name: "Bob", details: {age: 2}},
727
		p3: {name: "Emma", details: {age: 12}},
728
		p7: {name: "Jeff", details: {age: 13.5}},
729
		p6: {name: "Julia", details: {age: 0.6}},
730
		p5: {name: "Xavier", details: {age: 0}}
731
	};
732
 
733
	// ................................ Assert ..................................
734
 
735
	assert.equal($.templates("{{props mypeople sort='prop.name'}}{{:prop.name}}: age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople}), "Bob: age 2 - Emma: age 12 - Jeff: age 13.5 - Jo: age 22 - Julia: age 0.6 - Xavier: age 0 - ", "{{props mypeople  sort='name'}}");
736
	assert.equal($.templates("{{props mypeople sort='prop.details.age'}}{{:prop.name}}: age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople}), "Xavier: age 0 - Julia: age 0.6 - Bob: age 2 - Emma: age 12 - Jeff: age 13.5 - Jo: age 22 - ", "{{props mypeople  sort='details.age'}}");
737
 
738
if (!isIE8) { // IE8 does not support filter. Need to add polyfill on sites that want this support
739
	assert.equal($.templates("{{props mypeople sort='prop.details.age' reverse=true filter=~underLimit limit=20}}{{:prop.name}}: age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Jeff: age 13.5 - Emma: age 12 - Bob: age 2 - Julia: age 0.6 - Xavier: age 0 - ", "{{props mypeople sort='details.age' reverse=true filter=~underLimit...}}");
740
	assert.equal($.templates("{{props mypeople sort='prop.details.age' reverse=true filter=~underLimit limit=20 start=1 end=-1}}{{:prop.name}}: age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Emma: age 12 - Bob: age 2 - Julia: age 0.6 - ", "{{props mypeople  sort='details.age' reverse=true filter=~underLimit... start=1 end=-1}}");
741
	assert.equal($.templates("{{props mypeople sort='prop.details.age' reverse=true filter=~underLimit limit=20 start=1 end=-1}}{{:prop.name}}: age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Emma: age 12 - Bob: age 2 - Julia: age 0.6 - ", "{{props mypeople  sort='details.age' reverse=true filter=~underLimit... start=1 end=-1}}");
742
}
743
	// =============================== Arrange ===============================
744
 
745
	var mypeople2 = {
746
		p1: {name: "Bill", details: {age: 22, role: "Lead"}},
747
		p2: {name: "Anne", details: {age: 32, role: "Assistant"}},
748
		p3: {name: "Emma", details: {age: 19.1, role: "Team member"}},
749
		p7: {name: "Jeff", details: {age: 33.5, role: "Lead"}},
750
		p6: {name: "Xavier", details: {age: 32, role: "Team member"}},
751
		p5: {name: "Julia", details: {age: 18, role: "Assistant"}},
752
		p4: {name: "Bill", details: {age: 32, role: "Team member"}}
753
	};
754
 
755
	// ................................ Assert ..................................
756
 
757
	assert.equal($.templates("{{props mypeople sort=~sortAgeName}}{{:prop.name}}: ({{:prop.details.role}}) age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople2}, {sortAgeName: sortAgeName}),
758
		"Julia: (Assistant) age 18 - Anne: (Assistant) age 32 - Bill: (Lead) age 22 - Jeff: (Lead) age 33.5 - Emma: (Team member) age 19.1 - Bill: (Team member) age 32 - Xavier: (Team member) age 32 - ",
759
		"{{props mypeople sort=~sortAgeName}}: custom sort function");
760
 
761
	// ................................ Assert ..................................
762
 
763
	assert.equal($.templates("{{props mypeople sort=~sortAgeName reverseAge=true}}{{:prop.name}}: ({{:prop.details.role}}) age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople2}, {sortAgeName: sortAgeName}),
764
		"Anne: (Assistant) age 32 - Julia: (Assistant) age 18 - Jeff: (Lead) age 33.5 - Bill: (Lead) age 22 - Bill: (Team member) age 32 - Xavier: (Team member) age 32 - Emma: (Team member) age 19.1 - ",
765
		"{{props mypeople sort=~sortAgeName}}: custom sort function - this pointer is tagCtx");
766
 
767
	// ................................ Assert ..................................
768
 
769
	assert.equal($.templates("{{props ''}}{{else mypeople sort=~sortAgeName reverseAge=true}}{{:prop.name}}: ({{:prop.details.role}}) age {{:prop.details.age}} - {{/props}}").render({mypeople: mypeople2}, {sortAgeName: sortAgeName}),
770
		"Anne: (Assistant) age 32 - Julia: (Assistant) age 18 - Jeff: (Lead) age 33.5 - Bill: (Lead) age 22 - Bill: (Team member) age 32 - Xavier: (Team member) age 32 - Emma: (Team member) age 19.1 - ",
771
		"{{props ''}}{{else mypeople sort=~sortAgeName}}: custom sort function - this pointer is tagCtx (else block)");
772
 
773
	// =============================== Arrange ===============================
774
 
775
	$.views.tags("props2", {
776
		baseTag: "props"
777
	});
778
 
779
	// ................................ Assert ..................................
780
 
781
if (!isIE8) { // IE8 does not support filter. Need to add polyfill on sites that want this support
782
	assert.equal($.templates("{{props2 mypeople sort='prop.details.age' reverse=true filter=~underLimit limit=20 start=1 end=-1}}{{:prop.name}}: age {{:prop.details.age}} - {{/props2}}").render({mypeople: mypeople}, {underLimit: underLimit}), "Emma: age 12 - Bob: age 2 - Julia: age 0.6 - ", "{{for2 mypeople  sort='details.age' reverse=true filter=~underLimit... start=1 end=-1}} Derived tag");
783
}
784
});
785
 
786
QUnit.module("{{!-- --}}");
787
QUnit.test("{{!-- --}}", function(assert) {
788
	// =============================== Arrange ===============================
789
	var result,
790
		tmpl = $.templates("a {{:'--1'}}\n {{for '--2} }'}} {{:}} {{/for}} \n b"),
791
		tmplWrappedInComment = $.templates("a {{!-- {{:'--1'}}\n {{for '--2} }'}} {{:}} {{/for}} \n--}} b");
792
 
793
	// ................................ Assert ..................................
794
	result = tmpl.render() + "|" + tmplWrappedInComment.render();
795
	assert.equal(result, "a --1\n  --2} } \n b|a  b",
796
		"{{!-- --}} comments out blocks including newlines and --");
797
});
798
 
799
QUnit.module("allowCode");
800
QUnit.test("{{*}}", function(assert) {
801
	// =============================== Arrange ===============================
802
	$.views.settings.allowCode(false);
803
	global.glob = {a: "AA"};
804
 
805
	var tmpl = $.templates("_{{*:glob.a}}_");
806
 
807
	// ................................ Assert ..................................
808
	assert.equal(tmpl.render(), "__",
809
		"{{*:expression}} returns nothing if allowCode not set to true");
810
 
811
	// =============================== Arrange ===============================
812
	$.views.settings.allowCode(true);
813
 
814
	var result = "" + !!tmpl.allowCode + " " + tmpl.render(); // Still returns "__" until we recompile
815
 
816
	tmpl.allowCode = true;
817
 
818
	result += "|" + !!tmpl.allowCode + " " + tmpl.render(); // Still returns "__" until we recompile
819
 
820
	// ................................ Assert ..................................
821
	assert.equal(result, "false __|true __",
822
		"If $.settings.allowCode() or tmpl.allowCode are set to true, previously compiled template is unchanged, so {{*}} still inactive");
823
 
824
	// ................................ Act ..................................
825
	tmpl = $.templates("_{{*:glob.a}}_");
826
 
827
	result = "" + !!tmpl.allowCode + " " + tmpl.render(); // Now {{*}} is active
828
 
829
	// ................................ Assert ..................................
830
	assert.equal(result, "true _AA_",
831
		"If $.settings.allowCode() set to true, {{*: expression}} returns evaluated expression, with access to globals");
832
 
833
	// =============================== Arrange ===============================
834
	$.views.settings.allowCode(false);
835
 
836
	tmpl = $.templates({
837
		markup: "_{{*:glob.a}}_",
838
		allowCode: true
839
	});
840
 
841
	// ................................ Assert ..................................
842
	assert.equal(tmpl.render(), "_AA_",
843
		"If template allowCode property set to true, {{*: expression}} returns evaluated expression, with access to globals");
844
 
845
	// ................................ Act ..................................
846
	tmpl = $.templates({
847
		markup: "_{{*:glob.a}}_"
848
	});
849
 
850
	result = "" + !!tmpl.allowCode + ":" + tmpl();
851
 
852
	tmpl = $.templates({markup: tmpl, allowCode: true});
853
 
854
	result += "|" + tmpl.allowCode + ":" + tmpl();
855
 
856
	// ................................ Assert ..................................
857
	assert.equal(result, "false:__|true:_AA_",
858
		"Can recompile tmpl to allow code, using tmpl = $.templates({markup: tmpl, allowCode: true})");
859
 
860
	// ................................ Act ..................................
861
	$.templates("myTmpl", {
862
		markup: "_{{*:glob.a}}_"
863
	});
864
 
865
	tmpl = $.templates.myTmpl;
866
 
867
	result = "" + !!tmpl.allowCode + ":" + tmpl();
868
 
869
	$.templates("myTmpl", {markup: $.templates.myTmpl, allowCode: true});
870
 
871
	tmpl = $.templates.myTmpl;
872
 
873
	result += "|" + tmpl.allowCode + ":" + tmpl();
874
 
875
	// ................................ Assert ..................................
876
	assert.equal(result, "false:__|true:_AA_",
877
		'Can recompile named tmpl to allow code, using $.templates("myTemplateName", {markup: $.templates.myTmpl, allowCode:true})"');
878
 
879
	// =============================== Arrange ===============================
880
	$.views.settings.allowCode(true);
881
 
882
	// ................................ Act ..................................
883
	global.myVar = 0;
884
 
885
	tmpl = $.templates(
886
		"{{* myvar=2; myvar+=4; }}"
887
		+ "Initial value: {{*:myvar}} "
888
		+ "{{* myvar+=11; }}"
889
		+ "New value: {{*:myvar}}");
890
 
891
	// ................................ Assert ..................................
892
	assert.equal(tmpl.render(), "Initial value: 6 New value: 17",
893
		"{{* expression}} or {{*: expression}} can access globals as window.myVar or myVar");
894
 
895
	// ................................ Act ..................................
896
	global.people = people;
897
	tmpl = $.templates("{{:start}}"
898
 
899
		+ "{{* for (var i=0, l=people.length; i<l; i++) { }}"
900
			+ " {{:title}} = {{*: people[i].name + ' ' + data.sep + ' '}}!"
901
		+ "{{* } }}"
902
 
903
		+ "{{:end}}");
904
 
905
	// ................................ Assert ..................................
906
	assert.equal(tmpl.render({title: "name", start: "Start", end: "End", sep: "..."}), "Start name = Jo ... ! name = Bill ... !End",
907
		"If allowCode set to true, on recompiling the template, {{*:expression}} returns evaluated expression, with access to globals");
908
 
909
	// ................................ Act ..................................
910
	global.myFunction = function() {
911
		return "myGlobalfunction ";
912
	};
913
	document.title = "myTitle";
914
	tmpl = $.templates("{{for people}}"
915
		+ "{{*: ' ' + glob.a}} {{*: data.name}} {{*: view.index}} {{*: view.ctx.myHelper}} {{*: myFunction() + document.title}}"
916
	+ "{{/for}}");
917
 
918
	// ................................ Assert ..................................
919
	assert.equal(tmpl.render({people: people}, {myHelper: "hi"}), " AA Jo 0 hi myGlobalfunction myTitle AA Bill 1 hi myGlobalfunction myTitle",
920
		"{{* expression}} or {{*: expression}} can access globals, the data, the view, the view context, global functions etc.");
921
 
922
	document.title = "";
923
 
924
	$.views.settings.allowCode(false);
925
 
926
});
927
 
928
QUnit.module("useViews");
929
QUnit.test("", function(assert) {
930
 
931
	// =============================== Arrange ===============================
932
	$.views.settings.allowCode(true);
933
	$.views.tags("exclaim", "!!! ");
934
	var message = "",
935
 
936
		tmpl = $.templates(
937
			"{{for towns}}"
938
				+ "{{>name}}"
939
				+ "{{*:view.index===view.parent.data.length-2 ? ' and ' : view.index<view.parent.data.length-2 ? ', ': ''}}"
940
			+ "{{/for}}");
941
 
942
	// ................................ Act ..................................
943
	try {
944
		tmpl.render({towns: towns});
945
	} catch(e) {
946
		message = e.message;
947
	}
948
 
949
	// ................................ Assert ..................................
950
	assert.ok(!tmpl.useViews && message.indexOf("undefined") > 0,
951
		"A simple template with useViews=false will not provide access to the views through allowCode");
952
 
953
	// ................................ Act ..................................
954
	message = "";
955
	tmpl.useViews = true;
956
 
957
	// ................................ Assert ..................................
958
	assert.equal(tmpl.render({towns: towns}), "Seattle, Paris and Delhi",
959
		"If tmpl.useViews set to true (for an existing template - without recompiling), the template renders with view hierarchy");
960
 
961
	// ................................ Act ..................................
962
	tmpl.useViews = false;
963
 
964
	$.views.settings.advanced({useViews: true});
965
	// ................................ Assert ..................................
966
	assert.equal(tmpl.render({towns: towns}), "Seattle, Paris and Delhi",
967
		"If tmpl.useViews is set to false, but $.views.settings.advanced({useViews: ...}) is set to true, the template renders with view hierarchy, (without recompiling).");
968
 
969
	// ................................ Act ..................................
970
	$.views.settings.advanced({useViews: false});
971
 
972
	tmpl = $.templates({markup: tmpl,
973
		useViews: true
974
	});
975
 
976
	// ................................ Assert ..................................
977
		tmpl = $.templates(
978
			"{{:#type}} "
979
			+ "{{for towns}}"
980
				+ "{{>name}}"
981
				+ "{{*:view.index===view.parent.data.length-2 ? ' and ' : view.index<view.parent.data.length-2 ? ', ': ''}}"
982
			+ "{{/for}}");
983
 
984
	var html = tmpl.render({towns: towns});
985
 
986
	assert.equal(tmpl.useViews && html, "data Seattle, Paris and Delhi",
987
		"Recompiling the template with useViews: true will create a template that has tmpl.useViews = true, which renders with a 'data' view");
988
 
989
	// ................................ Act ..................................
990
	tmpl.useViews = false;
991
 
992
	html = tmpl.render({towns: towns});
993
 
994
	// ................................ Assert ..................................
995
	assert.equal(!tmpl.useViews && html, "top Seattle, Paris and Delhi",
996
		"If tmpl.useViews set to false (for an existing template - without recompiling), the template renders without a 'data' view");
997
 
998
	// ................................ Act ..................................
999
	$.views.settings.advanced({useViews: true});
1000
 
1001
	tmpl = $.templates({markup: tmpl});
1002
 
1003
	$.views.settings.advanced({useViews: false});
1004
 
1005
	// ................................ Assert ..................................
1006
	assert.equal(tmpl.useViews && tmpl.render({towns: towns}), "data Seattle, Paris and Delhi",
1007
		"If $.views.settings.advanced({useViews: ...}) was true when the template was compiled, then the template renders with views, even if $.views.settings.advanced({useViews: ...}) is no longer set to true");
1008
 
1009
	// =============================== Arrange ===============================
1010
	$.views.settings.advanced({useViews: false});
1011
 
1012
		tmpl = $.templates(
1013
			"{{exclaim/}}"
1014
			+ "{{for towns}}"
1015
				+ "{{>name}}"
1016
				+ "{{*:view.index===view.parent.data.length-2 ? ' and ' : view.index<view.parent.data.length-2 ? ', ': ''}}"
1017
			+ "{{/for}}");
1018
 
1019
	// ................................ Assert ..................................
1020
	assert.equal(tmpl.useViews && tmpl.render({towns: towns}), "!!! Seattle, Paris and Delhi",
1021
		"A template with richer features, (such as a custom tag, or nested tags) will automatically have tmpl.useViews=true and will render with views, even if $.views.settings.advanced({useViews: ...}) is set to false");
1022
 
1023
	// ................................ Act ..................................
1024
	var originalUseViews = tmpl.useViews;
1025
	tmpl.useViews = false;
1026
 
1027
	// ................................ Assert ..................................
1028
	assert.equal(originalUseViews && !tmpl.useViews && tmpl.render({towns: towns}), "!!! Seattle, Paris and Delhi",
1029
		"Setting tmpl.useViews=false will NOT prevent a richer template from rendering views.");
1030
 
1031
	// =============================== Arrange ===============================
1032
		tmpl = $.templates(
1033
			"{{for towns}}"
1034
				+ "{{>name}}"
1035
				+ "{{*:view.index===view.parent.data.length-2 ? ' and ' : view.index<view.parent.data.length-2 ? ', ': ''}}"
1036
			+ "{{/for}}");
1037
 
1038
	// ................................ Act ..................................
1039
	originalUseViews = tmpl.useViews;
1040
	tmpl.useViews = true;
1041
 
1042
	// ................................ Assert ..................................
1043
	assert.equal(!originalUseViews && tmpl.useViews && tmpl.render({towns: towns}), "Seattle, Paris and Delhi",
1044
		"Setting tmpl.useViews=true WILL prevent a simpler template from rendering without views.");
1045
 
1046
	// ................................ Act ..................................
1047
	tmpl.useViews = originalUseViews;
1048
	$.views.settings.advanced({useViews: true});
1049
 
1050
	// ................................ Assert ..................................
1051
	assert.equal(!tmpl.useViews && tmpl.render({towns: towns}), "Seattle, Paris and Delhi",
1052
		"Setting $.views.settings.advanced({useViews: true}) WILL prevent a simpler template from rendering without views.");
1053
 
1054
	// =========================== Reset settings ============================
1055
	$.views.settings.advanced({useViews: false});
1056
	$.views.settings.allowCode(false);
1057
	document.title = "";
1058
 
1059
	// =============================== Arrange ===============================
1060
	tmpl = $.templates("{{:a.getHtml()}} {{if true}}{{:b}} {{/if}}");
1061
	var innerTmpl = $.templates("{{:inner}}"),
1062
 
1063
		data = {
1064
			a: {
1065
				getHtml: function() {
1066
					return $.templates("{{:inner}}").render(this);
1067
				},
1068
				inner: "INNER"
1069
			},
1070
			b: "OUTER"
1071
		};
1072
 
1073
	// ................................ Act ..................................
1074
		html = tmpl.render(data);
1075
 
1076
	// ................................ Assert ..................................
1077
	assert.equal(!tmpl.useViews && !innerTmpl.useViews && html, "INNER OUTER ",
1078
		"Nested top-level programmatic template calls which do not use views work correctly");
1079
		// See https://github.com/BorisMoore/jsrender/issues/333
1080
 
1081
	// ................................ Act ..................................
1082
	tmpl = $.templates({
1083
		markup: "{{:a.getHtml()}} {{if true}}{{:b}} {{/if}}",
1084
		useViews: true
1085
	});
1086
	innerTmpl = $.templates({
1087
		markup: "{{:inner}}",
1088
		useViews: true
1089
	});
1090
	html = tmpl.render(data);
1091
 
1092
	// ................................ Assert ..................................
1093
	assert.equal(tmpl.useViews && innerTmpl.useViews && html, "INNER OUTER ",
1094
		"Nested top-level programmatic template calls using views work correctly");
1095
		// See https://github.com/BorisMoore/jsrender/issues/333
1096
 
1097
});
1098
 
1099
QUnit.module("All tags");
1100
QUnit.test("itemVar", function(assert) {
1101
	var otherPeople = [
1102
		{name: "Jo", otherTels: [1, 2]},
1103
		{name: "Bill", tels: [91,92]},
1104
		{name: "Fred"}
1105
	];
1106
	var message = "";
1107
	try {
1108
		$.templates(
1109
			"{{for people itemVar='person'}}"
1110
				+ "{{:~person.name}} "
1111
			+ "{{/for}}"
1112
			).render({people: people});
1113
	} catch(e) {
1114
		message = e.message;
1115
	}
1116
 
1117
	assert.equal(message, "Syntax error\nUse itemVar='~myItem'",
1118
		"Setting itemVar='something' without initial '~' throws syntax error");
1119
 
1120
	assert.equal($.templates(
1121
		"{{for people itemVar='~person'}}"
1122
			+ "{{:~person.name}} "
1123
		+ "{{/for}}"
1124
		).render({people: people}),
1125
		"Jo Bill ",
1126
		"Setting {{for people itemVar='~person'}} creates ~person contextual parameter");
1127
 
1128
	assert.equal($.templates(
1129
		"{{for people}}"
1130
			+ "{{:name}}"
1131
		+ "{{else others itemVar='~otherPerson'}}"
1132
			+ "{{:~otherPerson.name}} "
1133
		+ "{{/for}}"
1134
		).render({others: people}),
1135
		"Jo Bill ",
1136
		"Can use itemVar on {{for}}{{else}} too: {{else others itemVar='~otherPerson'}}");
1137
 
1138
	assert.equal($.templates(
1139
		"{{for people}}"
1140
			+ "{{if tels itemVar='~person'}}"
1141
				+ "{{:name}} {{:~person.name}} "
1142
			+ "{{else otherTels itemVar='~sameperson'}}"
1143
				+ "{{:~sameperson.name}} "
1144
			+ "{{else itemVar='~stillperson'}}"
1145
				+ "{{:~stillperson.name}} "
1146
			+ "{{/if}}"
1147
		+ "{{/for}}"
1148
		).render({people: otherPeople}),
1149
		"Jo Bill Bill Fred ",
1150
		"itemVar works also on {{if}}{{else}}{{/if}} even though the context is same as outer context for {{if}}.");
1151
 
1152
	assert.equal($.templates(
1153
		"{{for people itemVar='~person'}}"
1154
			+ "{{for tels itemVar='~tel'}}"
1155
				+ "{{:~person.name}} "
1156
				+ "{{:~tel}} "
1157
			+ "{{else otherTels itemVar='~othertel'}}"
1158
				+ "{{:~person.name}} "
1159
				+ "{{:~othertel}} "
1160
			+ "{{else itemVar='~theperson'}}"
1161
				+ "{{:~theperson===~person&&~person===#data}} "
1162
				+ "{{:~theperson.name}} "
1163
				+ "no phones"
1164
			+ "{{/for}}"
1165
		+ "{{/for}}"
1166
		).render({people: otherPeople}),
1167
		"Jo 1 Jo 2 Bill 91 Bill 92 true Fred no phones",
1168
		"itemVar works also on {{for arr1}}{{else arr2}}{{else}}{{/for}}" +
1169
		"\neven though the context for the final {{else}} is the same as outer context for {{if}}.");
1170
 
1171
	assert.equal($.templates(
1172
		"{{for people itemVar='~person'}}"
1173
			+ "{{:~person.name}}"
1174
			+ "{{if ~person.tels itemVar='~ifVar'}}"
1175
					+ " Phones:"
1176
					+ "{{for ~ifVar.tels itemVar='~tel'}}"
1177
						+ " {{:~tel}}"
1178
					+ "{{/for}}"
1179
				+ "{{/if}}. "
1180
			+ "{{/for}}"
1181
		).render({people: otherPeople}),
1182
		"Jo. Bill Phones: 91 92. Fred. ",
1183
		"Using itemVar and passing context to nested templates");
1184
 
1185
	assert.equal($.templates(
1186
		"{{for people itemVar='~person'}}"
1187
			+ "{{:~person.name}}"
1188
			+ "{{for ~person.tels itemVar='~tel'}}"
1189
				+ " {{:~tel}}"
1190
			+ "{{else otherTels itemVar='~tel'}}"
1191
				+ " {{:~tel}}"
1192
			+ "{{else}}"
1193
				+ " (No phones)"
1194
			+ "{{/for}}"
1195
			+ ". "
1196
		+ "{{/for}}"
1197
		).render({people: otherPeople}),
1198
		"Jo 1 2. Bill 91 92. Fred (No phones). ",
1199
		"Additional example using itemVar and passing context to nested templates");
1200
 
1201
	assert.equal($.templates({
1202
		markup:
1203
			"{{wrappedFor people 'u' itemVar='~person'}}"
1204
				+ "{{:~person.name}} "
1205
				+ "{{wrappedFor ~person.tels 'i' itemVar='~tel'}}"
1206
					+ "{{:~tel}} "
1207
				+ "{{else otherTels 'b' itemVar='~tel'}}"
1208
					+ "{{:~tel}} "
1209
				+ "{{/wrappedFor}}"
1210
			+ "{{/wrappedFor}}",
1211
			tags: {
1212
				wrappedFor: function(val, wrapTag) {
1213
					if (val) {
1214
						return "<" + wrapTag + ">" + this.tagCtx.render(val) + "</" + wrapTag + ">";
1215
					}
1216
				}
1217
			}
1218
		}).render({people: otherPeople}),
1219
		"<u>Jo  <b>1 2 </b>Bill <i>91 92 </i> Fred   </u>",
1220
		"itemVar with custom tags {{wrappedFor}}{{else}}{{/wrappedFor}}, and passing context to nested templates");
1221
 
1222
	assert.equal($.templates(
1223
		"{{for people itemVar='~person'}}"
1224
			+ "{{props ~person itemVar='~prop'}}"
1225
				+ "{{:~prop.key}}: {{:~prop.prop}} "
1226
			+ "{{/props}}"
1227
		+ "{{/for}}"
1228
		).render({people: otherPeople}),
1229
		"name: Jo otherTels: 1,2 name: Bill tels: 91,92 name: Fred ",
1230
		"itemVar with {{props}}, and passing context to nested templates");
1231
 
1232
	assert.equal($.templates(
1233
		"{{for people itemVar='~person'}}"
1234
			+ "{{props ~person.tels itemVar='~prop'}}"
1235
				+ "{{:~person.name}} Tel: {{:~prop.key}}: {{:~prop.prop}} "
1236
			+ "{{else itemVar='~personWithoutTels'}}"
1237
				+ "{{:~personWithoutTels.name}}: has no tels "
1238
			+ "{{/props}}"
1239
		+ "{{/for}}"
1240
		).render({people: otherPeople}),
1241
		"Jo: has no tels Bill Tel: 0: 91 Bill Tel: 1: 92 Fred: has no tels ",
1242
		"itemVar with {{props}}{{else}}{{/props}}, and passing context to nested templates");
1243
});
1244
 
1245
QUnit.module("api no jQuery");
1246
QUnit.test("templates", function(assert) {
1247
	// ................................ Arrange ..................................
1248
	$.templates("./test/templates/file/path.html", null); // In case template has been stored in a previous test
1249
 
1250
	// ................................ Act ..................................
1251
	var tmpl0 = $.templates({markup: "./test/templates/file/path.html"}); // Compile template but do not cache
1252
 
1253
	// ............................... Assert .................................
1254
	assert.equal(!$.templates["./test/templates/file/path.html"] && tmpl0.render({name: "Jo0"}),
1255
		isIE8 ? "\nServerRenderedTemplate_Jo0_B" : "ServerRenderedTemplate_Jo0_B",
1256
		"Compile server-generated template, without caching");
1257
 
1258
	// ................................ Act ..................................
1259
	var tmpl1 = $.templates("./test/templates/file/path.html"); // Compile and cache, using path as key
1260
 
1261
	// ............................... Assert .................................
1262
	assert.equal(tmpl1 !== tmpl0 && $.templates["./test/templates/file/path.html"] === tmpl1 && tmpl1.render({name: "Jo1"}),
1263
		isIE8 ? "\nServerRenderedTemplate_Jo1_B" : "ServerRenderedTemplate_Jo1_B",
1264
		"Compile server-generated template, and cache on file path");
1265
 
1266
	// ................................ Act ..................................
1267
	var tmpl2 = $.templates("./test/templates/file/path.html"); // Use cached template, accessed by path as key
1268
 
1269
	// ............................... Assert .................................
1270
	assert.equal(tmpl2 === tmpl1 && tmpl1.render({name: "Jo2"}),
1271
		isIE8 ? "\nServerRenderedTemplate_Jo2_B" : "ServerRenderedTemplate_Jo2_B",
1272
		"Re-use cached server-generated template");
1273
 
1274
	// ................................ Act ..................................
1275
	var tmpl3 = $.templates({markup: "./test/templates/file/path.html"}); // Re-compile template but do not cache. Leaved cached template.
1276
 
1277
	// ............................... Assert .................................
1278
	assert.equal(tmpl3 !== tmpl0 && tmpl3 !== tmpl1 && $.templates["./test/templates/file/path.html"] === tmpl1 && tmpl3.render({name: "Jo3"}),
1279
		isIE8 ? "\nServerRenderedTemplate_Jo3_B" : "ServerRenderedTemplate_Jo3_B",
1280
		"Recompile server-generated template, without caching");
1281
 
1282
	// ................................ Reset ................................
1283
	delete $.templates["./test/templates/file/path.html"];
1284
	if (isBrowser) {
1285
		document.getElementById("./test/templates/file/path.html").removeAttribute("data-jsv-tmpl");
1286
	}
1287
 
1288
	// =============================== Arrange ===============================
1289
	tmplString = "A_{{:name}}_B";
1290
 
1291
	var tmpl = $.templates(tmplString);
1292
	// ............................... Assert .................................
1293
	assert.equal(tmpl.render(person), "A_Jo_B",
1294
		'Compile from string: var tmpl = $.templates(tmplString);');
1295
 
1296
	// ............................... Assert .................................
1297
	assert.equal(tmpl(person), "A_Jo_B",
1298
		'Compiled template is itself the render function: html = tmpl(data);');
1299
 
1300
	// =============================== Arrange ===============================
1301
	var fnToString = tmpl.fn.toString();
1302
 
1303
	// ............................... Assert .................................
1304
	assert.equal($.templates("", tmplString).fn.toString() === fnToString && $.templates(null, tmplString).fn.toString() === fnToString && $.templates(undefined, tmplString).fn.toString() === fnToString, true,
1305
		'if name is "", null, or undefined, then var tmpl = $.templates(name, tmplString)' +
1306
		'\nis equivalent to var tmpl = $.templates(tmplString);');
1307
 
1308
	// =============================== Arrange ===============================
1309
	$.templates("myTmpl", tmplString);
1310
 
1311
	// ............................... Assert .................................
1312
	assert.equal($.render.myTmpl(person), "A_Jo_B",
1313
		'Compile and register named template: $.templates("myTmpl", tmplString);');
1314
 
1315
	// =============================== Arrange ===============================
1316
	$.templates({myTmpl2: tmplString, myTmpl3: "X_{{:name}}_Y"});
1317
 
1318
	// ............................... Assert .................................
1319
	assert.equal($.render.myTmpl2(person) + $.render.myTmpl3(person), "A_Jo_BX_Jo_Y",
1320
		'Compile and register named templates: $.templates({myTmpl: tmplString, myTmpl2: tmplString2});');
1321
 
1322
	// =============================== Arrange ===============================
1323
	$.templates("!'-#==", "x");
1324
	$.templates({'&^~>"2': "y"});
1325
	assert.equal($.render["!'-#=="](person) + $.render['&^~>"2'](person), "xy",
1326
		'Named templates can have arbitrary names;');
1327
 
1328
	$.templates({myTmpl4: "A_B"});
1329
 
1330
	// ............................... Assert .................................
1331
	assert.equal($.render.myTmpl4(person), "A_B",
1332
		'$.templates({myTmpl: htmlWithNoTags});');
1333
 
1334
	// =============================== Arrange ===============================
1335
	$.templates("myTmpl5", {
1336
		markup: tmplString
1337
	});
1338
 
1339
	// ............................... Assert .................................
1340
	assert.equal($.render.myTmpl5(person), "A_Jo_B",
1341
		'$.templates("myTmpl", {markup: markupString});');
1342
 
1343
	// ............................... Assert .................................
1344
	assert.equal($.templates("", {markup: tmplString}).render(person), "A_Jo_B",
1345
		'Compile from template object without registering: var tmpl = $.templates("", {markup: markupString});');
1346
 
1347
	// ............................... Assert .................................
1348
	assert.equal($.templates({markup: tmplString}).render(person), "A_Jo_B",
1349
		'Compile from template object without registering: var tmpl = $.templates({markup: markupString});');
1350
 
1351
	// =============================== Arrange ===============================
1352
	$.templates({
1353
		myTmpl6: {
1354
			markup: tmplString
1355
		}
1356
	});
1357
 
1358
	// ............................... Assert .................................
1359
	assert.equal($.render.myTmpl6(person), "A_Jo_B",
1360
		'$.templates({myTmpl: {markup: markupString}});');
1361
 
1362
	// =============================== Arrange ===============================
1363
	$.templates("myTmpl7", tmpl);
1364
 
1365
	// ............................... Assert .................................
1366
	assert.equal($.render.myTmpl7(person), "A_Jo_B",
1367
		'Cloning a template: $.templates("newName", tmpl);');
1368
 
1369
	// ............................... Assert .................................
1370
	assert.equal($.templates(tmpl) === tmpl, true,
1371
		'$.templates(tmpl) returns tmpl');
1372
 
1373
	// ............................... Assert .................................
1374
	assert.equal($.templates("", tmpl) === tmpl, true,
1375
		'$.templates("", tmpl) returns tmpl');
1376
 
1377
	// =============================== Arrange ===============================
1378
	var tmplWithHelper = $.templates("A_{{:name}}_B{{:~foo}}");
1379
	var result = tmplWithHelper(person, {foo: "thisFoo"});
1380
 
1381
	var tmplWithHelper2 = $.templates({markup: tmplWithHelper, helpers: {foo: "thatFoo"}});
1382
	result += "|" + tmplWithHelper2(person);
1383
 
1384
	// ............................... Assert .................................
1385
	assert.equal(result, "A_Jo_BthisFoo|A_Jo_BthatFoo",
1386
		'Cloning a template to add/replace/change some template properties: var tmpl2 = $.templates({markup: tmpl1, otherOptions...});');
1387
 
1388
	// ............................... Assert .................................
1389
	assert.equal($.templates("", tmpl) === tmpl, true,
1390
		'$.templates(tmpl) returns tmpl');
1391
 
1392
	// ............................... Assert .................................
1393
	assert.equal($.templates("").render(), "",
1394
		'$.templates("") is a template with empty string as content');
1395
 
1396
	// =============================== Arrange ===============================
1397
	$.templates("myEmptyTmpl", "");
1398
 
1399
	// ............................... Assert .................................
1400
	assert.equal($.templates.myEmptyTmpl.render(), "",
1401
		'$.templates("myEmptyTmpl", "") is a template with empty string as content');
1402
 
1403
	// =============================== Arrange ===============================
1404
	$.templates("myTmpl", null);
1405
 
1406
	// ............................... Assert .................................
1407
	assert.equal($.templates.myTmpl === undefined && $.render.myTmpl === undefined, true,
1408
		'Remove a named template: $.templates("myTmpl", null);');
1409
});
1410
 
1411
QUnit.test("render", function(assert) {
1412
	var tmpl1 = $.templates("myTmpl8", tmplString);
1413
	$.templates({
1414
		simple: "Content{{:#data}}|",
1415
		templateForArray: "Content{{for #data}}{{:#index}}{{/for}}{{:~foo}}",
1416
		primitiveDataTypes: "|{{:#data}}"
1417
	});
1418
 
1419
	assert.equal(tmpl1.render(person), "A_Jo_B", 'tmpl1.render(data);');
1420
	assert.equal($.render.myTmpl8(person), "A_Jo_B", '$.render.myTmpl8(data);');
1421
 
1422
	$.templates("myTmpl9", "A_{{for}}inner{{:name}}content{{/for}}_B");
1423
	assert.equal($.templates.myTmpl9.tmpls[0].render(person), "innerJocontent", 'Access nested templates: $.templates["myTmpl9[0]"];');
1424
 
1425
	$.templates("myTmpl10", "top index:{{:#index}}|{{for 1}}nested index:{{:#get('item').index}}|{{if #get('item').index===0}}nested if index:{{:#get('item').index}}|{{else}}nested else index:{{:#get('item').index}}|{{/if}}{{/for}}");
1426
 
1427
	assert.equal($.render.myTmpl10(people), "top index:0|nested index:0|nested if index:0|top index:1|nested index:1|nested else index:1|",
1428
										"#get('item').index gives the integer index even in nested blocks");
1429
 
1430
	$.templates("myTmpl11", "top index:{{:#index}}|{{for people}}nested index:{{:#index}}|{{if #index===0}}nested if index:{{:#get('item').index}}|{{else}}nested else index:{{:#get('item').index}}|{{/if}}{{/for}}");
1431
 
1432
	assert.equal($.render.myTmpl11({people: people}), "top index:|nested index:0|nested if index:0|nested index:1|nested else index:1|",
1433
										"#get('item').index gives the integer index even in nested blocks");
1434
 
1435
	$.views.tags({
1436
		myWrap: {}
1437
	});
1438
 
1439
	var templateWithIndex = $.templates(
1440
			'{{for people}}'
1441
			+ 'a{{:#index}} '
1442
			+ '{{if true}}b{{:#index}}{{/if}} '
1443
			+ 'c{{:#index}} '
1444
			+ '{{myWrap}}d{{:#index}} {{/myWrap}}'
1445
		+ '{{/for}}');
1446
 
1447
	$.views.settings.debugMode(true);
1448
	var result = templateWithIndex.render({people: [1,2]});
1449
 
1450
	$.views.settings.debugMode(false);
1451
	var result2 = templateWithIndex.render({people: [1,2]});
1452
 
1453
	assert.equal(result2 === result && result,
1454
		"a0 bFor #index in nested block use #getIndex(). c0 dFor #index in nested block use #getIndex(). a1 bFor #index in nested block use #getIndex(). c1 dFor #index in nested block use #getIndex(). ",
1455
		"#index gives error message in nested blocks (whether or not debugMode is true).");
1456
 
1457
	var templateWithGetIndex = $.templates(
1458
			'{{for people}}'
1459
			+ 'a{{:#getIndex()}} '
1460
			+ '{{if true}}b{{:#getIndex()}}{{/if}} '
1461
			+ 'c{{:#getIndex()}} '
1462
			+ '{{myWrap}}d{{:#getIndex()}} {{/myWrap}}'
1463
		+ '{{/for}}');
1464
 
1465
	assert.equal(templateWithGetIndex.render({people: [1,2]}),
1466
		"a0 b0 c0 d0 a1 b1 c1 d1 ",
1467
		"#getIndex gives inherited index in nested blocks.");
1468
 
1469
	$.views.helpers({myKeyIsCorrect: function(view) {
1470
		return view.parent.views[view._.key] === view;
1471
	}});
1472
	$.templates("myTmpl12", "{{for people}}nested {{:~myKeyIsCorrect(#view)}}|{{if #index===0}}nested if {{:~myKeyIsCorrect(#view)}}|{{else}}nested else {{:~myKeyIsCorrect(#view)}}|{{/if}}{{/for}}");
1473
 
1474
	assert.equal($.render.myTmpl12({people: people}), "nested true|nested if true|nested true|nested else true|",
1475
										'view._key gives the key of this view in the parent views collection/object');
1476
 
1477
	assert.equal($.templates(tmplString).render(person), "A_Jo_B", 'Compile from string: var html = $.templates(tmplString).render(data);');
1478
	assert.equal($.render.myTmpl8(people), "A_Jo_BA_Bill_B", '$.render.myTmpl(array);');
1479
	assert.equal($.render.simple([]), "", 'Empty array renders empty string');
1480
	assert.equal($.render.simple(["",false,null,undefined,1]), "Content|Contentfalse|Content|Content|Content1|", 'Empty string, false, null or undefined members of array are also rendered');
1481
	assert.equal($.render.simple(null), "Content|", 'null renders once with #data null');
1482
	assert.equal($.render.simple(), "Content|", 'Undefined renders once with #data undefined');
1483
	assert.equal($.render.simple(false), "Contentfalse|", 'false renders once with #data false');
1484
	assert.equal($.render.simple(0), "Content0|", '0 renders once with #data 0');
1485
	assert.equal($.render.simple(""), "Content|", '"" renders once with #data ""');
1486
 
1487
	assert.equal($.render.templateForArray([[null,undefined,1]]), "Content012", 'Can render a template against an array without iteration, by wrapping array in an array');
1488
	assert.equal($.render.templateForArray([null,undefined,1], true), "Content012", 'render(array, true) renders an array without iteration');
1489
	assert.equal($.render.templateForArray([null,undefined,1], {foo:"foovalue"}, true), "Content012foovalue", 'render(array, helpers, true) renders an array without iteration, while passing in helpers');
1490
	assert.equal($.templates.templateForArray.render([null,undefined,1], {foo:"foovalue"}, true), "Content012foovalue", 'render(array, helpers, true) renders an array without iteration, while passing in helpers');
1491
	assert.equal($.render.templateForArray([[]]), "Content", 'Can render a template against an empty array without iteration, by wrapping array in an array');
1492
	assert.equal($.render.templateForArray([], true), "Content", 'Can render a template against an empty array without iteration, by passing in true as second parameter');
1493
	assert.equal($.render.templateForArray([], {foo: "foovalue"}, true), "Contentfoovalue", 'Can render a template against an empty array without iteration, by by passing in true as third parameter');
1494
	assert.equal($.render.primitiveDataTypes([0,1,"abc","",,true,false]), "|0|1|abc|||true|false", 'Primitive types render correctly, even if falsey');
1495
});
1496
 
1497
QUnit.test("converters", function(assert) {
1498
	function loc(data) {
1499
		switch (data) {case "desktop": return "bureau";}
1500
		return data;
1501
	}
1502
	$.views.converters({loc2: loc});
1503
	assert.equal($.templates("{{loc2:#data}}:{{loc2:'desktop'}}").render("desktop"), "bureau:bureau", "$.views.converters({loc: locFunction})");
1504
 
1505
	var locFn = $.views.converters("loc", loc);
1506
	assert.equal(locFn === loc && $.views.converters.loc === loc && $.views.converters.loc2 === loc, true, 'locFunction === $.views.converters.loc === $.views.converters.loc2');
1507
 
1508
	$.views.converters({loc2: null});
1509
	assert.equal($.views.converters.loc2, undefined, '$.views.converters({loc2: null}) to remove registered converter');
1510
 
1511
	assert.equal($.templates("{{attr:a}}").render({a: 0}), "0", '{{attr:0}} returns "0"');
1512
	assert.equal($.templates("{{attr:a}}").render({}), "", "{{attr:undefined}} returns empty string");
1513
	assert.equal($.templates("{{attr:a}}").render({a: ""}), "", "{{attr:''}} returns empty string");
1514
	assert.equal($.templates("{{attr:a}}").render({a: null}), "", '{{attr:null}} returns empty string');
1515
	assert.equal($.templates("{{attr:a}}").render({a: "<>&'" + '"'}), "&lt;&gt;&amp;&#39;&#34;", '{{attr:"<>&' + "'" + '}} returns "&lt;&gt;&amp;&#39;&#34;"');
1516
 
1517
	assert.equal($.templates("{{>a}}").render({a: 0}), "0", '{{>0}} returns "0"');
1518
	assert.equal($.templates("{{>a}}").render({}), "", "{{>undefined}} returns empty string");
1519
	assert.equal($.templates("{{>a}}").render({a: ""}), "", "{{>''}} returns empty string");
1520
	assert.equal($.templates("{{>a}}").render({a: null}), "", "{{>null}} returns empty string");
1521
	assert.equal($.templates("{{>a}}").render({a: "<>&'" + '"'}), "&lt;&gt;&amp;&#39;&#34;", '{{>"<>&' + "'" + '}} returns "&lt;&gt;&amp;&#39;&#34;"');
1522
 
1523
	assert.equal($.templates("{{loc:a}}").render({a: 0}), "0", '{{cnvt:0}} returns "0"');
1524
	assert.equal($.templates("{{loc:a}}").render({}), "", '{{cnvt:undefined}} returns empty string');
1525
	assert.equal($.templates("{{loc:a}}").render({a: ""}), "", "{{cnvt:''}} returns empty string");
1526
	assert.equal($.templates("{{loc:a}}").render({a: null}), "", "{{cnvt:null}} returns empty string");
1527
 
1528
	assert.equal($.templates("{{attr:a}}|{{>a}}|{{loc:a}}|{{:a}}").render({}), "|||", "{{attr:undefined}}|{{>undefined}}|{{loc:undefined}}|{{:undefined}} returns correct values");
1529
	assert.equal($.templates("{{attr:a}}|{{>a}}|{{loc:a}}|{{:a}}").render({a:0}), "0|0|0|0", "{{attr:0}}|{{>0}}|{{loc:0}}|{{:0}} returns correct values");
1530
	assert.equal($.templates("{{attr:a}}|{{>a}}|{{loc:a}}|{{:a}}").render({a:false}), "false|false|false|false", "{{attr:false}}|{{>false}}|{{loc:false}}|{{:false}} returns correct values");
1531
});
1532
 
1533
QUnit.test("{{sometag convert=converter}}", function(assert) {
1534
	function loc(data) {
1535
		switch (data) {
1536
			case "desktop": return "bureau";
1537
			case "a<b": return "a moins <que b";}
1538
		return data;
1539
	}
1540
	$.views.converters("loc", loc);
1541
 
1542
	assert.equal($.templates("1{{:#data convert='loc'}} 2{{:'desktop' convert='loc'}} 3{{:#data convert=~myloc}} 4{{:'desktop' convert=~myloc}}").render("desktop", {myloc: loc}), "1bureau 2bureau 3bureau 4bureau", "{{: convert=~myconverter}}");
1543
	assert.equal($.templates("1:{{:'a<b' convert=~myloc}} 2:{{> 'a<b'}} 3:{{html: 'a<b' convert=~myloc}} 4:{{> 'a<b' convert=~myloc}} 5:{{attr: 'a<b' convert=~myloc}}").render(1, {myloc: loc}),
1544
		"1:a moins <que b 2:a&lt;b 3:a&lt;b 4:a&lt;b 5:a moins <que b",
1545
		"{{foo: convert=~myconverter}} convert=converter is used rather than {{foo:, but with {{html: convert=~myconverter}}" +
1546
		"\nor {{> convert=~myconverter}} html converter takes precedence and ~myconverter is ignored");
1547
	assert.equal($.templates("{{if true convert=~invert}}yes{{else false convert=~invert}}no{{else}}neither{{/if}}").render('desktop', {invert: function(val) {return !val;}}), "no", "{{if expression convert=~myconverter}}...{{else expression2 convert=~myconverter}}... ");
1548
	assert.equal($.templates("{{for #data convert=~reverse}}{{:#data}}{{/for}}").render([1,2,3], {reverse: function(val) {return val.reverse();}}, true), "321", "{{for expression convert=~myconverter}}");
1549
});
1550
 
1551
QUnit.test("tags", function(assert) {
1552
	// ................................ Reset ..................................
1553
	towns = [{name: "Seattle"}, {name: "Paris"}, {name: "Delhi"}];
1554
 
1555
	// ................................ Act ..................................
1556
	assert.equal($.templates("{{sort people reverse=true}}{{:name}}{{/sort}}").render({people: people}), "BillJo", "$.views.tags({sort: sortFunction})");
1557
 
1558
	assert.equal($.templates("{^{sort people reverse=true}}{^{:name}}{{/sort}}").render({people: people}), "BillJo", "Calling render() with inline data-binding {^{...}} renders normally without binding");
1559
 
1560
	assert.equal($.templates("{{sort people reverse=true towns}}{{:name}}{{/sort}}").render({people: people, towns:towns}), "DelhiParisSeattleBillJo", "Multiple parameters in arbitrary order: {{sort people reverse=true towns}}");
1561
 
1562
	assert.equal($.templates("{{sort reverse=false people reverse=true towns}}{{:name}}{{/sort}}").render({people: people, towns:towns}), "DelhiParisSeattleBillJo", "Duplicate named parameters - last wins: {{sort reverse=false people reverse=true towns}}");
1563
 
1564
	var sort2 = $.views.tags("sort2", sort);
1565
	assert.equal(sort2.render === sort && $.views.tags.sort.render === sort && $.views.tags.sort2.render === sort, true, 'sortFunction === $.views.tags.sort.render === $.views.tags.sort2.render');
1566
 
1567
	$.views.tags("sort2", null);
1568
	assert.equal($.views.tags.sort2, undefined, '$.views.tags("sort2", null) to remove registered tag');
1569
 
1570
	$.views.tags("boldTag", {
1571
		render: function() {
1572
			return "<em>" + this.tagCtx.render() + "</em>";
1573
		},
1574
		template: "{{:#data}}"
1575
	});
1576
	assert.equal($.templates("{{boldTag}}{{:#data}}{{/boldTag}}").render("theData"), "<em>theData</em>",
1577
		'Data context inside a block tag using tagCtx.render() is the same as the outer context');
1578
 
1579
	assert.equal($.templates("{{boldTag/}}").render("theData"), "<em>theData</em>",
1580
		'Data context inside the built-in template of a self-closing tag using tagCtx.render() is the same as the outer context');
1581
 
1582
	assert.equal($.templates("{{sort people reverse=true}}{{:name}}{{/sort}}").render({people: people}), "BillJo", "$.views.tags({sort: sortFunction})");
1583
 
1584
	// =============================== Arrange ===============================
1585
	// ................................ Act ..................................
1586
	var eventData = "",
1587
 
1588
		renderedOutput = $.templates({
1589
			markup: '{^{myWidget name/}}',
1590
			tags: {
1591
				myWidget: {
1592
					init: function(tagCtx, linkCtx) {
1593
						eventData += " init";
1594
					},
1595
					render: function(name, things) {
1596
						eventData += " render";
1597
						return name + " " + this.getType();
1598
					},
1599
					getType: function() {
1600
						eventData += " getType";
1601
						return this.type;
1602
					},
1603
					type: "special"
1604
				}
1605
			}
1606
		}).render(person);
1607
 
1608
	// ............................... Assert .................................
1609
	assert.equal(renderedOutput + "|" + eventData, "Jo special| init render getType", '{^{myWidget/}} - Events fire in order during rendering: init render');
1610
 
1611
	// =============================== Arrange ===============================
1612
	$.views.tags({
1613
		noRenderNoTemplate: {},
1614
		voidRender: function() {},
1615
		emptyRender: function() {return "";},
1616
		emptyTemplate: {
1617
			template: ""
1618
		},
1619
		templateReturnsEmpty: {
1620
			template: "{{:a}}"
1621
		}
1622
	});
1623
 
1624
	// ............................... Assert .................................
1625
	assert.equal($.templates("a{{noRenderNoTemplate/}}b{^{noRenderNoTemplate/}}c{{noRenderNoTemplate}}{{/noRenderNoTemplate}}d{^{noRenderNoTemplate}}{{/noRenderNoTemplate}}e").render(1), "abcde",
1626
	"non-rendering tag (no template, no render function) renders empty string");
1627
 
1628
	assert.equal($.templates("a{{voidRender/}}b{^{voidRender/}}c{{voidRender}}{{/voidRender}}d{^{voidRender}}{{/voidRender}}e").render(1), "abcde",
1629
	"non-rendering tag (no template, no return from render function) renders empty string");
1630
 
1631
	assert.equal($.templates("a{{emptyRender/}}b{^{emptyRender/}}c{{emptyRender}}{{/emptyRender}}d{^{emptyRender}}{{/emptyRender}}e").render(1), "abcde",
1632
	"non-rendering tag (no template, empty string returned from render function) renders empty string");
1633
 
1634
	assert.equal($.templates("a{{emptyTemplate/}}b{^{emptyTemplate/}}c{{emptyTemplate}}{{/emptyTemplate}}d{^{emptyTemplate}}{{/emptyTemplate}}e").render(1), "abcde",
1635
	"non-rendering tag (template has no content, no render function) renders empty string");
1636
 
1637
	assert.equal($.templates("a{{templateReturnsEmpty/}}b{^{templateReturnsEmpty/}}c{{templateReturnsEmpty}}{{/templateReturnsEmpty}}d{^{templateReturnsEmpty}}{{/templateReturnsEmpty}}e").render(1), "abcde",
1638
	"non-rendering tag (template returns empty string, no render function) renders empty string");
1639
 
1640
	$.views.tags({
1641
		tagJustTemplate: {
1642
			argDefault: false,
1643
			template: "{{:#data ? name||length : 'Not defined'}} "
1644
		},
1645
		tagJustTemplateObject: {
1646
			argDefault: false,
1647
			template: {markup: "{{:#data ? name||length : 'Not defined'}} "}
1648
		},
1649
		tagWithTemplateWhichIteratesAgainstCurrentData: {
1650
			template: "{{:#data ? name : 'Not defined'}} ",
1651
			render: function() {
1652
				return this.tagCtx.render(); // Renders against current data - and iterates if array
1653
			}
1654
		},
1655
		tagJustRender: function(val) {
1656
			return val.name + " ";
1657
		},
1658
		tagJustRenderArray: function(val) {
1659
			return val.length + " ";
1660
		},
1661
		tagWithTemplateNoIteration: {
1662
			contentCtx: true,
1663
			render: function(val) {
1664
				return this.tagCtx.render(val, true); // Render without iteration
1665
			},
1666
			template: "{{:#data.length}} "
1667
		},
1668
		tagWithTemplateNoIterationWithHelpers: {
1669
			render: function(val) {
1670
				return this.tagCtx.render(val, {foo: "foovalue"}, true); // Render without iteration
1671
			},
1672
			template: "{{:#data.length}} {{:~foo}}"
1673
		},
1674
		tagWithTemplateWhichIteratesFirstArg: {
1675
			template: "{{:#data ? name : 'Not defined'}} ",
1676
			render: function(val) {
1677
				return this.tagCtx.render(val); // Renders against first arg - defaults to current data - and iterates if array
1678
			}
1679
		},
1680
		tagWithTemplateWhichIteratesFirstArgNoDefaultArg: {
1681
			template: "{{:#data ? name : 'Not defined'}} ",
1682
			argDefault: false,
1683
			render: function(val) {
1684
				return this.tagCtx.render(val); // Renders against first arg and iterates if array. Does not default to current data
1685
			}
1686
		}
1687
	});
1688
 
1689
	assert.equal($.templates("a{{include person}}{{tagJustTemplate/}}{{/include}}").render({person: {name: "Jo"}}), "aJo ",
1690
	"Tag with just a template and no param renders once against current data, if object");
1691
 
1692
	assert.equal($.templates("a{{include person}}{{tagJustTemplateObject/}}{{/include}}").render({person: {name: "Jo"}}), "aJo ",
1693
	"Tag with just a template object and no param renders once against current data, if object");
1694
 
1695
	assert.equal($.templates("a{{include person}}{{tagJustTemplate undefinedProperty/}}{{/include}}").render({person: {name: "Jo"}}), "aNot defined ",
1696
	"Tag with just a template and a parameter which is not defined renders once against 'undefined'");
1697
 
1698
	assert.equal($.templates("a{{include people}}{{tagJustTemplate/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "a2 ",
1699
	"Tag with just a template and no param renders once against current data, even if array" +
1700
	"\n- but can add render method with tagCtx.render(val) to iterate - (next test)");
1701
 
1702
	assert.equal($.templates("a{{include people}}{{tagWithTemplateWhichIteratesAgainstCurrentData/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "aJo Mary ",
1703
	"Tag with a template and no param and render method calling tagCtx.render() iterates against current data if array");
1704
 
1705
	assert.equal($.templates("a{{include people}}{{tagWithTemplateWhichIteratesAgainstCurrentData thisisignored/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "aJo Mary ",
1706
	"Tag with a template and no param and render method calling tagCtx.render() iterates against current data if array" +
1707
	"\n- and ignores argument if provided");
1708
 
1709
	assert.equal($.templates("a{{include people}}{{tagWithTemplateWhichIteratesFirstArg/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "aJo Mary ",
1710
	"Tag with a template and no param and render method calling tagCtx.render(val) renders against first arg" +
1711
	"\n- or defaults to current data, and iterates if array");
1712
 
1713
	assert.equal($.templates("a{{tagWithTemplateWhichIteratesFirstArg people/}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "aJo Mary ",
1714
	"Tag with a template and no param and render method calling tagCtx.render(val) iterates against argument if array");
1715
 
1716
	assert.equal($.templates("a{{include people}}{{tagWithTemplateNoIteration/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "a2 ",
1717
	"If current data is an array, a tag with a template and a render method calling" +
1718
	"\ntagCtx.render(val, true) and no param renders against array without iteration");
1719
 
1720
	assert.equal($.templates("a{{include people}}{{tagWithTemplateWhichIteratesFirstArgNoDefaultArg/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "aNot defined ",
1721
	"Tag with a template and no param and render method calling tagCtx.render(val) but with tag.argDefault=false renders against first arg" +
1722
	"\n- and does not default to current data if arg is undefined");
1723
 
1724
	assert.equal($.templates("a{{include people}}{{tagWithTemplateNoIterationWithHelpers/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "a2 foovalue",
1725
	"If current data is an array, a tag with a template and a render method calling" +
1726
	"\ntagCtx.render(val, helpers, true) and no param renders against array without iteration");
1727
 
1728
	assert.equal($.templates("a{{include person}}{{tagJustRender/}}{{/include}}").render({person: {name: "Jo"}}), "aJo ",
1729
	"Tag with just a render and no param renders once against current data, if object");
1730
 
1731
	assert.equal($.templates("a{{include people}}{{tagJustRenderArray/}}{{/include}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "a2 ",
1732
	"Tag with just a render and no param renders once against current data, even if array - but render method can choose to iterate");
1733
 
1734
	assert.equal($.templates("a{{tagJustTemplate person/}}").render({person: {name: "Jo"}}), "aJo ",
1735
	"Tag with just a template and renders once against first argument data, if object");
1736
 
1737
	assert.equal($.templates("a{{tagJustTemplate people/}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "a2 ",
1738
	"Tag with just a template renders once against first argument data even if it is an array" +
1739
	"\n- but can add render method with tagCtx.render(val) to iterate - (next test)");
1740
 
1741
	assert.equal($.templates("a{{tagWithTemplateWhichIteratesFirstArg people/}}").render({people: [{name: "Jo"}, {name: "Mary"}]}), "aJo Mary ",
1742
	"Tag with a template and render method calling tagCtx.render(val) renders against first param data, and iterates if array");
1743
 
1744
});
1745
 
1746
QUnit.test("derived tags", function(assert) {
1747
	// =============================== Arrange ===============================
1748
	var tmpl = $.templates("a:{{A 1/}} b:{{B 2/}}"),
1749
 
1750
		tagA = $.views.tags("A",
1751
			function(val) {return "A" + val;},
1752
			tmpl
1753
		);
1754
 
1755
		$.views.tags("B",
1756
			{
1757
				baseTag: tagA,
1758
				render: function(val) {
1759
					return "B" + val + this.base(val);
1760
				}
1761
			},
1762
			tmpl
1763
		);
1764
 
1765
	// ................................ Act ..................................
1766
	var result = tmpl.render({});
1767
 
1768
	// ............................... Assert .................................
1769
	assert.equal(result, "a:A1 b:B2A2", "One level tag inheritance chain - calling base method");
1770
 
1771
	// =============================== Arrange ===============================
1772
	tmpl = $.templates("a:{{A 1 2 3/}} b:{{B 11 12 13/}} c:{{C 21 22 23/}} d:{{D 31 32 33/}} e:{{E 41 42 43/}}");
1773
 
1774
		tagA = $.views.tags("A",
1775
			function(val) {return "A" + val;},
1776
			tmpl
1777
		);
1778
 
1779
		$.views.tags("B",
1780
			{
1781
				baseTag: tagA,
1782
				foo: function(val) {
1783
					return "FOO-B:" + val;
1784
				},
1785
				render: function(val) {
1786
					return "B" + val + this.base(val);
1787
				}
1788
			},
1789
			tmpl
1790
		);
1791
 
1792
		var tagC = $.views.tags("C",
1793
			{
1794
				baseTag: "A",
1795
				foo: function(val) {
1796
					return "FOO-C:" + val;
1797
				},
1798
				bar: function(x, y, z) {
1799
					return "BAR-C" + x + y + z;
1800
				},
1801
				render: function(val) {
1802
					return "C" + val + this.base(val) + this.foo(val) + this.bar.apply(this, this.tagCtx.args);
1803
				}
1804
			},
1805
			tmpl
1806
		);
1807
 
1808
		$.views.tags("D",
1809
			{
1810
				baseTag: tagC,
1811
				render: function(val) {
1812
					return "D" + val + this.base(val);
1813
				}
1814
			},
1815
			tmpl
1816
		);
1817
 
1818
		$.views.tags("E",
1819
			{
1820
				baseTag: "D",
1821
				foo: function(val) {
1822
					return "FOO-E" + val + this.base(val);
1823
				},
1824
				bar: function(x, y, z) {
1825
					return "BAR-E" + x + y + z + this.baseApply(arguments);
1826
				},
1827
				render: function(val) {
1828
					return "E" + val + this.base(val);
1829
				}
1830
			},
1831
			tmpl
1832
		);
1833
 
1834
	// ................................ Act ..................................
1835
	result = tmpl.render({});
1836
 
1837
	// ............................... Assert .................................
1838
	assert.equal(result, "a:A1 b:B11A11 c:C21A21FOO-C:21BAR-C212223 d:D31C31A31FOO-C:31BAR-C313233 e:E41D41C41A41FOO-E41FOO-C:41BAR-E414243BAR-C414243", "Complex multi-level inheritance chain");
1839
 
1840
	// =============================== Arrange ===============================
1841
	$.views.settings.debugMode(true);
1842
	tmpl = $.templates("a:{{A 1 2 3/}}");
1843
 
1844
		tagA = $.views.tags("A",
1845
			function(val) {
1846
				return "A" + val + this.baseApply(arguments);
1847
			},
1848
			tmpl
1849
		);
1850
	$.views.settings.debugMode(false);
1851
 
1852
	// ................................ Act ..................................
1853
	result = tmpl.render({});
1854
 
1855
	// ............................... Assert .................................
1856
	assert.equal(result.slice(0, 10), "a:{Error: ", "Calling base or baseApply when there is no base tag: Type Error");
1857
 
1858
	// =============================== Arrange ===============================
1859
	tmpl = $.templates("a:{{A 1 2 3/}} b:{{B 11 12 13/}} c:{{C 21 22 23/}}");
1860
 
1861
		tagA = $.views.tags("A",
1862
			function(val) {
1863
				return "A" + val;
1864
			},
1865
			tmpl
1866
		);
1867
 
1868
		$.views.tags("B",
1869
			{
1870
				baseTag: tagA,
1871
				render: function(val) {
1872
					return "B" + val + this.base(val);
1873
				}
1874
			},
1875
			tmpl
1876
		);
1877
 
1878
		tagC = $.views.tags("C",
1879
			{
1880
				baseTag: "A",
1881
				bar: function(x, y, z) {
1882
					return "BAR-C" + x + y + z + " Missing base method call: " + this.base(x) + this.baseApply(arguments) + ".";
1883
				},
1884
				render: function(val) {
1885
					return "C" + val + this.bar.apply(this, this.tagCtx.args);
1886
				}
1887
			},
1888
			tmpl
1889
		);
1890
 
1891
	// ................................ Act ..................................
1892
	result = tmpl.render({});
1893
 
1894
	// ............................... Assert .................................
1895
	assert.equal(result, "a:A1 b:B11A11 c:C21BAR-C212223 Missing base method call: .",
1896
	'Calling base or baseApply when there is no corresponding method on base tag implementation: noop - returning ""');
1897
 
1898
});
1899
 
1900
QUnit.test('{{include}} and wrapping content', function(assert) {
1901
	var result = $.templates({
1902
			markup:
1903
					'Before {{include tmpl="wrapper"}}'
1904
					+ '{{:name}}'
1905
				+ '{{/include}} After',
1906
			templates: {
1907
				wrapper: "header{{include tmpl=#content/}}footer"
1908
			}
1909
		}).render(people);
1910
 
1911
	assert.equal(result, "Before headerJofooter AfterBefore headerBillfooter After", 'Using {{include ... tmpl="wrapper"}}wrapped{{/include}}');
1912
 
1913
	result = $.templates({
1914
		markup:
1915
			 'This (render method) replaces: {{mytag override="replacementText" tmpl="wrapper"}}'
1916
				+ '{{:name}}'
1917
			+ '{{/mytag}} | '
1918
			+ 'This (original template) adds: {{mytag}}'
1919
				+ '{{:name}}'
1920
			+ '{{/mytag}} | '
1921
			+ 'This (new template) wraps: {{mytag setTmpl="wrapper"}}'
1922
				+ '{{:name}}'
1923
			+ '{{/mytag}} | ',
1924
		tags: {
1925
			mytag: {
1926
				template: "add{{include tmpl=#content/}}",
1927
				init: function() {
1928
					this.template = this.tagCtx.props.setTmpl || this.template;
1929
				},
1930
				render: function() {
1931
					return this.tagCtx.props.override;
1932
				}
1933
			}
1934
		},
1935
		templates: {
1936
			wrapper: "header{{include tmpl=#content/}}footer"
1937
		}
1938
	}).render(people);
1939
 
1940
	assert.equal(result,
1941
		"This (render method) replaces: replacementText |"
1942
		+ " This (original template) adds: addJo |"
1943
		+ " This (new template) wraps: headerJofooter |"
1944
		+ " This (render method) replaces: replacementText |"
1945
		+ " This (original template) adds: addBill |"
1946
		+ " This (new template) wraps: headerBillfooter | ",
1947
		'Custom tag with wrapped content: {{mytag ... tmpl="wrapper"}}wrapped{{/myTmpl}}');
1948
 
1949
	result = $.templates({
1950
		markup:
1951
				'Before {{include tmpl="wrapper"}}'
1952
				+ '{{:name}}'
1953
			+ '{{/include}} After',
1954
		templates: {
1955
			wrapper: "header{{for people tmpl=#content/}}footer"
1956
		}
1957
	}).render({people: people});
1958
 
1959
	assert.equal(result, "Before headerJoBillfooter After", 'Using {{for ... tmpl="wrapper"}}wrapped{{/for}}');
1960
 
1961
	result = $.templates({
1962
		markup:
1963
				'This replaces:{{mytag override="replacementText"}}'
1964
				+ '{{:name}}'
1965
			+ '{{/mytag}}'
1966
			+ 'This wraps:{{mytag tmpl="wrapper"}}'
1967
				+ '{{:name}}'
1968
			+ '{{/mytag}}',
1969
		tags: {
1970
			mytag: function() {
1971
				return this.tagCtx.props.override;
1972
			}
1973
		},
1974
		templates: {
1975
			wrapper: "header{{for people tmpl=#content/}}footer"
1976
		}
1977
	}).render({people: people});
1978
 
1979
	assert.equal(result, "This replaces:replacementTextThis wraps:headerJoBillfooter", 'Using {{mytag ... tmpl="wrapper"}}wrapped{{/myTmpl}}');
1980
 
1981
	result = $.templates({
1982
		markup:
1983
		'{{mytag}}'
1984
			+ '{{:name}}'
1985
		+ '{{/mytag}} | '
1986
		+ '{{mytag tmpl="innerwrap"}}'
1987
			+ '{{:name}}'
1988
		+ '{{/mytag}} | '
1989
		+ '{{mytag tmpl="middlewrap"}}'
1990
			+ '{{:name}}'
1991
		+ '{{/mytag}} | '
1992
		+ '{{mytag tmpl="wrapper"}}'
1993
			+ '{{:name}}'
1994
		+ '{{/mytag}} | '
1995
 
1996
		+ '{{mytag2}}'
1997
			+ '{{:name}}'
1998
		+ '{{/mytag2}} | '
1999
		+ '{{mytag2 tmpl="innerwrap"}}'
2000
			+ '{{:name}}'
2001
		+ '{{/mytag2}} | '
2002
		+ '{{mytag2 tmpl="middlewrap"}}'
2003
			+ '{{:name}}'
2004
		+ '{{/mytag2}} | '
2005
		+ '{{mytag2 tmpl="wrapper"}}'
2006
			+ '{{:name}}'
2007
		+ '{{/mytag2}} | ',
2008
		templates: {
2009
			wrapper: "middle {{include tmpl=#content/}} {{include tmpl='middlewrap'/}} {{include tmpl='innerwrap'/}}/middle",
2010
			middlewrap: "inner {{include tmpl=#content/}} and {{include tmpl='innerwrap'/}} /inner",
2011
			innerwrap: "innermost {{include tmpl=#content/}} /innermost"
2012
		},
2013
		tags: {
2014
			mytag: {
2015
			template: "outer {{include tmpl=#content/}} /outer"
2016
			},
2017
			mytag2: {
2018
			}
2019
		}
2020
	}).render(people);
2021
 
2022
	assert.equal(result,
2023
		"outer Jo /outer |"
2024
		+ " outer innermost Jo /innermost /outer |"
2025
		+ " outer inner Jo and innermost Jo /innermost /inner /outer |"
2026
		+ " outer middle Jo inner Jo and innermost Jo /innermost /inner innermost Jo /innermost/middle /outer |"
2027
 
2028
		+ " Jo |"
2029
		+ " innermost Jo /innermost |"
2030
		+ " inner Jo and innermost Jo /innermost /inner |"
2031
		+ " middle Jo inner Jo and innermost Jo /innermost /inner innermost Jo /innermost/middle |"
2032
 
2033
		+ " outer Bill /outer |"
2034
		+ " outer innermost Bill /innermost /outer |"
2035
		+ " outer inner Bill and innermost Bill /innermost /inner /outer |"
2036
		+ " outer middle Bill inner Bill and innermost Bill /innermost /inner innermost Bill /innermost/middle /outer |"
2037
 
2038
		+ " Bill |"
2039
		+ " innermost Bill /innermost |"
2040
		+ " inner Bill and innermost Bill /innermost /inner |"
2041
		+ " middle Bill inner Bill and innermost Bill /innermost /inner innermost Bill /innermost/middle | ",
2042
		'Cascading multi-level wrappers around #content'
2043
	);
2044
 
2045
	var data = [{
2046
	phones: [
2047
		{number: "Ph0", alt: "Alt0"},
2048
		{number: "Ph1", alt: "Alt1"},
2049
		{number: "Ph2", alt: "Alt2"}
2050
	]
2051
	}];
2052
 
2053
	result = $.templates({
2054
		markup:
2055
		  '{{mytag tmpl="phonelist"}}'
2056
			+ '{{:number}} '
2057
		+ '{{/mytag}} | '
2058
		+ '{{mytag2 tmpl="phonelist"}}'
2059
		  + '{{:number}} '
2060
		+ '{{/mytag2}}',
2061
		templates: {
2062
			phonelist: "{{for phones}}{{include tmpl=#content/}}{{/for}}"
2063
		},
2064
		tags: {
2065
			mytag: {
2066
				template: "outer {{include tmpl=#content/}} /outer"
2067
			},
2068
			mytag2: {
2069
			}
2070
		}
2071
	}).render(data);
2072
 
2073
	assert.equal(result,
2074
		"outer Ph0 Ph1 Ph2  /outer | Ph0 Ph1 Ph2 ",
2075
		'Cascading multi-level wrapper around #content with {{for}}'
2076
	);
2077
 
2078
	result = $.templates({
2079
		markup:
2080
		  '{{mytag tmpl="phonelist"}}'
2081
			+ '{{:number}}'
2082
		+ '{{else tmpl="altlist"}}'
2083
		  + '{{:alt}}'
2084
		+ '{{else tmpl="altlist2"}}'
2085
		  + '{{:alt}}'
2086
		+ '{{/mytag}}'
2087
		+ '{{mytag2 tmpl="phonelist"}}'
2088
		  + '{{:number}}'
2089
		+ '{{else tmpl="altlist"}}'
2090
		  + '{{:alt}}'
2091
		+ '{{else tmpl="altlist2"}}'
2092
		  + '{{:alt}}'
2093
		+ '{{/mytag2}}',
2094
		templates: {
2095
			phonelist: "A< {{for phones}}{{include tmpl=#content/}} {{/for}} > ",
2096
			altlist: "B< {{for phones tmpl='titlewrap'/}} > ",
2097
			altlist2: "C< {{for phones}}{{include tmpl='titlewrap'/}}{{/for}} > ",
2098
			titlewrap: "alternate: {{include tmpl=#content/}} "
2099
		},
2100
		tags: {
2101
			mytag: {
2102
				template: "outer {{include tmpl=#content/}} /outer | "
2103
			},
2104
			mytag2: {
2105
			}
2106
		}
2107
	}).render(data);
2108
 
2109
	assert.equal(result,
2110
		  "outer A< Ph0 Ph1 Ph2  >  /outer |"
2111
		+ " outer B< alternate: Alt0 alternate: Alt1 alternate: Alt2  >  /outer |"
2112
		+ " outer C< alternate: Alt0 alternate: Alt1 alternate: Alt2  >  /outer |"
2113
		+ " A< Ph0 Ph1 Ph2  >"
2114
		+ " B< alternate: Alt0 alternate: Alt1 alternate: Alt2  >"
2115
		+ " C< alternate: Alt0 alternate: Alt1 alternate: Alt2  > ",
2116
		'Cascading multi-level wrapper around #content with {{for}}{{else}}'
2117
	);
2118
});
2119
 
2120
QUnit.test("helpers", function(assert) {
2121
	$.views.helpers({
2122
		not: function(value) {
2123
			return !value;
2124
		},
2125
		concat: function() {
2126
			return "".concat.apply("", arguments) + "top";
2127
		}
2128
	});
2129
	assert.equal($.templates("{{:~concat(a, 'b', ~not(false))}}").render({a: "aVal"}), "aValbtruetop", "~concat('a')");
2130
 
2131
	function toUpperCase(value) {
2132
		return value.toUpperCase();
2133
	}
2134
	var toUpperCaseFn = $.views.helpers("toUpperCase", toUpperCase);
2135
	assert.equal($.templates("{{:~toUpperCase(name)}} {{:~toUpperCase('Foo')}}").render(person), "JO FOO", '$.views.helpers("toUpperCase", toUpperCaseFn);... {{:~toUpperCase(name)}}');
2136
 
2137
	$.views.helpers({toUpperCase2: toUpperCase});
2138
	assert.equal(toUpperCaseFn === toUpperCase && $.views.helpers.toUpperCase === toUpperCase && $.views.helpers.toUpperCase2 === toUpperCase, true, 'sortFunction === $.views.helpers.toUpperCase === $.views.helpers("toUpperCase")');
2139
 
2140
	$.views.helpers("toUpperCase2", null);
2141
	assert.equal($.views.helpers.toUpperCase2, undefined, '$.views.helpers("toUpperCase2", null) to remove registered helper');
2142
});
2143
 
2144
QUnit.test("settings", function(assert) {
2145
	// ................................ Act ..................................
2146
	// Delimiters
2147
 
2148
	$.views.settings.delimiters("@%","%@");
2149
	var result = $.templates("A_@%if true%@yes@%/if%@_B").render()
2150
		+ "|" + $.views.settings.delimiters() + "|" + $.views.sub.settings.delimiters;
2151
 
2152
	$.views.settings.delimiters("<<",">>", "*");
2153
 
2154
	result += "|" + $.views.settings.delimiters() + "|" + $.views.sub.settings.delimiters;
2155
 
2156
	$.views.settings.delimiters("{{","}}", "^");
2157
	result += "|" + $.templates("A_{{if true}}YES{{/if}}_B").render()
2158
		+ "|" + $.views.settings.delimiters() + "|" + $.views.sub.settings.delimiters;
2159
 
2160
	// ............................... Assert .................................
2161
	assert.equal(result, "A_yes_B|@%,%@,^|@%,%@,^|<<,>>,*|<<,>>,*|A_YES_B|{{,}},^|{{,}},^", "Custom delimiters with render()");
2162
 
2163
	// =============================== Arrange ===============================
2164
	// Debug mode false
2165
 
2166
	var oldDebugMode = $.views.settings.debugMode();
2167
	var app = {choose: true, name: "Jo"};
2168
	result = "";
2169
 
2170
	// ................................ Act ..................................
2171
	$.views.settings.debugMode(false);
2172
 
2173
	try {
2174
		result = $.templates('{{:missing.willThrow}}').render(app);
2175
	} catch(e) {
2176
		result += !!e.message;
2177
	}
2178
 
2179
	// ............................... Assert .................................
2180
	assert.equal($.views.settings.debugMode() + " " + result, 'false true',
2181
		'Debug mode false: {{:missing.willThrow}} throws error');
2182
 
2183
	// ................................ Act ..................................
2184
	// Debug mode true
2185
 
2186
	$.views.settings.debugMode(true);
2187
 
2188
	try {
2189
		result = $.templates('{{:missing.willThrow}}').render(app);
2190
	} catch(e) {
2191
		result += !!e.message;
2192
	}
2193
 
2194
	// ............................... Assert .................................
2195
	assert.equal($.views.settings.debugMode() + " " + result.slice(0, 8), 'true {Error: ',
2196
		'Debug mode true: {{:missing.willThrow}} renders error');
2197
 
2198
	// ................................ Act ..................................
2199
	// Debug mode 'onError' handler function with return value
2200
 
2201
	$.views.settings.debugMode(function(e, fallback, view) {
2202
		var data = this;
2203
		return "Override error - " + (fallback||"") + "_" + (view ? data.name + " " + (e.message.indexOf("undefined")>-1): e); // For syntax errors e is a string, and view is undefined
2204
	});
2205
 
2206
	// ................................ Act ..................................
2207
	result = typeof $.views.settings.debugMode() + " ";
2208
	result += $.templates('{{:missing.willThrow}}').render(app);
2209
 
2210
	// ............................... Assert .................................
2211
	assert.equal(result, "function Override error - _Jo true",
2212
		"Debug mode 'onError' handler override, with {{:missing.willThrow}}");
2213
 
2214
	// ................................ Act ..................................
2215
	result = typeof $.views.settings.debugMode() + " ";
2216
	result += $.templates('{{:missing.willThrow onError="myFallback"}}').render(app);
2217
 
2218
	// ............................... Assert .................................
2219
	assert.equal(result, "function Override error - myFallback_Jo true",
2220
		'Debug mode \'onError\' handler override, with onError fallback: {{:missing.willThrow onError="myFallback"}}');
2221
 
2222
	// ................................ Act ..................................
2223
	result = typeof $.views.settings.debugMode() + " ";
2224
	result += $.templates('{{if missing.willThrow onError="myFallback"}}yes{{/if}}').render(app);
2225
 
2226
	// ............................... Assert .................................
2227
	assert.equal(result, 'function Override error - myFallback_Jo true',
2228
		'Debug mode \'onError\' handler override, with onError fallback: {{if missing.willThrow onError="myFallback"}}');
2229
 
2230
	// ................................ Act ..................................
2231
	// Debug mode 'onError' handler function without return value
2232
	var ret = "";
2233
	$.views.settings.debugMode(function(e, fallback, view) {
2234
		var data = this;
2235
		ret = "Override error - " + (fallback||"") + "_" + data.name + " " + (e.message.indexOf("undefined")>-1); // For syntax errors e is a string, and view is undefined
2236
	});
2237
 
2238
	// ................................ Act ..................................
2239
	result = typeof $.views.settings.debugMode() + " ";
2240
	result += $.templates('{{:missing.willThrow}}').render(app);
2241
 
2242
	// ............................... Assert .................................
2243
	assert.equal(ret + "|" + result.slice(0, 17), "Override error - _Jo true|function {Error: ",
2244
		"Debug mode 'onError' handler (no return) with {{:missing.willThrow}}");
2245
 
2246
	// ................................ Act ..................................
2247
	result = typeof $.views.settings.debugMode() + " ";
2248
	result += $.templates('{{:missing.willThrow onError="myFallback"}}').render(app);
2249
 
2250
	// ............................... Assert .................................
2251
	assert.equal(ret + "|" + result, "Override error - myFallback_Jo true|function myFallback",
2252
		'Debug mode \'onError\' handler (no return) with onError fallback: {{:missing.willThrow onError="myFallback"}}');
2253
 
2254
	// ................................ Act ..................................
2255
	result = typeof $.views.settings.debugMode() + " ";
2256
	result += $.templates('{{if missing.willThrow onError="myFallback"}}yes{{/if}}').render(app);
2257
 
2258
	// ............................... Assert .................................
2259
	assert.equal(ret + "|" + result, "Override error - myFallback_Jo true|function myFallback",
2260
		'Debug mode \'onError\' handler (no return) with onError fallback: {{if missing.willThrow onError="myFallback"}}');
2261
 
2262
	// ................................ Reset ..................................
2263
	$.views.settings.debugMode(oldDebugMode);
2264
});
2265
 
2266
QUnit.test("template encapsulation", function(assert) {
2267
		// =============================== Arrange ===============================
2268
$.templates({
2269
		myTmpl6: {
2270
			markup: "{{sort reverse=true people}}{{:name}}{{/sort}}",
2271
			tags: {
2272
				sort: sort
2273
			}
2274
		}
2275
	});
2276
 
2277
	// ............................... Assert .................................
2278
	assert.equal($.render.myTmpl6({people: people}), "BillJo", '$.templates("myTmpl", tmplObjWithNestedItems);');
2279
 
2280
	// =============================== Arrange ===============================
2281
	$.views.helpers("h1", "globalHelper");
2282
 
2283
	var tmpl = $.templates({
2284
		markup: "{{if true}}{{:~h1}} {{:~h2}} {{:~h3}}{{/if}}",
2285
		helpers: {
2286
			h2: "templateHelper"
2287
		}
2288
	});
2289
 
2290
	// ............................... Assert .................................
2291
	assert.equal(tmpl.render({}, {h3:"optionHelper"}), "globalHelper templateHelper optionHelper", 'Passing in helpers - global, template or option');
2292
 
2293
	// =============================== Arrange ===============================
2294
	tmpl = $.templates({
2295
		markup: "{{if true}}{{:~h1}}{{/if}}",
2296
		helpers: {
2297
			h1: "templateHelper"
2298
		}
2299
	});
2300
 
2301
	// ............................... Assert .................................
2302
	assert.equal(tmpl.render({}), "templateHelper", 'template helper overrides global helper');
2303
 
2304
	// =============================== Arrange ===============================
2305
	tmpl = $.templates({
2306
		markup: "{{if true}}{{:~h1}}{{/if}}"
2307
	});
2308
 
2309
	// ............................... Assert .................................
2310
	assert.equal(tmpl.render({}, {h1: "optionHelper"}), "optionHelper", 'option helper overrides global helper');
2311
 
2312
	// =============================== Arrange ===============================
2313
	tmpl = $.templates({
2314
		markup: "{{if true}}{{:~h2}}{{/if}}",
2315
		helpers: {
2316
			h2: "templateHelper"
2317
		}
2318
	});
2319
 
2320
	// ............................... Assert .................................
2321
	assert.equal(tmpl.render({}, {h2: "optionHelper"}), "templateHelper", 'template helper overrides option helper');
2322
 
2323
	// =============================== Arrange ===============================
2324
	$.views.converters("c1", function(val) {return val + "globalCvt";});
2325
 
2326
	tmpl = $.templates({
2327
		markup: "{{if true}}{{c1:1}}{{c2:2}}{{/if}}",
2328
		converters: {
2329
			c2: function(val) {return val + "templateCvt";}
2330
		}
2331
	});
2332
 
2333
	// ............................... Assert .................................
2334
	assert.equal(tmpl.render({}), "1globalCvt2templateCvt", 'template converter and global converter');
2335
 
2336
	// =============================== Arrange ===============================
2337
	tmpl = $.templates({
2338
		markup: "{{if true}}{{c1:1}}{{/if}}",
2339
		converters: {
2340
			c1: function(val) {return val + "templateCvt";}
2341
		}
2342
	});
2343
 
2344
	// ............................... Assert .................................
2345
	assert.equal(tmpl.render({}), "1templateCvt", 'template converter overrides global converter');
2346
 
2347
	// =============================== Arrange ===============================
2348
 
2349
	$.templates({
2350
		cascade: "outerCascade",
2351
		nesting: {
2352
			markup: "{{if true}} {{c1:~h1}} {{include tmpl='inner'/}}{{/if}} {{include tmpl='cascade'/}}",
2353
			helpers: {
2354
				h1: "templateHelper"
2355
			},
2356
			converters: {
2357
				c1: function(val) {return val + " templateCvt";}
2358
			},
2359
			templates: {
2360
				cascade: "innerCascade",
2361
				inner: {
2362
					markup: "{{if true}}{{c1:~h1}}{{/if}} {{include tmpl='cascade'/}}",
2363
					helpers: {
2364
						h1: "innerTemplateHelper"
2365
					},
2366
					converters: {
2367
						c1: function(val) {return val + " innerTemplateCvt";}
2368
					},
2369
					templates: {
2370
						cascade: "innerInnerCascade"
2371
					}
2372
				}
2373
			}
2374
		}
2375
	});
2376
 
2377
	// ............................... Assert .................................
2378
	assert.equal($.templates.nesting.render({}, {b: "optionHelper"}), " templateHelper templateCvt innerTemplateHelper innerTemplateCvt innerInnerCascade innerCascade",
2379
		'Inner template, helper, and converter override outer template, helper, and converter');
2380
});
2381
 
2382
QUnit.module("Custom tags");
2383
 
2384
QUnit.test("contentCtx", function(assert) {
2385
 
2386
var tmpl = $.templates("{{for 'parent'}}{{mytag 'arg1' 'arg2' 'arg3'}}  {{:#data}}A{{else 'elseArg1'}}  {{:#data}}B{{else}}  {{:#data}}C{{/mytag}}{{/for}}");
2387
 
2388
	// =============================== Arrange ===============================
2389
 
2390
$.views.tags({mytag: {
2391
}}, tmpl);
2392
 
2393
	// ............................... Assert .................................
2394
	assert.equal(tmpl.render("outer"), "  arg1A  elseArg1B  parentC", 'No contentCtx - context is 1st arg or parentView.data');
2395
 
2396
	// =============================== Arrange ===============================
2397
 
2398
$.views.tags({mytag: {
2399
	contentCtx: function(val) {
2400
		return 0;
2401
	}
2402
}}, tmpl);
2403
 
2404
	// ............................... Assert .................................
2405
	assert.equal(tmpl.render("outer"), "  0A  0B  0C", 'contentCtx returns 0 - context is 0 (even for falsy values like 0');
2406
 
2407
	// =============================== Arrange ===============================
2408
 
2409
$.views.tags({mytag: {
2410
	contentCtx: function(val) {
2411
		return val;
2412
	}
2413
}}, tmpl);
2414
 
2415
	// ............................... Assert .................................
2416
	assert.equal(tmpl.render("outer"), "  arg1A  elseArg1B  parentC", 'contentCtx returns first arg/parentView - context is 1st arg or parentView.data');
2417
 
2418
	// =============================== Arrange ===============================
2419
 
2420
$.views.tags({mytag: {
2421
	contentCtx: function(val) {
2422
		return this.tagCtx.view;
2423
	}
2424
}}, tmpl);
2425
 
2426
	// ............................... Assert .................................
2427
	assert.equal(tmpl.render("outer"), "  parentA  parentB  parentC", 'contentCtx returns this.tagCtx.view - context is parentView.data');
2428
 
2429
	// =============================== Arrange ===============================
2430
 
2431
$.views.tags({mytag: {
2432
	contentCtx: function(val) {
2433
		return this.tagCtx.view.data;
2434
	}
2435
}}, tmpl);
2436
 
2437
	// ............................... Assert .................................
2438
	assert.equal(tmpl.render("outer"), "  parentA  parentB  parentC", 'contentCtx returns this.tagCtx.view.data - context is parentView.data');
2439
 
2440
	// =============================== Arrange ===============================
2441
 
2442
$.views.tags({mytag: {
2443
	contentCtx: function(val) {
2444
		return this.tagCtxs[0].args[this.tagCtx.index];
2445
	}
2446
}}, tmpl);
2447
 
2448
	// ............................... Assert .................................
2449
	assert.equal(tmpl.render("outer"), "  arg1A  arg2B  arg3C", 'contentCtx returns arg from first tagCtx indexed on else - context is arg1/arg2/arg3');
2450
 
2451
	// =============================== Arrange ===============================
2452
 
2453
	tmpl = $.templates("{{for 'outerparent'}}{{for 'parent'}}{{mytag 'arg1' 'arg2' 'arg3'}}  {{:#data}}A{{else 'elseArg1'}}  {{:#data}}B{{else}}  {{:#data}}C{{/mytag}}{{/for}}{{/for}}");
2454
 
2455
$.views.tags({mytag: {
2456
	contentCtx: function(val) {
2457
		return this.tagCtx.view.parent;
2458
	}
2459
}}, tmpl);
2460
 
2461
	// ............................... Assert .................................
2462
	assert.equal(tmpl.render("outer"), "  outerparentA  outerparentB  outerparentC", 'contentCtx returns this.tagCtx.view.parent');
2463
 
2464
});
2465
 
2466
})();