Back to Repositories

Validating Template Parsing Quirks in Shopify/liquid

This integration test suite examines parsing quirks and edge cases in Liquid template processing. It verifies syntax error handling, filter parsing, and variable processing across different error modes (strict and lax).

Test Coverage Overview

The test suite provides comprehensive coverage of Liquid template parsing edge cases.

  • CSS parsing validation
  • Bracket and syntax error detection
  • Filter processing verification
  • Variable markup handling
  • Error mode testing (strict vs lax)

Implementation Analysis

The testing approach systematically validates parsing behavior through Minitest assertions. Tests utilize both strict and lax error modes to verify proper handling of malformed templates, invalid syntax, and edge cases.

  • Error mode switching with with_error_mode helper
  • Template parsing validation
  • Assertion-based verification
  • Edge case handling

Technical Details

  • Testing Framework: Minitest
  • Language: Ruby
  • Key Components: Liquid::Template, SyntaxError handling
  • Test Helpers: Custom error mode switcher

Best Practices Demonstrated

The test suite exemplifies thorough error condition testing and boundary case validation.

  • Comprehensive error mode testing
  • Systematic edge case coverage
  • Clear test case organization
  • Explicit expected behavior documentation

shopify/liquid

test/integration/parsing_quirks_test.rb

            
# frozen_string_literal: true

require 'test_helper'

class ParsingQuirksTest < Minitest::Test
  include Liquid

  def test_parsing_css
    text = " div { font-weight: bold; } "
    assert_equal(text, Template.parse(text).render!)
  end

  def test_raise_on_single_close_bracet
    assert_raises(SyntaxError) do
      Template.parse("text {{method} oh nos!")
    end
  end

  def test_raise_on_label_and_no_close_bracets
    assert_raises(SyntaxError) do
      Template.parse("TEST {{ ")
    end
  end

  def test_raise_on_label_and_no_close_bracets_percent
    assert_raises(SyntaxError) do
      Template.parse("TEST {% ")
    end
  end

  def test_error_on_empty_filter
    assert(Template.parse("{{test}}"))

    with_error_mode(:lax) do
      assert(Template.parse("{{|test}}"))
    end

    with_error_mode(:strict) do
      assert_raises(SyntaxError) { Template.parse("{{|test}}") }
      assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
    end
  end

  def test_meaningless_parens_error
    with_error_mode(:strict) do
      assert_raises(SyntaxError) do
        markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
        Template.parse("{% if #{markup} %} YES {% endif %}")
      end
    end
  end

  def test_unexpected_characters_syntax_error
    with_error_mode(:strict) do
      assert_raises(SyntaxError) do
        markup = "true && false"
        Template.parse("{% if #{markup} %} YES {% endif %}")
      end
      assert_raises(SyntaxError) do
        markup = "false || true"
        Template.parse("{% if #{markup} %} YES {% endif %}")
      end
    end
  end

  def test_no_error_on_lax_empty_filter
    assert(Template.parse("{{test |a|b|}}", error_mode: :lax))
    assert(Template.parse("{{test}}", error_mode: :lax))
    assert(Template.parse("{{|test|}}", error_mode: :lax))
  end

  def test_meaningless_parens_lax
    with_error_mode(:lax) do
      assigns = { 'b' => 'bar', 'c' => 'baz' }
      markup  = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
      assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
    end
  end

  def test_unexpected_characters_silently_eat_logic_lax
    with_error_mode(:lax) do
      markup = "true && false"
      assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}")
      markup = "false || true"
      assert_template_result('', "{% if #{markup} %} YES {% endif %}")
    end
  end

  def test_raise_on_invalid_tag_delimiter
    assert_raises(Liquid::SyntaxError) do
      Template.new.parse('{% end %}')
    end
  end

  def test_unanchored_filter_arguments
    with_error_mode(:lax) do
      assert_template_result('hi', "{{ 'hi there' | split$$$:' ' | first }}")

      assert_template_result('x', "{{ 'X' | downcase) }}")

      # After the messed up quotes a filter without parameters (reverse) should work
      # but one with parameters (remove) shouldn't be detected.
      assert_template_result('here',  "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
      assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
    end
  end

  def test_invalid_variables_work
    with_error_mode(:lax) do
      assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
      assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
    end
  end

  def test_extra_dots_in_ranges
    with_error_mode(:lax) do
      assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
    end
  end

  def test_blank_variable_markup
    assert_template_result('', "{{}}")
  end

  def test_lookup_on_var_with_literal_name
    assigns = { "blank" => { "x" => "result" } }
    assert_template_result('result', "{{ blank.x }}", assigns)
    assert_template_result('result', "{{ blank['x'] }}", assigns)
  end

  def test_contains_in_id
    assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', { 'containsallshipments' => true })
  end

  def test_incomplete_expression
    with_error_mode(:lax) do
      assert_template_result("false", "{% liquid assign foo = false -\n%}{{ foo }}")
      assert_template_result("false", "{% liquid assign foo = false >\n%}{{ foo }}")
      assert_template_result("false", "{% liquid assign foo = false <\n%}{{ foo }}")
      assert_template_result("false", "{% liquid assign foo = false =\n%}{{ foo }}")
      assert_template_result("false", "{% liquid assign foo = false !\n%}{{ foo }}")
      assert_template_result("false", "{% liquid assign foo = false 1\n%}{{ foo }}")
      assert_template_result("false", "{% liquid assign foo = false a\n%}{{ foo }}")
    end
  end
end # ParsingQuirksTest