1 | (function() {
|
2 | var expect, util, Environment, Template, fs;
|
3 |
|
4 | if(typeof require != 'undefined') {
|
5 | expect = require('expect.js');
|
6 | util = require('./util');
|
7 | Environment = require('../src/environment').Environment;
|
8 | Template = require('../src/environment').Template;
|
9 | fs = require('fs');
|
10 | }
|
11 | else {
|
12 | expect = window.expect;
|
13 | util = window.util;
|
14 | Environment = nunjucks.Environment;
|
15 | Template = nunjucks.Template;
|
16 | }
|
17 |
|
18 | var render = util.render;
|
19 | var equal = util.equal;
|
20 | var finish = util.finish;
|
21 |
|
22 | describe('compiler', function() {
|
23 | it('should compile templates', function(done) {
|
24 | equal('Hello world', 'Hello world');
|
25 | equal('Hello world, {{ name }}',
|
26 | { name: 'James' },
|
27 | 'Hello world, James');
|
28 |
|
29 | equal('Hello world, {{name}}{{suffix}}, how are you',
|
30 | { name: 'James',
|
31 | suffix: ' Long'},
|
32 | 'Hello world, James Long, how are you');
|
33 |
|
34 | finish(done);
|
35 | });
|
36 |
|
37 | it('should escape newlines', function(done) {
|
38 | equal('foo\\nbar', 'foo\\nbar');
|
39 | finish(done);
|
40 | });
|
41 |
|
42 | it('should compile references', function(done) {
|
43 | equal('{{ foo.bar }}',
|
44 | { foo: { bar: 'baz' }},
|
45 | 'baz');
|
46 |
|
47 | equal('{{ foo["bar"] }}',
|
48 | { foo: { bar: 'baz' }},
|
49 | 'baz');
|
50 |
|
51 | finish(done);
|
52 | });
|
53 |
|
54 | it('should fail silently on undefined values', function(done) {
|
55 | equal('{{ foo }}', '');
|
56 | equal('{{ foo.bar }}', '');
|
57 | equal('{{ foo.bar.baz }}', '');
|
58 | equal('{{ foo.bar.baz["biz"].mumble }}', '');
|
59 | finish(done);
|
60 | });
|
61 |
|
62 | it('should not treat falsy values the same as undefined', function(done) {
|
63 | equal('{{ foo }}', {foo: 0}, '0');
|
64 | equal('{{ foo }}', {foo: false}, 'false');
|
65 | finish(done);
|
66 | });
|
67 |
|
68 | it('should compile function calls', function(done) {
|
69 | equal('{{ foo("msg") }}',
|
70 | { foo: function(str) { return str + 'hi'; }},
|
71 | 'msghi');
|
72 | finish(done);
|
73 | });
|
74 |
|
75 | it('should compile function calls with correct scope', function(done) {
|
76 | equal('{{ foo.bar() }}', {
|
77 | foo: {
|
78 | bar: function() { return this.baz; },
|
79 | baz: 'hello'
|
80 | }
|
81 | }, 'hello');
|
82 |
|
83 | finish(done);
|
84 | });
|
85 |
|
86 | it('should compile if blocks', function(done) {
|
87 | var tmpl = ('Give me some {% if hungry %}pizza' +
|
88 | '{% else %}water{% endif %}');
|
89 |
|
90 | equal(tmpl, { hungry: true }, 'Give me some pizza');
|
91 | equal(tmpl, { hungry: false }, 'Give me some water');
|
92 | equal('{% if not hungry %}good{% endif %}',
|
93 | { hungry: false },
|
94 | 'good');
|
95 |
|
96 | equal('{% if hungry and like_pizza %}good{% endif %}',
|
97 | { hungry: true, like_pizza: true },
|
98 | 'good');
|
99 |
|
100 | equal('{% if hungry or like_pizza %}good{% endif %}',
|
101 | { hungry: false, like_pizza: true },
|
102 | 'good');
|
103 |
|
104 | equal('{% if (hungry or like_pizza) and anchovies %}good{% endif %}',
|
105 | { hungry: false, like_pizza: true, anchovies: true },
|
106 | 'good');
|
107 |
|
108 | equal('{% if food == "pizza" %}pizza{% endif %}' +
|
109 | '{% if food =="beer" %}beer{% endif %}',
|
110 | { food: 'beer' },
|
111 | 'beer');
|
112 |
|
113 | finish(done);
|
114 | });
|
115 |
|
116 | it('should compile the ternary operator', function(done) {
|
117 | equal('{{ "foo" if bar else "baz" }}', 'baz');
|
118 | equal('{{ "foo" if bar else "baz" }}', { bar: true }, 'foo');
|
119 |
|
120 | finish(done);
|
121 | });
|
122 |
|
123 | it('should compile inline conditionals', function(done) {
|
124 | var tmpl = 'Give me some {{ "pizza" if hungry else "water" }}';
|
125 |
|
126 | equal(tmpl, { hungry: true }, 'Give me some pizza');
|
127 | equal(tmpl, { hungry: false }, 'Give me some water');
|
128 | equal('{{ "good" if not hungry }}',
|
129 | { hungry: false }, 'good');
|
130 | equal('{{ "good" if hungry and like_pizza }}',
|
131 | { hungry: true, like_pizza: true }, 'good');
|
132 | equal('{{ "good" if hungry or like_pizza }}',
|
133 | { hungry: false, like_pizza: true }, 'good');
|
134 | equal('{{ "good" if (hungry or like_pizza) and anchovies }}',
|
135 | { hungry: false, like_pizza: true, anchovies: true }, 'good');
|
136 | equal('{{ "pizza" if food == "pizza" }}' +
|
137 | '{{ "beer" if food == "beer" }}',
|
138 | { food: 'beer' }, 'beer');
|
139 |
|
140 | finish(done);
|
141 | });
|
142 |
|
143 | function runLoopTests(block, end) {
|
144 | equal('{% ' + block + ' i in arr %}{{ i }}{% ' + end + ' %}',
|
145 | { arr: [1, 2, 3, 4, 5] }, '12345');
|
146 |
|
147 | equal('{% ' + block + ' a, b, c in arr %}' +
|
148 | '{{ a }},{{ b }},{{ c }}.{% ' + end + ' %}',
|
149 | { arr: [['x', 'y', 'z'], ['1', '2', '3']] }, 'x,y,z.1,2,3.');
|
150 |
|
151 | equal('{% ' + block + ' item in arr | batch(2) %}{{ item[0] }}{% ' + end + ' %}',
|
152 | { arr: ['a', 'b', 'c', 'd'] }, 'ac');
|
153 |
|
154 | equal('{% ' + block + ' k, v in { one: 1, two: 2 } %}' +
|
155 | '-{{ k }}:{{ v }}-{% ' + end + ' %}', '-one:1--two:2-');
|
156 |
|
157 | equal('{% ' + block + ' i in [7,3,6] %}{{ loop.index }}{% ' + end + ' %}', '123');
|
158 | equal('{% ' + block + ' i in [7,3,6] %}{{ loop.index0 }}{% ' + end + ' %}', '012');
|
159 | equal('{% ' + block + ' i in [7,3,6] %}{{ loop.revindex }}{% ' + end + ' %}', '321');
|
160 | equal('{% ' + block + ' i in [7,3,6] %}{{ loop.revindex0 }}{% ' + end + ' %}', '210');
|
161 | equal('{% ' + block + ' i in [7,3,6] %}{% if loop.first %}{{ i }}{% endif %}{% ' + end + ' %}',
|
162 | '7');
|
163 | equal('{% ' + block + ' i in [7,3,6] %}{% if loop.last %}{{ i }}{% endif %}{% ' + end + ' %}',
|
164 | '6');
|
165 | equal('{% ' + block + ' i in [7,3,6] %}{{ loop.length }}{% ' + end + ' %}', '333');
|
166 | equal('{% ' + block + ' i in foo %}{{ i }}{% ' + end + ' %}', '');
|
167 | equal('{% ' + block + ' i in foo.bar %}{{ i }}{% ' + end + ' %}', { foo: {} }, '');
|
168 | equal('{% ' + block + ' i in foo %}{{ i }}{% ' + end + ' %}', { foo: null }, '');
|
169 |
|
170 | equal('{% ' + block + ' x, y in points %}[{{ x }},{{ y }}]{% ' + end + ' %}',
|
171 | { points: [[1,2], [3,4], [5,6]] },
|
172 | '[1,2][3,4][5,6]');
|
173 |
|
174 | equal('{% ' + block + ' x, y in points %}{{ loop.index }}{% ' + end + ' %}',
|
175 | { points: [[1,2], [3,4], [5,6]] },
|
176 | '123');
|
177 |
|
178 | equal('{% ' + block + ' x, y in points %}{{ loop.revindex }}{% ' + end + ' %}',
|
179 | { points: [[1,2], [3,4], [5,6]] },
|
180 | '321');
|
181 |
|
182 | equal('{% ' + block + ' k, v in items %}({{ k }},{{ v }}){% ' + end + ' %}',
|
183 | { items: { foo: 1, bar: 2 }},
|
184 | '(foo,1)(bar,2)');
|
185 |
|
186 | equal('{% ' + block + ' k, v in items %}{{ loop.index }}{% ' + end + ' %}',
|
187 | { items: { foo: 1, bar: 2 }},
|
188 | '12');
|
189 |
|
190 | equal('{% ' + block + ' k, v in items %}{{ loop.revindex }}{% ' + end + ' %}',
|
191 | { items: { foo: 1, bar: 2 }},
|
192 | '21');
|
193 |
|
194 | equal('{% ' + block + ' k, v in items %}{{ loop.length }}{% ' + end + ' %}',
|
195 | { items: { foo: 1, bar: 2 }},
|
196 | '22');
|
197 |
|
198 | equal('{% ' + block + ' item, v in items %}{% include "item.html" %}{% ' + end + ' %}',
|
199 | { items: { foo: 1, bar: 2 }},
|
200 | 'showing fooshowing bar');
|
201 |
|
202 | render('{% set item = passed_var %}' +
|
203 | '{% include "item.html" %}\n' +
|
204 | '{% ' + block + ' i in passed_iter %}' +
|
205 | '{% set item = i %}' +
|
206 | '{% include "item.html" %}\n' +
|
207 | '{% ' + end + ' %}',
|
208 | {
|
209 | passed_var: 'test',
|
210 | passed_iter: ['1', '2', '3']
|
211 | },
|
212 | {},
|
213 | function(err, res) {
|
214 | expect(res).to.be('showing test\nshowing 1\nshowing 2\nshowing 3\n');
|
215 | });
|
216 | }
|
217 |
|
218 | it('should compile for blocks', function(done) {
|
219 | runLoopTests('for', 'endfor');
|
220 | finish(done);
|
221 | });
|
222 |
|
223 | it('should compile asyncEach', function(done) {
|
224 | runLoopTests('asyncEach', 'endeach');
|
225 | finish(done);
|
226 | });
|
227 |
|
228 | it('should compile asyncAll', function(done) {
|
229 | runLoopTests('asyncAll', 'endall');
|
230 | finish(done);
|
231 | });
|
232 |
|
233 | it('should compile async control', function(done) {
|
234 | if(fs) {
|
235 | var opts = {
|
236 | asyncFilters: {
|
237 | getContents: function(tmpl, cb) {
|
238 | fs.readFile(tmpl, cb);
|
239 | },
|
240 |
|
241 | getContentsArr: function(arr, cb) {
|
242 | fs.readFile(arr[0], function(err, res) {
|
243 | cb(err, [res]);
|
244 | });
|
245 | }
|
246 | }
|
247 | };
|
248 |
|
249 | render('{{ tmpl | getContents }}',
|
250 | { tmpl: 'tests/templates/for-async-content.html' },
|
251 | opts,
|
252 | function(err, res) {
|
253 | expect(res).to.be('somecontenthere');
|
254 | });
|
255 |
|
256 | render('{% if tmpl %}{{ tmpl | getContents }}{% endif %}',
|
257 | { tmpl: 'tests/templates/for-async-content.html' },
|
258 | opts,
|
259 | function(err, res) {
|
260 | expect(res).to.be('somecontenthere');
|
261 | });
|
262 |
|
263 | render('{% if tmpl | getContents %}yes{% endif %}',
|
264 | { tmpl: 'tests/templates/for-async-content.html' },
|
265 | opts,
|
266 | function(err, res) {
|
267 | expect(res).to.be('yes');
|
268 | });
|
269 |
|
270 | render('{% for t in [tmpl, tmpl] %}{{ t | getContents }}*{% endfor %}',
|
271 | { tmpl: 'tests/templates/for-async-content.html' },
|
272 | opts,
|
273 | function(err, res) {
|
274 | expect(res).to.be('somecontenthere*somecontenthere*');
|
275 | });
|
276 |
|
277 | render('{% for t in [tmpl, tmpl] | getContentsArr %}{{ t }}{% endfor %}',
|
278 | { tmpl: 'tests/templates/for-async-content.html' },
|
279 | opts,
|
280 | function(err, res) {
|
281 | expect(res).to.be('somecontenthere');
|
282 | });
|
283 |
|
284 | render('{% if test %}{{ tmpl | getContents }}{% endif %}oof',
|
285 | { tmpl: 'tests/templates/for-async-content.html' },
|
286 | opts,
|
287 | function(err, res) {
|
288 | expect(res).to.be('oof');
|
289 | });
|
290 |
|
291 | render('{% if tmpl %}' +
|
292 | '{% for i in [0, 1] %}{{ tmpl | getContents }}*{% endfor %}' +
|
293 | '{% endif %}',
|
294 | { tmpl: 'tests/templates/for-async-content.html' },
|
295 | opts,
|
296 | function(err, res) {
|
297 | expect(res).to.be('somecontenthere*somecontenthere*');
|
298 | });
|
299 |
|
300 | render('{% block content %}{{ tmpl | getContents }}{% endblock %}',
|
301 | { tmpl: 'tests/templates/for-async-content.html' },
|
302 | opts,
|
303 | function(err, res) {
|
304 | expect(res).to.be('somecontenthere');
|
305 | });
|
306 |
|
307 | render('{% block content %}hello{% endblock %} {{ tmpl | getContents }}',
|
308 | { tmpl: 'tests/templates/for-async-content.html' },
|
309 | opts,
|
310 | function(err, res) {
|
311 | expect(res).to.be('hello somecontenthere');
|
312 | });
|
313 |
|
314 | render('{% block content %}{% include "async.html" %}{% endblock %}',
|
315 | { tmpl: 'tests/templates/for-async-content.html' },
|
316 | opts,
|
317 | function(err, res) {
|
318 | expect(res).to.be('somecontenthere\n');
|
319 | });
|
320 |
|
321 | render('{% asyncEach i in [0, 1] %}{% include "async.html" %}{% endeach %}',
|
322 | { tmpl: 'tests/templates/for-async-content.html' },
|
323 | opts,
|
324 | function(err, res) {
|
325 | expect(res).to.be('somecontenthere\nsomecontenthere\n');
|
326 | });
|
327 |
|
328 | render('{% asyncAll i in [0, 1, 2, 3, 4] %}-{{ i }}:{% include "async.html" %}-{% endall %}',
|
329 | { tmpl: 'tests/templates/for-async-content.html' },
|
330 | opts,
|
331 | function(err, res) {
|
332 | expect(res).to.be('-0:somecontenthere\n-' +
|
333 | '-1:somecontenthere\n-' +
|
334 | '-2:somecontenthere\n-' +
|
335 | '-3:somecontenthere\n-' +
|
336 | '-4:somecontenthere\n-');
|
337 | });
|
338 | }
|
339 |
|
340 | finish(done);
|
341 | });
|
342 |
|
343 | it('should compile operators', function(done) {
|
344 | equal('{{ 3 + 4 - 5 * 6 / 10 }}', '4');
|
345 | equal('{{ 4**5 }}', '1024');
|
346 | equal('{{ 9//5 }}', '1');
|
347 | equal('{{ 9%5 }}', '4');
|
348 | equal('{{ -5 }}', '-5');
|
349 |
|
350 | equal('{% if 3 < 4 %}yes{% endif %}', 'yes');
|
351 | equal('{% if 3 > 4 %}yes{% endif %}', '');
|
352 | equal('{% if 9 >= 10 %}yes{% endif %}', '');
|
353 | equal('{% if 10 >= 10 %}yes{% endif %}', 'yes');
|
354 | equal('{% if 9 <= 10 %}yes{% endif %}', 'yes');
|
355 | equal('{% if 10 <= 10 %}yes{% endif %}', 'yes');
|
356 | equal('{% if 11 <= 10 %}yes{% endif %}', '');
|
357 |
|
358 | equal('{% if 10 != 10 %}yes{% endif %}', '');
|
359 | equal('{% if 10 == 10 %}yes{% endif %}', 'yes');
|
360 |
|
361 | equal('{% if foo(20) > bar %}yes{% endif %}',
|
362 | { foo: function(n) { return n - 1; },
|
363 | bar: 15 },
|
364 | 'yes');
|
365 |
|
366 | finish(done);
|
367 | });
|
368 |
|
369 | it('should compile macros', function(done) {
|
370 | equal('{% macro foo() %}This is a macro{% endmacro %}' +
|
371 | '{{ foo() }}',
|
372 | 'This is a macro');
|
373 |
|
374 | equal('{% macro foo(x, y) %}{{ y }}{% endmacro %}' +
|
375 | '{{ foo(1) }}',
|
376 | '');
|
377 |
|
378 | equal('{% macro foo(x) %}{{ x|title }}{% endmacro %}' +
|
379 | '{{ foo("foo") }}',
|
380 | 'Foo');
|
381 |
|
382 | equal('{% macro foo(x, y) %}{{ y }}{% endmacro %}' +
|
383 | '{{ foo(1, 2) }}',
|
384 | '2');
|
385 |
|
386 | equal('{% macro foo(x, y, z=5) %}{{ y }}{% endmacro %}' +
|
387 | '{{ foo(1, 2) }}',
|
388 | '2');
|
389 |
|
390 | equal('{% macro foo(x, y, z=5) %}{{ z }}{% endmacro %}' +
|
391 | '{{ foo(1, 2) }}',
|
392 | '5');
|
393 |
|
394 | equal('{% macro foo(x, y, z=5) %}{{ y }}{% endmacro %}' +
|
395 | '{{ foo(1, y=2) }}',
|
396 | '2');
|
397 |
|
398 | equal('{% macro foo(x, y, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
399 | '{% endmacro %}' +
|
400 | '{{ foo(x=1, y=2) }}',
|
401 | '125');
|
402 |
|
403 | equal('{% macro foo(x, y, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
404 | '{% endmacro %}' +
|
405 | '{{ foo(x=1, y=2, z=3) }}',
|
406 | '123');
|
407 |
|
408 | equal('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
409 | '{% endmacro %}' +
|
410 | '{{ foo(1, z=3) }}',
|
411 | '123');
|
412 |
|
413 | equal('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
414 | '{% endmacro %}' +
|
415 | '{{ foo(1) }}',
|
416 | '125');
|
417 |
|
418 | equal('{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
419 | '{% endmacro %}' +
|
420 | '{{ foo(1, 10, 20) }}',
|
421 | '11020');
|
422 |
|
423 | equal('{% extends "base.html" %}' +
|
424 | '{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
425 | '{% endmacro %}' +
|
426 | '{% block block1 %}' +
|
427 | '{{ foo(1) }}' +
|
428 | '{% endblock %}',
|
429 | 'Foo125BazFizzle');
|
430 |
|
431 | equal('{% block bar %}' +
|
432 | '{% macro foo(x, y=2, z=5) %}{{ x }}{{ y }}{{ z }}' +
|
433 | '{% endmacro %}' +
|
434 | '{% endblock %}' +
|
435 | '{% block baz %}' +
|
436 | '{{ foo(1) }}' +
|
437 | '{% endblock %}',
|
438 | '125');
|
439 |
|
440 | equal('{% macro foo() %}{% include "include.html" %}{% endmacro %}' +
|
441 | '{{ foo() }}',
|
442 | { name: 'james' },
|
443 | 'FooInclude james');
|
444 |
|
445 | finish(done);
|
446 | });
|
447 |
|
448 | it('should import templates', function(done) {
|
449 | equal('{% import "import.html" as imp %}' +
|
450 | '{{ imp.foo() }} {{ imp.bar }}',
|
451 | "Here's a macro baz");
|
452 |
|
453 | equal('{% from "import.html" import foo as baz, bar %}' +
|
454 | '{{ bar }} {{ baz() }}',
|
455 | "baz Here's a macro");
|
456 |
|
457 |
|
458 |
|
459 |
|
460 | equal('{% for i in [1,2] %}' +
|
461 | 'start: {{ num }}' +
|
462 | '{% from "import.html" import bar as num %}' +
|
463 | 'end: {{ num }}' +
|
464 | '{% endfor %}' +
|
465 | 'final: {{ num }}',
|
466 | 'start: end: bazstart: bazend: bazfinal: ');
|
467 |
|
468 | finish(done);
|
469 | });
|
470 |
|
471 | it('should inherit templates', function(done) {
|
472 | equal('{% extends "base.html" %}', 'FooBarBazFizzle');
|
473 | equal('hola {% extends "base.html" %} hizzle mumble', 'FooBarBazFizzle');
|
474 |
|
475 | equal('{% extends "base.html" %}{% block block1 %}BAR{% endblock %}',
|
476 | 'FooBARBazFizzle');
|
477 |
|
478 | equal('{% extends "base.html" %}' +
|
479 | '{% block block1 %}BAR{% endblock %}' +
|
480 | '{% block block2 %}BAZ{% endblock %}',
|
481 | 'FooBARBAZFizzle');
|
482 |
|
483 | equal('hola {% extends tmpl %} hizzle mumble',
|
484 | { tmpl: 'base.html' },
|
485 | 'FooBarBazFizzle');
|
486 |
|
487 | var count = 0;
|
488 | render('{% extends "base.html" %}' +
|
489 | '{% block notReal %}{{ foo() }}{% endblock %}',
|
490 | { foo: function() { count++; }},
|
491 | function(err, res) {
|
492 | expect(count).to.be(0);
|
493 | });
|
494 |
|
495 | finish(done);
|
496 | });
|
497 |
|
498 | it('should render nested blocks in child template', function(done) {
|
499 | equal('{% extends "base.html" %}' +
|
500 | '{% block block1 %}{% block nested %}BAR{% endblock %}{% endblock %}',
|
501 | 'FooBARBazFizzle');
|
502 |
|
503 | finish(done);
|
504 | });
|
505 |
|
506 | it('should render parent blocks with super()', function(done) {
|
507 | equal('{% extends "base.html" %}' +
|
508 | '{% block block1 %}{{ super() }}BAR{% endblock %}',
|
509 | 'FooBarBARBazFizzle');
|
510 |
|
511 |
|
512 | equal('{% extends "base-inherit.html" %}' +
|
513 | '{% block block1 %}*{{ super() }}*{% endblock %}',
|
514 | 'Foo**Bar**BazFizzle');
|
515 |
|
516 | finish(done);
|
517 | });
|
518 |
|
519 | it('should include templates', function(done) {
|
520 | equal('hello world {% include "include.html" %}',
|
521 | 'hello world FooInclude ');
|
522 |
|
523 | equal('hello world {% include "include.html" %}',
|
524 | { name: 'james' },
|
525 | 'hello world FooInclude james');
|
526 |
|
527 | equal('hello world {% include tmpl %}',
|
528 | { name: 'thedude', tmpl: "include.html" },
|
529 | 'hello world FooInclude thedude');
|
530 |
|
531 | equal('hello world {% include data.tmpl %}',
|
532 | { name: 'thedude', data: {tmpl: "include.html"} },
|
533 | 'hello world FooInclude thedude');
|
534 |
|
535 | finish(done);
|
536 | });
|
537 |
|
538 | it('should maintain nested scopes', function(done) {
|
539 | equal('{% for i in [1,2] %}' +
|
540 | '{% for i in [3,4] %}{{ i }}{% endfor %}' +
|
541 | '{{ i }}{% endfor %}',
|
542 | '341342');
|
543 |
|
544 | finish(done);
|
545 | });
|
546 |
|
547 | it('should allow blocks in for loops', function(done) {
|
548 | equal('{% extends "base2.html" %}' +
|
549 | '{% block item %}hello{{ item }}{% endblock %}',
|
550 | 'hello1hello2');
|
551 |
|
552 | finish(done);
|
553 | });
|
554 |
|
555 | it('should make includes inherit scope', function(done) {
|
556 | equal('{% for item in [1,2] %}' +
|
557 | '{% include "item.html" %}' +
|
558 | '{% endfor %}',
|
559 | 'showing 1showing 2');
|
560 |
|
561 | finish(done);
|
562 | });
|
563 |
|
564 | it('should compile a set block', function(done) {
|
565 | equal('{% set username = "foo" %}{{ username }}',
|
566 | { username: 'james' },
|
567 | 'foo');
|
568 |
|
569 | equal('{% set x, y = "foo" %}{{ x }}{{ y }}',
|
570 | 'foofoo');
|
571 |
|
572 | equal('{% set x = 1 + 2 %}{{ x }}',
|
573 | '3');
|
574 |
|
575 | equal('{% for i in [1] %}{% set foo=1 %}{% endfor %}{{ foo }}',
|
576 | { foo: 2 },
|
577 | '2');
|
578 |
|
579 | equal('{% include "set.html" %}{{ foo }}',
|
580 | { foo: 'bar' },
|
581 | 'bar');
|
582 |
|
583 | equal('{% set username = username + "pasta" %}{{ username }}',
|
584 | { username: 'basta' },
|
585 | 'bastapasta');
|
586 |
|
587 |
|
588 | equal('{% for i in [1] %}{% set val=5 %}{% endfor %}' +
|
589 | '{{ val }}',
|
590 | '');
|
591 |
|
592 | equal('{% for i in [1,2,3] %}' +
|
593 | '{% if not val %}{% set val=5 %}{% endif %}' +
|
594 | '{% set val=val+1 %}{{ val }}' +
|
595 | '{% endfor %}' +
|
596 | 'afterwards: {{ val }}',
|
597 | '678afterwards: ');
|
598 |
|
599 |
|
600 |
|
601 |
|
602 | equal('{% set val=1 %}' +
|
603 | '{% for i in [1] %}{% set val=5 %}{% endfor %}' +
|
604 | '{{ val }}',
|
605 | '5');
|
606 |
|
607 | equal('{% set val=5 %}' +
|
608 | '{% for i in [1,2,3] %}' +
|
609 | '{% set val=val+1 %}{{ val }}' +
|
610 | '{% endfor %}' +
|
611 | 'afterwards: {{ val }}',
|
612 | '678afterwards: 8');
|
613 |
|
614 | finish(done);
|
615 | });
|
616 |
|
617 | it('should compile set with frame references', function(done) {
|
618 | equal('{% set username = user.name %}{{ username }}',
|
619 | { user: { name: 'james' } },
|
620 | 'james');
|
621 |
|
622 | finish(done);
|
623 | });
|
624 |
|
625 | it('should compile set assignments of the same variable', function(done) {
|
626 | equal('{% set x = "hello" %}' +
|
627 | '{% if false %}{% set x = "world" %}{% endif %}' +
|
628 | '{{ x }}',
|
629 | 'hello');
|
630 |
|
631 | equal('{% set x = "blue" %}' +
|
632 | '{% if true %}{% set x = "green" %}{% endif %}' +
|
633 | '{{ x }}',
|
634 | 'green');
|
635 |
|
636 | finish(done);
|
637 | });
|
638 |
|
639 | it('should throw errors', function(done) {
|
640 | render('{% from "import.html" import boozle %}',
|
641 | {},
|
642 | { noThrow: true },
|
643 | function(err) {
|
644 | expect(err).to.match(/cannot import 'boozle'/);
|
645 | });
|
646 |
|
647 | finish(done);
|
648 | });
|
649 |
|
650 | it('should allow custom tag compilation', function(done) {
|
651 | function testExtension() {
|
652 | this.tags = ['test'];
|
653 |
|
654 | this.parse = function(parser, nodes) {
|
655 | parser.advanceAfterBlockEnd();
|
656 |
|
657 | var content = parser.parseUntilBlocks("endtest");
|
658 | var tag = new nodes.CallExtension(this, 'run', null, [content]);
|
659 | parser.advanceAfterBlockEnd();
|
660 |
|
661 | return tag;
|
662 | };
|
663 |
|
664 | this.run = function(context, content) {
|
665 |
|
666 | return content().split("").reverse().join("");
|
667 | };
|
668 | }
|
669 |
|
670 | var opts = { extensions: { 'testExtension': new testExtension() }};
|
671 | render('{% test %}123456789{% endtest %}', null, opts, function(err, res) {
|
672 | expect(res).to.be('987654321');
|
673 | });
|
674 |
|
675 | finish(done);
|
676 | });
|
677 |
|
678 | it('should allow custom tag compilation without content', function(done) {
|
679 | function testExtension() {
|
680 | this.tags = ['test'];
|
681 |
|
682 | this.parse = function(parser, nodes) {
|
683 | var tok = parser.nextToken();
|
684 | var args = parser.parseSignature(null, true);
|
685 | parser.advanceAfterBlockEnd(tok.value);
|
686 |
|
687 | return new nodes.CallExtension(this, 'run', args, null);
|
688 | };
|
689 |
|
690 | this.run = function(context, arg1) {
|
691 |
|
692 | return arg1.split("").reverse().join("");
|
693 | };
|
694 | }
|
695 |
|
696 | var opts = { extensions: { 'testExtension': new testExtension() }};
|
697 | render('{% test "123456" %}', null, opts, function(err, res) {
|
698 | expect(res).to.be('654321');
|
699 | });
|
700 |
|
701 | finish(done);
|
702 | });
|
703 |
|
704 | it('should allow complicated custom tag compilation', function(done) {
|
705 | function testExtension() {
|
706 | this.tags = ['test'];
|
707 |
|
708 |
|
709 | this._name = 'testExtension';
|
710 |
|
711 | this.parse = function(parser, nodes, lexer) {
|
712 | var body, intermediate = null;
|
713 | parser.advanceAfterBlockEnd();
|
714 |
|
715 | body = parser.parseUntilBlocks('intermediate', 'endtest');
|
716 |
|
717 | if(parser.skipSymbol('intermediate')) {
|
718 | parser.skip(lexer.TOKEN_BLOCK_END);
|
719 | intermediate = parser.parseUntilBlocks('endtest');
|
720 | }
|
721 |
|
722 | parser.advanceAfterBlockEnd();
|
723 |
|
724 | return new nodes.CallExtension(this, 'run', null, [body, intermediate]);
|
725 | };
|
726 |
|
727 | this.run = function(context, body, intermediate) {
|
728 | var output = body().split("").join(",");
|
729 | if(intermediate) {
|
730 |
|
731 | output += intermediate().split("").reverse().join("");
|
732 | }
|
733 | return output;
|
734 | };
|
735 | }
|
736 |
|
737 | var opts = { extensions: { 'testExtension': new testExtension() }};
|
738 |
|
739 | render('{% test %}abcdefg{% endtest %}', null, opts, function(err, res) {
|
740 | expect(res).to.be('a,b,c,d,e,f,g');
|
741 | });
|
742 |
|
743 | render('{% test %}abcdefg{% intermediate %}second half{% endtest %}',
|
744 | null,
|
745 | opts,
|
746 | function(err, res) {
|
747 | expect(res).to.be('a,b,c,d,e,f,gflah dnoces');
|
748 | });
|
749 |
|
750 | finish(done);
|
751 | });
|
752 |
|
753 | it('should allow custom tag with args compilation', function(done) {
|
754 | function testExtension() {
|
755 | this.tags = ['test'];
|
756 |
|
757 |
|
758 | this._name = 'testExtension';
|
759 |
|
760 | this.parse = function(parser, nodes, lexer) {
|
761 | var body, args = null;
|
762 | var tok = parser.nextToken();
|
763 |
|
764 |
|
765 | args = parser.parseSignature(true);
|
766 | parser.advanceAfterBlockEnd(tok.value);
|
767 |
|
768 | body = parser.parseUntilBlocks('endtest');
|
769 | parser.advanceAfterBlockEnd();
|
770 |
|
771 | return new nodes.CallExtension(this, 'run', args, [body]);
|
772 | };
|
773 |
|
774 | this.run = function(context, prefix, kwargs, body) {
|
775 | if(typeof prefix == 'function') {
|
776 | body = prefix;
|
777 | prefix = '';
|
778 | kwargs = {};
|
779 | }
|
780 | else if(typeof kwargs == 'function') {
|
781 | body = kwargs;
|
782 | kwargs = {};
|
783 | }
|
784 |
|
785 | var output = prefix + body().split('').reverse().join('');
|
786 | if(kwargs.cutoff) {
|
787 | output = output.slice(0, kwargs.cutoff);
|
788 | }
|
789 |
|
790 | return output;
|
791 | };
|
792 | }
|
793 |
|
794 | var opts = { extensions: {'testExtension': new testExtension() }};
|
795 |
|
796 | render('{% test %}foobar{% endtest %}', null, opts, function(err, res) {
|
797 | expect(res).to.be('raboof');
|
798 | });
|
799 |
|
800 | render('{% test("biz") %}foobar{% endtest %}', null, opts, function(err, res) {
|
801 | expect(res).to.be('bizraboof');
|
802 | });
|
803 |
|
804 | render('{% test("biz", cutoff=5) %}foobar{% endtest %}', null, opts, function(err, res) {
|
805 | expect(res).to.be('bizra');
|
806 | });
|
807 |
|
808 | finish(done);
|
809 | });
|
810 |
|
811 | it('should not autoescape by default', function(done) {
|
812 | equal('{{ foo }}', { foo: '"\'<>&'}, '"\'<>&');
|
813 | finish(done);
|
814 | });
|
815 |
|
816 | it('should autoescape if autoescape is on', function(done) {
|
817 | render('{{ foo }}', { foo: '"\'<>&'}, { autoescape: true }, function(err, res) {
|
818 | expect(res).to.be('"'<>&');
|
819 | });
|
820 |
|
821 | render('{{ foo|reverse }}', { foo: '"\'<>&'}, { autoescape: true }, function(err, res) {
|
822 | expect(res).to.be('&><'"');
|
823 | });
|
824 |
|
825 | render('{{ foo|reverse|safe }}', { foo: '"\'<>&'}, { autoescape: true }, function(err, res) {
|
826 | expect(res).to.be('&><\'"');
|
827 | });
|
828 |
|
829 | finish(done);
|
830 | });
|
831 |
|
832 | it('should not autoescape safe strings', function(done) {
|
833 | render('{{ foo|safe }}', { foo: '"\'<>&'}, { autoescape: true }, function(err, res) {
|
834 | expect(res).to.be('"\'<>&');
|
835 | });
|
836 |
|
837 | finish(done);
|
838 | });
|
839 |
|
840 | it('should not autoescape macros', function(done) {
|
841 | render(
|
842 | '{% macro foo(x, y) %}{{ x }} and {{ y }}{% endmacro %}' +
|
843 | '{{ foo("<>&", "<>") }}',
|
844 | null,
|
845 | { autoescape: true },
|
846 | function(err, res) {
|
847 | expect(res).to.be('<>& and <>');
|
848 | }
|
849 | );
|
850 |
|
851 | render(
|
852 | '{% macro foo(x, y) %}{{ x|safe }} and {{ y }}{% endmacro %}' +
|
853 | '{{ foo("<>&", "<>") }}',
|
854 | null,
|
855 | { autoescape: true },
|
856 | function(err, res) {
|
857 | expect(res).to.be('<>& and <>');
|
858 | }
|
859 | );
|
860 |
|
861 | finish(done);
|
862 | });
|
863 |
|
864 | it('should not autoescape super()', function(done) {
|
865 | render(
|
866 | '{% extends "base3.html" %}' +
|
867 | '{% block block1 %}{{ super() }}{% endblock %}',
|
868 | null,
|
869 | { autoescape: true },
|
870 | function(err, res) {
|
871 | expect(res).to.be('<b>Foo</b>');
|
872 | }
|
873 | );
|
874 |
|
875 | finish(done);
|
876 | });
|
877 |
|
878 | it('should not autoescape when extension set false', function(done) {
|
879 | function testExtension() {
|
880 | this.tags = ['test'];
|
881 |
|
882 | this.autoescape = false;
|
883 |
|
884 | this.parse = function(parser, nodes) {
|
885 | var tok = parser.nextToken();
|
886 | var args = parser.parseSignature(null, true);
|
887 | parser.advanceAfterBlockEnd(tok.value);
|
888 | return new nodes.CallExtension(this, 'run', args, null);
|
889 | };
|
890 |
|
891 | this.run = function(context) {
|
892 |
|
893 | return '<b>Foo</b>';
|
894 | };
|
895 | }
|
896 |
|
897 | var opts = {
|
898 | extensions: { 'testExtension': new testExtension() },
|
899 | autoescape: true
|
900 | };
|
901 |
|
902 | render(
|
903 | '{% test "123456" %}',
|
904 | null,
|
905 | opts,
|
906 | function(err, res) {
|
907 | expect(res).to.be('<b>Foo</b>');
|
908 | }
|
909 | );
|
910 |
|
911 | finish(done);
|
912 | });
|
913 |
|
914 | it('should pass context as this to filters', function(done) {
|
915 | render(
|
916 | '{{ foo | hallo }}',
|
917 | { foo: 1, bar: 2 },
|
918 | { filters: {
|
919 | 'hallo': function(foo) { return foo + this.lookup('bar'); }
|
920 | }},
|
921 | function(err, res) {
|
922 | expect(res).to.be('3');
|
923 | }
|
924 | );
|
925 |
|
926 | finish(done);
|
927 | });
|
928 | });
|
929 | })();
|