liquid 4.0.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +33 -0
- data/README.md +6 -0
- data/lib/liquid.rb +17 -5
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +164 -54
- data/lib/liquid/condition.rb +39 -18
- data/lib/liquid/context.rb +106 -51
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -18
- data/lib/liquid/expression.rb +29 -34
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +11 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +30 -23
- data/lib/liquid/locales/en.yml +3 -1
- data/lib/liquid/parse_context.rb +16 -4
- data/lib/liquid/parse_tree_visitor.rb +2 -2
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/range_lookup.rb +5 -3
- data/lib/liquid/register.rb +6 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +63 -44
- data/lib/liquid/static_registers.rb +44 -0
- data/lib/liquid/strainer_factory.rb +36 -0
- data/lib/liquid/strainer_template.rb +53 -0
- data/lib/liquid/tablerowloop_drop.rb +6 -4
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tags/assign.rb +24 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +33 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +25 -14
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +26 -0
- data/lib/liquid/tags/for.rb +68 -44
- data/lib/liquid/tags/if.rb +35 -23
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +34 -47
- data/lib/liquid/tags/increment.rb +7 -3
- data/lib/liquid/tags/raw.rb +14 -11
- data/lib/liquid/tags/render.rb +84 -0
- data/lib/liquid/tags/table_row.rb +23 -19
- data/lib/liquid/tags/unless.rb +15 -15
- data/lib/liquid/template.rb +55 -71
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +17 -9
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +5 -3
- data/lib/liquid/variable.rb +46 -41
- data/lib/liquid/variable_lookup.rb +8 -6
- data/lib/liquid/version.rb +2 -1
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +47 -1
- data/test/integration/capture_test.rb +18 -10
- data/test/integration/context_test.rb +608 -5
- data/test/integration/document_test.rb +4 -2
- data/test/integration/drop_test.rb +67 -83
- data/test/integration/error_handling_test.rb +73 -61
- data/test/integration/expression_test.rb +46 -0
- data/test/integration/filter_test.rb +53 -42
- data/test/integration/hash_ordering_test.rb +5 -3
- data/test/integration/output_test.rb +26 -24
- data/test/integration/parsing_quirks_test.rb +19 -7
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +30 -21
- data/test/integration/standard_filter_test.rb +339 -281
- data/test/integration/tag/disableable_test.rb +59 -0
- data/test/integration/tag_test.rb +45 -0
- data/test/integration/tags/break_tag_test.rb +4 -2
- data/test/integration/tags/continue_tag_test.rb +4 -2
- data/test/integration/tags/echo_test.rb +13 -0
- data/test/integration/tags/for_tag_test.rb +107 -51
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +70 -54
- data/test/integration/tags/increment_tag_test.rb +4 -2
- data/test/integration/tags/liquid_tag_test.rb +116 -0
- data/test/integration/tags/raw_tag_test.rb +14 -11
- data/test/integration/tags/render_tag_test.rb +213 -0
- data/test/integration/tags/standard_tag_test.rb +38 -31
- data/test/integration/tags/statements_test.rb +23 -21
- data/test/integration/tags/table_row_test.rb +2 -0
- data/test/integration/tags/unless_else_tag_test.rb +4 -2
- data/test/integration/template_test.rb +118 -124
- data/test/integration/trim_mode_test.rb +78 -44
- data/test/integration/variable_test.rb +43 -32
- data/test/test_helper.rb +75 -22
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +79 -77
- data/test/unit/file_system_unit_test.rb +6 -4
- data/test/unit/i18n_unit_test.rb +7 -5
- data/test/unit/lexer_unit_test.rb +11 -9
- data/test/{integration → unit}/parse_tree_visitor_test.rb +2 -2
- data/test/unit/parser_unit_test.rb +37 -35
- data/test/unit/partial_cache_unit_test.rb +128 -0
- data/test/unit/regexp_unit_test.rb +17 -15
- data/test/unit/static_registers_unit_test.rb +156 -0
- data/test/unit/strainer_factory_unit_test.rb +100 -0
- data/test/unit/strainer_template_unit_test.rb +82 -0
- data/test/unit/tag_unit_test.rb +5 -3
- data/test/unit/tags/case_tag_unit_test.rb +3 -1
- data/test/unit/tags/for_tag_unit_test.rb +4 -2
- data/test/unit/tags/if_tag_unit_test.rb +3 -1
- data/test/unit/template_factory_unit_test.rb +12 -0
- data/test/unit/template_unit_test.rb +19 -10
- data/test/unit/tokenizer_unit_test.rb +19 -17
- data/test/unit/variable_unit_test.rb +51 -49
- metadata +73 -47
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/context_unit_test.rb +0 -489
- data/test/unit/strainer_unit_test.rb +0 -164
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'test_helper'
         | 
| 2 4 |  | 
| 3 5 | 
             
            class DocumentTest < Minitest::Test
         | 
| @@ -7,13 +9,13 @@ class DocumentTest < Minitest::Test | |
| 7 9 | 
             
                exc = assert_raises(SyntaxError) do
         | 
| 8 10 | 
             
                  Template.parse("{% else %}")
         | 
| 9 11 | 
             
                end
         | 
| 10 | 
            -
                assert_equal | 
| 12 | 
            +
                assert_equal(exc.message, "Liquid syntax error: Unexpected outer 'else' tag")
         | 
| 11 13 | 
             
              end
         | 
| 12 14 |  | 
| 13 15 | 
             
              def test_unknown_tag
         | 
| 14 16 | 
             
                exc = assert_raises(SyntaxError) do
         | 
| 15 17 | 
             
                  Template.parse("{% foo %}")
         | 
| 16 18 | 
             
                end
         | 
| 17 | 
            -
                assert_equal | 
| 19 | 
            +
                assert_equal(exc.message, "Liquid syntax error: Unknown tag 'foo'")
         | 
| 18 20 | 
             
              end
         | 
| 19 21 | 
             
            end
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'test_helper'
         | 
| 2 4 |  | 
| 3 5 | 
             
            class ContextDrop < Liquid::Drop
         | 
| @@ -31,7 +33,7 @@ class ProductDrop < Liquid::Drop | |
| 31 33 |  | 
| 32 34 | 
             
              class CatchallDrop < Liquid::Drop
         | 
| 33 35 | 
             
                def liquid_method_missing(method)
         | 
| 34 | 
            -
                   | 
| 36 | 
            +
                  "catchall_method: #{method}"
         | 
| 35 37 | 
             
                end
         | 
| 36 38 | 
             
              end
         | 
| 37 39 |  | 
| @@ -47,10 +49,6 @@ class ProductDrop < Liquid::Drop | |
| 47 49 | 
             
                ContextDrop.new
         | 
| 48 50 | 
             
              end
         | 
| 49 51 |  | 
| 50 | 
            -
              def user_input
         | 
| 51 | 
            -
                "foo".taint
         | 
| 52 | 
            -
              end
         | 
| 53 | 
            -
             | 
| 54 52 | 
             
              protected
         | 
| 55 53 |  | 
| 56 54 | 
             
              def callmenot
         | 
| @@ -109,165 +107,151 @@ class DropsTest < Minitest::Test | |
| 109 107 |  | 
| 110 108 | 
             
              def test_product_drop
         | 
| 111 109 | 
             
                tpl = Liquid::Template.parse('  ')
         | 
| 112 | 
            -
                assert_equal | 
| 113 | 
            -
              end
         | 
| 114 | 
            -
             | 
| 115 | 
            -
              def test_rendering_raises_on_tainted_attr
         | 
| 116 | 
            -
                with_taint_mode(:error) do
         | 
| 117 | 
            -
                  tpl = Liquid::Template.parse('{{ product.user_input }}')
         | 
| 118 | 
            -
                  assert_raises TaintedError do
         | 
| 119 | 
            -
                    tpl.render!('product' => ProductDrop.new)
         | 
| 120 | 
            -
                  end
         | 
| 121 | 
            -
                end
         | 
| 122 | 
            -
              end
         | 
| 123 | 
            -
             | 
| 124 | 
            -
              def test_rendering_warns_on_tainted_attr
         | 
| 125 | 
            -
                with_taint_mode(:warn) do
         | 
| 126 | 
            -
                  tpl = Liquid::Template.parse('{{ product.user_input }}')
         | 
| 127 | 
            -
                  context = Context.new('product' => ProductDrop.new)
         | 
| 128 | 
            -
                  tpl.render!(context)
         | 
| 129 | 
            -
                  assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
         | 
| 130 | 
            -
                  assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
         | 
| 131 | 
            -
                end
         | 
| 132 | 
            -
              end
         | 
| 133 | 
            -
             | 
| 134 | 
            -
              def test_rendering_doesnt_raise_on_escaped_tainted_attr
         | 
| 135 | 
            -
                with_taint_mode(:error) do
         | 
| 136 | 
            -
                  tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
         | 
| 137 | 
            -
                  tpl.render!('product' => ProductDrop.new)
         | 
| 138 | 
            -
                end
         | 
| 110 | 
            +
                assert_equal('  ', tpl.render!('product' => ProductDrop.new))
         | 
| 139 111 | 
             
              end
         | 
| 140 112 |  | 
| 141 113 | 
             
              def test_drop_does_only_respond_to_whitelisted_methods
         | 
| 142 | 
            -
                assert_equal | 
| 143 | 
            -
                assert_equal | 
| 144 | 
            -
                assert_equal | 
| 145 | 
            -
                assert_equal | 
| 146 | 
            -
                assert_equal | 
| 147 | 
            -
                assert_equal | 
| 114 | 
            +
                assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new))
         | 
| 115 | 
            +
                assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new))
         | 
| 116 | 
            +
                assert_equal("", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new))
         | 
| 117 | 
            +
                assert_equal("", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new))
         | 
| 118 | 
            +
                assert_equal("", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new))
         | 
| 119 | 
            +
                assert_equal("", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new))
         | 
| 148 120 | 
             
              end
         | 
| 149 121 |  | 
| 150 122 | 
             
              def test_drops_respond_to_to_liquid
         | 
| 151 | 
            -
                assert_equal | 
| 152 | 
            -
                assert_equal | 
| 123 | 
            +
                assert_equal("text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new))
         | 
| 124 | 
            +
                assert_equal("text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new))
         | 
| 153 125 | 
             
              end
         | 
| 154 126 |  | 
| 155 127 | 
             
              def test_text_drop
         | 
| 156 128 | 
             
                output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
         | 
| 157 | 
            -
                assert_equal | 
| 129 | 
            +
                assert_equal(' text1 ', output)
         | 
| 158 130 | 
             
              end
         | 
| 159 131 |  | 
| 160 132 | 
             
              def test_catchall_unknown_method
         | 
| 161 133 | 
             
                output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
         | 
| 162 | 
            -
                assert_equal | 
| 134 | 
            +
                assert_equal(' catchall_method: unknown ', output)
         | 
| 163 135 | 
             
              end
         | 
| 164 136 |  | 
| 165 137 | 
             
              def test_catchall_integer_argument_drop
         | 
| 166 138 | 
             
                output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
         | 
| 167 | 
            -
                assert_equal | 
| 139 | 
            +
                assert_equal(' catchall_method: 8 ', output)
         | 
| 168 140 | 
             
              end
         | 
| 169 141 |  | 
| 170 142 | 
             
              def test_text_array_drop
         | 
| 171 143 | 
             
                output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
         | 
| 172 | 
            -
                assert_equal | 
| 144 | 
            +
                assert_equal(' text1  text2 ', output)
         | 
| 173 145 | 
             
              end
         | 
| 174 146 |  | 
| 175 147 | 
             
              def test_context_drop
         | 
| 176 148 | 
             
                output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
         | 
| 177 | 
            -
                assert_equal | 
| 149 | 
            +
                assert_equal(' carrot ', output)
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              def test_context_drop_array_with_map
         | 
| 153 | 
            +
                output = Liquid::Template.parse(' {{ contexts | map: "bar" }} ').render!('contexts' => [ContextDrop.new, ContextDrop.new], 'bar' => "carrot")
         | 
| 154 | 
            +
                assert_equal(' carrotcarrot ', output)
         | 
| 178 155 | 
             
              end
         | 
| 179 156 |  | 
| 180 157 | 
             
              def test_nested_context_drop
         | 
| 181 158 | 
             
                output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
         | 
| 182 | 
            -
                assert_equal | 
| 159 | 
            +
                assert_equal(' monkey ', output)
         | 
| 183 160 | 
             
              end
         | 
| 184 161 |  | 
| 185 162 | 
             
              def test_protected
         | 
| 186 163 | 
             
                output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
         | 
| 187 | 
            -
                assert_equal | 
| 164 | 
            +
                assert_equal('  ', output)
         | 
| 188 165 | 
             
              end
         | 
| 189 166 |  | 
| 190 167 | 
             
              def test_object_methods_not_allowed
         | 
| 191 168 | 
             
                [:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
         | 
| 192 169 | 
             
                  output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
         | 
| 193 | 
            -
                  assert_equal | 
| 170 | 
            +
                  assert_equal('  ', output)
         | 
| 194 171 | 
             
                end
         | 
| 195 172 | 
             
              end
         | 
| 196 173 |  | 
| 197 174 | 
             
              def test_scope
         | 
| 198 | 
            -
                assert_equal | 
| 199 | 
            -
                assert_equal | 
| 200 | 
            -
                assert_equal | 
| 175 | 
            +
                assert_equal('1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new))
         | 
| 176 | 
            +
                assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
         | 
| 177 | 
            +
                assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
         | 
| 201 178 | 
             
              end
         | 
| 202 179 |  | 
| 203 180 | 
             
              def test_scope_though_proc
         | 
| 204 | 
            -
                assert_equal | 
| 205 | 
            -
                assert_equal | 
| 206 | 
            -
                assert_equal | 
| 181 | 
            +
                assert_equal('1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }))
         | 
| 182 | 
            +
                assert_equal('2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))
         | 
| 183 | 
            +
                assert_equal('3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc { |c| c['context.scopes'] }, 'dummy' => [1]))
         | 
| 207 184 | 
             
              end
         | 
| 208 185 |  | 
| 209 186 | 
             
              def test_scope_with_assigns
         | 
| 210 | 
            -
                assert_equal | 
| 211 | 
            -
                assert_equal | 
| 212 | 
            -
                assert_equal | 
| 213 | 
            -
                assert_equal | 
| 187 | 
            +
                assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new))
         | 
| 188 | 
            +
                assert_equal('variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1]))
         | 
| 189 | 
            +
                assert_equal('test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new))
         | 
| 190 | 
            +
                assert_equal('test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new))
         | 
| 214 191 | 
             
              end
         | 
| 215 192 |  | 
| 216 193 | 
             
              def test_scope_from_tags
         | 
| 217 | 
            -
                assert_equal | 
| 218 | 
            -
                assert_equal | 
| 219 | 
            -
                assert_equal | 
| 194 | 
            +
                assert_equal('1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
         | 
| 195 | 
            +
                assert_equal('12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
         | 
| 196 | 
            +
                assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1]))
         | 
| 220 197 | 
             
              end
         | 
| 221 198 |  | 
| 222 199 | 
             
              def test_access_context_from_drop
         | 
| 223 | 
            -
                assert_equal | 
| 200 | 
            +
                assert_equal('123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3]))
         | 
| 224 201 | 
             
              end
         | 
| 225 202 |  | 
| 226 203 | 
             
              def test_enumerable_drop
         | 
| 227 | 
            -
                assert_equal | 
| 204 | 
            +
                assert_equal('123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new))
         | 
| 228 205 | 
             
              end
         | 
| 229 206 |  | 
| 230 207 | 
             
              def test_enumerable_drop_size
         | 
| 231 | 
            -
                assert_equal | 
| 208 | 
            +
                assert_equal('3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new))
         | 
| 232 209 | 
             
              end
         | 
| 233 210 |  | 
| 234 211 | 
             
              def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
         | 
| 235 212 | 
             
                ["select", "each", "map", "cycle"].each do |method|
         | 
| 236 | 
            -
                  assert_equal | 
| 237 | 
            -
                  assert_equal | 
| 238 | 
            -
                  assert_equal | 
| 239 | 
            -
                  assert_equal | 
| 213 | 
            +
                  assert_equal(method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
         | 
| 214 | 
            +
                  assert_equal(method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
         | 
| 215 | 
            +
                  assert_equal(method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
         | 
| 216 | 
            +
                  assert_equal(method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
         | 
| 240 217 | 
             
                end
         | 
| 241 218 | 
             
              end
         | 
| 242 219 |  | 
| 243 220 | 
             
              def test_some_enumerable_methods_still_get_invoked
         | 
| 244 | 
            -
                [ | 
| 245 | 
            -
                  assert_equal | 
| 246 | 
            -
                  assert_equal | 
| 247 | 
            -
                  assert_equal | 
| 248 | 
            -
                  assert_equal | 
| 221 | 
            +
                [:count, :max].each do |method|
         | 
| 222 | 
            +
                  assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
         | 
| 223 | 
            +
                  assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
         | 
| 224 | 
            +
                  assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
         | 
| 225 | 
            +
                  assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
         | 
| 249 226 | 
             
                end
         | 
| 250 227 |  | 
| 251 | 
            -
                assert_equal | 
| 228 | 
            +
                assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
         | 
| 252 229 |  | 
| 253 | 
            -
                [ | 
| 254 | 
            -
                  assert_equal | 
| 255 | 
            -
                  assert_equal | 
| 256 | 
            -
                  assert_equal | 
| 257 | 
            -
                  assert_equal | 
| 230 | 
            +
                [:min, :first].each do |method|
         | 
| 231 | 
            +
                  assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
         | 
| 232 | 
            +
                  assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
         | 
| 233 | 
            +
                  assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
         | 
| 234 | 
            +
                  assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
         | 
| 258 235 | 
             
                end
         | 
| 259 236 | 
             
              end
         | 
| 260 237 |  | 
| 261 238 | 
             
              def test_empty_string_value_access
         | 
| 262 | 
            -
                assert_equal | 
| 239 | 
            +
                assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => ''))
         | 
| 263 240 | 
             
              end
         | 
| 264 241 |  | 
| 265 242 | 
             
              def test_nil_value_access
         | 
| 266 | 
            -
                assert_equal | 
| 243 | 
            +
                assert_equal('', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil))
         | 
| 267 244 | 
             
              end
         | 
| 268 245 |  | 
| 269 246 | 
             
              def test_default_to_s_on_drops
         | 
| 270 | 
            -
                assert_equal | 
| 271 | 
            -
                assert_equal | 
| 247 | 
            +
                assert_equal('ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new))
         | 
| 248 | 
            +
                assert_equal('EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new))
         | 
| 249 | 
            +
              end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
              def test_invokable_methods
         | 
| 252 | 
            +
                assert_equal(%w(to_liquid catchall context texts).to_set, ProductDrop.invokable_methods)
         | 
| 253 | 
            +
                assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)
         | 
| 254 | 
            +
                assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)
         | 
| 255 | 
            +
                assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)
         | 
| 272 256 | 
             
              end
         | 
| 273 257 | 
             
            end # DropsTest
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'test_helper'
         | 
| 2 4 |  | 
| 3 5 | 
             
            class ErrorHandlingTest < Minitest::Test
         | 
| @@ -33,31 +35,31 @@ class ErrorHandlingTest < Minitest::Test | |
| 33 35 | 
             
                TEXT
         | 
| 34 36 |  | 
| 35 37 | 
             
                output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)
         | 
| 36 | 
            -
                assert_equal | 
| 38 | 
            +
                assert_equal(expected, output)
         | 
| 37 39 | 
             
              end
         | 
| 38 40 |  | 
| 39 41 | 
             
              def test_standard_error
         | 
| 40 42 | 
             
                template = Liquid::Template.parse(' {{ errors.standard_error }} ')
         | 
| 41 | 
            -
                assert_equal | 
| 43 | 
            +
                assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))
         | 
| 42 44 |  | 
| 43 | 
            -
                assert_equal | 
| 44 | 
            -
                assert_equal | 
| 45 | 
            +
                assert_equal(1, template.errors.size)
         | 
| 46 | 
            +
                assert_equal(StandardError, template.errors.first.class)
         | 
| 45 47 | 
             
              end
         | 
| 46 48 |  | 
| 47 49 | 
             
              def test_syntax
         | 
| 48 50 | 
             
                template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
         | 
| 49 | 
            -
                assert_equal | 
| 51 | 
            +
                assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))
         | 
| 50 52 |  | 
| 51 | 
            -
                assert_equal | 
| 52 | 
            -
                assert_equal | 
| 53 | 
            +
                assert_equal(1, template.errors.size)
         | 
| 54 | 
            +
                assert_equal(SyntaxError, template.errors.first.class)
         | 
| 53 55 | 
             
              end
         | 
| 54 56 |  | 
| 55 57 | 
             
              def test_argument
         | 
| 56 58 | 
             
                template = Liquid::Template.parse(' {{ errors.argument_error }} ')
         | 
| 57 | 
            -
                assert_equal | 
| 59 | 
            +
                assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))
         | 
| 58 60 |  | 
| 59 | 
            -
                assert_equal | 
| 60 | 
            -
                assert_equal | 
| 61 | 
            +
                assert_equal(1, template.errors.size)
         | 
| 62 | 
            +
                assert_equal(ArgumentError, template.errors.first.class)
         | 
| 61 63 | 
             
              end
         | 
| 62 64 |  | 
| 63 65 | 
             
              def test_missing_endtag_parse_time_error
         | 
| @@ -76,22 +78,21 @@ class ErrorHandlingTest < Minitest::Test | |
| 76 78 |  | 
| 77 79 | 
             
              def test_lax_unrecognized_operator
         | 
| 78 80 | 
             
                template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
         | 
| 79 | 
            -
                assert_equal | 
| 80 | 
            -
                assert_equal | 
| 81 | 
            -
                assert_equal | 
| 81 | 
            +
                assert_equal(' Liquid error: Unknown operator =! ', template.render)
         | 
| 82 | 
            +
                assert_equal(1, template.errors.size)
         | 
| 83 | 
            +
                assert_equal(Liquid::ArgumentError, template.errors.first.class)
         | 
| 82 84 | 
             
              end
         | 
| 83 85 |  | 
| 84 86 | 
             
              def test_with_line_numbers_adds_numbers_to_parser_errors
         | 
| 85 87 | 
             
                err = assert_raises(SyntaxError) do
         | 
| 86 | 
            -
                  Liquid::Template.parse( | 
| 88 | 
            +
                  Liquid::Template.parse('
         | 
| 87 89 | 
             
                      foobar
         | 
| 88 90 |  | 
| 89 91 | 
             
                      {% "cat" | foobar %}
         | 
| 90 92 |  | 
| 91 93 | 
             
                      bla
         | 
| 92 | 
            -
                     | 
| 93 | 
            -
                    line_numbers: true
         | 
| 94 | 
            -
                  )
         | 
| 94 | 
            +
                    ',
         | 
| 95 | 
            +
                    line_numbers: true)
         | 
| 95 96 | 
             
                end
         | 
| 96 97 |  | 
| 97 98 | 
             
                assert_match(/Liquid syntax error \(line 4\)/, err.message)
         | 
| @@ -99,15 +100,14 @@ class ErrorHandlingTest < Minitest::Test | |
| 99 100 |  | 
| 100 101 | 
             
              def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
         | 
| 101 102 | 
             
                err = assert_raises(SyntaxError) do
         | 
| 102 | 
            -
                  Liquid::Template.parse( | 
| 103 | 
            +
                  Liquid::Template.parse('
         | 
| 103 104 | 
             
                      foobar
         | 
| 104 105 |  | 
| 105 106 | 
             
                      {%- "cat" | foobar -%}
         | 
| 106 107 |  | 
| 107 108 | 
             
                      bla
         | 
| 108 | 
            -
                     | 
| 109 | 
            -
                    line_numbers: true
         | 
| 110 | 
            -
                  )
         | 
| 109 | 
            +
                    ',
         | 
| 110 | 
            +
                    line_numbers: true)
         | 
| 111 111 | 
             
                end
         | 
| 112 112 |  | 
| 113 113 | 
             
                assert_match(/Liquid syntax error \(line 4\)/, err.message)
         | 
| @@ -122,11 +122,10 @@ class ErrorHandlingTest < Minitest::Test | |
| 122 122 | 
             
                    bla
         | 
| 123 123 | 
             
                        ',
         | 
| 124 124 | 
             
                  error_mode: :warn,
         | 
| 125 | 
            -
                  line_numbers: true
         | 
| 126 | 
            -
                )
         | 
| 125 | 
            +
                  line_numbers: true)
         | 
| 127 126 |  | 
| 128 | 
            -
                assert_equal | 
| 129 | 
            -
                  template.warnings.map(&:message)
         | 
| 127 | 
            +
                assert_equal(['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
         | 
| 128 | 
            +
                  template.warnings.map(&:message))
         | 
| 130 129 | 
             
              end
         | 
| 131 130 |  | 
| 132 131 | 
             
              def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
         | 
| @@ -139,11 +138,10 @@ class ErrorHandlingTest < Minitest::Test | |
| 139 138 | 
             
                      bla
         | 
| 140 139 | 
             
                            ',
         | 
| 141 140 | 
             
                    error_mode: :strict,
         | 
| 142 | 
            -
                    line_numbers: true
         | 
| 143 | 
            -
                  )
         | 
| 141 | 
            +
                    line_numbers: true)
         | 
| 144 142 | 
             
                end
         | 
| 145 143 |  | 
| 146 | 
            -
                assert_equal | 
| 144 | 
            +
                assert_equal('Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message)
         | 
| 147 145 | 
             
              end
         | 
| 148 146 |  | 
| 149 147 | 
             
              def test_syntax_errors_in_nested_blocks_have_correct_line_number
         | 
| @@ -157,46 +155,45 @@ class ErrorHandlingTest < Minitest::Test | |
| 157 155 |  | 
| 158 156 | 
             
                      bla
         | 
| 159 157 | 
             
                            ',
         | 
| 160 | 
            -
                    line_numbers: true
         | 
| 161 | 
            -
                  )
         | 
| 158 | 
            +
                    line_numbers: true)
         | 
| 162 159 | 
             
                end
         | 
| 163 160 |  | 
| 164 | 
            -
                assert_equal | 
| 161 | 
            +
                assert_equal("Liquid syntax error (line 5): Unknown tag 'foo'", err.message)
         | 
| 165 162 | 
             
              end
         | 
| 166 163 |  | 
| 167 164 | 
             
              def test_strict_error_messages
         | 
| 168 165 | 
             
                err = assert_raises(SyntaxError) do
         | 
| 169 166 | 
             
                  Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
         | 
| 170 167 | 
             
                end
         | 
| 171 | 
            -
                assert_equal | 
| 168 | 
            +
                assert_equal('Liquid syntax error: Unexpected character = in "1 =! 2"', err.message)
         | 
| 172 169 |  | 
| 173 170 | 
             
                err = assert_raises(SyntaxError) do
         | 
| 174 171 | 
             
                  Liquid::Template.parse('{{%%%}}', error_mode: :strict)
         | 
| 175 172 | 
             
                end
         | 
| 176 | 
            -
                assert_equal | 
| 173 | 
            +
                assert_equal('Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message)
         | 
| 177 174 | 
             
              end
         | 
| 178 175 |  | 
| 179 176 | 
             
              def test_warnings
         | 
| 180 177 | 
             
                template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
         | 
| 181 | 
            -
                assert_equal | 
| 182 | 
            -
                assert_equal | 
| 183 | 
            -
                assert_equal | 
| 184 | 
            -
                assert_equal | 
| 185 | 
            -
                assert_equal | 
| 178 | 
            +
                assert_equal(3, template.warnings.size)
         | 
| 179 | 
            +
                assert_equal('Unexpected character ~ in "~~~"', template.warnings[0].to_s(false))
         | 
| 180 | 
            +
                assert_equal('Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false))
         | 
| 181 | 
            +
                assert_equal('Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false))
         | 
| 182 | 
            +
                assert_equal('', template.render)
         | 
| 186 183 | 
             
              end
         | 
| 187 184 |  | 
| 188 185 | 
             
              def test_warning_line_numbers
         | 
| 189 186 | 
             
                template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
         | 
| 190 | 
            -
                assert_equal | 
| 191 | 
            -
                assert_equal | 
| 192 | 
            -
                assert_equal | 
| 193 | 
            -
                assert_equal | 
| 194 | 
            -
                assert_equal | 
| 187 | 
            +
                assert_equal('Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message)
         | 
| 188 | 
            +
                assert_equal('Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message)
         | 
| 189 | 
            +
                assert_equal('Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message)
         | 
| 190 | 
            +
                assert_equal(3, template.warnings.size)
         | 
| 191 | 
            +
                assert_equal([1, 2, 3], template.warnings.map(&:line_number))
         | 
| 195 192 | 
             
              end
         | 
| 196 193 |  | 
| 197 194 | 
             
              # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
         | 
| 198 195 | 
             
              def test_exceptions_propagate
         | 
| 199 | 
            -
                assert_raises | 
| 196 | 
            +
                assert_raises(Exception) do
         | 
| 200 197 | 
             
                  template = Liquid::Template.parse('{{ errors.exception }}')
         | 
| 201 198 | 
             
                  template.render('errors' => ErrorDrop.new)
         | 
| 202 199 | 
             
                end
         | 
| @@ -205,41 +202,47 @@ class ErrorHandlingTest < Minitest::Test | |
| 205 202 | 
             
              def test_default_exception_renderer_with_internal_error
         | 
| 206 203 | 
             
                template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
         | 
| 207 204 |  | 
| 208 | 
            -
                output = template.render( | 
| 205 | 
            +
                output = template.render('errors' => ErrorDrop.new)
         | 
| 209 206 |  | 
| 210 | 
            -
                assert_equal | 
| 211 | 
            -
                assert_equal | 
| 207 | 
            +
                assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
         | 
| 208 | 
            +
                assert_equal([Liquid::InternalError], template.errors.map(&:class))
         | 
| 212 209 | 
             
              end
         | 
| 213 210 |  | 
| 214 211 | 
             
              def test_setting_default_exception_renderer
         | 
| 215 212 | 
             
                old_exception_renderer = Liquid::Template.default_exception_renderer
         | 
| 216 213 | 
             
                exceptions = []
         | 
| 217 | 
            -
                Liquid::Template.default_exception_renderer = ->(e) { | 
| 214 | 
            +
                Liquid::Template.default_exception_renderer = ->(e) {
         | 
| 215 | 
            +
                  exceptions << e
         | 
| 216 | 
            +
                  ''
         | 
| 217 | 
            +
                }
         | 
| 218 218 | 
             
                template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
         | 
| 219 219 |  | 
| 220 | 
            -
                output = template.render( | 
| 220 | 
            +
                output = template.render('errors' => ErrorDrop.new)
         | 
| 221 221 |  | 
| 222 | 
            -
                assert_equal | 
| 223 | 
            -
                assert_equal | 
| 222 | 
            +
                assert_equal('This is a runtime error: ', output)
         | 
| 223 | 
            +
                assert_equal([Liquid::ArgumentError], template.errors.map(&:class))
         | 
| 224 224 | 
             
              ensure
         | 
| 225 225 | 
             
                Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
         | 
| 226 226 | 
             
              end
         | 
| 227 227 |  | 
| 228 228 | 
             
              def test_exception_renderer_exposing_non_liquid_error
         | 
| 229 | 
            -
                template | 
| 229 | 
            +
                template   = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
         | 
| 230 230 | 
             
                exceptions = []
         | 
| 231 | 
            -
                handler | 
| 231 | 
            +
                handler    = ->(e) {
         | 
| 232 | 
            +
                  exceptions << e
         | 
| 233 | 
            +
                  e.cause
         | 
| 234 | 
            +
                }
         | 
| 232 235 |  | 
| 233 236 | 
             
                output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
         | 
| 234 237 |  | 
| 235 | 
            -
                assert_equal | 
| 236 | 
            -
                assert_equal | 
| 237 | 
            -
                assert_equal | 
| 238 | 
            -
                assert_equal | 
| 238 | 
            +
                assert_equal('This is a runtime error: runtime error', output)
         | 
| 239 | 
            +
                assert_equal([Liquid::InternalError], exceptions.map(&:class))
         | 
| 240 | 
            +
                assert_equal(exceptions, template.errors)
         | 
| 241 | 
            +
                assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
         | 
| 239 242 | 
             
              end
         | 
| 240 243 |  | 
| 241 244 | 
             
              class TestFileSystem
         | 
| 242 | 
            -
                def read_template_file( | 
| 245 | 
            +
                def read_template_file(_template_path)
         | 
| 243 246 | 
             
                  "{{ errors.argument_error }}"
         | 
| 244 247 | 
             
                end
         | 
| 245 248 | 
             
              end
         | 
| @@ -249,12 +252,21 @@ class ErrorHandlingTest < Minitest::Test | |
| 249 252 |  | 
| 250 253 | 
             
                begin
         | 
| 251 254 | 
             
                  Liquid::Template.file_system = TestFileSystem.new
         | 
| 255 | 
            +
             | 
| 252 256 | 
             
                  template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
         | 
| 253 | 
            -
                  page | 
| 257 | 
            +
                  page     = template.render('errors' => ErrorDrop.new)
         | 
| 254 258 | 
             
                ensure
         | 
| 255 259 | 
             
                  Liquid::Template.file_system = old_file_system
         | 
| 256 260 | 
             
                end
         | 
| 257 | 
            -
                assert_equal | 
| 258 | 
            -
                assert_equal | 
| 261 | 
            +
                assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
         | 
| 262 | 
            +
                assert_equal("product", template.errors.first.template_name)
         | 
| 263 | 
            +
              end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
              def test_bug_compatible_silencing_of_errors_in_blank_nodes
         | 
| 266 | 
            +
                output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}not blank{% assign x = 3 %}{% endif %}{{ x }}").render
         | 
| 267 | 
            +
                assert_equal("Liquid error: comparison of Integer with String failed0", output)
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                output = Liquid::Template.parse("{% assign x = 0 %}{% if 1 < '2' %}{% assign x = 3 %}{% endif %}{{ x }}").render
         | 
| 270 | 
            +
                assert_equal("0", output)
         | 
| 259 271 | 
             
              end
         | 
| 260 272 | 
             
            end
         |