# frozen_string_literal: true require 'test_helper' require 'timeout' class VariableTest < Minitest::Test include Liquid def test_simple_variable assert_template_result('worked', "{{test}}", { 'test' => 'worked' }) assert_template_result('worked wonderfully', "{{test}}", { 'test' => 'worked wonderfully' }) end def test_variable_render_calls_to_liquid assert_template_result('foobar', '{{ foo }}', { 'foo' => ThingWithToLiquid.new }) end def test_variable_lookup_calls_to_liquid_value assert_template_result('1', '{{ foo }}', { 'foo' => IntegerDrop.new('1') }) assert_template_result('2', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3] }) assert_template_result('one', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' } }) assert_template_result('Yay', '{{ foo }}', { 'foo' => BooleanDrop.new(true) }) assert_template_result('YAY', '{{ foo | upcase }}', { 'foo' => BooleanDrop.new(true) }) end def test_if_tag_calls_to_liquid_value assert_template_result('one', '{% if foo == 1 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') }) assert_template_result('one', '{% if foo == eqv %}one{% endif %}', { 'foo' => IntegerDrop.new(1), 'eqv' => IntegerDrop.new(1) }) assert_template_result('one', '{% if 0 < foo %}one{% endif %}', { 'foo' => IntegerDrop.new('1') }) assert_template_result('one', '{% if foo > 0 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') }) assert_template_result('one', '{% if b > a %}one{% endif %}', { 'b' => IntegerDrop.new(1), 'a' => IntegerDrop.new(0) }) assert_template_result('true', '{% if foo == true %}true{% endif %}', { 'foo' => BooleanDrop.new(true) }) assert_template_result('true', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(true) }) assert_template_result('', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(false) }) assert_template_result('', '{% if foo == true %}True{% endif %}', { 'foo' => BooleanDrop.new(false) }) assert_template_result('', '{% if foo and true %}SHOULD NOT HAPPEN{% endif %}', { 'foo' => BooleanDrop.new(false) }) assert_template_result('one', '{% if a contains x %}one{% endif %}', { 'a' => [1], 'x' => IntegerDrop.new(1) }) end def test_unless_tag_calls_to_liquid_value assert_template_result('', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(true) }) assert_template_result('true', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(false) }) end def test_case_tag_calls_to_liquid_value assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', { 'foo' => IntegerDrop.new('1') }) end def test_simple_with_whitespaces assert_template_result(" worked ", " {{ test }} ", { "test" => "worked" }) assert_template_result(" worked wonderfully ", " {{ test }} ", { "test" => "worked wonderfully" }) end def test_expression_with_whitespace_in_square_brackets assert_template_result('result', "{{ a[ 'b' ] }}", { 'a' => { 'b' => 'result' } }) assert_template_result('result', "{{ a[ self[ 'b' ] ] }}", { 'b' => 'c', 'a' => { 'c' => 'result' } }) end def test_ignore_unknown assert_template_result("", "{{ test }}") end def test_using_blank_as_variable_name assert_template_result("", "{% assign foo = blank %}{{ foo }}") end def test_using_empty_as_variable_name assert_template_result("", "{% assign foo = empty %}{{ foo }}") end def test_hash_scoping assert_template_result('worked', "{{ test.test }}", { 'test' => { 'test' => 'worked' } }) assert_template_result('worked', "{{ test . test }}", { 'test' => { 'test' => 'worked' } }) end def test_false_renders_as_false assert_template_result("false", "{{ foo }}", { 'foo' => false }) assert_template_result("false", "{{ false }}") end def test_nil_renders_as_empty_string assert_template_result("", "{{ nil }}") assert_template_result("cat", "{{ nil | append: 'cat' }}") end def test_preset_assigns template = Template.parse(%({{ test }})) template.assigns['test'] = 'worked' assert_equal('worked', template.render!) end def test_reuse_parsed_template template = Template.parse(%({{ greeting }} {{ name }})) template.assigns['greeting'] = 'Goodbye' assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi')) assert_equal('Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi')) assert_equal('Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian')) assert_equal('Goodbye Brian', template.render!('name' => 'Brian')) assert_equal({ 'greeting' => 'Goodbye' }, template.assigns) end def test_assigns_not_polluted_from_template template = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }})) template.assigns['test'] = 'baz' assert_equal('bazbar', template.render!) assert_equal('bazbar', template.render!) assert_equal('foobar', template.render!('test' => 'foo')) assert_equal('bazbar', template.render!) end def test_hash_with_default_proc template = Template.parse(%(Hello {{ test }})) assigns = Hash.new { |_h, k| raise "Unknown variable '#{k}'" } assigns['test'] = 'Tobi' assert_equal('Hello Tobi', template.render!(assigns)) assigns.delete('test') e = assert_raises(RuntimeError) do template.render!(assigns) end assert_equal("Unknown variable 'test'", e.message) end def test_multiline_variable assert_template_result("worked", "{{\ntest\n}}", { "test" => "worked" }) end def test_render_symbol assert_template_result('bar', '{{ foo }}', { 'foo' => :bar }) end def test_nested_array assert_template_result('', '{{ foo }}', { 'foo' => [[nil]] }) end def test_dynamic_find_var assert_template_result('bar', '{{ self[key] }}', { 'key' => 'foo', 'foo' => 'bar' }) end def test_raw_value_variable assert_template_result('bar', '{{ self[key] }}', { 'key' => 'foo', 'foo' => 'bar' }) end def test_dynamic_find_var_with_drop assert_template_result( 'bar', '{{ self[list[settings.zero]] }}', { 'list' => ['foo'], 'settings' => SettingsDrop.new("zero" => 0), 'foo' => 'bar', }, ) assert_template_result( 'foo', '{{ self[list[settings.zero]["foo"]] }}', { 'list' => [{ 'foo' => 'bar' }], 'settings' => SettingsDrop.new("zero" => 0), 'bar' => 'foo', }, ) end def test_double_nested_variable_lookup assert_template_result( 'bar', '{{ list[list[settings.zero]]["foo"] }}', { 'list' => [1, { 'foo' => 'bar' }], 'settings' => SettingsDrop.new("zero" => 0), 'bar' => 'foo', }, ) end def test_variable_lookup_should_not_hang_with_invalid_syntax Timeout.timeout(1) do assert_template_result( 'bar', "{{['foo'}}", { 'foo' => 'bar', }, error_mode: :lax, ) end very_long_key = "1234567890" * 100 template_list = [ "{{['#{very_long_key}']}}", # valid "{{['#{very_long_key}'}}", # missing closing bracket "{{[['#{very_long_key}']}}", # extra open bracket ] template_list.each do |template| Timeout.timeout(1) do assert_template_result( 'bar', template, { very_long_key => 'bar', }, error_mode: :lax, ) end end end def test_filter_with_single_trailing_comma template = '{{ "hello" | append: "world", }}' with_error_modes(:strict) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/is not a valid expression/, error.message) end with_error_modes(:strict2) do assert_template_result('helloworld', template) end end def test_multiple_filters_with_trailing_commas template = '{{ "hello" | append: "1", | append: "2", }}' with_error_modes(:strict) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/is not a valid expression/, error.message) end with_error_modes(:strict2) do assert_template_result('hello12', template) end end def test_filter_with_colon_but_no_arguments template = '{{ "test" | upcase: }}' with_error_modes(:strict) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/is not a valid expression/, error.message) end with_error_modes(:strict2) do assert_template_result('TEST', template) end end def test_filter_chain_with_colon_no_args template = '{{ "test" | append: "x" | upcase: }}' with_error_modes(:strict) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/is not a valid expression/, error.message) end with_error_modes(:strict2) do assert_template_result('TESTX', template) end end def test_combining_trailing_comma_and_empty_args template = '{{ "test" | append: "x", | upcase: }}' with_error_modes(:strict) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/is not a valid expression/, error.message) end with_error_modes(:strict2) do assert_template_result('TESTX', template) end end end