liquid 4.0.4 → 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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +32 -4
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +164 -54
  6. data/lib/liquid/condition.rb +39 -18
  7. data/lib/liquid/context.rb +106 -51
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -34
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +11 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +3 -1
  19. data/lib/liquid/parse_context.rb +16 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +5 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +62 -43
  30. data/lib/liquid/static_registers.rb +44 -0
  31. data/lib/liquid/strainer_factory.rb +36 -0
  32. data/lib/liquid/strainer_template.rb +53 -0
  33. data/lib/liquid/tablerowloop_drop.rb +6 -4
  34. data/lib/liquid/tag/disableable.rb +22 -0
  35. data/lib/liquid/tag/disabler.rb +21 -0
  36. data/lib/liquid/tag.rb +28 -6
  37. data/lib/liquid/tags/assign.rb +24 -10
  38. data/lib/liquid/tags/break.rb +8 -3
  39. data/lib/liquid/tags/capture.rb +11 -8
  40. data/lib/liquid/tags/case.rb +33 -27
  41. data/lib/liquid/tags/comment.rb +5 -3
  42. data/lib/liquid/tags/continue.rb +8 -3
  43. data/lib/liquid/tags/cycle.rb +25 -14
  44. data/lib/liquid/tags/decrement.rb +6 -3
  45. data/lib/liquid/tags/echo.rb +26 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +35 -23
  48. data/lib/liquid/tags/ifchanged.rb +11 -10
  49. data/lib/liquid/tags/include.rb +34 -47
  50. data/lib/liquid/tags/increment.rb +7 -3
  51. data/lib/liquid/tags/raw.rb +14 -11
  52. data/lib/liquid/tags/render.rb +84 -0
  53. data/lib/liquid/tags/table_row.rb +23 -19
  54. data/lib/liquid/tags/unless.rb +15 -15
  55. data/lib/liquid/template.rb +55 -69
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +17 -9
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +5 -3
  60. data/lib/liquid/variable.rb +47 -19
  61. data/lib/liquid/variable_lookup.rb +8 -6
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +17 -5
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +608 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -57
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +339 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +118 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +43 -32
  99. data/test/test_helper.rb +75 -14
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +1 -1
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +19 -17
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +75 -47
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/test/unit/context_unit_test.rb +0 -490
  123. 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 TestFileSystem
@@ -6,6 +8,9 @@ class TestFileSystem
6
8
  when "product"
7
9
  "Product: {{ product.title }} "
8
10
 
11
+ when "product_alias"
12
+ "Product: {{ product.title }} "
13
+
9
14
  when "locale_variables"
10
15
  "Locale: {{echo1}} {{echo2}}"
11
16
 
@@ -40,16 +45,16 @@ class TestFileSystem
40
45
  end
41
46
 
42
47
  class OtherFileSystem
43
- def read_template_file(template_path)
48
+ def read_template_file(_template_path)
44
49
  'from OtherFileSystem'
45
50
  end
46
51
  end
47
52
 
48
53
  class CountingFileSystem
49
54
  attr_reader :count
50
- def read_template_file(template_path)
55
+ def read_template_file(_template_path)
51
56
  @count ||= 0
52
- @count += 1
57
+ @count += 1
53
58
  'from CountingFileSystem'
54
59
  end
55
60
  end
@@ -59,15 +64,16 @@ class CustomInclude < Liquid::Tag
59
64
 
60
65
  def initialize(tag_name, markup, tokens)
61
66
  markup =~ Syntax
62
- @template_name = $1
67
+ @template_name = Regexp.last_match(1)
63
68
  super
64
69
  end
65
70
 
66
71
  def parse(tokens)
67
72
  end
68
73
 
69
- def render(context)
70
- @template_name[1..-2]
74
+ def render_to_output_buffer(_context, output)
75
+ output << @template_name[1..-2]
76
+ output
71
77
  end
72
78
  end
73
79
 
@@ -79,61 +85,71 @@ class IncludeTagTest < Minitest::Test
79
85
  end
80
86
 
81
87
  def test_include_tag_looks_for_file_system_in_registers_first
82
- assert_equal 'from OtherFileSystem',
83
- Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new })
88
+ assert_equal('from OtherFileSystem',
89
+ Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: OtherFileSystem.new }))
84
90
  end
85
91
 
86
92
  def test_include_tag_with
87
- assert_template_result "Product: Draft 151cm ",
88
- "{% include 'product' with products[0] %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ]
93
+ assert_template_result("Product: Draft 151cm ",
94
+ "{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
95
+ end
96
+
97
+ def test_include_tag_with_alias
98
+ assert_template_result("Product: Draft 151cm ",
99
+ "{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
100
+ end
101
+
102
+ def test_include_tag_for_alias
103
+ assert_template_result("Product: Draft 151cm Product: Element 155cm ",
104
+ "{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
89
105
  end
90
106
 
91
107
  def test_include_tag_with_default_name
92
- assert_template_result "Product: Draft 151cm ",
93
- "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' }
108
+ assert_template_result("Product: Draft 151cm ",
109
+ "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' })
94
110
  end
95
111
 
96
112
  def test_include_tag_for
97
- assert_template_result "Product: Draft 151cm Product: Element 155cm ",
98
- "{% include 'product' for products %}", "products" => [ { 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' } ]
113
+ assert_template_result("Product: Draft 151cm Product: Element 155cm ",
114
+ "{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
99
115
  end
100
116
 
101
117
  def test_include_tag_with_local_variables
102
- assert_template_result "Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}"
118
+ assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}")
103
119
  end
104
120
 
105
121
  def test_include_tag_with_multiple_local_variables
106
- assert_template_result "Locale: test123 test321",
107
- "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}"
122
+ assert_template_result("Locale: test123 test321",
123
+ "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}")
108
124
  end
109
125
 
110
126
  def test_include_tag_with_multiple_local_variables_from_context
111
- assert_template_result "Locale: test123 test321",
127
+ assert_template_result("Locale: test123 test321",
112
128
  "{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
113
- 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' }
129
+ 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' })
114
130
  end
115
131
 
116
132
  def test_included_templates_assigns_variables
117
- assert_template_result "bar", "{% include 'assignments' %}{{ foo }}"
133
+ assert_template_result("bar", "{% include 'assignments' %}{{ foo }}")
118
134
  end
119
135
 
120
136
  def test_nested_include_tag
121
- assert_template_result "body body_detail", "{% include 'body' %}"
137
+ assert_template_result("body body_detail", "{% include 'body' %}")
122
138
 
123
- assert_template_result "header body body_detail footer", "{% include 'nested_template' %}"
139
+ assert_template_result("header body body_detail footer", "{% include 'nested_template' %}")
124
140
  end
125
141
 
126
142
  def test_nested_include_with_variable
127
- assert_template_result "Product: Draft 151cm details ",
128
- "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' }
143
+ assert_template_result("Product: Draft 151cm details ",
144
+ "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' })
129
145
 
130
- assert_template_result "Product: Draft 151cm details Product: Element 155cm details ",
131
- "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }]
146
+ assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
147
+ "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }])
132
148
  end
133
149
 
134
150
  def test_recursively_included_template_does_not_produce_endless_loop
135
151
  infinite_file_system = Class.new do
136
- def read_template_file(template_path)
152
+ def read_template_file(_template_path)
137
153
  "-{% include 'loop' %}"
138
154
  end
139
155
  end
@@ -146,41 +162,41 @@ class IncludeTagTest < Minitest::Test
146
162
  end
147
163
 
148
164
  def test_dynamically_choosen_template
149
- assert_template_result "Test123", "{% include template %}", "template" => 'Test123'
150
- assert_template_result "Test321", "{% include template %}", "template" => 'Test321'
165
+ assert_template_result("Test123", "{% include template %}", "template" => 'Test123')
166
+ assert_template_result("Test321", "{% include template %}", "template" => 'Test321')
151
167
 
152
- assert_template_result "Product: Draft 151cm ", "{% include template for product %}",
153
- "template" => 'product', 'product' => { 'title' => 'Draft 151cm' }
168
+ assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
169
+ "template" => 'product', 'product' => { 'title' => 'Draft 151cm' })
154
170
  end
155
171
 
156
172
  def test_include_tag_caches_second_read_of_same_partial
157
173
  file_system = CountingFileSystem.new
158
- assert_equal 'from CountingFileSystemfrom CountingFileSystem',
159
- Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
160
- assert_equal 1, file_system.count
174
+ assert_equal('from CountingFileSystemfrom CountingFileSystem',
175
+ Template.parse("{% include 'pick_a_source' %}{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
176
+ assert_equal(1, file_system.count)
161
177
  end
162
178
 
163
179
  def test_include_tag_doesnt_cache_partials_across_renders
164
180
  file_system = CountingFileSystem.new
165
- assert_equal 'from CountingFileSystem',
166
- Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
167
- assert_equal 1, file_system.count
181
+ assert_equal('from CountingFileSystem',
182
+ Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
183
+ assert_equal(1, file_system.count)
168
184
 
169
- assert_equal 'from CountingFileSystem',
170
- Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system })
171
- assert_equal 2, file_system.count
185
+ assert_equal('from CountingFileSystem',
186
+ Template.parse("{% include 'pick_a_source' %}").render!({}, registers: { file_system: file_system }))
187
+ assert_equal(2, file_system.count)
172
188
  end
173
189
 
174
190
  def test_include_tag_within_if_statement
175
- assert_template_result "foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}"
191
+ assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}")
176
192
  end
177
193
 
178
194
  def test_custom_include_tag
179
195
  original_tag = Liquid::Template.tags['include']
180
196
  Liquid::Template.tags['include'] = CustomInclude
181
197
  begin
182
- assert_equal "custom_foo",
183
- Template.parse("{% include 'custom_foo' %}").render!
198
+ assert_equal("custom_foo",
199
+ Template.parse("{% include 'custom_foo' %}").render!)
184
200
  ensure
185
201
  Liquid::Template.tags['include'] = original_tag
186
202
  end
@@ -190,8 +206,8 @@ class IncludeTagTest < Minitest::Test
190
206
  original_tag = Liquid::Template.tags['include']
191
207
  Liquid::Template.tags['include'] = CustomInclude
192
208
  begin
193
- assert_equal "custom_foo_if_true",
194
- Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!
209
+ assert_equal("custom_foo_if_true",
210
+ Template.parse("{% if true %}{% include 'custom_foo_if_true' %}{% endif %}").render!)
195
211
  ensure
196
212
  Liquid::Template.tags['include'] = original_tag
197
213
  end
@@ -202,7 +218,7 @@ class IncludeTagTest < Minitest::Test
202
218
 
203
219
  a = Liquid::Template.parse(' {% include "nested_template" %}')
204
220
  a.render!
205
- assert_empty a.errors
221
+ assert_empty(a.errors)
206
222
  end
207
223
 
208
224
  def test_passing_options_to_included_templates
@@ -210,13 +226,13 @@ class IncludeTagTest < Minitest::Test
210
226
  Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
211
227
  end
212
228
  with_error_mode(:lax) do
213
- assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
229
+ assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}'))
214
230
  end
215
231
  assert_raises(Liquid::SyntaxError) do
216
232
  Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
217
233
  end
218
234
  with_error_mode(:lax) do
219
- assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
235
+ assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}'))
220
236
  end
221
237
  end
222
238
 
@@ -232,22 +248,22 @@ class IncludeTagTest < Minitest::Test
232
248
  end
233
249
 
234
250
  def test_including_via_variable_value
235
- assert_template_result "from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}"
251
+ assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}")
236
252
 
237
- assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' }
253
+ assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' })
238
254
 
239
- assert_template_result "Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' }
255
+ assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' })
240
256
  end
241
257
 
242
258
  def test_including_with_strict_variables
243
259
  template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
244
260
  template.render(nil, strict_variables: true)
245
261
 
246
- assert_equal [], template.errors
262
+ assert_equal([], template.errors)
247
263
  end
248
264
 
249
265
  def test_break_through_include
250
- assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}"
251
- assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}"
266
+ assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}")
267
+ assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}")
252
268
  end
253
269
  end # IncludeTagTest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class IncrementTagTest < Minitest::Test
@@ -13,11 +15,11 @@ class IncrementTagTest < Minitest::Test
13
15
  end
14
16
 
15
17
  def test_dec
16
- assert_template_result('9', '{%decrement port %}', { 'port' => 10 })
18
+ assert_template_result('9', '{%decrement port %}', 'port' => 10)
17
19
  assert_template_result('-1 -2', '{%decrement port %} {%decrement port%}', {})
18
20
  assert_template_result('1 5 2 2 5',
19
21
  '{%increment port %} {%increment starboard%} ' \
20
22
  '{%increment port %} {%decrement port%} ' \
21
- '{%decrement starboard %}', { 'port' => 1, 'starboard' => 5 })
23
+ '{%decrement starboard %}', 'port' => 1, 'starboard' => 5)
22
24
  end
23
25
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class LiquidTagTest < Minitest::Test
6
+ include Liquid
7
+
8
+ def test_liquid_tag
9
+ assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
10
+ {%- liquid
11
+ echo array | join: " "
12
+ -%}
13
+ LIQUID
14
+
15
+ assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
16
+ {%- liquid
17
+ for value in array
18
+ echo value
19
+ unless forloop.last
20
+ echo " "
21
+ endunless
22
+ endfor
23
+ -%}
24
+ LIQUID
25
+
26
+ assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3])
27
+ {%- liquid
28
+ for value in array
29
+ assign double_value = value | times: 2
30
+ echo double_value | times: 2
31
+ unless forloop.last
32
+ echo " "
33
+ endunless
34
+ endfor
35
+
36
+ echo " "
37
+ echo double_value
38
+ -%}
39
+ LIQUID
40
+
41
+ assert_template_result('abc', <<~LIQUID)
42
+ {%- liquid echo "a" -%}
43
+ b
44
+ {%- liquid echo "c" -%}
45
+ LIQUID
46
+ end
47
+
48
+ def test_liquid_tag_errors
49
+ assert_match_syntax_error("syntax error (line 1): Unknown tag 'error'", <<~LIQUID)
50
+ {%- liquid error no such tag -%}
51
+ LIQUID
52
+
53
+ assert_match_syntax_error("syntax error (line 7): Unknown tag 'error'", <<~LIQUID)
54
+ {{ test }}
55
+
56
+ {%-
57
+ liquid
58
+ for value in array
59
+
60
+ error no such tag
61
+ endfor
62
+ -%}
63
+ LIQUID
64
+
65
+ assert_match_syntax_error("syntax error (line 2): Unknown tag '!!! the guards are vigilant'", <<~LIQUID)
66
+ {%- liquid
67
+ !!! the guards are vigilant
68
+ -%}
69
+ LIQUID
70
+
71
+ assert_match_syntax_error("syntax error (line 4): 'for' tag was never closed", <<~LIQUID)
72
+ {%- liquid
73
+ for value in array
74
+ echo 'forgot to close the for tag'
75
+ -%}
76
+ LIQUID
77
+ end
78
+
79
+ def test_line_number_is_correct_after_a_blank_token
80
+ assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n\n error %}")
81
+ assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n \n error %}")
82
+ end
83
+
84
+ def test_nested_liquid_tag
85
+ assert_template_result('good', <<~LIQUID)
86
+ {%- if true %}
87
+ {%- liquid
88
+ echo "good"
89
+ %}
90
+ {%- endif -%}
91
+ LIQUID
92
+ end
93
+
94
+ def test_cannot_open_blocks_living_past_a_liquid_tag
95
+ assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID)
96
+ {%- liquid
97
+ if true
98
+ -%}
99
+ {%- endif -%}
100
+ LIQUID
101
+ end
102
+
103
+ def test_cannot_close_blocks_created_before_a_liquid_tag
104
+ assert_match_syntax_error("syntax error (line 3): 'endif' is not a valid delimiter for liquid tags. use %}", <<~LIQUID)
105
+ {%- if true -%}
106
+ 42
107
+ {%- liquid endif -%}
108
+ LIQUID
109
+ end
110
+
111
+ def test_liquid_tag_in_raw
112
+ assert_template_result("{% liquid echo 'test' %}\n", <<~LIQUID)
113
+ {% raw %}{% liquid echo 'test' %}{% endraw %}
114
+ LIQUID
115
+ end
116
+ end
@@ -1,26 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class RawTagTest < Minitest::Test
4
6
  include Liquid
5
7
 
6
8
  def test_tag_in_raw
7
- assert_template_result '{% comment %} test {% endcomment %}',
8
- '{% raw %}{% comment %} test {% endcomment %}{% endraw %}'
9
+ assert_template_result('{% comment %} test {% endcomment %}',
10
+ '{% raw %}{% comment %} test {% endcomment %}{% endraw %}')
9
11
  end
10
12
 
11
13
  def test_output_in_raw
12
- assert_template_result '{{ test }}', '{% raw %}{{ test }}{% endraw %}'
14
+ assert_template_result('{{ test }}', '{% raw %}{{ test }}{% endraw %}')
13
15
  end
14
16
 
15
17
  def test_open_tag_in_raw
16
- assert_template_result ' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}'
17
- assert_template_result ' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}'
18
- assert_template_result ' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}'
19
- assert_template_result ' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}'
20
- assert_template_result ' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}'
21
- assert_template_result ' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}'
22
- assert_template_result ' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}'
23
- assert_template_result ' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}'
18
+ assert_template_result(' Foobar {% invalid ', '{% raw %} Foobar {% invalid {% endraw %}')
19
+ assert_template_result(' Foobar invalid %} ', '{% raw %} Foobar invalid %} {% endraw %}')
20
+ assert_template_result(' Foobar {{ invalid ', '{% raw %} Foobar {{ invalid {% endraw %}')
21
+ assert_template_result(' Foobar invalid }} ', '{% raw %} Foobar invalid }} {% endraw %}')
22
+ assert_template_result(' Foobar {% invalid {% {% endraw ', '{% raw %} Foobar {% invalid {% {% endraw {% endraw %}')
23
+ assert_template_result(' Foobar {% {% {% ', '{% raw %} Foobar {% {% {% {% endraw %}')
24
+ assert_template_result(' test {% raw %} {% endraw %}', '{% raw %} test {% raw %} {% {% endraw %}endraw %}')
25
+ assert_template_result(' Foobar {{ invalid 1', '{% raw %} Foobar {{ invalid {% endraw %}{{ 1 }}')
26
+ assert_template_result(' Foobar {% foo {% bar %}', '{% raw %} Foobar {% foo {% bar %}{% endraw %}')
24
27
  end
25
28
 
26
29
  def test_invalid_raw
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class RenderTagTest < Minitest::Test
6
+ include Liquid
7
+
8
+ def test_render_with_no_arguments
9
+ Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content')
10
+ assert_template_result('rendered content', '{% render "source" %}')
11
+ end
12
+
13
+ def test_render_tag_looks_for_file_system_in_registers_first
14
+ file_system = StubFileSystem.new('pick_a_source' => 'from register file system')
15
+ assert_equal('from register file system',
16
+ Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system }))
17
+ end
18
+
19
+ def test_render_passes_named_arguments_into_inner_scope
20
+ Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}')
21
+ assert_template_result('My Product', '{% render "product", inner_product: outer_product %}',
22
+ 'outer_product' => { 'title' => 'My Product' })
23
+ end
24
+
25
+ def test_render_accepts_literals_as_arguments
26
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}')
27
+ assert_template_result('123', '{% render "snippet", price: 123 %}')
28
+ end
29
+
30
+ def test_render_accepts_multiple_named_arguments
31
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}')
32
+ assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}')
33
+ end
34
+
35
+ def test_render_does_not_inherit_parent_scope_variables
36
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}')
37
+ assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}')
38
+ end
39
+
40
+ def test_render_does_not_inherit_variable_with_same_name_as_snippet
41
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}')
42
+ assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
43
+ end
44
+
45
+ def test_render_does_not_mutate_parent_scope
46
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
47
+ assert_template_result('', "{% render 'snippet' %}{{ inner }}")
48
+ end
49
+
50
+ def test_nested_render_tag
51
+ Liquid::Template.file_system = StubFileSystem.new(
52
+ 'one' => "one {% render 'two' %}",
53
+ 'two' => 'two'
54
+ )
55
+ assert_template_result('one two', "{% render 'one' %}")
56
+ end
57
+
58
+ def test_recursively_rendered_template_does_not_produce_endless_loop
59
+ Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}')
60
+
61
+ assert_raises(Liquid::StackLevelError) do
62
+ Template.parse('{% render "loop" %}').render!
63
+ end
64
+ end
65
+
66
+ def test_sub_contexts_count_towards_the_same_recursion_limit
67
+ Liquid::Template.file_system = StubFileSystem.new(
68
+ 'loop_render' => '{% render "loop_render" %}',
69
+ )
70
+ assert_raises(Liquid::StackLevelError) do
71
+ Template.parse('{% render "loop_render" %}').render!
72
+ end
73
+ end
74
+
75
+ def test_dynamically_choosen_templates_are_not_allowed
76
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
77
+
78
+ assert_raises(Liquid::SyntaxError) do
79
+ Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
80
+ end
81
+ end
82
+
83
+ def test_include_tag_caches_second_read_of_same_partial
84
+ file_system = StubFileSystem.new('snippet' => 'echo')
85
+ assert_equal('echoecho',
86
+ Template.parse('{% render "snippet" %}{% render "snippet" %}')
87
+ .render!({}, registers: { file_system: file_system }))
88
+ assert_equal(1, file_system.file_read_count)
89
+ end
90
+
91
+ def test_render_tag_doesnt_cache_partials_across_renders
92
+ file_system = StubFileSystem.new('snippet' => 'my message')
93
+
94
+ assert_equal('my message',
95
+ Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }))
96
+ assert_equal(1, file_system.file_read_count)
97
+
98
+ assert_equal('my message',
99
+ Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system }))
100
+ assert_equal(2, file_system.file_read_count)
101
+ end
102
+
103
+ def test_render_tag_within_if_statement
104
+ Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message')
105
+ assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}')
106
+ end
107
+
108
+ def test_break_through_render
109
+ Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}')
110
+ assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}')
111
+ assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}')
112
+ end
113
+
114
+ def test_increment_is_isolated_between_renders
115
+ Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}')
116
+ assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}')
117
+ end
118
+
119
+ def test_decrement_is_isolated_between_renders
120
+ Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}')
121
+ assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}')
122
+ end
123
+
124
+ def test_includes_will_not_render_inside_render_tag
125
+ Liquid::Template.file_system = StubFileSystem.new(
126
+ 'foo' => 'bar',
127
+ 'test_include' => '{% include "foo" %}'
128
+ )
129
+
130
+ exc = assert_raises(Liquid::DisabledError) do
131
+ Liquid::Template.parse('{% render "test_include" %}').render!
132
+ end
133
+ assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
134
+ end
135
+
136
+ def test_includes_will_not_render_inside_nested_sibling_tags
137
+ Liquid::Template.file_system = StubFileSystem.new(
138
+ 'foo' => 'bar',
139
+ 'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
140
+ 'test_include' => '{% include "foo" %}'
141
+ )
142
+
143
+ output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
144
+ assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
145
+ end
146
+
147
+ def test_render_tag_with
148
+ Liquid::Template.file_system = StubFileSystem.new(
149
+ 'product' => "Product: {{ product.title }} ",
150
+ 'product_alias' => "Product: {{ product.title }} ",
151
+ )
152
+
153
+ assert_template_result("Product: Draft 151cm ",
154
+ "{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
155
+ end
156
+
157
+ def test_render_tag_with_alias
158
+ Liquid::Template.file_system = StubFileSystem.new(
159
+ 'product' => "Product: {{ product.title }} ",
160
+ 'product_alias' => "Product: {{ product.title }} ",
161
+ )
162
+
163
+ assert_template_result("Product: Draft 151cm ",
164
+ "{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
165
+ end
166
+
167
+ def test_render_tag_for_alias
168
+ Liquid::Template.file_system = StubFileSystem.new(
169
+ 'product' => "Product: {{ product.title }} ",
170
+ 'product_alias' => "Product: {{ product.title }} ",
171
+ )
172
+
173
+ assert_template_result("Product: Draft 151cm Product: Element 155cm ",
174
+ "{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
175
+ end
176
+
177
+ def test_render_tag_for
178
+ Liquid::Template.file_system = StubFileSystem.new(
179
+ 'product' => "Product: {{ product.title }} ",
180
+ 'product_alias' => "Product: {{ product.title }} ",
181
+ )
182
+
183
+ assert_template_result("Product: Draft 151cm Product: Element 155cm ",
184
+ "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
185
+ end
186
+
187
+ def test_render_tag_forloop
188
+ Liquid::Template.file_system = StubFileSystem.new(
189
+ 'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
190
+ )
191
+
192
+ assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
193
+ "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
194
+ end
195
+
196
+ def test_render_tag_for_drop
197
+ Liquid::Template.file_system = StubFileSystem.new(
198
+ 'loop' => "{{ value.foo }}",
199
+ )
200
+
201
+ assert_template_result("123",
202
+ "{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
203
+ end
204
+
205
+ def test_render_tag_with_drop
206
+ Liquid::Template.file_system = StubFileSystem.new(
207
+ 'loop' => "{{ value }}",
208
+ )
209
+
210
+ assert_template_result("TestEnumerable",
211
+ "{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
212
+ end
213
+ end