|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require 'test_helper' |
| 4 | +require 'test_boolean_helper' |
| 5 | + |
| 6 | +class LogicalExpressionTest < Minitest::Test |
| 7 | + include Liquid |
| 8 | + |
| 9 | + def setup |
| 10 | + @ss = StringScanner.new("") |
| 11 | + @cache = {} |
| 12 | + end |
| 13 | + |
| 14 | + def test_logical_detection |
| 15 | + assert(Expression::LogicalExpression.logical?("foo and bar")) |
| 16 | + assert(Expression::LogicalExpression.logical?("foo or bar")) |
| 17 | + assert(Expression::LogicalExpression.logical?("true and false")) |
| 18 | + assert(Expression::LogicalExpression.logical?("1 or 0")) |
| 19 | + |
| 20 | + refute(Expression::LogicalExpression.logical?("foo")) |
| 21 | + refute(Expression::LogicalExpression.logical?("1 == 1")) |
| 22 | + refute(Expression::LogicalExpression.logical?("a contains b")) |
| 23 | + refute(Expression::LogicalExpression.logical?("not foo")) |
| 24 | + end |
| 25 | + |
| 26 | + def test_parenthesized_logical_detection |
| 27 | + assert(Expression::LogicalExpression.logical?("a and (b or c)")) |
| 28 | + assert(Expression::LogicalExpression.logical?("(a or b) and c")) |
| 29 | + end |
| 30 | + |
| 31 | + def test_boolean_operator_detection |
| 32 | + assert(Expression::LogicalExpression.boolean_operator?("and")) |
| 33 | + assert(Expression::LogicalExpression.boolean_operator?("or")) |
| 34 | + |
| 35 | + refute(Expression::LogicalExpression.boolean_operator?("not")) |
| 36 | + refute(Expression::LogicalExpression.boolean_operator?("==")) |
| 37 | + refute(Expression::LogicalExpression.boolean_operator?("contains")) |
| 38 | + refute(Expression::LogicalExpression.boolean_operator?("foo")) |
| 39 | + end |
| 40 | + |
| 41 | + def test_basic_parsing |
| 42 | + result = Expression::LogicalExpression.parse("true and false", @ss, @cache) |
| 43 | + assert_instance_of(Condition, result) |
| 44 | + |
| 45 | + result = Expression::LogicalExpression.parse("a or b", @ss, @cache) |
| 46 | + assert_instance_of(Condition, result) |
| 47 | + end |
| 48 | + |
| 49 | + def test_parsing_with_different_expressions |
| 50 | + # Test with simple variable expressions |
| 51 | + result = Expression::LogicalExpression.parse("var1 and var2", @ss, @cache) |
| 52 | + assert_instance_of(Condition, result) |
| 53 | + |
| 54 | + # Test with comparison expressions |
| 55 | + result = Expression::LogicalExpression.parse("a == 1 and b != 2", @ss, @cache) |
| 56 | + assert_instance_of(Condition, result) |
| 57 | + end |
| 58 | + |
| 59 | + def test_parsing_complex_expressions |
| 60 | + # Test with nested logical expressions |
| 61 | + result = Expression::LogicalExpression.parse("a and b or c", @ss, @cache) |
| 62 | + assert_instance_of(Condition, result) |
| 63 | + |
| 64 | + result = Expression::LogicalExpression.parse("a or b and c", @ss, @cache) |
| 65 | + assert_instance_of(Condition, result) |
| 66 | + end |
| 67 | + |
| 68 | + def test_parsing_parenthesized_expressions |
| 69 | + result = Expression::LogicalExpression.parse("(a and b) or c", @ss, @cache) |
| 70 | + assert_instance_of(Condition, result) |
| 71 | + |
| 72 | + result = Expression::LogicalExpression.parse("a and (b or c)", @ss, @cache) |
| 73 | + assert_instance_of(Condition, result) |
| 74 | + |
| 75 | + # Test with complex expressions |
| 76 | + result = Expression::LogicalExpression.parse("(a or b) and (c or d)", @ss, @cache) |
| 77 | + assert_instance_of(Condition, result) |
| 78 | + end |
| 79 | + |
| 80 | + def test_evaluation_of_parsed_expressions |
| 81 | + context = Liquid::Context.new( |
| 82 | + "a" => true, |
| 83 | + "b" => false, |
| 84 | + "c" => true, |
| 85 | + "d" => false, |
| 86 | + ) |
| 87 | + |
| 88 | + # Test simple logical expressions |
| 89 | + expr = Expression::LogicalExpression.parse("a and c", @ss, @cache) |
| 90 | + assert_equal(true, expr.evaluate(context)) |
| 91 | + |
| 92 | + expr = Expression::LogicalExpression.parse("a and b", @ss, @cache) |
| 93 | + assert_equal(false, expr.evaluate(context)) |
| 94 | + |
| 95 | + expr = Expression::LogicalExpression.parse("b or c", @ss, @cache) |
| 96 | + assert_equal(true, expr.evaluate(context)) |
| 97 | + |
| 98 | + expr = Expression::LogicalExpression.parse("b or d", @ss, @cache) |
| 99 | + assert_equal(false, expr.evaluate(context)) |
| 100 | + end |
| 101 | + |
| 102 | + def test_evaluation_of_complex_expressions |
| 103 | + context = Liquid::Context.new( |
| 104 | + "a" => true, |
| 105 | + "b" => false, |
| 106 | + "c" => true, |
| 107 | + "d" => false, |
| 108 | + ) |
| 109 | + |
| 110 | + # Test complex logical expressions |
| 111 | + expr = Expression::LogicalExpression.parse("a and b or c", @ss, @cache) |
| 112 | + assert_equal(true, expr.evaluate(context)) |
| 113 | + end |
| 114 | + |
| 115 | + def test_evaluation_of_parenthesized_expressions |
| 116 | + context = Liquid::Context.new( |
| 117 | + "a" => true, |
| 118 | + "b" => false, |
| 119 | + "c" => true, |
| 120 | + "d" => false, |
| 121 | + ) |
| 122 | + |
| 123 | + expr = Expression::LogicalExpression.parse("a and (b or d)", @ss, @cache) |
| 124 | + assert_equal(false, expr.evaluate(context)) |
| 125 | + |
| 126 | + expr = Expression::LogicalExpression.parse("(a or b) and (c or d)", @ss, @cache) |
| 127 | + assert_equal(true, expr.evaluate(context)) |
| 128 | + |
| 129 | + expr = Expression::LogicalExpression.parse("(a or b) and (b or d)", @ss, @cache) |
| 130 | + assert_equal(false, expr.evaluate(context)) |
| 131 | + end |
| 132 | + |
| 133 | + def test_precedence_rules |
| 134 | + context = Liquid::Context.new( |
| 135 | + "a" => true, |
| 136 | + "b" => false, |
| 137 | + "c" => true, |
| 138 | + ) |
| 139 | + |
| 140 | + # Test precedence rules (AND has higher precedence than OR) |
| 141 | + # This should be interpreted as: a and (b or c) |
| 142 | + expr1 = Expression::LogicalExpression.parse("a and b or c", @ss, @cache) |
| 143 | + assert_equal(true, expr1.evaluate(context)) |
| 144 | + |
| 145 | + # Change context to make the expressions evaluate differently |
| 146 | + context = Liquid::Context.new( |
| 147 | + "a" => false, |
| 148 | + "b" => false, |
| 149 | + "c" => true, |
| 150 | + ) |
| 151 | + |
| 152 | + # With these values, "a and (b or c)" would be false |
| 153 | + expr1 = Expression::LogicalExpression.parse("a and b or c", @ss, @cache) |
| 154 | + assert_equal(false, expr1.evaluate(context)) |
| 155 | + end |
| 156 | + |
| 157 | + def test_precedence_with_parentheses |
| 158 | + context = Liquid::Context.new( |
| 159 | + "a" => true, |
| 160 | + "b" => false, |
| 161 | + "c" => true, |
| 162 | + ) |
| 163 | + |
| 164 | + # This should be interpreted as: (a and b) or c |
| 165 | + expr2 = Expression::LogicalExpression.parse("(a and b) or c", @ss, @cache) |
| 166 | + assert_equal(true, expr2.evaluate(context)) |
| 167 | + |
| 168 | + # Change context to make the expressions evaluate differently |
| 169 | + context = Liquid::Context.new( |
| 170 | + "a" => false, |
| 171 | + "b" => false, |
| 172 | + "c" => true, |
| 173 | + ) |
| 174 | + |
| 175 | + # But "(a and b) or c" would be true |
| 176 | + expr2 = Expression::LogicalExpression.parse("(a and b) or c", @ss, @cache) |
| 177 | + assert_equal(true, expr2.evaluate(context)) |
| 178 | + end |
| 179 | + |
| 180 | + def test_integration_with_if_tag |
| 181 | + # Test that our expressions work properly in actual templates |
| 182 | + assert_template_result("true", "{% if true and true %}true{% else %}false{% endif %}") |
| 183 | + assert_template_result("false", "{% if true and false %}true{% else %}false{% endif %}") |
| 184 | + assert_template_result("true", "{% if false or true %}true{% else %}false{% endif %}") |
| 185 | + assert_template_result("false", "{% if false or false %}true{% else %}false{% endif %}") |
| 186 | + end |
| 187 | + |
| 188 | + def test_integration_with_parenthesized_if_tag |
| 189 | + # Test with parenthesized expressions |
| 190 | + assert_template_result("true", "{% if (true and false) or true %}true{% else %}false{% endif %}") |
| 191 | + assert_template_result("false", "{% if true and (false or false) %}true{% else %}false{% endif %}") |
| 192 | + assert_template_result("true", "{% if true and (false or true) %}true{% else %}false{% endif %}") |
| 193 | + end |
| 194 | + |
| 195 | + def test_integration_with_variables |
| 196 | + # Test with variables |
| 197 | + template = "{% if a and b %}true{% else %}false{% endif %}" |
| 198 | + assert_template_result("true", template, { "a" => true, "b" => true }) |
| 199 | + assert_template_result("false", template, { "a" => true, "b" => false }) |
| 200 | + |
| 201 | + template = "{% if a or b %}true{% else %}false{% endif %}" |
| 202 | + assert_template_result("true", template, { "a" => true, "b" => false }) |
| 203 | + assert_template_result("false", template, { "a" => false, "b" => false }) |
| 204 | + end |
| 205 | + |
| 206 | + def test_integration_with_parenthesized_variables |
| 207 | + # Test with parenthesized expressions |
| 208 | + template = "{% if (a and b) or c %}true{% else %}false{% endif %}" |
| 209 | + assert_template_result("true", template, { "a" => true, "b" => true, "c" => false }) |
| 210 | + assert_template_result("true", template, { "a" => false, "b" => false, "c" => true }) |
| 211 | + assert_template_result("false", template, { "a" => false, "b" => false, "c" => false }) |
| 212 | + |
| 213 | + template = "{% if a and (b or c) %}true{% else %}false{% endif %}" |
| 214 | + assert_template_result("true", template, { "a" => true, "b" => true, "c" => false }) |
| 215 | + assert_template_result("true", template, { "a" => true, "b" => false, "c" => true }) |
| 216 | + assert_template_result("false", template, { "a" => true, "b" => false, "c" => false }) |
| 217 | + assert_template_result("false", template, { "a" => false, "b" => true, "c" => true }) |
| 218 | + end |
| 219 | +end |
0 commit comments