liquid 4.0.3 → 5.1.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +54 -0
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +166 -54
  6. data/lib/liquid/condition.rb +41 -20
  7. data/lib/liquid/context.rb +107 -52
  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 +20 -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 +13 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +95 -46
  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 +40 -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 +34 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +39 -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 +23 -15
  55. data/lib/liquid/template.rb +53 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +46 -41
  61. data/lib/liquid/variable_lookup.rb +11 -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 +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  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 +385 -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 +132 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +74 -32
  99. data/test/test_helper.rb +113 -22
  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 +16 -2
  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 +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +76 -50
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/lib/liquid/truffle.rb +0 -5
  123. data/test/truffle/truffle_test.rb +0 -9
  124. data/test/unit/context_unit_test.rb +0 -489
  125. 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
  require 'timeout'
3
5
 
@@ -38,8 +40,8 @@ class TemplateTest < Minitest::Test
38
40
 
39
41
  def test_instance_assigns_persist_on_same_template_object_between_parses
40
42
  t = Template.new
41
- assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
42
- assert_equal 'from instance assigns', t.parse("{{ foo }}").render!
43
+ assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
44
+ assert_equal('from instance assigns', t.parse("{{ foo }}").render!)
43
45
  end
44
46
 
45
47
  def test_warnings_is_not_exponential_time
@@ -49,251 +51,243 @@ class TemplateTest < Minitest::Test
49
51
  end
50
52
 
51
53
  t = Template.parse(str)
52
- assert_equal [], Timeout.timeout(1) { t.warnings }
54
+ assert_equal([], Timeout.timeout(1) { t.warnings })
53
55
  end
54
56
 
55
57
  def test_instance_assigns_persist_on_same_template_parsing_between_renders
56
58
  t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
57
- assert_equal 'foo', t.render!
58
- assert_equal 'foofoo', t.render!
59
+ assert_equal('foo', t.render!)
60
+ assert_equal('foofoo', t.render!)
59
61
  end
60
62
 
61
63
  def test_custom_assigns_do_not_persist_on_same_template
62
64
  t = Template.new
63
- assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
64
- assert_equal '', t.parse("{{ foo }}").render!
65
+ assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
66
+ assert_equal('', t.parse("{{ foo }}").render!)
65
67
  end
66
68
 
67
69
  def test_custom_assigns_squash_instance_assigns
68
70
  t = Template.new
69
- assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
70
- assert_equal 'from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns')
71
+ assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
72
+ assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
71
73
  end
72
74
 
73
75
  def test_persistent_assigns_squash_instance_assigns
74
76
  t = Template.new
75
- assert_equal 'from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!
77
+ assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
76
78
  t.assigns['foo'] = 'from persistent assigns'
77
- assert_equal 'from persistent assigns', t.parse("{{ foo }}").render!
79
+ assert_equal('from persistent assigns', t.parse("{{ foo }}").render!)
78
80
  end
79
81
 
80
82
  def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
81
83
  t = Template.new
82
- t.assigns['number'] = -> { @global ||= 0; @global += 1 }
83
- assert_equal '1', t.parse("{{number}}").render!
84
- assert_equal '1', t.parse("{{number}}").render!
85
- assert_equal '1', t.render!
84
+ t.assigns['number'] = -> {
85
+ @global ||= 0
86
+ @global += 1
87
+ }
88
+ assert_equal('1', t.parse("{{number}}").render!)
89
+ assert_equal('1', t.parse("{{number}}").render!)
90
+ assert_equal('1', t.render!)
86
91
  @global = nil
87
92
  end
88
93
 
89
94
  def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
90
95
  t = Template.new
91
- assigns = { 'number' => -> { @global ||= 0; @global += 1 } }
92
- assert_equal '1', t.parse("{{number}}").render!(assigns)
93
- assert_equal '1', t.parse("{{number}}").render!(assigns)
94
- assert_equal '1', t.render!(assigns)
96
+ assigns = { 'number' => -> {
97
+ @global ||= 0
98
+ @global += 1
99
+ } }
100
+ assert_equal('1', t.parse("{{number}}").render!(assigns))
101
+ assert_equal('1', t.parse("{{number}}").render!(assigns))
102
+ assert_equal('1', t.render!(assigns))
95
103
  @global = nil
96
104
  end
97
105
 
98
106
  def test_resource_limits_works_with_custom_length_method
99
107
  t = Template.parse("{% assign foo = bar %}")
100
108
  t.resource_limits.render_length_limit = 42
101
- assert_equal "", t.render!("bar" => SomethingWithLength.new)
109
+ assert_equal("", t.render!("bar" => SomethingWithLength.new))
102
110
  end
103
111
 
104
112
  def test_resource_limits_render_length
105
113
  t = Template.parse("0123456789")
106
- t.resource_limits.render_length_limit = 5
107
- assert_equal "Liquid error: Memory limits exceeded", t.render
108
- assert t.resource_limits.reached?
114
+ t.resource_limits.render_length_limit = 9
115
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
116
+ assert(t.resource_limits.reached?)
109
117
 
110
118
  t.resource_limits.render_length_limit = 10
111
- assert_equal "0123456789", t.render!
112
- refute_nil t.resource_limits.render_length
119
+ assert_equal("0123456789", t.render!)
113
120
  end
114
121
 
115
122
  def test_resource_limits_render_score
116
123
  t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
117
124
  t.resource_limits.render_score_limit = 50
118
- assert_equal "Liquid error: Memory limits exceeded", t.render
119
- assert t.resource_limits.reached?
125
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
126
+ assert(t.resource_limits.reached?)
120
127
 
121
128
  t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
122
129
  t.resource_limits.render_score_limit = 50
123
- assert_equal "Liquid error: Memory limits exceeded", t.render
124
- assert t.resource_limits.reached?
130
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
131
+ assert(t.resource_limits.reached?)
125
132
 
126
133
  t.resource_limits.render_score_limit = 200
127
- assert_equal (" foo " * 100), t.render!
128
- refute_nil t.resource_limits.render_score
129
- end
130
-
131
- def test_resource_limits_assign_score
132
- t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
133
- t.resource_limits.assign_score_limit = 1
134
- assert_equal "Liquid error: Memory limits exceeded", t.render
135
- assert t.resource_limits.reached?
136
-
137
- t.resource_limits.assign_score_limit = 2
138
- assert_equal "", t.render!
139
- refute_nil t.resource_limits.assign_score
140
- end
141
-
142
- def test_resource_limits_assign_score_nested
143
- t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
144
-
145
- t.resource_limits.assign_score_limit = 3
146
- assert_equal "Liquid error: Memory limits exceeded", t.render
147
- assert t.resource_limits.reached?
148
-
149
- t.resource_limits.assign_score_limit = 5
150
- assert_equal "", t.render!
134
+ assert_equal((" foo " * 100), t.render!)
135
+ refute_nil(t.resource_limits.render_score)
151
136
  end
152
137
 
153
138
  def test_resource_limits_aborts_rendering_after_first_error
154
139
  t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
155
140
  t.resource_limits.render_score_limit = 50
156
- assert_equal "Liquid error: Memory limits exceeded", t.render
157
- assert t.resource_limits.reached?
141
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
142
+ assert(t.resource_limits.reached?)
158
143
  end
159
144
 
160
145
  def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
161
- t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
146
+ t = Template.parse("{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}")
162
147
  t.render!
163
- assert t.resource_limits.assign_score > 0
164
- assert t.resource_limits.render_score > 0
165
- assert t.resource_limits.render_length > 0
148
+ assert(t.resource_limits.assign_score > 0)
149
+ assert(t.resource_limits.render_score > 0)
166
150
  end
167
151
 
168
152
  def test_render_length_persists_between_blocks
169
153
  t = Template.parse("{% if true %}aaaa{% endif %}")
170
- t.resource_limits.render_length_limit = 7
171
- assert_equal "Liquid error: Memory limits exceeded", t.render
172
- t.resource_limits.render_length_limit = 8
173
- assert_equal "aaaa", t.render
154
+ t.resource_limits.render_length_limit = 3
155
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
156
+ t.resource_limits.render_length_limit = 4
157
+ assert_equal("aaaa", t.render)
174
158
 
175
159
  t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
176
- t.resource_limits.render_length_limit = 13
177
- assert_equal "Liquid error: Memory limits exceeded", t.render
178
- t.resource_limits.render_length_limit = 14
179
- assert_equal "aaaabbb", t.render
160
+ t.resource_limits.render_length_limit = 6
161
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
162
+ t.resource_limits.render_length_limit = 7
163
+ assert_equal("aaaabbb", t.render)
180
164
 
181
165
  t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
182
166
  t.resource_limits.render_length_limit = 5
183
- assert_equal "Liquid error: Memory limits exceeded", t.render
184
- t.resource_limits.render_length_limit = 11
185
- assert_equal "Liquid error: Memory limits exceeded", t.render
186
- t.resource_limits.render_length_limit = 12
187
- assert_equal "ababab", t.render
167
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
168
+ t.resource_limits.render_length_limit = 6
169
+ assert_equal("ababab", t.render)
170
+ end
171
+
172
+ def test_render_length_uses_number_of_bytes_not_characters
173
+ t = Template.parse("{% if true %}すごい{% endif %}")
174
+ t.resource_limits.render_length_limit = 8
175
+ assert_equal("Liquid error: Memory limits exceeded", t.render)
176
+ t.resource_limits.render_length_limit = 9
177
+ assert_equal("すごい", t.render)
188
178
  end
189
179
 
190
180
  def test_default_resource_limits_unaffected_by_render_with_context
191
181
  context = Context.new
192
- t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
182
+ t = Template.parse("{% for a in (1..100) %}x{% assign foo = 1 %} {% endfor %}")
193
183
  t.render!(context)
194
- assert context.resource_limits.assign_score > 0
195
- assert context.resource_limits.render_score > 0
196
- assert context.resource_limits.render_length > 0
184
+ assert(context.resource_limits.assign_score > 0)
185
+ assert(context.resource_limits.render_score > 0)
197
186
  end
198
187
 
199
188
  def test_can_use_drop_as_context
200
189
  t = Template.new
201
190
  t.registers['lulz'] = 'haha'
202
191
  drop = TemplateContextDrop.new
203
- assert_equal 'fizzbuzz', t.parse('{{foo}}').render!(drop)
204
- assert_equal 'bar', t.parse('{{bar}}').render!(drop)
205
- assert_equal 'haha', t.parse("{{baz}}").render!(drop)
192
+ assert_equal('fizzbuzz', t.parse('{{foo}}').render!(drop))
193
+ assert_equal('bar', t.parse('{{bar}}').render!(drop))
194
+ assert_equal('haha', t.parse("{{baz}}").render!(drop))
206
195
  end
207
196
 
208
197
  def test_render_bang_force_rethrow_errors_on_passed_context
209
- context = Context.new({ 'drop' => ErroneousDrop.new })
198
+ context = Context.new('drop' => ErroneousDrop.new)
210
199
  t = Template.new.parse('{{ drop.bad_method }}')
211
200
 
212
- e = assert_raises RuntimeError do
201
+ e = assert_raises(RuntimeError) do
213
202
  t.render!(context)
214
203
  end
215
- assert_equal 'ruby error in drop', e.message
204
+ assert_equal('ruby error in drop', e.message)
216
205
  end
217
206
 
218
207
  def test_exception_renderer_that_returns_string
219
208
  exception = nil
220
- handler = ->(e) { exception = e; '<!-- error -->' }
209
+ handler = ->(e) {
210
+ exception = e
211
+ '<!-- error -->'
212
+ }
221
213
 
222
214
  output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
223
215
 
224
- assert exception.is_a?(Liquid::ZeroDivisionError)
225
- assert_equal '<!-- error -->', output
216
+ assert(exception.is_a?(Liquid::ZeroDivisionError))
217
+ assert_equal('<!-- error -->', output)
226
218
  end
227
219
 
228
220
  def test_exception_renderer_that_raises
229
221
  exception = nil
230
222
  assert_raises(Liquid::ZeroDivisionError) do
231
- Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
223
+ Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) {
224
+ exception = e
225
+ raise
226
+ })
232
227
  end
233
- assert exception.is_a?(Liquid::ZeroDivisionError)
228
+ assert(exception.is_a?(Liquid::ZeroDivisionError))
234
229
  end
235
230
 
236
231
  def test_global_filter_option_on_render
237
232
  global_filter_proc = ->(output) { "#{output} filtered" }
238
- rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
233
+ rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
239
234
 
240
- assert_equal 'bob filtered', rendered_template
235
+ assert_equal('bob filtered', rendered_template)
241
236
  end
242
237
 
243
238
  def test_global_filter_option_when_native_filters_exist
244
239
  global_filter_proc = ->(output) { "#{output} filtered" }
245
- rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
240
+ rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
246
241
 
247
- assert_equal 'BOB filtered', rendered_template
242
+ assert_equal('BOB filtered', rendered_template)
248
243
  end
249
244
 
250
245
  def test_undefined_variables
251
- t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
252
- result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
246
+ t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
247
+ result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
253
248
 
254
- assert_equal '33 32 ', result
255
- assert_equal 3, t.errors.count
256
- assert_instance_of Liquid::UndefinedVariable, t.errors[0]
257
- assert_equal 'Liquid error: undefined variable y', t.errors[0].message
258
- assert_instance_of Liquid::UndefinedVariable, t.errors[1]
259
- assert_equal 'Liquid error: undefined variable b', t.errors[1].message
260
- assert_instance_of Liquid::UndefinedVariable, t.errors[2]
261
- assert_equal 'Liquid error: undefined variable d', t.errors[2].message
249
+ assert_equal('33 32 ', result)
250
+ assert_equal(3, t.errors.count)
251
+ assert_instance_of(Liquid::UndefinedVariable, t.errors[0])
252
+ assert_equal('Liquid error: undefined variable y', t.errors[0].message)
253
+ assert_instance_of(Liquid::UndefinedVariable, t.errors[1])
254
+ assert_equal('Liquid error: undefined variable b', t.errors[1].message)
255
+ assert_instance_of(Liquid::UndefinedVariable, t.errors[2])
256
+ assert_equal('Liquid error: undefined variable d', t.errors[2].message)
262
257
  end
263
258
 
264
259
  def test_nil_value_does_not_raise
265
- Liquid::Template.error_mode = :strict
266
- t = Template.parse("some{{x}}thing")
260
+ t = Template.parse("some{{x}}thing", error_mode: :strict)
267
261
  result = t.render!({ 'x' => nil }, strict_variables: true)
268
262
 
269
- assert_equal 0, t.errors.count
270
- assert_equal 'something', result
263
+ assert_equal(0, t.errors.count)
264
+ assert_equal('something', result)
271
265
  end
272
266
 
273
267
  def test_undefined_variables_raise
274
268
  t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
275
269
 
276
- assert_raises UndefinedVariable do
277
- t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
270
+ assert_raises(UndefinedVariable) do
271
+ t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
278
272
  end
279
273
  end
280
274
 
281
275
  def test_undefined_drop_methods
282
276
  d = DropWithUndefinedMethod.new
283
277
  t = Template.new.parse('{{ foo }} {{ woot }}')
284
- result = t.render(d, { strict_variables: true })
278
+ result = t.render(d, strict_variables: true)
285
279
 
286
- assert_equal 'foo ', result
287
- assert_equal 1, t.errors.count
288
- assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
280
+ assert_equal('foo ', result)
281
+ assert_equal(1, t.errors.count)
282
+ assert_instance_of(Liquid::UndefinedDropMethod, t.errors[0])
289
283
  end
290
284
 
291
285
  def test_undefined_drop_methods_raise
292
286
  d = DropWithUndefinedMethod.new
293
287
  t = Template.new.parse('{{ foo }} {{ woot }}')
294
288
 
295
- assert_raises UndefinedDropMethod do
296
- t.render!(d, { strict_variables: true })
289
+ assert_raises(UndefinedDropMethod) do
290
+ t.render!(d, strict_variables: true)
297
291
  end
298
292
  end
299
293
 
@@ -304,29 +298,43 @@ class TemplateTest < Minitest::Test
304
298
  "-#{v}-"
305
299
  end
306
300
  end
307
- result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true })
301
+ result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)
308
302
 
309
- assert_equal '123 ', result
310
- assert_equal 1, t.errors.count
311
- assert_instance_of Liquid::UndefinedFilter, t.errors[0]
312
- assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
303
+ assert_equal('123 ', result)
304
+ assert_equal(1, t.errors.count)
305
+ assert_instance_of(Liquid::UndefinedFilter, t.errors[0])
306
+ assert_equal('Liquid error: undefined filter somefilter1', t.errors[0].message)
313
307
  end
314
308
 
315
309
  def test_undefined_filters_raise
316
310
  t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
317
311
 
318
- assert_raises UndefinedFilter do
319
- t.render!({ 'x' => 'foo' }, { strict_filters: true })
312
+ assert_raises(UndefinedFilter) do
313
+ t.render!({ 'x' => 'foo' }, strict_filters: true)
320
314
  end
321
315
  end
322
316
 
323
317
  def test_using_range_literal_works_as_expected
324
318
  t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
325
- result = t.render({ 'x' => 1, 'y' => 5 })
326
- assert_equal '1..5', result
319
+ result = t.render('x' => 1, 'y' => 5)
320
+ assert_equal('1..5', result)
327
321
 
328
322
  t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
329
- result = t.render({ 'x' => 1, 'y' => 5 })
330
- assert_equal '12345', result
323
+ result = t.render('x' => 1, 'y' => 5)
324
+ assert_equal('12345', result)
325
+ end
326
+
327
+ def test_source_string_subclass
328
+ string_subclass = Class.new(String) do
329
+ # E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String
330
+ def to_s
331
+ self
332
+ end
333
+ end
334
+ source = string_subclass.new("{% assign x = 2 -%} x= {{- x }}")
335
+ assert_instance_of(string_subclass, source)
336
+ output = Template.parse(source).render!
337
+ assert_equal("x=2", output)
338
+ assert_instance_of(String, output)
331
339
  end
332
340
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class TrimModeTest < Minitest::Test
@@ -67,7 +69,7 @@ class TrimModeTest < Minitest::Test
67
69
  # Make sure the trim isn't applied to standard tags
68
70
  def test_standard_tags
69
71
  whitespace = ' '
70
- text = <<-END_TEMPLATE
72
+ text = <<-END_TEMPLATE
71
73
  <div>
72
74
  <p>
73
75
  {% if true %}
@@ -76,14 +78,14 @@ class TrimModeTest < Minitest::Test
76
78
  </p>
77
79
  </div>
78
80
  END_TEMPLATE
79
- expected = <<-END_EXPECTED
80
- <div>
81
- <p>
82
- #{whitespace}
83
- yes
84
- #{whitespace}
85
- </p>
86
- </div>
81
+ expected = <<~END_EXPECTED
82
+ <div>
83
+ <p>
84
+ #{whitespace}
85
+ yes
86
+ #{whitespace}
87
+ </p>
88
+ </div>
87
89
  END_EXPECTED
88
90
  assert_template_result(expected, text)
89
91
 
@@ -96,70 +98,70 @@ class TrimModeTest < Minitest::Test
96
98
  </p>
97
99
  </div>
98
100
  END_TEMPLATE
99
- expected = <<-END_EXPECTED
100
- <div>
101
- <p>
102
- #{whitespace}
103
- </p>
104
- </div>
101
+ expected = <<~END_EXPECTED
102
+ <div>
103
+ <p>
104
+ #{whitespace}
105
+ </p>
106
+ </div>
105
107
  END_EXPECTED
106
108
  assert_template_result(expected, text)
107
109
  end
108
110
 
109
111
  # Make sure the trim isn't too agressive
110
112
  def test_no_trim_output
111
- text = '<p>{{- \'John\' -}}</p>'
113
+ text = '<p>{{- \'John\' -}}</p>'
112
114
  expected = '<p>John</p>'
113
115
  assert_template_result(expected, text)
114
116
  end
115
117
 
116
118
  # Make sure the trim isn't too agressive
117
119
  def test_no_trim_tags
118
- text = '<p>{%- if true -%}yes{%- endif -%}</p>'
120
+ text = '<p>{%- if true -%}yes{%- endif -%}</p>'
119
121
  expected = '<p>yes</p>'
120
122
  assert_template_result(expected, text)
121
123
 
122
- text = '<p>{%- if false -%}no{%- endif -%}</p>'
124
+ text = '<p>{%- if false -%}no{%- endif -%}</p>'
123
125
  expected = '<p></p>'
124
126
  assert_template_result(expected, text)
125
127
  end
126
128
 
127
129
  def test_single_line_outer_tag
128
- text = '<p> {%- if true %} yes {% endif -%} </p>'
130
+ text = '<p> {%- if true %} yes {% endif -%} </p>'
129
131
  expected = '<p> yes </p>'
130
132
  assert_template_result(expected, text)
131
133
 
132
- text = '<p> {%- if false %} no {% endif -%} </p>'
134
+ text = '<p> {%- if false %} no {% endif -%} </p>'
133
135
  expected = '<p></p>'
134
136
  assert_template_result(expected, text)
135
137
  end
136
138
 
137
139
  def test_single_line_inner_tag
138
- text = '<p> {% if true -%} yes {%- endif %} </p>'
140
+ text = '<p> {% if true -%} yes {%- endif %} </p>'
139
141
  expected = '<p> yes </p>'
140
142
  assert_template_result(expected, text)
141
143
 
142
- text = '<p> {% if false -%} no {%- endif %} </p>'
144
+ text = '<p> {% if false -%} no {%- endif %} </p>'
143
145
  expected = '<p> </p>'
144
146
  assert_template_result(expected, text)
145
147
  end
146
148
 
147
149
  def test_single_line_post_tag
148
- text = '<p> {% if true -%} yes {% endif -%} </p>'
150
+ text = '<p> {% if true -%} yes {% endif -%} </p>'
149
151
  expected = '<p> yes </p>'
150
152
  assert_template_result(expected, text)
151
153
 
152
- text = '<p> {% if false -%} no {% endif -%} </p>'
154
+ text = '<p> {% if false -%} no {% endif -%} </p>'
153
155
  expected = '<p> </p>'
154
156
  assert_template_result(expected, text)
155
157
  end
156
158
 
157
159
  def test_single_line_pre_tag
158
- text = '<p> {%- if true %} yes {%- endif %} </p>'
160
+ text = '<p> {%- if true %} yes {%- endif %} </p>'
159
161
  expected = '<p> yes </p>'
160
162
  assert_template_result(expected, text)
161
163
 
162
- text = '<p> {%- if false %} no {%- endif %} </p>'
164
+ text = '<p> {%- if false %} no {%- endif %} </p>'
163
165
  expected = '<p> </p>'
164
166
  assert_template_result(expected, text)
165
167
  end
@@ -328,7 +330,7 @@ class TrimModeTest < Minitest::Test
328
330
  assert_template_result(expected, text)
329
331
 
330
332
  whitespace = ' '
331
- text = <<-END_TEMPLATE
333
+ text = <<-END_TEMPLATE
332
334
  <div>
333
335
  <p>
334
336
  {% if false -%}
@@ -337,12 +339,12 @@ class TrimModeTest < Minitest::Test
337
339
  </p>
338
340
  </div>
339
341
  END_TEMPLATE
340
- expected = <<-END_EXPECTED
341
- <div>
342
- <p>
343
- #{whitespace}
344
- </p>
345
- </div>
342
+ expected = <<~END_EXPECTED
343
+ <div>
344
+ <p>
345
+ #{whitespace}
346
+ </p>
347
+ </div>
346
348
  END_EXPECTED
347
349
  assert_template_result(expected, text)
348
350
  end
@@ -502,7 +504,7 @@ class TrimModeTest < Minitest::Test
502
504
 
503
505
  def test_raw_output
504
506
  whitespace = ' '
505
- text = <<-END_TEMPLATE
507
+ text = <<-END_TEMPLATE
506
508
  <div>
507
509
  {% raw %}
508
510
  {%- if true -%}
@@ -513,17 +515,49 @@ class TrimModeTest < Minitest::Test
513
515
  {% endraw %}
514
516
  </div>
515
517
  END_TEMPLATE
516
- expected = <<-END_EXPECTED
517
- <div>
518
- #{whitespace}
519
- {%- if true -%}
520
- <p>
521
- {{- 'John' -}}
522
- </p>
523
- {%- endif -%}
524
- #{whitespace}
525
- </div>
518
+ expected = <<~END_EXPECTED
519
+ <div>
520
+ #{whitespace}
521
+ {%- if true -%}
522
+ <p>
523
+ {{- 'John' -}}
524
+ </p>
525
+ {%- endif -%}
526
+ #{whitespace}
527
+ </div>
526
528
  END_EXPECTED
527
529
  assert_template_result(expected, text)
528
530
  end
531
+
532
+ def test_pre_trim_blank_preceding_text
533
+ template = Liquid::Template.parse("\n{%- raw %}{% endraw %}")
534
+ assert_equal("", template.render)
535
+
536
+ template = Liquid::Template.parse("\n{%- if true %}{% endif %}")
537
+ assert_equal("", template.render)
538
+
539
+ template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}")
540
+ assert_equal("BC", template.render)
541
+ end
542
+
543
+ def test_bug_compatible_pre_trim
544
+ template = Liquid::Template.parse("\n {%- raw %}{% endraw %}", bug_compatible_whitespace_trimming: true)
545
+ assert_equal("\n", template.render)
546
+
547
+ template = Liquid::Template.parse("\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
548
+ assert_equal("\n", template.render)
549
+
550
+ template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}", bug_compatible_whitespace_trimming: true)
551
+ assert_equal("B C", template.render)
552
+
553
+ template = Liquid::Template.parse("B\n {%- raw %}{% endraw %}", bug_compatible_whitespace_trimming: true)
554
+ assert_equal("B", template.render)
555
+
556
+ template = Liquid::Template.parse("B\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
557
+ assert_equal("B", template.render)
558
+ end
559
+
560
+ def test_trim_blank
561
+ assert_template_result('foobar', 'foo {{-}} bar')
562
+ end
529
563
  end # TrimModeTest