loose_liquid_lib/stdlib/blocks/
if_block.rs

1use std::fmt;
2use std::io::Write;
3
4use liquid_core::error::ResultLiquidExt;
5use liquid_core::model::{ValueView, ValueViewCmp};
6use liquid_core::parser::BlockElement;
7use liquid_core::parser::TagToken;
8use liquid_core::Expression;
9use liquid_core::Language;
10use liquid_core::Renderable;
11use liquid_core::Runtime;
12use liquid_core::Template;
13use liquid_core::{BlockReflection, ParseBlock, TagBlock, TagTokenIter};
14use liquid_core::{Error, Result};
15
16#[derive(Copy, Clone, Debug, Default)]
17pub struct IfBlock;
18
19impl IfBlock {
20    pub fn new() -> Self {
21        Self::default()
22    }
23}
24
25impl BlockReflection for IfBlock {
26    fn start_tag(&self) -> &str {
27        "if"
28    }
29
30    fn end_tag(&self) -> &str {
31        "endif"
32    }
33
34    fn description(&self) -> &str {
35        ""
36    }
37}
38
39impl ParseBlock for IfBlock {
40    fn parse(
41        &self,
42        arguments: TagTokenIter<'_>,
43        mut tokens: TagBlock<'_, '_>,
44        options: &Language,
45    ) -> Result<Box<dyn Renderable>> {
46        let conditional = parse_if(arguments, &mut tokens, options)?;
47
48        tokens.assert_empty();
49        Ok(conditional)
50    }
51
52    fn reflection(&self) -> &dyn BlockReflection {
53        self
54    }
55}
56
57fn parse_if(
58    arguments: TagTokenIter<'_>,
59    tokens: &mut TagBlock<'_, '_>,
60    options: &Language,
61) -> Result<Box<dyn Renderable>> {
62    let condition = parse_condition(arguments)?;
63
64    let mut if_true = Vec::new();
65    let mut if_false = None;
66
67    while let Some(element) = tokens.next()? {
68        match element {
69            BlockElement::Tag(tag) => match tag.name() {
70                "else" => {
71                    if_false = Some(tokens.parse_all(options)?);
72                    break;
73                }
74                "elsif" => {
75                    if_false = Some(vec![parse_if(tag.into_tokens(), tokens, options)?]);
76                    break;
77                }
78                _ => if_true.push(tag.parse(tokens, options)?),
79            },
80            element => if_true.push(element.parse(tokens, options)?),
81        }
82    }
83
84    let if_true = Template::new(if_true);
85    let if_false = if_false.map(Template::new);
86
87    Ok(Box::new(Conditional {
88        condition,
89        mode: true,
90        if_true,
91        if_false,
92    }))
93}
94
95#[derive(Copy, Clone, Debug, Default)]
96pub struct UnlessBlock;
97
98impl UnlessBlock {
99    pub fn new() -> Self {
100        Self::default()
101    }
102}
103
104impl BlockReflection for UnlessBlock {
105    fn start_tag(&self) -> &str {
106        "unless"
107    }
108
109    fn end_tag(&self) -> &str {
110        "endunless"
111    }
112
113    fn description(&self) -> &str {
114        ""
115    }
116}
117
118impl ParseBlock for UnlessBlock {
119    fn parse(
120        &self,
121        arguments: TagTokenIter<'_>,
122        mut tokens: TagBlock<'_, '_>,
123        options: &Language,
124    ) -> Result<Box<dyn Renderable>> {
125        let condition = parse_condition(arguments)?;
126
127        let mut if_true = Vec::new();
128        let mut if_false = None;
129
130        while let Some(element) = tokens.next()? {
131            match element {
132                BlockElement::Tag(tag) => match tag.name() {
133                    "else" => {
134                        if_false = Some(tokens.parse_all(options)?);
135                        break;
136                    }
137                    _ => if_true.push(tag.parse(&mut tokens, options)?),
138                },
139                element => if_true.push(element.parse(&mut tokens, options)?),
140            }
141        }
142
143        let if_true = Template::new(if_true);
144        let if_false = if_false.map(Template::new);
145
146        tokens.assert_empty();
147        Ok(Box::new(Conditional {
148            condition,
149            mode: false,
150            if_true,
151            if_false,
152        }))
153    }
154
155    fn reflection(&self) -> &dyn BlockReflection {
156        self
157    }
158}
159
160#[derive(Debug)]
161struct Conditional {
162    condition: Condition,
163    mode: bool,
164    if_true: Template,
165    if_false: Option<Template>,
166}
167
168impl Conditional {
169    fn compare(&self, runtime: &dyn Runtime) -> Result<bool> {
170        let result = self.condition.evaluate(runtime)?;
171
172        Ok(result == self.mode)
173    }
174
175    fn trace(&self) -> String {
176        format!("{{% if {} %}}", self.condition)
177    }
178}
179
180impl Renderable for Conditional {
181    fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> {
182        let condition = self.compare(runtime).trace_with(|| self.trace().into())?;
183        if condition {
184            self.if_true
185                .render_to(writer, runtime)
186                .trace_with(|| self.trace().into())?;
187        } else if let Some(ref template) = self.if_false {
188            template
189                .render_to(writer, runtime)
190                .trace("{{% else %}}")
191                .trace_with(|| self.trace().into())?;
192        }
193
194        Ok(())
195    }
196}
197
198#[derive(Clone, Debug)]
199enum Condition {
200    Binary(BinaryCondition),
201    Existence(ExistenceCondition),
202    Conjunction(Box<Condition>, Box<Condition>),
203    Disjunction(Box<Condition>, Box<Condition>),
204}
205
206impl Condition {
207    pub fn evaluate(&self, runtime: &dyn Runtime) -> Result<bool> {
208        match *self {
209            Condition::Binary(ref c) => c.evaluate(runtime),
210            Condition::Existence(ref c) => c.evaluate(runtime),
211            Condition::Conjunction(ref left, ref right) => {
212                Ok(left.evaluate(runtime)? && right.evaluate(runtime)?)
213            }
214            Condition::Disjunction(ref left, ref right) => {
215                Ok(left.evaluate(runtime)? || right.evaluate(runtime)?)
216            }
217        }
218    }
219}
220
221impl fmt::Display for Condition {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        match *self {
224            Condition::Binary(ref c) => write!(f, "{}", c),
225            Condition::Existence(ref c) => write!(f, "{}", c),
226            Condition::Conjunction(ref left, ref right) => write!(f, "{} and {}", left, right),
227            Condition::Disjunction(ref left, ref right) => write!(f, "{} or {}", left, right),
228        }
229    }
230}
231
232#[derive(Clone, Debug)]
233struct BinaryCondition {
234    lh: Expression,
235    comparison: ComparisonOperator,
236    rh: Expression,
237}
238
239impl BinaryCondition {
240    pub fn evaluate(&self, runtime: &dyn Runtime) -> Result<bool> {
241        let a = self.lh.evaluate(runtime)?;
242        let ca = ValueViewCmp::new(a.as_view());
243        let b = self.rh.evaluate(runtime)?;
244        let cb = ValueViewCmp::new(b.as_view());
245
246        let result = match self.comparison {
247            ComparisonOperator::Equals => ca == cb,
248            ComparisonOperator::NotEquals => ca != cb,
249            ComparisonOperator::LessThan => ca < cb,
250            ComparisonOperator::GreaterThan => ca > cb,
251            ComparisonOperator::LessThanEquals => ca <= cb,
252            ComparisonOperator::GreaterThanEquals => ca >= cb,
253            ComparisonOperator::Contains => contains_check(a.as_view(), b.as_view())?,
254        };
255
256        Ok(result)
257    }
258}
259
260impl fmt::Display for BinaryCondition {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        write!(f, "{} {} {}", self.lh, self.comparison, self.rh)
263    }
264}
265
266fn contains_check(a: &dyn ValueView, b: &dyn ValueView) -> Result<bool> {
267    if let Some(a) = a.as_scalar() {
268        let b = b.to_kstr();
269        Ok(a.to_kstr().contains(b.as_str()))
270    } else if let Some(a) = a.as_object() {
271        let b = b.as_scalar();
272        let check = b
273            .map(|b| a.contains_key(b.to_kstr().as_str()))
274            .unwrap_or(false);
275        Ok(check)
276    } else if let Some(a) = a.as_array() {
277        for elem in a.values() {
278            if ValueViewCmp::new(elem) == ValueViewCmp::new(b) {
279                return Ok(true);
280            }
281        }
282        Ok(false)
283    } else {
284        Err(unexpected_value_error(
285            "string | array | object",
286            Some(a.type_name()),
287        ))
288    }
289}
290
291#[derive(Clone, Debug)]
292enum ComparisonOperator {
293    Equals,
294    NotEquals,
295    LessThan,
296    GreaterThan,
297    LessThanEquals,
298    GreaterThanEquals,
299    Contains,
300}
301
302impl fmt::Display for ComparisonOperator {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        let out = match *self {
305            ComparisonOperator::Equals => "==",
306            ComparisonOperator::NotEquals => "!=",
307            ComparisonOperator::LessThanEquals => "<=",
308            ComparisonOperator::GreaterThanEquals => ">=",
309            ComparisonOperator::LessThan => "<",
310            ComparisonOperator::GreaterThan => ">",
311            ComparisonOperator::Contains => "contains",
312        };
313        write!(f, "{}", out)
314    }
315}
316
317impl ComparisonOperator {
318    fn from_str(s: &str) -> ::std::result::Result<Self, ()> {
319        match s {
320            "==" => Ok(ComparisonOperator::Equals),
321            "!=" | "<>" => Ok(ComparisonOperator::NotEquals),
322            "<" => Ok(ComparisonOperator::LessThan),
323            ">" => Ok(ComparisonOperator::GreaterThan),
324            "<=" => Ok(ComparisonOperator::LessThanEquals),
325            ">=" => Ok(ComparisonOperator::GreaterThanEquals),
326            "contains" => Ok(ComparisonOperator::Contains),
327            _ => Err(()),
328        }
329    }
330}
331
332#[derive(Clone, Debug)]
333struct ExistenceCondition {
334    lh: Expression,
335}
336
337impl ExistenceCondition {
338    pub fn evaluate(&self, runtime: &dyn Runtime) -> Result<bool> {
339        let a = self.lh.try_evaluate(runtime);
340        let a = a.unwrap_or_default();
341        let is_truthy = a.query_state(liquid_core::model::State::Truthy);
342        Ok(is_truthy)
343    }
344}
345
346impl fmt::Display for ExistenceCondition {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        write!(f, "{}", self.lh)
349    }
350}
351
352struct PeekableTagTokenIter<'a> {
353    iter: TagTokenIter<'a>,
354    peeked: Option<Option<TagToken<'a>>>,
355}
356
357impl<'a> Iterator for PeekableTagTokenIter<'a> {
358    type Item = TagToken<'a>;
359
360    fn next(&mut self) -> Option<TagToken<'a>> {
361        match self.peeked.take() {
362            Some(v) => v,
363            None => self.iter.next(),
364        }
365    }
366}
367
368impl<'a> PeekableTagTokenIter<'a> {
369    pub fn expect_next(&mut self, error_msg: &str) -> Result<TagToken<'a>> {
370        self.next().ok_or_else(|| self.iter.raise_error(error_msg))
371    }
372
373    fn peek(&mut self) -> Option<&TagToken<'a>> {
374        if self.peeked.is_none() {
375            self.peeked = Some(self.iter.next());
376        }
377        match self.peeked {
378            Some(Some(ref value)) => Some(value),
379            Some(None) => None,
380            None => unreachable!(),
381        }
382    }
383}
384
385fn parse_atom_condition(arguments: &mut PeekableTagTokenIter<'_>) -> Result<Condition> {
386    let lh = arguments
387        .expect_next("Value expected.")?
388        .expect_value()
389        .into_result()?;
390    let cond = match arguments
391        .peek()
392        .map(TagToken::as_str)
393        .and_then(|op| ComparisonOperator::from_str(op).ok())
394    {
395        Some(op) => {
396            arguments.next();
397            let rh = arguments
398                .expect_next("Value expected.")?
399                .expect_value()
400                .into_result()?;
401            Condition::Binary(BinaryCondition {
402                lh,
403                comparison: op,
404                rh,
405            })
406        }
407        None => Condition::Existence(ExistenceCondition { lh }),
408    };
409
410    Ok(cond)
411}
412
413fn parse_conjunction_chain(arguments: &mut PeekableTagTokenIter<'_>) -> Result<Condition> {
414    let mut lh = parse_atom_condition(arguments)?;
415
416    while let Some("and") = arguments.peek().map(TagToken::as_str) {
417        arguments.next();
418        let rh = parse_atom_condition(arguments)?;
419        lh = Condition::Conjunction(Box::new(lh), Box::new(rh));
420    }
421
422    Ok(lh)
423}
424
425/// Common parsing for "if" and "unless" condition
426fn parse_condition(arguments: TagTokenIter<'_>) -> Result<Condition> {
427    let mut arguments = PeekableTagTokenIter {
428        iter: arguments,
429        peeked: None,
430    };
431    let mut lh = parse_conjunction_chain(&mut arguments)?;
432
433    while let Some(token) = arguments.next() {
434        token
435            .expect_str("or")
436            .into_result_custom_msg("\"and\" or \"or\" expected.")?;
437
438        let rh = parse_conjunction_chain(&mut arguments)?;
439        lh = Condition::Disjunction(Box::new(lh), Box::new(rh));
440    }
441
442    Ok(lh)
443}
444
445/// Format an error for an unexpected value.
446fn unexpected_value_error<S: ToString>(expected: &str, actual: Option<S>) -> Error {
447    let actual = actual.map(|x| x.to_string());
448    unexpected_value_error_string(expected, actual)
449}
450
451fn unexpected_value_error_string(expected: &str, actual: Option<String>) -> Error {
452    let actual = actual.unwrap_or_else(|| "nothing".to_owned());
453    Error::with_msg(format!("Expected {}, found `{}`", expected, actual))
454}
455
456#[cfg(test)]
457mod test {
458    use super::*;
459
460    use liquid_core::model::Object;
461    use liquid_core::model::Value;
462    use liquid_core::parser;
463    use liquid_core::runtime;
464    use liquid_core::runtime::RuntimeBuilder;
465
466    fn options() -> Language {
467        let mut options = Language::default();
468        options.blocks.register("if".to_string(), IfBlock.into());
469        options
470            .blocks
471            .register("unless".to_string(), UnlessBlock.into());
472        options
473    }
474
475    #[test]
476    fn number_comparison() {
477        let text = "{% if 6 < 7  %}if true{% endif %}";
478        let template = parser::parse(text, &options())
479            .map(runtime::Template::new)
480            .unwrap();
481
482        let runtime = RuntimeBuilder::new().build();
483        let output = template.render(&runtime).unwrap();
484        assert_eq!(output, "if true");
485
486        let text = "{% if 7 < 6  %}if true{% else %}if false{% endif %}";
487        let template = parser::parse(text, &options())
488            .map(runtime::Template::new)
489            .unwrap();
490
491        let runtime = RuntimeBuilder::new().build();
492        let output = template.render(&runtime).unwrap();
493        assert_eq!(output, "if false");
494    }
495
496    #[test]
497    fn string_comparison() {
498        let text = r#"{% if "one" == "one"  %}if true{% endif %}"#;
499        let template = parser::parse(text, &options())
500            .map(runtime::Template::new)
501            .unwrap();
502
503        let runtime = RuntimeBuilder::new().build();
504        let output = template.render(&runtime).unwrap();
505        assert_eq!(output, "if true");
506
507        let text = r#"{% if "one" == "two"  %}if true{% else %}if false{% endif %}"#;
508        let template = parser::parse(text, &options())
509            .map(runtime::Template::new)
510            .unwrap();
511
512        let runtime = RuntimeBuilder::new().build();
513        let output = template.render(&runtime).unwrap();
514        assert_eq!(output, "if false");
515    }
516
517    #[test]
518    fn implicit_comparison() {
519        let text = concat!(
520            "{% if truthy %}",
521            "yep",
522            "{% else %}",
523            "nope",
524            "{% endif %}"
525        );
526
527        let template = parser::parse(text, &options())
528            .map(runtime::Template::new)
529            .unwrap();
530
531        // Non-existence
532        let runtime = RuntimeBuilder::new().build();
533        let output = template.render(&runtime).unwrap();
534        assert_eq!(output, "nope");
535
536        // Explicit nil
537        let runtime = RuntimeBuilder::new().build();
538        runtime.set_global("truthy".into(), Value::Nil);
539        let output = template.render(&runtime).unwrap();
540        assert_eq!(output, "nope");
541
542        // false
543        let runtime = RuntimeBuilder::new().build();
544        runtime.set_global("truthy".into(), Value::scalar(false));
545        let output = template.render(&runtime).unwrap();
546        assert_eq!(output, "nope");
547
548        // true
549        let runtime = RuntimeBuilder::new().build();
550        runtime.set_global("truthy".into(), Value::scalar(true));
551        let output = template.render(&runtime).unwrap();
552        assert_eq!(output, "yep");
553    }
554
555    #[test]
556    fn unless() {
557        let text = concat!(
558            "{% unless some_value == 1 %}",
559            "unless body",
560            "{% endunless %}"
561        );
562
563        let template = parser::parse(text, &options())
564            .map(runtime::Template::new)
565            .unwrap();
566
567        let runtime = RuntimeBuilder::new().build();
568        runtime.set_global("some_value".into(), Value::scalar(1f64));
569        let output = template.render(&runtime).unwrap();
570        assert_eq!(output, "");
571
572        let runtime = RuntimeBuilder::new().build();
573        runtime.set_global("some_value".into(), Value::scalar(42f64));
574        let output = template.render(&runtime).unwrap();
575        assert_eq!(output, "unless body");
576    }
577
578    #[test]
579    fn nested_if_else() {
580        let text = concat!(
581            "{% if truthy %}",
582            "yep, ",
583            "{% if also_truthy %}",
584            "also truthy",
585            "{% else %}",
586            "not also truthy",
587            "{% endif %}",
588            "{% else %}",
589            "nope",
590            "{% endif %}"
591        );
592        let template = parser::parse(text, &options())
593            .map(runtime::Template::new)
594            .unwrap();
595
596        let runtime = RuntimeBuilder::new().build();
597        runtime.set_global("truthy".into(), Value::scalar(true));
598        runtime.set_global("also_truthy".into(), Value::scalar(false));
599        let output = template.render(&runtime).unwrap();
600        assert_eq!(output, "yep, not also truthy");
601    }
602
603    #[test]
604    fn multiple_elif_blocks() {
605        let text = concat!(
606            "{% if a == 1 %}",
607            "first",
608            "{% elsif a == 2 %}",
609            "second",
610            "{% elsif a == 3 %}",
611            "third",
612            "{% else %}",
613            "fourth",
614            "{% endif %}"
615        );
616
617        let template = parser::parse(text, &options())
618            .map(runtime::Template::new)
619            .unwrap();
620
621        let runtime = RuntimeBuilder::new().build();
622        runtime.set_global("a".into(), Value::scalar(1f64));
623        let output = template.render(&runtime).unwrap();
624        assert_eq!(output, "first");
625
626        let runtime = RuntimeBuilder::new().build();
627        runtime.set_global("a".into(), Value::scalar(2f64));
628        let output = template.render(&runtime).unwrap();
629        assert_eq!(output, "second");
630
631        let runtime = RuntimeBuilder::new().build();
632        runtime.set_global("a".into(), Value::scalar(3f64));
633        let output = template.render(&runtime).unwrap();
634        assert_eq!(output, "third");
635
636        let runtime = RuntimeBuilder::new().build();
637        runtime.set_global("a".into(), Value::scalar("else"));
638        let output = template.render(&runtime).unwrap();
639        assert_eq!(output, "fourth");
640    }
641
642    #[test]
643    fn string_contains_with_literals() {
644        let text = "{% if \"Star Wars\" contains \"Star\" %}if true{% endif %}";
645        let template = parser::parse(text, &options())
646            .map(runtime::Template::new)
647            .unwrap();
648
649        let runtime = RuntimeBuilder::new().build();
650        let output = template.render(&runtime).unwrap();
651        assert_eq!(output, "if true");
652
653        let text = "{% if \"Star Wars\" contains \"Alf\"  %}if true{% else %}if false{% endif %}";
654        let template = parser::parse(text, &options())
655            .map(runtime::Template::new)
656            .unwrap();
657
658        let runtime = RuntimeBuilder::new().build();
659        let output = template.render(&runtime).unwrap();
660        assert_eq!(output, "if false");
661    }
662
663    #[test]
664    fn string_contains_with_variables() {
665        let text = "{% if movie contains \"Star\"  %}if true{% endif %}";
666        let template = parser::parse(text, &options())
667            .map(runtime::Template::new)
668            .unwrap();
669
670        let runtime = RuntimeBuilder::new().build();
671        runtime.set_global("movie".into(), Value::scalar("Star Wars"));
672        let output = template.render(&runtime).unwrap();
673        assert_eq!(output, "if true");
674
675        let text = "{% if movie contains \"Star\"  %}if true{% else %}if false{% endif %}";
676        let template = parser::parse(text, &options())
677            .map(runtime::Template::new)
678            .unwrap();
679
680        let runtime = RuntimeBuilder::new().build();
681        runtime.set_global("movie".into(), Value::scalar("Batman"));
682        let output = template.render(&runtime).unwrap();
683        assert_eq!(output, "if false");
684    }
685
686    #[test]
687    fn contains_with_object_and_key() {
688        let text = "{% if movies contains \"Star Wars\" %}if true{% endif %}";
689        let template = parser::parse(text, &options())
690            .map(runtime::Template::new)
691            .unwrap();
692
693        let runtime = RuntimeBuilder::new().build();
694        let mut obj = Object::new();
695        obj.insert("Star Wars".into(), Value::scalar("1977"));
696        runtime.set_global("movies".into(), Value::Object(obj));
697        let output = template.render(&runtime).unwrap();
698        assert_eq!(output, "if true");
699    }
700
701    #[test]
702    fn contains_with_object_and_missing_key() {
703        let text = "{% if movies contains \"Star Wars\" %}if true{% else %}if false{% endif %}";
704        let template = parser::parse(text, &options())
705            .map(runtime::Template::new)
706            .unwrap();
707
708        let runtime = RuntimeBuilder::new().build();
709        let obj = Object::new();
710        runtime.set_global("movies".into(), Value::Object(obj));
711        let output = template.render(&runtime).unwrap();
712        assert_eq!(output, "if false");
713    }
714
715    #[test]
716    fn contains_with_array_and_match() {
717        let text = "{% if movies contains \"Star Wars\" %}if true{% endif %}";
718        let template = parser::parse(text, &options())
719            .map(runtime::Template::new)
720            .unwrap();
721
722        let runtime = RuntimeBuilder::new().build();
723        let arr = vec![
724            Value::scalar("Star Wars"),
725            Value::scalar("Star Trek"),
726            Value::scalar("Alien"),
727        ];
728        runtime.set_global("movies".into(), Value::Array(arr));
729        let output = template.render(&runtime).unwrap();
730        assert_eq!(output, "if true");
731    }
732
733    #[test]
734    fn contains_with_array_and_no_match() {
735        let text = "{% if movies contains \"Star Wars\" %}if true{% else %}if false{% endif %}";
736        let template = parser::parse(text, &options())
737            .map(runtime::Template::new)
738            .unwrap();
739
740        let runtime = RuntimeBuilder::new().build();
741        let arr = vec![Value::scalar("Alien")];
742        runtime.set_global("movies".into(), Value::Array(arr));
743        let output = template.render(&runtime).unwrap();
744        assert_eq!(output, "if false");
745    }
746
747    #[test]
748    fn multiple_conditions_and() {
749        let text = "{% if 1 == 1 and 2 == 2 %}if true{% else %}if false{% endif %}";
750        let template = parser::parse(text, &options())
751            .map(runtime::Template::new)
752            .unwrap();
753
754        let runtime = RuntimeBuilder::new().build();
755        let output = template.render(&runtime).unwrap();
756        assert_eq!(output, "if true");
757
758        let text = "{% if 1 == 1 and 2 != 2 %}if true{% else %}if false{% endif %}";
759        let template = parser::parse(text, &options())
760            .map(runtime::Template::new)
761            .unwrap();
762
763        let runtime = RuntimeBuilder::new().build();
764        let output = template.render(&runtime).unwrap();
765        assert_eq!(output, "if false");
766    }
767
768    #[test]
769    fn multiple_conditions_or() {
770        let text = "{% if 1 == 1 or 2 != 2 %}if true{% else %}if false{% endif %}";
771        let template = parser::parse(text, &options())
772            .map(runtime::Template::new)
773            .unwrap();
774
775        let runtime = RuntimeBuilder::new().build();
776        let output = template.render(&runtime).unwrap();
777        assert_eq!(output, "if true");
778
779        let text = "{% if 1 != 1 or 2 != 2 %}if true{% else %}if false{% endif %}";
780        let template = parser::parse(text, &options())
781            .map(runtime::Template::new)
782            .unwrap();
783
784        let runtime = RuntimeBuilder::new().build();
785        let output = template.render(&runtime).unwrap();
786        assert_eq!(output, "if false");
787    }
788
789    #[test]
790    fn multiple_conditions_and_or() {
791        let text = "{% if 1 == 1 or 2 == 2 and 3 != 3 %}if true{% else %}if false{% endif %}";
792        let template = parser::parse(text, &options())
793            .map(runtime::Template::new)
794            .unwrap();
795
796        let runtime = RuntimeBuilder::new().build();
797        let output = template.render(&runtime).unwrap();
798        assert_eq!(output, "if true");
799    }
800}