liquid 3.0.0 → 4.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.
- checksums.yaml +4 -4
- data/History.md +130 -62
- data/README.md +31 -0
- data/lib/liquid/block.rb +31 -124
- data/lib/liquid/block_body.rb +75 -59
- data/lib/liquid/condition.rb +23 -22
- data/lib/liquid/context.rb +51 -60
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +17 -16
- data/lib/liquid/errors.rb +20 -24
- data/lib/liquid/expression.rb +15 -3
- data/lib/liquid/extensions.rb +13 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +5 -5
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +6 -4
- data/lib/liquid/locales/en.yml +5 -1
- data/lib/liquid/parse_context.rb +37 -0
- data/lib/liquid/parser.rb +1 -1
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +121 -61
- data/lib/liquid/strainer.rb +32 -29
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +17 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +2 -2
- data/lib/liquid/tags/case.rb +19 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +6 -6
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +95 -75
- data/lib/liquid/tags/if.rb +48 -43
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +61 -52
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +12 -31
- data/lib/liquid/tags/unless.rb +4 -5
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +46 -45
- data/lib/liquid/variable_lookup.rb +9 -5
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +9 -7
- data/test/integration/assign_test.rb +18 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/capture_test.rb +10 -0
- data/test/integration/context_test.rb +2 -2
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +42 -40
- data/test/integration/error_handling_test.rb +99 -46
- data/test/integration/filter_test.rb +72 -19
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +34 -27
- data/test/integration/parsing_quirks_test.rb +15 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +9 -7
- data/test/integration/standard_filter_test.rb +198 -42
- data/test/integration/tags/break_tag_test.rb +1 -2
- data/test/integration/tags/continue_tag_test.rb +0 -1
- data/test/integration/tags/for_tag_test.rb +133 -98
- data/test/integration/tags/if_else_tag_test.rb +96 -77
- data/test/integration/tags/include_tag_test.rb +34 -30
- data/test/integration/tags/increment_tag_test.rb +10 -11
- data/test/integration/tags/raw_tag_test.rb +7 -1
- data/test/integration/tags/standard_tag_test.rb +121 -122
- data/test/integration/tags/statements_test.rb +3 -5
- data/test/integration/tags/table_row_test.rb +20 -19
- data/test/integration/tags/unless_else_tag_test.rb +6 -6
- data/test/integration/template_test.rb +190 -49
- data/test/integration/trim_mode_test.rb +525 -0
- data/test/integration/variable_test.rb +23 -13
- data/test/test_helper.rb +44 -9
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +86 -77
- data/test/unit/context_unit_test.rb +48 -57
- data/test/unit/file_system_unit_test.rb +3 -3
- data/test/unit/i18n_unit_test.rb +2 -2
- data/test/unit/lexer_unit_test.rb +11 -8
- data/test/unit/parser_unit_test.rb +2 -2
- data/test/unit/regexp_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +85 -8
- data/test/unit/tag_unit_test.rb +7 -2
- data/test/unit/tags/case_tag_unit_test.rb +1 -1
- data/test/unit/tags/for_tag_unit_test.rb +2 -2
- data/test/unit/tags/if_tag_unit_test.rb +1 -1
- data/test/unit/template_unit_test.rb +14 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +66 -43
- metadata +55 -50
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/token.rb +0 -18
- data/test/unit/module_ex_unit_test.rb +0 -87
- /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -3,26 +3,36 @@ require 'test_helper'
|
|
3
3
|
class AssignTest < Minitest::Test
|
4
4
|
include Liquid
|
5
5
|
|
6
|
+
def test_assign_with_hyphen_in_variable_name
|
7
|
+
template_source = <<-END_TEMPLATE
|
8
|
+
{% assign this-thing = 'Print this-thing' %}
|
9
|
+
{{ this-thing }}
|
10
|
+
END_TEMPLATE
|
11
|
+
template = Template.parse(template_source)
|
12
|
+
rendered = template.render!
|
13
|
+
assert_equal "Print this-thing", rendered.strip
|
14
|
+
end
|
15
|
+
|
6
16
|
def test_assigned_variable
|
7
17
|
assert_template_result('.foo.',
|
8
|
-
|
9
|
-
|
18
|
+
'{% assign foo = values %}.{{ foo[0] }}.',
|
19
|
+
'values' => %w(foo bar baz))
|
10
20
|
|
11
21
|
assert_template_result('.bar.',
|
12
|
-
|
13
|
-
|
22
|
+
'{% assign foo = values %}.{{ foo[1] }}.',
|
23
|
+
'values' => %w(foo bar baz))
|
14
24
|
end
|
15
25
|
|
16
26
|
def test_assign_with_filter
|
17
27
|
assert_template_result('.bar.',
|
18
|
-
|
19
|
-
|
28
|
+
'{% assign foo = values | split: "," %}.{{ foo[1] }}.',
|
29
|
+
'values' => "foo,bar,baz")
|
20
30
|
end
|
21
31
|
|
22
32
|
def test_assign_syntax_error
|
23
33
|
assert_match_syntax_error(/assign/,
|
24
|
-
|
25
|
-
|
34
|
+
'{% assign foo not values %}.',
|
35
|
+
'values' => "foo,bar,baz")
|
26
36
|
end
|
27
37
|
|
28
38
|
def test_assign_uses_error_mode
|
@@ -9,7 +9,7 @@ class FoobarTag < Liquid::Tag
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class BlankTestFileSystem
|
12
|
-
def read_template_file(template_path
|
12
|
+
def read_template_file(template_path)
|
13
13
|
template_path
|
14
14
|
end
|
15
15
|
end
|
@@ -31,7 +31,7 @@ class BlankTest < Minitest::Test
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def test_new_tags_are_not_blank_by_default
|
34
|
-
assert_template_result(" "*N, wrap_in_for("{% foobar %}"))
|
34
|
+
assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_loops_are_blank
|
@@ -47,7 +47,7 @@ class BlankTest < Minitest::Test
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def test_mark_as_blank_only_during_parsing
|
50
|
-
assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
|
50
|
+
assert_template_result(" " * (N + 1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_comments_are_blank
|
@@ -60,9 +60,9 @@ class BlankTest < Minitest::Test
|
|
60
60
|
|
61
61
|
def test_nested_blocks_are_blank_but_only_if_all_children_are
|
62
62
|
assert_template_result("", wrap(wrap(" ")))
|
63
|
-
assert_template_result("\n but this is not "*(N+1),
|
64
|
-
wrap(
|
65
|
-
{% if true %} but this is not {% endif %}
|
63
|
+
assert_template_result("\n but this is not " * (N + 1),
|
64
|
+
wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
|
65
|
+
{% if true %} but this is not {% endif %}'))
|
66
66
|
end
|
67
67
|
|
68
68
|
def test_assigns_are_blank
|
@@ -76,31 +76,31 @@ class BlankTest < Minitest::Test
|
|
76
76
|
|
77
77
|
def test_whitespace_is_not_blank_if_other_stuff_is_present
|
78
78
|
body = " x "
|
79
|
-
assert_template_result(body*(N+1), wrap(body))
|
79
|
+
assert_template_result(body * (N + 1), wrap(body))
|
80
80
|
end
|
81
81
|
|
82
82
|
def test_increment_is_not_blank
|
83
|
-
assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
|
83
|
+
assert_template_result(" 0" * 2 * (N + 1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
|
84
84
|
end
|
85
85
|
|
86
86
|
def test_cycle_is_not_blank
|
87
|
-
assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}"))
|
87
|
+
assert_template_result(" " * ((N + 1) / 2) + " ", wrap("{% cycle ' ', ' ' %}"))
|
88
88
|
end
|
89
89
|
|
90
90
|
def test_raw_is_not_blank
|
91
|
-
assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
|
91
|
+
assert_template_result(" " * (N + 1), wrap(" {% raw %} {% endraw %}"))
|
92
92
|
end
|
93
93
|
|
94
94
|
def test_include_is_blank
|
95
95
|
Liquid::Template.file_system = BlankTestFileSystem.new
|
96
|
-
assert_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}")
|
97
|
-
assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}")
|
98
|
-
assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
|
96
|
+
assert_template_result "foobar" * (N + 1), wrap("{% include 'foobar' %}")
|
97
|
+
assert_template_result " foobar " * (N + 1), wrap("{% include ' foobar ' %}")
|
98
|
+
assert_template_result " " * (N + 1), wrap(" {% include ' ' %} ")
|
99
99
|
end
|
100
100
|
|
101
101
|
def test_case_is_blank
|
102
102
|
assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
|
103
103
|
assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
|
104
|
-
assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
|
104
|
+
assert_template_result(" x " * (N + 1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
|
105
105
|
end
|
106
106
|
end
|
@@ -7,6 +7,16 @@ class CaptureTest < Minitest::Test
|
|
7
7
|
assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
|
8
8
|
end
|
9
9
|
|
10
|
+
def test_capture_with_hyphen_in_variable_name
|
11
|
+
template_source = <<-END_TEMPLATE
|
12
|
+
{% capture this-thing %}Print this-thing{% endcapture %}
|
13
|
+
{{ this-thing }}
|
14
|
+
END_TEMPLATE
|
15
|
+
template = Template.parse(template_source)
|
16
|
+
rendered = template.render!
|
17
|
+
assert_equal "Print this-thing", rendered.strip
|
18
|
+
end
|
19
|
+
|
10
20
|
def test_capture_to_variable_from_outer_scope_if_existing
|
11
21
|
template_source = <<-END_TEMPLATE
|
12
22
|
{% assign var = '' %}
|
@@ -18,14 +18,14 @@ class ContextTest < Minitest::Test
|
|
18
18
|
|
19
19
|
with_global_filter(global) do
|
20
20
|
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
|
21
|
-
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :
|
21
|
+
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_has_key_will_not_add_an_error_for_missing_keys
|
26
26
|
with_error_mode :strict do
|
27
27
|
context = Context.new
|
28
|
-
context.
|
28
|
+
context.key?('unknown')
|
29
29
|
assert_empty context.errors
|
30
30
|
end
|
31
31
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class DocumentTest < Minitest::Test
|
4
|
+
include Liquid
|
5
|
+
|
6
|
+
def test_unexpected_outer_tag
|
7
|
+
exc = assert_raises(SyntaxError) do
|
8
|
+
Template.parse("{% else %}")
|
9
|
+
end
|
10
|
+
assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_unknown_tag
|
14
|
+
exc = assert_raises(SyntaxError) do
|
15
|
+
Template.parse("{% foo %}")
|
16
|
+
end
|
17
|
+
assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
|
18
|
+
end
|
19
|
+
end
|
@@ -13,13 +13,12 @@ class ContextDrop < Liquid::Drop
|
|
13
13
|
@context['forloop.index']
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
16
|
+
def liquid_method_missing(method)
|
17
|
+
@context[method]
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
class ProductDrop < Liquid::Drop
|
22
|
-
|
23
22
|
class TextDrop < Liquid::Drop
|
24
23
|
def array
|
25
24
|
['text1', 'text2']
|
@@ -31,8 +30,8 @@ class ProductDrop < Liquid::Drop
|
|
31
30
|
end
|
32
31
|
|
33
32
|
class CatchallDrop < Liquid::Drop
|
34
|
-
def
|
35
|
-
|
33
|
+
def liquid_method_missing(method)
|
34
|
+
'catchall_method: ' << method.to_s
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
@@ -53,13 +52,14 @@ class ProductDrop < Liquid::Drop
|
|
53
52
|
end
|
54
53
|
|
55
54
|
protected
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
|
56
|
+
def callmenot
|
57
|
+
"protected"
|
58
|
+
end
|
59
59
|
end
|
60
60
|
|
61
61
|
class EnumerableDrop < Liquid::Drop
|
62
|
-
def
|
62
|
+
def liquid_method_missing(method)
|
63
63
|
method
|
64
64
|
end
|
65
65
|
|
@@ -93,7 +93,7 @@ end
|
|
93
93
|
class RealEnumerableDrop < Liquid::Drop
|
94
94
|
include Enumerable
|
95
95
|
|
96
|
-
def
|
96
|
+
def liquid_method_missing(method)
|
97
97
|
method
|
98
98
|
end
|
99
99
|
|
@@ -124,8 +124,10 @@ class DropsTest < Minitest::Test
|
|
124
124
|
def test_rendering_warns_on_tainted_attr
|
125
125
|
with_taint_mode(:warn) do
|
126
126
|
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
127
|
-
|
128
|
-
|
127
|
+
context = Context.new('product' => ProductDrop.new)
|
128
|
+
tpl.render!(context)
|
129
|
+
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
130
|
+
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
@@ -151,37 +153,37 @@ class DropsTest < Minitest::Test
|
|
151
153
|
end
|
152
154
|
|
153
155
|
def test_text_drop
|
154
|
-
output = Liquid::Template.parse(
|
156
|
+
output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
|
155
157
|
assert_equal ' text1 ', output
|
156
158
|
end
|
157
159
|
|
158
|
-
def
|
159
|
-
output = Liquid::Template.parse(
|
160
|
-
assert_equal '
|
160
|
+
def test_catchall_unknown_method
|
161
|
+
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
|
162
|
+
assert_equal ' catchall_method: unknown ', output
|
161
163
|
end
|
162
164
|
|
163
|
-
def
|
164
|
-
output = Liquid::Template.parse(
|
165
|
-
assert_equal '
|
165
|
+
def test_catchall_integer_argument_drop
|
166
|
+
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
|
167
|
+
assert_equal ' catchall_method: 8 ', output
|
166
168
|
end
|
167
169
|
|
168
170
|
def test_text_array_drop
|
169
|
-
output = Liquid::Template.parse(
|
171
|
+
output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
|
170
172
|
assert_equal ' text1 text2 ', output
|
171
173
|
end
|
172
174
|
|
173
175
|
def test_context_drop
|
174
|
-
output = Liquid::Template.parse(
|
176
|
+
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
|
175
177
|
assert_equal ' carrot ', output
|
176
178
|
end
|
177
179
|
|
178
180
|
def test_nested_context_drop
|
179
|
-
output = Liquid::Template.parse(
|
181
|
+
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
|
180
182
|
assert_equal ' monkey ', output
|
181
183
|
end
|
182
184
|
|
183
185
|
def test_protected
|
184
|
-
output = Liquid::Template.parse(
|
186
|
+
output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
|
185
187
|
assert_equal ' ', output
|
186
188
|
end
|
187
189
|
|
@@ -193,43 +195,43 @@ class DropsTest < Minitest::Test
|
|
193
195
|
end
|
194
196
|
|
195
197
|
def test_scope
|
196
|
-
assert_equal '1', Liquid::Template.parse(
|
197
|
-
assert_equal '2', Liquid::Template.parse(
|
198
|
-
assert_equal '3', Liquid::Template.parse(
|
198
|
+
assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
|
199
|
+
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
200
|
+
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
199
201
|
end
|
200
202
|
|
201
203
|
def test_scope_though_proc
|
202
|
-
assert_equal '1', Liquid::Template.parse(
|
203
|
-
assert_equal '2', Liquid::Template.parse(
|
204
|
-
assert_equal '3', Liquid::Template.parse(
|
204
|
+
assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
|
205
|
+
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
|
206
|
+
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
|
205
207
|
end
|
206
208
|
|
207
209
|
def test_scope_with_assigns
|
208
|
-
assert_equal 'variable', Liquid::Template.parse(
|
209
|
-
assert_equal 'variable', Liquid::Template.parse(
|
210
|
-
assert_equal 'test', Liquid::Template.parse(
|
211
|
-
assert_equal 'test', Liquid::Template.parse(
|
210
|
+
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
|
211
|
+
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
212
|
+
assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
|
213
|
+
assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
|
212
214
|
end
|
213
215
|
|
214
216
|
def test_scope_from_tags
|
215
|
-
assert_equal '1', Liquid::Template.parse(
|
216
|
-
assert_equal '12', Liquid::Template.parse(
|
217
|
-
assert_equal '123', Liquid::Template.parse(
|
217
|
+
assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
218
|
+
assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
219
|
+
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
218
220
|
end
|
219
221
|
|
220
222
|
def test_access_context_from_drop
|
221
|
-
assert_equal '123', Liquid::Template.parse(
|
223
|
+
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
|
222
224
|
end
|
223
225
|
|
224
226
|
def test_enumerable_drop
|
225
|
-
assert_equal '123', Liquid::Template.parse(
|
227
|
+
assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
|
226
228
|
end
|
227
229
|
|
228
230
|
def test_enumerable_drop_size
|
229
|
-
assert_equal '3', Liquid::Template.parse(
|
231
|
+
assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
|
230
232
|
end
|
231
233
|
|
232
|
-
def
|
234
|
+
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
|
233
235
|
["select", "each", "map", "cycle"].each do |method|
|
234
236
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
235
237
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
@@ -1,24 +1,5 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class ErrorDrop < Liquid::Drop
|
4
|
-
def standard_error
|
5
|
-
raise Liquid::StandardError, 'standard error'
|
6
|
-
end
|
7
|
-
|
8
|
-
def argument_error
|
9
|
-
raise Liquid::ArgumentError, 'argument error'
|
10
|
-
end
|
11
|
-
|
12
|
-
def syntax_error
|
13
|
-
raise Liquid::SyntaxError, 'syntax error'
|
14
|
-
end
|
15
|
-
|
16
|
-
def exception
|
17
|
-
raise Exception, 'exception'
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
3
|
class ErrorHandlingTest < Minitest::Test
|
23
4
|
include Liquid
|
24
5
|
|
@@ -56,7 +37,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
56
37
|
end
|
57
38
|
|
58
39
|
def test_standard_error
|
59
|
-
template = Liquid::Template.parse(
|
40
|
+
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
|
60
41
|
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
|
61
42
|
|
62
43
|
assert_equal 1, template.errors.size
|
@@ -64,7 +45,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
64
45
|
end
|
65
46
|
|
66
47
|
def test_syntax
|
67
|
-
template = Liquid::Template.parse(
|
48
|
+
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
|
68
49
|
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
|
69
50
|
|
70
51
|
assert_equal 1, template.errors.size
|
@@ -72,7 +53,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
72
53
|
end
|
73
54
|
|
74
55
|
def test_argument
|
75
|
-
template = Liquid::Template.parse(
|
56
|
+
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
|
76
57
|
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
|
77
58
|
|
78
59
|
assert_equal 1, template.errors.size
|
@@ -94,7 +75,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
94
75
|
end
|
95
76
|
|
96
77
|
def test_lax_unrecognized_operator
|
97
|
-
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :
|
78
|
+
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
|
98
79
|
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
99
80
|
assert_equal 1, template.errors.size
|
100
81
|
assert_equal Liquid::ArgumentError, template.errors.first.class
|
@@ -102,31 +83,47 @@ class ErrorHandlingTest < Minitest::Test
|
|
102
83
|
|
103
84
|
def test_with_line_numbers_adds_numbers_to_parser_errors
|
104
85
|
err = assert_raises(SyntaxError) do
|
105
|
-
|
86
|
+
Liquid::Template.parse(%q(
|
106
87
|
foobar
|
107
88
|
|
108
89
|
{% "cat" | foobar %}
|
109
90
|
|
110
91
|
bla
|
111
|
-
|
112
|
-
:
|
92
|
+
),
|
93
|
+
line_numbers: true
|
113
94
|
)
|
114
95
|
end
|
115
96
|
|
116
|
-
assert_match
|
97
|
+
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
|
101
|
+
err = assert_raises(SyntaxError) do
|
102
|
+
Liquid::Template.parse(%q(
|
103
|
+
foobar
|
104
|
+
|
105
|
+
{%- "cat" | foobar -%}
|
106
|
+
|
107
|
+
bla
|
108
|
+
),
|
109
|
+
line_numbers: true
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
117
114
|
end
|
118
115
|
|
119
116
|
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
120
|
-
template = Liquid::Template.parse(
|
117
|
+
template = Liquid::Template.parse('
|
121
118
|
foobar
|
122
119
|
|
123
120
|
{% if 1 =! 2 %}ok{% endif %}
|
124
121
|
|
125
122
|
bla
|
126
|
-
|
127
|
-
:
|
128
|
-
:
|
129
|
-
|
123
|
+
',
|
124
|
+
error_mode: :warn,
|
125
|
+
line_numbers: true
|
126
|
+
)
|
130
127
|
|
131
128
|
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
|
132
129
|
template.warnings.map(&:message)
|
@@ -134,16 +131,16 @@ class ErrorHandlingTest < Minitest::Test
|
|
134
131
|
|
135
132
|
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
|
136
133
|
err = assert_raises(SyntaxError) do
|
137
|
-
Liquid::Template.parse(
|
134
|
+
Liquid::Template.parse('
|
138
135
|
foobar
|
139
136
|
|
140
137
|
{% if 1 =! 2 %}ok{% endif %}
|
141
138
|
|
142
139
|
bla
|
143
|
-
|
144
|
-
:
|
145
|
-
:
|
146
|
-
|
140
|
+
',
|
141
|
+
error_mode: :strict,
|
142
|
+
line_numbers: true
|
143
|
+
)
|
147
144
|
end
|
148
145
|
|
149
146
|
assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
|
@@ -151,7 +148,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
151
148
|
|
152
149
|
def test_syntax_errors_in_nested_blocks_have_correct_line_number
|
153
150
|
err = assert_raises(SyntaxError) do
|
154
|
-
Liquid::Template.parse(
|
151
|
+
Liquid::Template.parse('
|
155
152
|
foobar
|
156
153
|
|
157
154
|
{% if 1 != 2 %}
|
@@ -159,9 +156,9 @@ class ErrorHandlingTest < Minitest::Test
|
|
159
156
|
{% endif %}
|
160
157
|
|
161
158
|
bla
|
162
|
-
|
163
|
-
:
|
164
|
-
|
159
|
+
',
|
160
|
+
line_numbers: true
|
161
|
+
)
|
165
162
|
end
|
166
163
|
|
167
164
|
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
|
@@ -169,18 +166,18 @@ class ErrorHandlingTest < Minitest::Test
|
|
169
166
|
|
170
167
|
def test_strict_error_messages
|
171
168
|
err = assert_raises(SyntaxError) do
|
172
|
-
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :
|
169
|
+
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
|
173
170
|
end
|
174
171
|
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
|
175
172
|
|
176
173
|
err = assert_raises(SyntaxError) do
|
177
|
-
Liquid::Template.parse('{{%%%}}', :
|
174
|
+
Liquid::Template.parse('{{%%%}}', error_mode: :strict)
|
178
175
|
end
|
179
176
|
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
|
180
177
|
end
|
181
178
|
|
182
179
|
def test_warnings
|
183
|
-
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :
|
180
|
+
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
|
184
181
|
assert_equal 3, template.warnings.size
|
185
182
|
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
|
186
183
|
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
|
@@ -189,12 +186,12 @@ class ErrorHandlingTest < Minitest::Test
|
|
189
186
|
end
|
190
187
|
|
191
188
|
def test_warning_line_numbers
|
192
|
-
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :
|
189
|
+
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
|
193
190
|
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
|
194
191
|
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
195
192
|
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
196
193
|
assert_equal 3, template.warnings.size
|
197
|
-
assert_equal [1,2,3], template.warnings.map(&:line_number)
|
194
|
+
assert_equal [1, 2, 3], template.warnings.map(&:line_number)
|
198
195
|
end
|
199
196
|
|
200
197
|
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
@@ -204,4 +201,60 @@ class ErrorHandlingTest < Minitest::Test
|
|
204
201
|
template.render('errors' => ErrorDrop.new)
|
205
202
|
end
|
206
203
|
end
|
204
|
+
|
205
|
+
def test_default_exception_renderer_with_internal_error
|
206
|
+
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
207
|
+
|
208
|
+
output = template.render({ 'errors' => ErrorDrop.new })
|
209
|
+
|
210
|
+
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
|
211
|
+
assert_equal [Liquid::InternalError], template.errors.map(&:class)
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_setting_default_exception_renderer
|
215
|
+
old_exception_renderer = Liquid::Template.default_exception_renderer
|
216
|
+
exceptions = []
|
217
|
+
Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
|
218
|
+
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
219
|
+
|
220
|
+
output = template.render({ 'errors' => ErrorDrop.new })
|
221
|
+
|
222
|
+
assert_equal 'This is a runtime error: ', output
|
223
|
+
assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
|
224
|
+
ensure
|
225
|
+
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_exception_renderer_exposing_non_liquid_error
|
229
|
+
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
230
|
+
exceptions = []
|
231
|
+
handler = ->(e) { exceptions << e; e.cause }
|
232
|
+
|
233
|
+
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
234
|
+
|
235
|
+
assert_equal 'This is a runtime error: runtime error', output
|
236
|
+
assert_equal [Liquid::InternalError], exceptions.map(&:class)
|
237
|
+
assert_equal exceptions, template.errors
|
238
|
+
assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
|
239
|
+
end
|
240
|
+
|
241
|
+
class TestFileSystem
|
242
|
+
def read_template_file(template_path)
|
243
|
+
"{{ errors.argument_error }}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_included_template_name_with_line_numbers
|
248
|
+
old_file_system = Liquid::Template.file_system
|
249
|
+
|
250
|
+
begin
|
251
|
+
Liquid::Template.file_system = TestFileSystem.new
|
252
|
+
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
253
|
+
page = template.render('errors' => ErrorDrop.new)
|
254
|
+
ensure
|
255
|
+
Liquid::Template.file_system = old_file_system
|
256
|
+
end
|
257
|
+
assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
|
258
|
+
assert_equal "product", template.errors.first.template_name
|
259
|
+
end
|
207
260
|
end
|