Back to Repositories

Testing Liquid Template TableRow Tag Implementation in Shopify/liquid

This integration test suite validates the functionality of the tablerow tag in Liquid templating, focusing on HTML table generation with dynamic content and loop controls.

Test Coverage Overview

The test suite comprehensively covers tablerow tag functionality in Liquid templating, including:

  • Basic table row generation with different column configurations
  • Row and column counter handling
  • Collection iteration with quoted fragments
  • Enumerable drop implementation
  • Offset and limit parameter validation
  • Loop variable accessibility and attributes

Implementation Analysis

The testing approach employs Minitest framework with systematic validation of HTML output patterns.

Key patterns include:
  • Template result assertion with expected HTML structure
  • Edge case handling for nil values and invalid parameters
  • Integration with Liquid::Drop for custom enumerable objects
  • Loop control testing with break and continue statements

Technical Details

Testing infrastructure includes:
  • Minitest as the testing framework
  • Custom ArrayDrop class for enumerable testing
  • Liquid template parsing and rendering
  • HTML output validation
  • Error handling verification

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive edge case coverage
  • Systematic validation of HTML output structure
  • Clear test method naming and organization
  • Thorough error condition testing
  • Isolation of test cases for specific functionality

shopify/liquid

test/integration/tags/table_row_test.rb

            
# frozen_string_literal: true

require 'test_helper'

class TableRowTest < Minitest::Test
  include Liquid

  class ArrayDrop < Liquid::Drop
    include Enumerable

    def initialize(array)
      @array = array
    end

    def each(&block)
      @array.each(&block)
    end
  end

  def test_table_row
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
      '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
      { 'numbers' => [1, 2, 3, 4, 5, 6] },
    )

    assert_template_result(
      "<tr class=\"row1\">\n</tr>\n",
      '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
      { 'numbers' => [] },
    )
  end

  def test_table_row_with_different_cols
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
      '{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
      { 'numbers' => [1, 2, 3, 4, 5, 6] },
    )
  end

  def test_table_col_counter
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
      '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
      { 'numbers' => [1, 2, 3, 4, 5, 6] },
    )
  end

  def test_quoted_fragment
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
      "{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}",
      { 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },
    )
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
      "{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}",
      { 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } },
    )
  end

  def test_enumerable_drop
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
      '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
      { 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) },
    )
  end

  def test_offset_and_limit
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
      '{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
      { 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] },
    )
  end

  def test_blank_string_not_iterable
    assert_template_result(
      "<tr class=\"row1\">\n</tr>\n",
      "{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}",
      { 'characters' => '' },
    )
  end

  def test_cols_nil_constant_same_as_evaluated_nil_expression
    expect = "<tr class=\"row1\">\n" \
      "<td class=\"col1\">false</td>" \
      "<td class=\"col2\">false</td>" \
      "</tr>\n"

    assert_template_result(
      expect,
      "{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}",
    )

    assert_template_result(
      expect,
      "{% tablerow i in (1..2) cols:var %}{{ tablerowloop.col_last }}{% endtablerow %}",
      { "var" => nil },
    )
  end

  def test_nil_limit_is_treated_as_zero
    expect = "<tr class=\"row1\">\n" \
      "</tr>\n"

    assert_template_result(
      expect,
      "{% tablerow i in (1..2) limit:nil %}{{ i }}{% endtablerow %}",
    )

    assert_template_result(
      expect,
      "{% tablerow i in (1..2) limit:var %}{{ i }}{% endtablerow %}",
      { "var" => nil },
    )
  end

  def test_nil_offset_is_treated_as_zero
    expect = "<tr class=\"row1\">\n" \
      "<td class=\"col1\">1:false</td>" \
      "<td class=\"col2\">2:true</td>" \
      "</tr>\n"

    assert_template_result(
      expect,
      "{% tablerow i in (1..2) offset:nil %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
    )

    assert_template_result(
      expect,
      "{% tablerow i in (1..2) offset:var %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
      { "var" => nil },
    )
  end

  def test_tablerow_loop_drop_attributes
    template = <<~LIQUID.chomp
      {% tablerow i in (1...2) %}
      col: {{ tablerowloop.col }}
      col0: {{ tablerowloop.col0 }}
      col_first: {{ tablerowloop.col_first }}
      col_last: {{ tablerowloop.col_last }}
      first: {{ tablerowloop.first }}
      index: {{ tablerowloop.index }}
      index0: {{ tablerowloop.index0 }}
      last: {{ tablerowloop.last }}
      length: {{ tablerowloop.length }}
      rindex: {{ tablerowloop.rindex }}
      rindex0: {{ tablerowloop.rindex0 }}
      row: {{ tablerowloop.row }}
      {% endtablerow %}
    LIQUID

    expected_output = <<~OUTPUT
      <tr class="row1">
      <td class="col1">
      col: 1
      col0: 0
      col_first: true
      col_last: false
      first: true
      index: 1
      index0: 0
      last: false
      length: 2
      rindex: 2
      rindex0: 1
      row: 1
      </td><td class="col2">
      col: 2
      col0: 1
      col_first: false
      col_last: true
      first: false
      index: 2
      index0: 1
      last: true
      length: 2
      rindex: 1
      rindex0: 0
      row: 1
      </td></tr>
    OUTPUT

    assert_template_result(expected_output, template)
  end

  def test_table_row_renders_correct_error_message_for_invalid_parameters
    assert_template_result(
      "Liquid error (line 1): invalid integer",
      '{% tablerow n in (1...10) limit:true %} {{n}} {% endtablerow %}',
      render_errors: true,
    )

    assert_template_result(
      "Liquid error (line 1): invalid integer",
      '{% tablerow n in (1...10) offset:true %} {{n}} {% endtablerow %}',
      render_errors: true,
    )

    assert_template_result(
      "Liquid error (line 1): invalid integer",
      '{% tablerow n in (1...10) cols:true %} {{n}} {% endtablerow %}',
      render_errors: true,
    )
  end

  def test_table_row_handles_interrupts
    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td></tr>\n",
      '{% tablerow n in (1...3) cols:2 %} {{n}} {% break %} {{n}} {% endtablerow %}',
    )

    assert_template_result(
      "<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 3 </td></tr>\n",
      '{% tablerow n in (1...3) cols:2 %} {{n}} {% continue %} {{n}} {% endtablerow %}',
    )
  end

  def test_table_row_does_not_leak_interrupts
    template = <<~LIQUID
      {% for i in (1..2) -%}
      {% for j in (1..2) -%}
      {% tablerow k in (1..3) %}{% break %}{% endtablerow -%}
      loop j={{ j }}
      {% endfor -%}
      loop i={{ i }}
      {% endfor -%}
      after loop
    LIQUID

    expected = <<~STR
      <tr class="row1">
      <td class="col1"></td></tr>
      loop j=1
      <tr class="row1">
      <td class="col1"></td></tr>
      loop j=2
      loop i=1
      <tr class="row1">
      <td class="col1"></td></tr>
      loop j=1
      <tr class="row1">
      <td class="col1"></td></tr>
      loop j=2
      loop i=2
      after loop
    STR

    assert_template_result(
      expected,
      template,
    )
  end
end