UNPKG

34.4 kBJavaScriptView Raw
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 // TODO: Should the for loop create a new frame for each
458 // iteration? As it is, `num` is set on all iterations after
459 // the first one sets it
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 // two levels of `super` should work
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 // `set` should only set within its current scope
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 // however, like Python, if a variable has been set in an
600 // above scope, any other set should correctly resolve to
601 // that frame
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 // Reverse the string
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 // Reverse the string
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 /* normally this is automatically done by Environment */
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 // Reverse the string.
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 /* normally this is automatically done by Environment */
758 this._name = 'testExtension';
759
760 this.parse = function(parser, nodes, lexer) {
761 var body, args = null;
762 var tok = parser.nextToken();
763
764 // passing true makes it tolerate when no args exist
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('&quot;&#39;&lt;&gt;&amp;');
819 });
820
821 render('{{ foo|reverse }}', { foo: '"\'<>&'}, { autoescape: true }, function(err, res) {
822 expect(res).to.be('&amp;&gt;&lt;&#39;&quot;');
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('&lt;&gt;&amp; and &lt;&gt;');
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 &lt;&gt;');
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 // Reverse the string
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})();