1 use std::collections::{BTreeMap, HashMap};
2 use std::error::Error;
3 use std::sync::atomic::{AtomicUsize, Ordering};
4 use std::sync::Arc;
5 
6 use lazy_static::lazy_static;
7 use serde_derive::Serialize;
8 use serde_json::{json, Value};
9 
10 use crate::builtins::functions::Function;
11 use crate::context::Context;
12 use crate::errors::Result;
13 use crate::tera::Tera;
14 
15 use super::Review;
16 
render_template(content: &str, context: &Context) -> Result<String>17 fn render_template(content: &str, context: &Context) -> Result<String> {
18     let mut tera = Tera::default();
19     tera.add_raw_template("hello.html", content).unwrap();
20     tera.register_function("get_number", |_: &HashMap<String, Value>| Ok(Value::Number(10.into())));
21     tera.register_function("get_true", |_: &HashMap<String, Value>| Ok(Value::Bool(true.into())));
22     tera.register_function("get_string", |_: &HashMap<String, Value>| {
23         Ok(Value::String("Hello".to_string()))
24     });
25 
26     tera.render("hello.html", context)
27 }
28 
29 #[test]
render_simple_string()30 fn render_simple_string() {
31     let result = render_template("<h1>Hello world</h1>", &Context::new());
32     assert_eq!(result.unwrap(), "<h1>Hello world</h1>".to_owned());
33 }
34 
35 #[test]
render_variable_block_lit_expr()36 fn render_variable_block_lit_expr() {
37     let inputs = vec![
38         ("{{ 1 }}", "1"),
39         ("{{ 3.14 }}", "3.14"),
40         ("{{ \"hey\" }}", "hey"),
41         (r#"{{ "{{ hey }}" }}"#, "{{ hey }}"),
42         ("{{ true }}", "true"),
43         ("{{ false }}", "false"),
44         ("{{ false and true or true }}", "true"),
45         ("{{ 1 + 1 }}", "2"),
46         ("{{ 1 + 1.1 }}", "2.1"),
47         ("{{ 3 - 1 }}", "2"),
48         ("{{ 3 - 1.1 }}", "1.9"),
49         ("{{ 2 * 5 }}", "10"),
50         ("{{ 10 / 5 }}", "2"),
51         ("{{ 2.1 * 5 }}", "10.5"),
52         ("{{ 2.1 * 5.05 }}", "10.605"),
53         ("{{ 2 / 0.5 }}", "4"),
54         ("{{ 2.1 / 0.5 }}", "4.2"),
55         ("{{ 2 + 1 * 2 }}", "4"),
56         ("{{ (2 + 1) * 2 }}", "6"),
57         ("{{ 2 * 4 % 8 }}", "0"),
58         ("{{ 2.8 * 2 | round }}", "6"),
59         ("{{ 1 / 0 }}", "NaN"),
60         ("{{ true and 10 }}", "true"),
61         ("{{ true and not 10 }}", "false"),
62         ("{{ not true }}", "false"),
63         ("{{ [1, 2, 3] }}", "[1, 2, 3]"),
64     ];
65 
66     for (input, expected) in inputs {
67         println!("{:?} -> {:?}", input, expected);
68         assert_eq!(render_template(input, &Context::new()).unwrap(), expected);
69     }
70 }
71 
72 #[test]
render_variable_block_ident()73 fn render_variable_block_ident() {
74     let mut context = Context::new();
75     context.insert("name", &"john");
76     context.insert("malicious", &"<html>");
77     context.insert("a", &2);
78     context.insert("b", &3);
79     context.insert("numbers", &vec![1, 2, 3]);
80     context.insert("tuple_list", &vec![(1, 2, 3), (1, 2, 3)]);
81     context.insert("review", &Review::new());
82     context.insert("with_newline", &"Animal Alphabets\nB is for Bee-Eater");
83 
84     let inputs = vec![
85         ("{{ name }}", "john"),
86         ("{{ malicious }}", "&lt;html&gt;"),
87         ("{{ \"<html>\" }}", "&lt;html&gt;"),
88         ("{{ \" html \" | upper | trim }}", "HTML"),
89         ("{{ 'html' }}", "html"),
90         ("{{ `html` }}", "html"),
91         // https://github.com/Keats/tera/issues/273
92         (
93             r#"{{ 'hangar new "Will Smoth <will_s@example.com>"' | safe }}"#,
94             r#"hangar new "Will Smoth <will_s@example.com>""#,
95         ),
96         ("{{ malicious | safe }}", "<html>"),
97         ("{{ malicious | upper }}", "&lt;HTML&gt;"),
98         ("{{ malicious | upper | safe }}", "<HTML>"),
99         ("{{ malicious | safe | upper }}", "&lt;HTML&gt;"),
100         ("{{ review | length }}", "2"),
101         ("{{ review.paragraphs.1 }}", "B"),
102         ("{{ numbers }}", "[1, 2, 3]"),
103         ("{{ numbers.0 }}", "1"),
104         ("{{ tuple_list.1.1 }}", "2"),
105         ("{{ name and true }}", "true"),
106         ("{{ name | length }}", "4"),
107         ("{{ name is defined }}", "true"),
108         ("{{ not name is defined }}", "false"),
109         ("{{ name is not defined }}", "false"),
110         ("{{ not name is not defined }}", "true"),
111         ("{{ a is odd }}", "false"),
112         ("{{ a is odd or b is odd  }}", "true"),
113         ("{{ range(start=1, end=4) }}", "[1, 2, 3]"),
114         ("{{ a + b }}", "5"),
115         ("{{ a + 1.5 }}", "3.5"),
116         ("{{ 1 + 1 + 1 }}", "3"),
117         ("{{ 2 - 2 - 1 }}", "-1"),
118         ("{{ 1 - 1 + 1 }}", "1"),
119         ("{{ 1 + get_number() }}", "11"),
120         ("{{ get_number() + 1 }}", "11"),
121         ("{{ (1.9 + a) | round }}", "4"),
122         ("{{ 1.9 + a | round }}", "4"),
123         ("{{ numbers | length - 1 }}", "2"),
124         ("{{ 1.9 + a | round - 1 }}", "3"),
125         ("{{ 1.9 + a | round - 1.8 + a | round }}", "0"),
126         ("{{ 1.9 + a | round - 1.8 + a | round - 1 }}", "-1"),
127         // https://github.com/Keats/tera/issues/435
128         (
129             "{{ with_newline | replace(from='\n', to='<br>') | safe }}",
130             "Animal Alphabets<br>B is for Bee-Eater",
131         ),
132     ];
133 
134     for (input, expected) in inputs {
135         println!("{:?} -> {:?}", input, expected);
136         assert_eq!(render_template(input, &context).unwrap(), expected);
137     }
138 }
139 
140 #[test]
render_variable_block_logic_expr()141 fn render_variable_block_logic_expr() {
142     let mut context = Context::new();
143     context.insert("name", &"john");
144     context.insert("malicious", &"<html>");
145     context.insert("a", &2);
146     context.insert("b", &3);
147     context.insert("numbers", &vec![1, 2, 3]);
148     context.insert("tuple_list", &vec![(1, 2, 3), (1, 2, 3)]);
149     let mut hashmap = HashMap::new();
150     hashmap.insert("a", 1);
151     hashmap.insert("b", 10);
152     hashmap.insert("john", 100);
153     context.insert("object", &hashmap);
154 
155     let inputs = vec![
156         ("{{ (1.9 + a) | round > 10 }}", "false"),
157         ("{{ (1.9 + a) | round > 10 or b > a }}", "true"),
158         ("{{ 1.9 + a | round == 4 and numbers | length == 3}}", "true"),
159         ("{{ numbers | length > 1 }}", "true"),
160         ("{{ numbers | length == 1 }}", "false"),
161         ("{{ numbers | length - 2 == 1 }}", "true"),
162         ("{{ not name }}", "false"),
163         ("{{ not true }}", "false"),
164         ("{{ not undefined }}", "true"),
165         ("{{ name == 'john' }}", "true"),
166         ("{{ name != 'john' }}", "false"),
167         ("{{ name == 'john' | capitalize }}", "false"),
168         ("{{ name != 'john' | capitalize }}", "true"),
169         ("{{ 1 in numbers }}", "true"),
170         ("{{ 1 not in numbers }}", "false"),
171         ("{{ 40 not in numbers }}", "true"),
172         ("{{ 'e' in 'hello' }}", "true"),
173         ("{{ 'e' not in 'hello' }}", "false"),
174         ("{{ 'x' not in 'hello' }}", "true"),
175         ("{{ name in 'hello john' }}", "true"),
176         ("{{ name not in 'hello john' }}", "false"),
177         ("{{ name not in 'hello' }}", "true"),
178         ("{{ name in ['bob', 2, 'john'] }}", "true"),
179         ("{{ a in ['bob', 2, 'john'] }}", "true"),
180         ("{{ 'n' in name }}", "true"),
181         ("{{ '<' in malicious }}", "true"),
182         ("{{ 'a' in object }}", "true"),
183         ("{{ name in object }}", "true"),
184     ];
185 
186     for (input, expected) in inputs {
187         println!("{:?} -> {:?}", input, expected);
188         assert_eq!(render_template(input, &context).unwrap(), expected);
189     }
190 }
191 
192 #[test]
render_variable_block_autoescaping_disabled()193 fn render_variable_block_autoescaping_disabled() {
194     let mut context = Context::new();
195     context.insert("name", &"john");
196     context.insert("malicious", &"<html>");
197 
198     let inputs = vec![
199         ("{{ name }}", "john"),
200         ("{{ malicious }}", "<html>"),
201         ("{{ malicious | safe }}", "<html>"),
202         ("{{ malicious | upper }}", "<HTML>"),
203         ("{{ malicious | upper | safe }}", "<HTML>"),
204         ("{{ malicious | safe | upper }}", "<HTML>"),
205     ];
206 
207     for (input, expected) in inputs {
208         let mut tera = Tera::default();
209         tera.add_raw_template("hello.sql", input).unwrap();
210         assert_eq!(tera.render("hello.sql", &context).unwrap(), expected);
211     }
212 }
213 
214 #[test]
comments_are_ignored()215 fn comments_are_ignored() {
216     let inputs = vec![
217         ("Hello {# comment #}world", "Hello world"),
218         ("Hello {# comment {# nested #}world", "Hello world"),
219         ("My name {# was {{ name }} #}is No One.", "My name is No One."),
220     ];
221 
222     for (input, expected) in inputs {
223         println!("{:?} -> {:?}", input, expected);
224         assert_eq!(render_template(input, &Context::new()).unwrap(), expected);
225     }
226 }
227 
228 #[test]
escaping_happens_at_the_end()229 fn escaping_happens_at_the_end() {
230     let inputs = vec![
231         #[cfg(feature = "builtins")]
232         ("{{ url | urlencode | safe }}", "https%3A//www.example.org/apples-%26-oranges/"),
233         ("{{ '<html>' }}", "&lt;html&gt;"),
234         ("{{ '<html>' | safe }}", "<html>"),
235         ("{{ 'hello' | safe | replace(from='h', to='&') }}", "&amp;ello"),
236         ("{{ 'hello' | replace(from='h', to='&') | safe }}", "&ello"),
237     ];
238 
239     for (input, expected) in inputs {
240         let mut context = Context::new();
241         context.insert("url", "https://www.example.org/apples-&-oranges/");
242         assert_eq!(render_template(input, &context).unwrap(), expected);
243     }
244 }
245 
246 #[test]
filter_args_are_not_escaped()247 fn filter_args_are_not_escaped() {
248     let mut context = Context::new();
249     context.insert("my_var", &"hey");
250     context.insert("to", &"&");
251     let input = r#"{{ my_var | replace(from="h", to=to) }}"#;
252 
253     assert_eq!(render_template(input, &context).unwrap(), "&amp;ey");
254 }
255 
256 #[test]
render_include_tag()257 fn render_include_tag() {
258     let mut tera = Tera::default();
259     tera.add_raw_templates(vec![
260         ("world", "world"),
261         ("hello", "<h1>Hello {% include \"world\" %}</h1>"),
262     ])
263     .unwrap();
264     let result = tera.render("hello", &Context::new()).unwrap();
265     assert_eq!(result, "<h1>Hello world</h1>".to_owned());
266 }
267 
268 #[test]
can_set_variables_in_included_templates()269 fn can_set_variables_in_included_templates() {
270     let mut tera = Tera::default();
271     tera.add_raw_templates(vec![
272         ("world", r#"{% set a = "world" %}{{a}}"#),
273         ("hello", "<h1>Hello {% include \"world\" %}</h1>"),
274     ])
275     .unwrap();
276     let result = tera.render("hello", &Context::new()).unwrap();
277     assert_eq!(result, "<h1>Hello world</h1>".to_owned());
278 }
279 
280 #[test]
render_raw_tag()281 fn render_raw_tag() {
282     let inputs = vec![
283         ("{% raw %}hey{% endraw %}", "hey"),
284         ("{% raw %}{{hey}}{% endraw %}", "{{hey}}"),
285         ("{% raw %}{% if true %}{% endraw %}", "{% if true %}"),
286     ];
287 
288     for (input, expected) in inputs {
289         println!("{:?} -> {:?}", input, expected);
290         assert_eq!(render_template(input, &Context::new()).unwrap(), expected);
291     }
292 }
293 
294 #[test]
add_set_values_in_context()295 fn add_set_values_in_context() {
296     let mut context = Context::new();
297     context.insert("my_var", &"hey");
298     context.insert("malicious", &"<html>");
299     context.insert("admin", &true);
300     context.insert("num", &1);
301 
302     let inputs = vec![
303         ("{% set i = 1 %}{{ i }}", "1"),
304         ("{% set i = 1 + 2 %}{{ i }}", "3"),
305         (r#"{% set i = "hey" %}{{ i }}"#, "hey"),
306         (r#"{% set i = "<html>" %}{{ i | safe }}"#, "<html>"),
307         (r#"{% set i = "<html>" %}{{ i }}"#, "&lt;html&gt;"),
308         ("{% set i = my_var %}{{ i }}", "hey"),
309         ("{% set i = malicious %}{{ i | safe }}", "<html>"),
310         ("{% set i = malicious %}{{ i }}", "&lt;html&gt;"),
311         ("{% set i = my_var | upper %}{{ i }}", "HEY"),
312         ("{% set i = range(end=3) %}{{ i }}", "[0, 1, 2]"),
313         ("{% set i = admin or true %}{{ i }}", "true"),
314         ("{% set i = admin and num > 0 %}{{ i }}", "true"),
315         ("{% set i = 0 / 0 %}{{ i }}", "NaN"),
316         ("{% set i = [1,2] %}{{ i }}", "[1, 2]"),
317     ];
318 
319     for (input, expected) in inputs {
320         println!("{:?} -> {:?}", input, expected);
321         assert_eq!(render_template(input, &context).unwrap(), expected);
322     }
323 }
324 
325 #[test]
render_filter_section()326 fn render_filter_section() {
327     let inputs = vec![
328         ("{% filter upper %}Hello{% endfilter %}", "HELLO"),
329         ("{% filter upper %}Hello{% if true %} world{% endif %}{% endfilter %}", "HELLO WORLD"),
330         ("{% filter upper %}Hello {% for i in range(end=3) %}i{% endfor %}{% endfilter %}", "HELLO III"),
331         (
332             "{% filter upper %}Hello {% for i in range(end=3) %}{% if i == 1 %}{% break %} {% endif %}i{% endfor %}{% endfilter %}",
333             "HELLO I",
334         ),
335         ("{% filter title %}Hello {% if true %}{{ 'world' | upper | safe }}{% endif %}{% endfilter %}", "Hello World"),
336     ];
337 
338     let context = Context::new();
339     for (input, expected) in inputs {
340         println!("{:?} -> {:?}", input, expected);
341         assert_eq!(render_template(input, &context).unwrap(), expected);
342     }
343 }
344 
345 #[test]
render_tests()346 fn render_tests() {
347     let mut context = Context::new();
348     context.insert("is_true", &true);
349     context.insert("is_false", &false);
350     context.insert("age", &18);
351     context.insert("name", &"john");
352     let mut map = HashMap::new();
353     map.insert(0, 1);
354     context.insert("map", &map);
355     context.insert("numbers", &vec![1, 2, 3]);
356     context.insert::<Option<usize>, _>("maybe", &None);
357 
358     let inputs = vec![
359         ("{% if is_true is defined %}Admin{% endif %}", "Admin"),
360         ("{% if hello is undefined %}Admin{% endif %}", "Admin"),
361         ("{% if name is string %}Admin{% endif %}", "Admin"),
362         ("{% if age is number %}Admin{% endif %}", "Admin"),
363         ("{% if age is even %}Admin{% endif %}", "Admin"),
364         ("{% if age is odd %}Admin{%else%}even{% endif %}", "even"),
365         ("{% if age is divisibleby(2) %}Admin{% endif %}", "Admin"),
366         ("{% if numbers is iterable %}Admin{% endif %}", "Admin"),
367         ("{% if map is iterable %}Admin{% endif %}", "Admin"),
368         ("{% if map is object %}Admin{% endif %}", "Admin"),
369         ("{% if name is starting_with('j') %}Admin{% endif %}", "Admin"),
370         ("{% if name is ending_with('n') %}Admin{% endif %}", "Admin"),
371         ("{% if numbers is containing(2) %}Admin{% endif %}", "Admin"),
372         ("{% if name is matching('^j.*') %}Admin{% endif %}", "Admin"),
373         ("{% if maybe is defined %}Admin{% endif %}", "Admin"),
374     ];
375 
376     for (input, expected) in inputs {
377         println!("{:?} -> {:?}", input, expected);
378         assert_eq!(render_template(input, &context).unwrap(), expected);
379     }
380 }
381 
382 #[test]
render_if_elif_else()383 fn render_if_elif_else() {
384     let mut context = Context::new();
385     context.insert("is_true", &true);
386     context.insert("is_false", &false);
387     context.insert("age", &18);
388     context.insert("name", &"john");
389     context.insert("empty_string", &"");
390     context.insert("numbers", &vec![1, 2, 3]);
391 
392     let inputs = vec![
393         ("{% if is_true %}Admin{% endif %}", "Admin"),
394         ("{% if is_true or age + 1 > 18 %}Adult{% endif %}", "Adult"),
395         ("{% if is_true and age == 18 %}Adult{% endif %}", "Adult"),
396         // https://github.com/Keats/tera/issues/187
397         ("{% if 1 <= 2 %}a{% endif %}", "a"),
398         ("{% if 2 >= 1 %}a{% endif %}", "a"),
399         ("{% if 1 < 2 %}a{% endif %}", "a"),
400         ("{% if 2 > 1 %}a{% endif %}", "a"),
401         ("{% if 1 == 1 %}a{% endif %}", "a"),
402         ("{% if 1 != 2 %}a{% endif %}", "a"),
403         // testing string conditions
404         ("{% if 'true' %}a{% endif %}", "a"),
405         ("{% if name %}a{% endif %}", "a"),
406         ("{% if '' %}a{% endif %}", ""),
407         ("{% if empty_string %}a{% endif %}", ""),
408         ("{% if '' ~ name %}a{% endif %}", "a"),
409         ("{% if '' ~ empty_string %}a{% endif %}", ""),
410         // some not conditions
411         ("{% if not is_false %}a{% endif %}", "a"),
412         ("{% if not is_true %}a{% endif %}", ""),
413         ("{% if undefined %}a{% endif %}", ""),
414         ("{% if not undefined %}a{% endif %}", "a"),
415         ("{% if not is_false and is_true %}a{% endif %}", "a"),
416         ("{% if not is_false or numbers | length > 0 %}a{% endif %}", "a"),
417         // doesn't panic with NaN results
418         ("{% if 0 / 0 %}a{% endif %}", ""),
419         // if and else
420         ("{% if is_true %}Admin{% else %}User{% endif %}", "Admin"),
421         ("{% if is_false %}Admin{% else %}User{% endif %}", "User"),
422         // if and elifs
423         ("{% if is_true %}Admin{% elif is_false %}User{% endif %}", "Admin"),
424         ("{% if is_true %}Admin{% elif is_true %}User{% endif %}", "Admin"),
425         ("{% if is_true %}Admin{% elif numbers | length > 0 %}User{% endif %}", "Admin"),
426         // if, elifs and else
427         ("{% if is_true %}Admin{% elif is_false %}User{% else %}Hmm{% endif %}", "Admin"),
428         ("{% if false %}Admin{% elif is_false %}User{% else %}Hmm{% endif %}", "Hmm"),
429         // doesn't fallthrough elifs
430         // https://github.com/Keats/tera/issues/188
431         ("{% if 1 < 4 %}a{% elif 2 < 4 %}b{% elif 3 < 4 %}c{% else %}d{% endif %}", "a"),
432         // with in operator
433         (
434             "{% if 1 in numbers %}Admin{% elif 100 in numbers %}User{% else %}Hmm{% endif %}",
435             "Admin",
436         ),
437         ("{% if 100 in numbers %}Admin{% elif 1 in numbers %}User{% else %}Hmm{% endif %}", "User"),
438         ("{% if 'n' in name %}Admin{% else %}Hmm{% endif %}", "Admin"),
439         // function in if
440         ("{% if get_true() %}Truth{% endif %}", "Truth"),
441     ];
442 
443     for (input, expected) in inputs {
444         println!("{:?} -> {:?}", input, expected);
445         assert_eq!(render_template(input, &context).unwrap(), expected);
446     }
447 }
448 
449 #[test]
render_for()450 fn render_for() {
451     let mut context = Context::new();
452     let mut map = BTreeMap::new();
453     map.insert("name", "bob");
454     map.insert("age", "18");
455 
456     context.insert("data", &vec![1, 2, 3]);
457     context.insert("notes", &vec![1, 2, 3]);
458     context.insert("vectors", &vec![vec![0, 3, 6], vec![1, 4, 7]]);
459     context.insert("vectors_some_empty", &vec![vec![0, 3, 6], vec![], vec![1, 4, 7]]);
460     context.insert("map", &map);
461     context.insert("truthy", &2);
462 
463     let inputs = vec![
464         ("{% for i in data %}{{i}}{% endfor %}", "123"),
465         ("{% for key, val in map %}{{key}}:{{val}} {% endfor %}", "age:18 name:bob "),
466         (
467             "{% for i in data %}{{loop.index}}{{loop.index0}}{{loop.first}}{{loop.last}}{% endfor %}",
468             "10truefalse21falsefalse32falsetrue"
469         ),
470         (
471             "{% for vector in vectors %}{% for j in vector %}{{ j }}{% endfor %}{% endfor %}",
472             "036147"
473         ),
474         (
475             "{% for vector in vectors_some_empty %}{% for j in vector %}{{ j }}{% endfor %}{% endfor %}",
476             "036147"
477         ),
478         (
479             "{% for val in data %}{% if val == truthy %}on{% else %}off{% endif %}{% endfor %}",
480             "offonoff"
481         ),
482         ("{% for i in range(end=5) %}{{i}}{% endfor %}", "01234"),
483         ("{% for i in range(end=5) | reverse %}{{i}}{% endfor %}", "43210"),
484         (
485             "{% set looped = 0 %}{% for i in range(end=5) %}{% set looped = i %}{{looped}}{% endfor%}{{looped}}",
486             "012340"
487         ),
488         // https://github.com/Keats/tera/issues/184
489         ("{% for note in notes %}{{ note }}{% endfor %}", "123"),
490         ("{% for note in notes | reverse %}{{ note }}{% endfor %}", "321"),
491         ("{% for v in vectors %}{{ v.0 }}{% endfor %}", "01"),
492         // Loop control (`break` and `continue`)
493         // https://github.com/Keats/tera/issues/267
494         (
495             "{% for i in data %}{{ i }}{% if i == 2 %}{% break %}{% endif %}{% endfor %}",
496             "12"
497         ),
498         (
499             "{% for i in data %}{% if i == 2 %}{% continue %}{% endif %}{{ i }}{% endfor %}",
500             "13"
501         ),
502         (
503             "{% for v in vectors %}{% for i in v %}{% if i == 3 %}{% break %}{% endif %}{{ i }}{% endfor %}{% endfor %}",
504             "0147"
505         ),
506         (
507             "{% for v in vectors %}{% for i in v %}{% if i == 3 %}{% continue %}{% endif %}{{ i }}{% endfor %}{% endfor %}",
508             "06147"
509         ),
510         (
511             "{% for a in [1, true, 1.1, 'hello'] %}{{a}}{% endfor %}",
512             "1true1.1hello"
513         ),
514         // https://github.com/Keats/tera/issues/301
515         (
516             "{% set start = 0 %}{% set end = start + 3 %}{% for i in range(start=start, end=end) %}{{ i }}{% endfor%}",
517             "012"
518         ),
519         // https://github.com/Keats/tera/issues/395
520         (
521             "{% for a in [] %}{{a}}{% else %}hello{% endfor %}",
522             "hello"
523         ),
524         (
525             "{% for a in undefined_variable | default(value=[]) %}{{a}}{% else %}hello{% endfor %}",
526             "hello"
527         ),
528         (
529             "{% for a in [] %}{{a}}{% else %}{% if 1 == 2 %}A{% else %}B{% endif %}{% endfor %}",
530             "B"
531         ),
532     ];
533 
534     for (input, expected) in inputs {
535         println!("{:?} -> {:?}", input, expected);
536         assert_eq!(render_template(input, &context).unwrap(), expected);
537     }
538 }
539 
540 #[test]
render_magic_variable_isnt_escaped()541 fn render_magic_variable_isnt_escaped() {
542     let mut context = Context::new();
543     context.insert("html", &"<html>");
544 
545     let result = render_template("{{ __tera_context }}", &context);
546 
547     assert_eq!(
548         result.unwrap(),
549         r#"{
550   "html": "<html>"
551 }"#
552         .to_owned()
553     );
554 }
555 
556 // https://github.com/Keats/tera/issues/185
557 #[test]
ok_many_variable_blocks()558 fn ok_many_variable_blocks() {
559     let mut context = Context::new();
560     context.insert("username", &"bob");
561 
562     let mut tpl = String::new();
563     for _ in 0..200 {
564         tpl.push_str("{{ username }}")
565     }
566     let mut expected = String::new();
567     for _ in 0..200 {
568         expected.push_str("bob")
569     }
570     assert_eq!(render_template(&tpl, &context).unwrap(), expected);
571 }
572 
573 #[test]
can_set_variable_in_global_context_in_forloop()574 fn can_set_variable_in_global_context_in_forloop() {
575     let mut context = Context::new();
576     context.insert("tags", &vec![1, 2, 3]);
577     context.insert("default", &"default");
578 
579     let result = render_template(
580         r#"
581 {%- for i in tags -%}
582 {%- set default = 1 -%}
583 {%- set_global global_val = i -%}
584 {%- endfor -%}
585 {{ default }}{{ global_val }}"#,
586         &context,
587     );
588 
589     assert_eq!(result.unwrap(), "default3");
590 }
591 
592 #[test]
default_filter_works()593 fn default_filter_works() {
594     let mut context = Context::new();
595     let i: Option<usize> = None;
596     context.insert("existing", "hello");
597     context.insert("null", &i);
598 
599     let inputs = vec![
600         (r#"{{ existing | default(value="hey") }}"#, "hello"),
601         (r#"{{ val | default(value=1) }}"#, "1"),
602         (r#"{{ val | default(value="hey") | capitalize }}"#, "Hey"),
603         (r#"{{ obj.val | default(value="hey") | capitalize }}"#, "Hey"),
604         (r#"{{ obj.val | default(value="hey") | capitalize }}"#, "Hey"),
605         (r#"{{ not admin | default(value=false) }}"#, "true"),
606         (r#"{{ not admin | default(value=true) }}"#, "false"),
607         (r#"{{ null | default(value=true) }}"#, "true"),
608         (r#"{{ null | default(value="hey") | capitalize }}"#, "Hey"),
609     ];
610 
611     for (input, expected) in inputs {
612         println!("{:?} -> {:?}", input, expected);
613         assert_eq!(render_template(input, &context).unwrap(), expected);
614     }
615 }
616 
617 #[test]
filter_filter_works()618 fn filter_filter_works() {
619     #[derive(Debug, Serialize)]
620     struct Author {
621         id: u8,
622     };
623 
624     let mut context = Context::new();
625     context.insert("authors", &vec![Author { id: 1 }, Author { id: 2 }, Author { id: 3 }]);
626 
627     let inputs =
628         vec![(r#"{{ authors | filter(attribute="id", value=1) | first | get(key="id") }}"#, "1")];
629 
630     for (input, expected) in inputs {
631         println!("{:?} -> {:?}", input, expected);
632         assert_eq!(render_template(input, &context).unwrap(), expected);
633     }
634 }
635 
636 #[test]
filter_on_array_literal_works()637 fn filter_on_array_literal_works() {
638     let mut context = Context::new();
639     let i: Option<usize> = None;
640     context.insert("existing", "hello");
641     context.insert("null", &i);
642 
643     let inputs = vec![
644         (r#"{{ [1, 2, 3] | length }}"#, "3"),
645         (r#"{% set a = [1, 2, 3] | length %}{{ a }}"#, "3"),
646         (r#"{% for a in [1, 2, 3] | slice(start=1) %}{{ a }}{% endfor %}"#, "23"),
647     ];
648 
649     for (input, expected) in inputs {
650         println!("{:?} -> {:?}", input, expected);
651         assert_eq!(render_template(input, &context).unwrap(), expected);
652     }
653 }
654 
655 #[test]
can_do_string_concat()656 fn can_do_string_concat() {
657     let mut context = Context::new();
658     context.insert("a_string", "hello");
659     context.insert("another_string", "xXx");
660     context.insert("an_int", &1);
661     context.insert("a_float", &3.14);
662 
663     let inputs = vec![
664         (r#"{{ "hello" ~ " world" }}"#, "hello world"),
665         (r#"{{ "hello" ~ 1 }}"#, "hello1"),
666         (r#"{{ "hello" ~ 3.14 }}"#, "hello3.14"),
667         (r#"{{ 3.14 ~ "hello"}}"#, "3.14hello"),
668         (r#"{{ "hello" ~ get_string() }}"#, "helloHello"),
669         (r#"{{ get_string() ~ "hello" }}"#, "Hellohello"),
670         (r#"{{ get_string() ~ 3.14 }}"#, "Hello3.14"),
671         (r#"{{ a_string ~ " world" }}"#, "hello world"),
672         (r#"{{ a_string ~ ' world ' ~ another_string }}"#, "hello world xXx"),
673         (r#"{{ a_string ~ another_string }}"#, "helloxXx"),
674         (r#"{{ a_string ~ an_int }}"#, "hello1"),
675         (r#"{{ a_string ~ a_float }}"#, "hello3.14"),
676     ];
677 
678     for (input, expected) in inputs {
679         println!("{:?} -> {:?}", input, expected);
680         assert_eq!(render_template(input, &context).unwrap(), expected);
681     }
682 }
683 
684 #[test]
can_fail_rendering_from_template()685 fn can_fail_rendering_from_template() {
686     let mut context = Context::new();
687     context.insert("title", "hello");
688 
689     let res = render_template(
690         r#"{{ throw(message="Error: " ~ title ~ " did not include a summary") }}"#,
691         &context,
692     );
693 
694     let err = res.expect_err("This should always fail to render");
695     let source = err.source().expect("Must have a source");
696     assert_eq!(source.to_string(), "Function call 'throw' failed");
697 
698     let source = source.source().expect("Should have a nested error");
699     assert_eq!(source.to_string(), "Error: hello did not include a summary");
700 }
701 
702 #[test]
does_render_owned_for_loop_with_objects()703 fn does_render_owned_for_loop_with_objects() {
704     let mut context = Context::new();
705     let data = json!([
706         {"id": 1, "year": 2015},
707         {"id": 2, "year": 2015},
708         {"id": 3, "year": 2016},
709         {"id": 4, "year": 2017},
710         {"id": 5, "year": 2017},
711         {"id": 6, "year": 2017},
712         {"id": 7, "year": 2018},
713         {"id": 8},
714         {"id": 9, "year": null},
715     ]);
716     context.insert("something", &data);
717 
718     let tpl =
719         r#"{% for year, things in something | group_by(attribute="year") %}{{year}},{% endfor %}"#;
720     let expected = "2015,2016,2017,2018,";
721     assert_eq!(render_template(tpl, &context).unwrap(), expected);
722 }
723 
724 #[test]
does_render_owned_for_loop_with_objects_string_keys()725 fn does_render_owned_for_loop_with_objects_string_keys() {
726     let mut context = Context::new();
727     let data = json!([
728         {"id": 1, "group": "a"},
729         {"id": 2, "group": "b"},
730         {"id": 3, "group": "c"},
731         {"id": 4, "group": "a"},
732         {"id": 5, "group": "b"},
733         {"id": 6, "group": "c"},
734         {"id": 7, "group": "a"},
735         {"id": 8},
736         {"id": 9, "year": null},
737     ]);
738     context.insert("something", &data);
739 
740     let tpl = r#"{% for group, things in something | group_by(attribute="group") %}{{group}},{% endfor %}"#;
741     let expected = "a,b,c,";
742     assert_eq!(render_template(tpl, &context).unwrap(), expected);
743 }
744 
745 #[test]
render_magic_variable_gets_all_contexts()746 fn render_magic_variable_gets_all_contexts() {
747     let mut context = Context::new();
748     context.insert("html", &"<html>");
749     context.insert("num", &1);
750     context.insert("i", &10);
751 
752     let result = render_template(
753         "{% set some_val = 1 %}{% for i in range(start=0, end=1) %}{% set for_val = i %}{{ __tera_context }}{% endfor %}",
754         &context
755     );
756 
757     assert_eq!(
758         result.unwrap(),
759         r#"{
760   "for_val": 0,
761   "html": "<html>",
762   "i": 0,
763   "num": 1,
764   "some_val": 1
765 }"#
766         .to_owned()
767     );
768 }
769 
770 #[test]
render_magic_variable_macro_doesnt_leak()771 fn render_magic_variable_macro_doesnt_leak() {
772     let mut context = Context::new();
773     context.insert("html", &"<html>");
774     context.insert("num", &1);
775     context.insert("i", &10);
776 
777     let mut tera = Tera::default();
778     tera.add_raw_templates(vec![
779         ("macros", "{% macro hello(arg=1) %}{{ __tera_context }}{% endmacro hello %}"),
780         ("tpl", "{% import \"macros\" as macros %}{{macros::hello()}}"),
781     ])
782     .unwrap();
783     let result = tera.render("tpl", &context);
784 
785     assert_eq!(
786         result.unwrap(),
787         r#"{
788   "arg": 1
789 }"#
790         .to_owned()
791     );
792 }
793 
794 // https://github.com/Keats/tera/issues/342
795 #[test]
redefining_loop_value_doesnt_break_loop()796 fn redefining_loop_value_doesnt_break_loop() {
797     let mut tera = Tera::default();
798     tera.add_raw_template(
799         "tpl",
800         r#"
801 {%- set string = "abcdefghdijklm" | split(pat="d") -%}
802 {% for i in string -%}
803     {%- set j = i ~ "lol" ~ " " -%}
804     {{ j }}
805 {%- endfor -%}
806         "#,
807     )
808     .unwrap();
809     let context = Context::new();
810     let result = tera.render("tpl", &context);
811 
812     assert_eq!(result.unwrap(), "abclol efghlol ijklmlol ");
813 }
814 
815 #[test]
can_use_concat_to_push_to_array()816 fn can_use_concat_to_push_to_array() {
817     let mut tera = Tera::default();
818     tera.add_raw_template(
819         "tpl",
820         r#"
821 {%- set ids = [] -%}
822 {% for i in range(end=5) -%}
823 {%- set_global ids = ids | concat(with=i) -%}
824 {%- endfor -%}
825 {{ids}}"#,
826     )
827     .unwrap();
828     let context = Context::new();
829     let result = tera.render("tpl", &context);
830 
831     assert_eq!(result.unwrap(), "[0, 1, 2, 3, 4]");
832 }
833 
834 struct Next(AtomicUsize);
835 
836 impl Function for Next {
call(&self, _args: &HashMap<String, Value>) -> Result<Value>837     fn call(&self, _args: &HashMap<String, Value>) -> Result<Value> {
838         Ok(Value::Number(self.0.fetch_add(1, Ordering::Relaxed).into()))
839     }
840 }
841 
842 #[derive(Clone)]
843 struct SharedNext(Arc<Next>);
844 
845 impl Function for SharedNext {
call(&self, args: &HashMap<String, Value>) -> Result<Value>846     fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
847         self.0.call(args)
848     }
849 }
850 
851 lazy_static! {
852     static ref NEXT_GLOBAL: SharedNext = SharedNext(Arc::new(Next(AtomicUsize::new(1))));
853 }
854 
855 #[test]
stateful_global_fn()856 fn stateful_global_fn() {
857     fn make_tera() -> Tera {
858         let mut tera = Tera::default();
859         tera.add_raw_template(
860             "fn.html",
861             "<h1>{{ get_next() }}, {{ get_next_shared() }}, {{ get_next() }}...</h1>",
862         )
863         .unwrap();
864 
865         tera.register_function("get_next", Next(AtomicUsize::new(1)));
866         tera.register_function("get_next_shared", NEXT_GLOBAL.clone());
867         tera
868     }
869 
870     assert_eq!(
871         make_tera().render("fn.html", &Context::new()).unwrap(),
872         "<h1>1, 1, 2...</h1>".to_owned()
873     );
874     assert_eq!(
875         make_tera().render("fn.html", &Context::new()).unwrap(),
876         "<h1>1, 2, 2...</h1>".to_owned()
877     );
878 }
879 
880 // https://github.com/Keats/tera/issues/373
881 #[test]
split_on_context_value()882 fn split_on_context_value() {
883     let mut tera = Tera::default();
884     tera.add_raw_template("split.html", r#"{{ body | split(pat="\n") }}"#).unwrap();
885     let mut context = Context::new();
886     context.insert("body", "multi\nple\nlines");
887     let res = tera.render("split.html", &context);
888     assert_eq!(res.unwrap(), "[multi, ple, lines]");
889 }
890 
891 // https://github.com/Keats/tera/issues/422
892 #[test]
default_filter_works_in_condition()893 fn default_filter_works_in_condition() {
894     let mut tera = Tera::default();
895     tera.add_raw_template("test.html", r#"{% if frobnicate|default(value=True) %}here{% endif %}"#)
896         .unwrap();
897     let res = tera.render("test.html", &Context::new());
898     assert_eq!(res.unwrap(), "here");
899 }
900 
901 #[test]
safe_filter_works()902 fn safe_filter_works() {
903     struct Safe;
904     impl crate::Filter for Safe {
905         fn filter(&self, value: &Value, _args: &HashMap<String, Value>) -> Result<Value> {
906             Ok(Value::String(format!("<div>{}</div>", value.as_str().unwrap())))
907         }
908 
909         fn is_safe(&self) -> bool {
910             true
911         }
912     }
913 
914     let mut tera = Tera::default();
915     tera.register_filter("safe_filter", Safe);
916     tera.add_raw_template("test.html", r#"{{ "Hello" | safe_filter }}"#).unwrap();
917 
918     let res = tera.render("test.html", &Context::new());
919     assert_eq!(res.unwrap(), "<div>Hello</div>");
920 }
921 
922 #[test]
safe_function_works()923 fn safe_function_works() {
924     struct Safe;
925     impl crate::Function for Safe {
926         fn call(&self, _args: &HashMap<String, Value>) -> Result<Value> {
927             Ok(Value::String("<div>Hello</div>".to_owned()))
928         }
929 
930         fn is_safe(&self) -> bool {
931             true
932         }
933     }
934 
935     let mut tera = Tera::default();
936     tera.register_function("safe_function", Safe);
937     tera.add_raw_template("test.html", "{{ safe_function() }}").unwrap();
938 
939     let res = tera.render("test.html", &Context::new());
940     assert_eq!(res.unwrap(), "<div>Hello</div>");
941 }
942