liquid 4.0.0.rc3 → 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.
- checksums.yaml +5 -5
- data/History.md +93 -2
- data/README.md +8 -0
- data/lib/liquid.rb +18 -5
- data/lib/liquid/block.rb +47 -20
- data/lib/liquid/block_body.rb +190 -76
- data/lib/liquid/condition.rb +69 -29
- data/lib/liquid/context.rb +122 -76
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -25
- data/lib/liquid/expression.rb +30 -31
- data/lib/liquid/extensions.rb +8 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +11 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +35 -26
- data/lib/liquid/locales/en.yml +4 -2
- data/lib/liquid/parse_context.rb +17 -4
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/range_lookup.rb +5 -3
- data/lib/liquid/register.rb +6 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +171 -57
- data/lib/liquid/static_registers.rb +44 -0
- data/lib/liquid/strainer_factory.rb +36 -0
- data/lib/liquid/strainer_template.rb +53 -0
- data/lib/liquid/tablerowloop_drop.rb +6 -4
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tags/assign.rb +32 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +41 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +35 -16
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +26 -0
- data/lib/liquid/tags/for.rb +79 -47
- data/lib/liquid/tags/if.rb +53 -30
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +42 -44
- data/lib/liquid/tags/increment.rb +7 -3
- data/lib/liquid/tags/raw.rb +14 -11
- data/lib/liquid/tags/render.rb +84 -0
- data/lib/liquid/tags/table_row.rb +32 -20
- data/lib/liquid/tags/unless.rb +15 -15
- data/lib/liquid/template.rb +60 -71
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +17 -9
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +6 -4
- data/lib/liquid/variable.rb +55 -38
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +3 -1
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +58 -0
- data/test/integration/capture_test.rb +18 -10
- data/test/integration/context_test.rb +608 -5
- data/test/integration/document_test.rb +4 -2
- data/test/integration/drop_test.rb +67 -83
- data/test/integration/error_handling_test.rb +90 -60
- data/test/integration/expression_test.rb +46 -0
- data/test/integration/filter_test.rb +53 -42
- data/test/integration/hash_ordering_test.rb +5 -3
- data/test/integration/output_test.rb +26 -24
- data/test/integration/parsing_quirks_test.rb +24 -8
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +41 -18
- data/test/integration/standard_filter_test.rb +523 -205
- data/test/integration/tag/disableable_test.rb +59 -0
- data/test/integration/tag_test.rb +45 -0
- data/test/integration/tags/break_tag_test.rb +4 -2
- data/test/integration/tags/continue_tag_test.rb +4 -2
- data/test/integration/tags/echo_test.rb +13 -0
- data/test/integration/tags/for_tag_test.rb +109 -53
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +83 -52
- data/test/integration/tags/increment_tag_test.rb +4 -2
- data/test/integration/tags/liquid_tag_test.rb +116 -0
- data/test/integration/tags/raw_tag_test.rb +14 -11
- data/test/integration/tags/render_tag_test.rb +213 -0
- data/test/integration/tags/standard_tag_test.rb +38 -31
- data/test/integration/tags/statements_test.rb +23 -21
- data/test/integration/tags/table_row_test.rb +2 -0
- data/test/integration/tags/unless_else_tag_test.rb +4 -2
- data/test/integration/template_test.rb +128 -121
- data/test/integration/trim_mode_test.rb +82 -44
- data/test/integration/variable_test.rb +46 -31
- data/test/test_helper.rb +75 -23
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +82 -72
- data/test/unit/file_system_unit_test.rb +6 -4
- data/test/unit/i18n_unit_test.rb +7 -5
- data/test/unit/lexer_unit_test.rb +12 -10
- data/test/unit/parse_tree_visitor_test.rb +247 -0
- data/test/unit/parser_unit_test.rb +37 -35
- data/test/unit/partial_cache_unit_test.rb +128 -0
- data/test/unit/regexp_unit_test.rb +17 -15
- data/test/unit/static_registers_unit_test.rb +156 -0
- data/test/unit/strainer_factory_unit_test.rb +100 -0
- data/test/unit/strainer_template_unit_test.rb +82 -0
- data/test/unit/tag_unit_test.rb +5 -3
- data/test/unit/tags/case_tag_unit_test.rb +3 -1
- data/test/unit/tags/for_tag_unit_test.rb +4 -2
- data/test/unit/tags/if_tag_unit_test.rb +3 -1
- data/test/unit/template_factory_unit_test.rb +12 -0
- data/test/unit/template_unit_test.rb +19 -10
- data/test/unit/tokenizer_unit_test.rb +19 -17
- data/test/unit/variable_unit_test.rb +51 -49
- metadata +83 -50
- data/lib/liquid/strainer.rb +0 -65
- data/test/unit/context_unit_test.rb +0 -483
- data/test/unit/strainer_unit_test.rb +0 -136
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
|
-
class
|
5
|
+
class ProfilerTest < Minitest::Test
|
4
6
|
include Liquid
|
5
7
|
|
6
8
|
class ProfilingFileSystem
|
@@ -17,35 +19,35 @@ class RenderProfilingTest < Minitest::Test
|
|
17
19
|
t = Template.parse("{{ 'a string' | upcase }}")
|
18
20
|
t.render!
|
19
21
|
|
20
|
-
assert_nil
|
22
|
+
assert_nil(t.profiler)
|
21
23
|
end
|
22
24
|
|
23
25
|
def test_parse_makes_available_simple_profiling
|
24
26
|
t = Template.parse("{{ 'a string' | upcase }}", profile: true)
|
25
27
|
t.render!
|
26
28
|
|
27
|
-
assert_equal
|
29
|
+
assert_equal(1, t.profiler.length)
|
28
30
|
|
29
31
|
node = t.profiler[0]
|
30
|
-
assert_equal
|
32
|
+
assert_equal(" 'a string' | upcase ", node.code)
|
31
33
|
end
|
32
34
|
|
33
35
|
def test_render_ignores_raw_strings_when_profiling
|
34
36
|
t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
|
35
37
|
t.render!
|
36
38
|
|
37
|
-
assert_equal
|
39
|
+
assert_equal(0, t.profiler.length)
|
38
40
|
end
|
39
41
|
|
40
42
|
def test_profiling_includes_line_numbers_of_liquid_nodes
|
41
43
|
t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
|
42
44
|
t.render!
|
43
|
-
assert_equal
|
45
|
+
assert_equal(2, t.profiler.length)
|
44
46
|
|
45
47
|
# {{ 'a string' | upcase }}
|
46
|
-
assert_equal
|
48
|
+
assert_equal(1, t.profiler[0].line_number)
|
47
49
|
# {{ increment test }}
|
48
|
-
assert_equal
|
50
|
+
assert_equal(2, t.profiler[1].line_number)
|
49
51
|
end
|
50
52
|
|
51
53
|
def test_profiling_includes_line_numbers_of_included_partials
|
@@ -55,9 +57,20 @@ class RenderProfilingTest < Minitest::Test
|
|
55
57
|
included_children = t.profiler[0].children
|
56
58
|
|
57
59
|
# {% assign template_name = 'a_template' %}
|
58
|
-
assert_equal
|
60
|
+
assert_equal(1, included_children[0].line_number)
|
59
61
|
# {{ template_name }}
|
60
|
-
assert_equal
|
62
|
+
assert_equal(2, included_children[1].line_number)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_profiling_render_tag
|
66
|
+
t = Template.parse("{% render 'a_template' %}", profile: true)
|
67
|
+
t.render!
|
68
|
+
|
69
|
+
render_children = t.profiler[0].children
|
70
|
+
render_children.each do |timing|
|
71
|
+
assert_equal('a_template', timing.partial)
|
72
|
+
end
|
73
|
+
assert_equal([1, 2], render_children.map(&:line_number))
|
61
74
|
end
|
62
75
|
|
63
76
|
def test_profiling_times_the_rendering_of_tokens
|
@@ -65,14 +78,45 @@ class RenderProfilingTest < Minitest::Test
|
|
65
78
|
t.render!
|
66
79
|
|
67
80
|
node = t.profiler[0]
|
68
|
-
refute_nil
|
81
|
+
refute_nil(node.render_time)
|
69
82
|
end
|
70
83
|
|
71
84
|
def test_profiling_times_the_entire_render
|
72
85
|
t = Template.parse("{% include 'a_template' %}", profile: true)
|
73
86
|
t.render!
|
74
87
|
|
75
|
-
assert
|
88
|
+
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
|
89
|
+
end
|
90
|
+
|
91
|
+
class SleepTag < Liquid::Tag
|
92
|
+
def initialize(tag_name, markup, parse_context)
|
93
|
+
super
|
94
|
+
@duration = Float(markup)
|
95
|
+
end
|
96
|
+
|
97
|
+
def render_to_output_buffer(_context, _output)
|
98
|
+
sleep(@duration)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_profiling_multiple_renders
|
103
|
+
with_custom_tag('sleep', SleepTag) do
|
104
|
+
context = Liquid::Context.new
|
105
|
+
t = Liquid::Template.parse("{% sleep 0.001 %}", profile: true)
|
106
|
+
context.template_name = 'index'
|
107
|
+
t.render!(context)
|
108
|
+
context.template_name = 'layout'
|
109
|
+
first_render_time = context.profiler.total_time
|
110
|
+
t.render!(context)
|
111
|
+
|
112
|
+
profiler = context.profiler
|
113
|
+
children = profiler.children
|
114
|
+
assert_operator(first_render_time, :>=, 0.001)
|
115
|
+
assert_operator(profiler.total_time, :>=, 0.001 + first_render_time)
|
116
|
+
assert_equal(["index", "layout"], children.map(&:template_name))
|
117
|
+
assert_equal([nil, nil], children.map(&:code))
|
118
|
+
assert_equal(profiler.total_time, children.map(&:total_time).reduce(&:+))
|
119
|
+
end
|
76
120
|
end
|
77
121
|
|
78
122
|
def test_profiling_uses_include_to_mark_children
|
@@ -80,7 +124,7 @@ class RenderProfilingTest < Minitest::Test
|
|
80
124
|
t.render!
|
81
125
|
|
82
126
|
include_node = t.profiler[1]
|
83
|
-
assert_equal
|
127
|
+
assert_equal(2, include_node.children.length)
|
84
128
|
end
|
85
129
|
|
86
130
|
def test_profiling_marks_children_with_the_name_of_included_partial
|
@@ -89,7 +133,7 @@ class RenderProfilingTest < Minitest::Test
|
|
89
133
|
|
90
134
|
include_node = t.profiler[1]
|
91
135
|
include_node.children.each do |child|
|
92
|
-
assert_equal
|
136
|
+
assert_equal("a_template", child.partial)
|
93
137
|
end
|
94
138
|
end
|
95
139
|
|
@@ -99,12 +143,12 @@ class RenderProfilingTest < Minitest::Test
|
|
99
143
|
|
100
144
|
a_template = t.profiler[1]
|
101
145
|
a_template.children.each do |child|
|
102
|
-
assert_equal
|
146
|
+
assert_equal("a_template", child.partial)
|
103
147
|
end
|
104
148
|
|
105
149
|
b_template = t.profiler[2]
|
106
150
|
b_template.children.each do |child|
|
107
|
-
assert_equal
|
151
|
+
assert_equal("b_template", child.partial)
|
108
152
|
end
|
109
153
|
end
|
110
154
|
|
@@ -114,12 +158,12 @@ class RenderProfilingTest < Minitest::Test
|
|
114
158
|
|
115
159
|
a_template1 = t.profiler[1]
|
116
160
|
a_template1.children.each do |child|
|
117
|
-
assert_equal
|
161
|
+
assert_equal("a_template", child.partial)
|
118
162
|
end
|
119
163
|
|
120
164
|
a_template2 = t.profiler[2]
|
121
165
|
a_template2.children.each do |child|
|
122
|
-
assert_equal
|
166
|
+
assert_equal("a_template", child.partial)
|
123
167
|
end
|
124
168
|
end
|
125
169
|
|
@@ -128,27 +172,42 @@ class RenderProfilingTest < Minitest::Test
|
|
128
172
|
t.render!
|
129
173
|
|
130
174
|
timing_count = 0
|
131
|
-
t.profiler.each do |
|
175
|
+
t.profiler.each do |_timing|
|
132
176
|
timing_count += 1
|
133
177
|
end
|
134
178
|
|
135
|
-
assert_equal
|
179
|
+
assert_equal(2, timing_count)
|
136
180
|
end
|
137
181
|
|
138
182
|
def test_profiling_marks_children_of_if_blocks
|
139
183
|
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
|
140
184
|
t.render!
|
141
185
|
|
142
|
-
assert_equal
|
143
|
-
assert_equal
|
186
|
+
assert_equal(1, t.profiler.length)
|
187
|
+
assert_equal(2, t.profiler[0].children.length)
|
144
188
|
end
|
145
189
|
|
146
190
|
def test_profiling_marks_children_of_for_blocks
|
147
191
|
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
|
148
|
-
t.render!(
|
192
|
+
t.render!("collection" => ["one", "two"])
|
149
193
|
|
150
|
-
assert_equal
|
194
|
+
assert_equal(1, t.profiler.length)
|
151
195
|
# Will profile each invocation of the for block
|
152
|
-
assert_equal
|
196
|
+
assert_equal(2, t.profiler[0].children.length)
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_profiling_supports_self_time
|
200
|
+
t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
|
201
|
+
t.render!("collection" => ["one", "two"])
|
202
|
+
leaf = t.profiler[0].children[0]
|
203
|
+
|
204
|
+
assert_operator(leaf.self_time, :>, 0)
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_profiling_supports_total_time
|
208
|
+
t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
|
209
|
+
t.render!
|
210
|
+
|
211
|
+
assert_operator(t.profiler[0].total_time, :>, 0)
|
153
212
|
end
|
154
213
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
module SecurityFilter
|
@@ -14,53 +16,74 @@ class SecurityTest < Minitest::Test
|
|
14
16
|
end
|
15
17
|
|
16
18
|
def test_no_instance_eval
|
17
|
-
text
|
19
|
+
text = %( {{ '1+1' | instance_eval }} )
|
18
20
|
expected = %( 1+1 )
|
19
21
|
|
20
|
-
assert_equal
|
22
|
+
assert_equal(expected, Template.parse(text).render!(@assigns))
|
21
23
|
end
|
22
24
|
|
23
25
|
def test_no_existing_instance_eval
|
24
|
-
text
|
26
|
+
text = %( {{ '1+1' | __instance_eval__ }} )
|
25
27
|
expected = %( 1+1 )
|
26
28
|
|
27
|
-
assert_equal
|
29
|
+
assert_equal(expected, Template.parse(text).render!(@assigns))
|
28
30
|
end
|
29
31
|
|
30
32
|
def test_no_instance_eval_after_mixing_in_new_filter
|
31
|
-
text
|
33
|
+
text = %( {{ '1+1' | instance_eval }} )
|
32
34
|
expected = %( 1+1 )
|
33
35
|
|
34
|
-
assert_equal
|
36
|
+
assert_equal(expected, Template.parse(text).render!(@assigns))
|
35
37
|
end
|
36
38
|
|
37
39
|
def test_no_instance_eval_later_in_chain
|
38
|
-
text
|
40
|
+
text = %( {{ '1+1' | add_one | instance_eval }} )
|
39
41
|
expected = %( 1+1 + 1 )
|
40
42
|
|
41
|
-
assert_equal
|
43
|
+
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
|
42
44
|
end
|
43
45
|
|
44
|
-
def
|
46
|
+
def test_does_not_permanently_add_filters_to_symbol_table
|
45
47
|
current_symbols = Symbol.all_symbols
|
46
48
|
|
47
|
-
|
49
|
+
# MRI imprecisely marks objects found on the C stack, which can result
|
50
|
+
# in uninitialized memory being marked. This can even result in the test failing
|
51
|
+
# deterministically for a given compilation of ruby. Using a separate thread will
|
52
|
+
# keep these writes of the symbol pointer on a separate stack that will be garbage
|
53
|
+
# collected after Thread#join.
|
54
|
+
Thread.new do
|
55
|
+
test = %( {{ "some_string" | a_bad_filter }} )
|
56
|
+
Template.parse(test).render!
|
57
|
+
nil
|
58
|
+
end.join
|
48
59
|
|
49
|
-
|
50
|
-
assert_equal [], (Symbol.all_symbols - current_symbols)
|
60
|
+
GC.start
|
51
61
|
|
52
|
-
|
53
|
-
assert_equal [], (Symbol.all_symbols - current_symbols)
|
62
|
+
assert_equal([], (Symbol.all_symbols - current_symbols))
|
54
63
|
end
|
55
64
|
|
56
65
|
def test_does_not_add_drop_methods_to_symbol_table
|
57
66
|
current_symbols = Symbol.all_symbols
|
58
67
|
|
59
68
|
assigns = { 'drop' => Drop.new }
|
60
|
-
assert_equal
|
61
|
-
assert_equal
|
62
|
-
assert_equal
|
69
|
+
assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!)
|
70
|
+
assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!)
|
71
|
+
assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!)
|
72
|
+
|
73
|
+
assert_equal([], (Symbol.all_symbols - current_symbols))
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_max_depth_nested_blocks_does_not_raise_exception
|
77
|
+
depth = Liquid::Block::MAX_DEPTH
|
78
|
+
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
|
79
|
+
assert_equal("rendered", Template.parse(code).render!)
|
80
|
+
end
|
63
81
|
|
64
|
-
|
82
|
+
def test_more_than_max_depth_nested_blocks_raises_exception
|
83
|
+
depth = Liquid::Block::MAX_DEPTH + 1
|
84
|
+
code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
|
85
|
+
assert_raises(Liquid::StackLevelError) do
|
86
|
+
Template.parse(code).render!
|
87
|
+
end
|
65
88
|
end
|
66
89
|
end # SecurityTest
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'test_helper'
|
4
5
|
|
@@ -17,7 +18,7 @@ class TestThing
|
|
17
18
|
"woot: #{@foo}"
|
18
19
|
end
|
19
20
|
|
20
|
-
def [](
|
21
|
+
def [](_whatever)
|
21
22
|
to_s
|
22
23
|
end
|
23
24
|
|
@@ -37,7 +38,7 @@ class TestEnumerable < Liquid::Drop
|
|
37
38
|
include Enumerable
|
38
39
|
|
39
40
|
def each(&block)
|
40
|
-
[
|
41
|
+
[{ "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 }].each(&block)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
@@ -59,34 +60,34 @@ class StandardFiltersTest < Minitest::Test
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def test_size
|
62
|
-
assert_equal
|
63
|
-
assert_equal
|
64
|
-
assert_equal
|
63
|
+
assert_equal(3, @filters.size([1, 2, 3]))
|
64
|
+
assert_equal(0, @filters.size([]))
|
65
|
+
assert_equal(0, @filters.size(nil))
|
65
66
|
end
|
66
67
|
|
67
68
|
def test_downcase
|
68
|
-
assert_equal
|
69
|
-
assert_equal
|
69
|
+
assert_equal('testing', @filters.downcase("Testing"))
|
70
|
+
assert_equal('', @filters.downcase(nil))
|
70
71
|
end
|
71
72
|
|
72
73
|
def test_upcase
|
73
|
-
assert_equal
|
74
|
-
assert_equal
|
74
|
+
assert_equal('TESTING', @filters.upcase("Testing"))
|
75
|
+
assert_equal('', @filters.upcase(nil))
|
75
76
|
end
|
76
77
|
|
77
78
|
def test_slice
|
78
|
-
assert_equal
|
79
|
-
assert_equal
|
80
|
-
assert_equal
|
81
|
-
assert_equal
|
82
|
-
assert_equal
|
83
|
-
assert_equal
|
84
|
-
assert_equal
|
85
|
-
assert_equal
|
86
|
-
assert_equal
|
87
|
-
assert_equal
|
88
|
-
assert_equal
|
89
|
-
assert_equal
|
79
|
+
assert_equal('oob', @filters.slice('foobar', 1, 3))
|
80
|
+
assert_equal('oobar', @filters.slice('foobar', 1, 1000))
|
81
|
+
assert_equal('', @filters.slice('foobar', 1, 0))
|
82
|
+
assert_equal('o', @filters.slice('foobar', 1, 1))
|
83
|
+
assert_equal('bar', @filters.slice('foobar', 3, 3))
|
84
|
+
assert_equal('ar', @filters.slice('foobar', -2, 2))
|
85
|
+
assert_equal('ar', @filters.slice('foobar', -2, 1000))
|
86
|
+
assert_equal('r', @filters.slice('foobar', -1))
|
87
|
+
assert_equal('', @filters.slice(nil, 0))
|
88
|
+
assert_equal('', @filters.slice('foobar', 100, 10))
|
89
|
+
assert_equal('', @filters.slice('foobar', -100, 10))
|
90
|
+
assert_equal('oob', @filters.slice('foobar', '1', '3'))
|
90
91
|
assert_raises(Liquid::ArgumentError) do
|
91
92
|
@filters.slice('foobar', nil)
|
92
93
|
end
|
@@ -97,371 +98,562 @@ class StandardFiltersTest < Minitest::Test
|
|
97
98
|
|
98
99
|
def test_slice_on_arrays
|
99
100
|
input = 'foobar'.split(//)
|
100
|
-
assert_equal
|
101
|
-
assert_equal
|
102
|
-
assert_equal
|
103
|
-
assert_equal
|
104
|
-
assert_equal
|
105
|
-
assert_equal
|
106
|
-
assert_equal
|
107
|
-
assert_equal
|
108
|
-
assert_equal
|
109
|
-
assert_equal
|
101
|
+
assert_equal(%w(o o b), @filters.slice(input, 1, 3))
|
102
|
+
assert_equal(%w(o o b a r), @filters.slice(input, 1, 1000))
|
103
|
+
assert_equal(%w(), @filters.slice(input, 1, 0))
|
104
|
+
assert_equal(%w(o), @filters.slice(input, 1, 1))
|
105
|
+
assert_equal(%w(b a r), @filters.slice(input, 3, 3))
|
106
|
+
assert_equal(%w(a r), @filters.slice(input, -2, 2))
|
107
|
+
assert_equal(%w(a r), @filters.slice(input, -2, 1000))
|
108
|
+
assert_equal(%w(r), @filters.slice(input, -1))
|
109
|
+
assert_equal(%w(), @filters.slice(input, 100, 10))
|
110
|
+
assert_equal(%w(), @filters.slice(input, -100, 10))
|
110
111
|
end
|
111
112
|
|
112
113
|
def test_truncate
|
113
|
-
assert_equal
|
114
|
-
assert_equal
|
115
|
-
assert_equal
|
116
|
-
assert_equal
|
117
|
-
assert_equal
|
114
|
+
assert_equal('1234...', @filters.truncate('1234567890', 7))
|
115
|
+
assert_equal('1234567890', @filters.truncate('1234567890', 20))
|
116
|
+
assert_equal('...', @filters.truncate('1234567890', 0))
|
117
|
+
assert_equal('1234567890', @filters.truncate('1234567890'))
|
118
|
+
assert_equal("测试...", @filters.truncate("测试测试测试测试", 5))
|
119
|
+
assert_equal('12341', @filters.truncate("1234567890", 5, 1))
|
118
120
|
end
|
119
121
|
|
120
122
|
def test_split
|
121
|
-
assert_equal
|
122
|
-
assert_equal
|
123
|
-
assert_equal
|
124
|
-
|
125
|
-
assert_equal
|
126
|
-
assert_equal [], @filters.split(nil, ' ')
|
123
|
+
assert_equal(['12', '34'], @filters.split('12~34', '~'))
|
124
|
+
assert_equal(['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~'))
|
125
|
+
assert_equal(['A?Z'], @filters.split('A?Z', '~'))
|
126
|
+
assert_equal([], @filters.split(nil, ' '))
|
127
|
+
assert_equal(['A', 'Z'], @filters.split('A1Z', 1))
|
127
128
|
end
|
128
129
|
|
129
130
|
def test_escape
|
130
|
-
assert_equal
|
131
|
-
assert_equal
|
132
|
-
assert_equal
|
131
|
+
assert_equal('<strong>', @filters.escape('<strong>'))
|
132
|
+
assert_equal('1', @filters.escape(1))
|
133
|
+
assert_equal('2001-02-03', @filters.escape(Date.new(2001, 2, 3)))
|
134
|
+
assert_nil(@filters.escape(nil))
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_h
|
138
|
+
assert_equal('<strong>', @filters.h('<strong>'))
|
139
|
+
assert_equal('1', @filters.h(1))
|
140
|
+
assert_equal('2001-02-03', @filters.h(Date.new(2001, 2, 3)))
|
141
|
+
assert_nil(@filters.h(nil))
|
133
142
|
end
|
134
143
|
|
135
144
|
def test_escape_once
|
136
|
-
assert_equal
|
145
|
+
assert_equal('<strong>Hulk</strong>', @filters.escape_once('<strong>Hulk</strong>'))
|
137
146
|
end
|
138
147
|
|
139
148
|
def test_url_encode
|
140
|
-
assert_equal
|
141
|
-
assert_equal
|
149
|
+
assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com'))
|
150
|
+
assert_equal('1', @filters.url_encode(1))
|
151
|
+
assert_equal('2001-02-03', @filters.url_encode(Date.new(2001, 2, 3)))
|
152
|
+
assert_nil(@filters.url_encode(nil))
|
142
153
|
end
|
143
154
|
|
144
155
|
def test_url_decode
|
145
|
-
assert_equal
|
146
|
-
assert_equal
|
147
|
-
assert_equal
|
148
|
-
assert_equal
|
156
|
+
assert_equal('foo bar', @filters.url_decode('foo+bar'))
|
157
|
+
assert_equal('foo bar', @filters.url_decode('foo%20bar'))
|
158
|
+
assert_equal('foo+1@example.com', @filters.url_decode('foo%2B1%40example.com'))
|
159
|
+
assert_equal('1', @filters.url_decode(1))
|
160
|
+
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
|
161
|
+
assert_nil(@filters.url_decode(nil))
|
162
|
+
exception = assert_raises(Liquid::ArgumentError) do
|
163
|
+
@filters.url_decode('%ff')
|
164
|
+
end
|
165
|
+
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
|
149
166
|
end
|
150
167
|
|
151
168
|
def test_truncatewords
|
152
|
-
assert_equal
|
153
|
-
assert_equal
|
154
|
-
assert_equal
|
155
|
-
assert_equal
|
156
|
-
|
169
|
+
assert_equal('one two three', @filters.truncatewords('one two three', 4))
|
170
|
+
assert_equal('one two...', @filters.truncatewords('one two three', 2))
|
171
|
+
assert_equal('one two three', @filters.truncatewords('one two three'))
|
172
|
+
assert_equal(
|
173
|
+
'Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13”...',
|
174
|
+
@filters.truncatewords('Two small (13” x 5.5” x 10” high) baskets fit inside one large basket (13” x 16” x 10.5” high) with cover.', 15)
|
175
|
+
)
|
176
|
+
assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
|
177
|
+
assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
|
157
178
|
end
|
158
179
|
|
159
180
|
def test_strip_html
|
160
|
-
assert_equal
|
161
|
-
assert_equal
|
162
|
-
assert_equal
|
163
|
-
assert_equal
|
164
|
-
assert_equal
|
165
|
-
assert_equal
|
166
|
-
assert_equal
|
181
|
+
assert_equal('test', @filters.strip_html("<div>test</div>"))
|
182
|
+
assert_equal('test', @filters.strip_html("<div id='test'>test</div>"))
|
183
|
+
assert_equal('', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>"))
|
184
|
+
assert_equal('', @filters.strip_html("<style type='text/css'>foo bar</style>"))
|
185
|
+
assert_equal('test', @filters.strip_html("<div\nclass='multiline'>test</div>"))
|
186
|
+
assert_equal('test', @filters.strip_html("<!-- foo bar \n test -->test"))
|
187
|
+
assert_equal('', @filters.strip_html(nil))
|
188
|
+
|
189
|
+
# Quirk of the existing implementation
|
190
|
+
assert_equal('foo;', @filters.strip_html("<<<script </script>script>foo;</script>"))
|
167
191
|
end
|
168
192
|
|
169
193
|
def test_join
|
170
|
-
assert_equal
|
171
|
-
assert_equal
|
194
|
+
assert_equal('1 2 3 4', @filters.join([1, 2, 3, 4]))
|
195
|
+
assert_equal('1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - '))
|
196
|
+
assert_equal('1121314', @filters.join([1, 2, 3, 4], 1))
|
172
197
|
end
|
173
198
|
|
174
199
|
def test_sort
|
175
|
-
assert_equal
|
176
|
-
assert_equal
|
200
|
+
assert_equal([1, 2, 3, 4], @filters.sort([4, 3, 2, 1]))
|
201
|
+
assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_sort_with_nils
|
205
|
+
assert_equal([1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1]))
|
206
|
+
assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a"))
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_sort_when_property_is_sometimes_missing_puts_nils_last
|
210
|
+
input = [
|
211
|
+
{ "price" => 4, "handle" => "alpha" },
|
212
|
+
{ "handle" => "beta" },
|
213
|
+
{ "price" => 1, "handle" => "gamma" },
|
214
|
+
{ "handle" => "delta" },
|
215
|
+
{ "price" => 2, "handle" => "epsilon" },
|
216
|
+
]
|
217
|
+
expectation = [
|
218
|
+
{ "price" => 1, "handle" => "gamma" },
|
219
|
+
{ "price" => 2, "handle" => "epsilon" },
|
220
|
+
{ "price" => 4, "handle" => "alpha" },
|
221
|
+
{ "handle" => "delta" },
|
222
|
+
{ "handle" => "beta" },
|
223
|
+
]
|
224
|
+
assert_equal(expectation, @filters.sort(input, "price"))
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_sort_natural
|
228
|
+
assert_equal(["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"]))
|
229
|
+
assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a"))
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_sort_natural_with_nils
|
233
|
+
assert_equal(["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"]))
|
234
|
+
assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a"))
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
|
238
|
+
input = [
|
239
|
+
{ "price" => "4", "handle" => "alpha" },
|
240
|
+
{ "handle" => "beta" },
|
241
|
+
{ "price" => "1", "handle" => "gamma" },
|
242
|
+
{ "handle" => "delta" },
|
243
|
+
{ "price" => 2, "handle" => "epsilon" },
|
244
|
+
]
|
245
|
+
expectation = [
|
246
|
+
{ "price" => "1", "handle" => "gamma" },
|
247
|
+
{ "price" => 2, "handle" => "epsilon" },
|
248
|
+
{ "price" => "4", "handle" => "alpha" },
|
249
|
+
{ "handle" => "delta" },
|
250
|
+
{ "handle" => "beta" },
|
251
|
+
]
|
252
|
+
assert_equal(expectation, @filters.sort_natural(input, "price"))
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_sort_natural_case_check
|
256
|
+
input = [
|
257
|
+
{ "key" => "X" },
|
258
|
+
{ "key" => "Y" },
|
259
|
+
{ "key" => "Z" },
|
260
|
+
{ "fake" => "t" },
|
261
|
+
{ "key" => "a" },
|
262
|
+
{ "key" => "b" },
|
263
|
+
{ "key" => "c" },
|
264
|
+
]
|
265
|
+
expectation = [
|
266
|
+
{ "key" => "a" },
|
267
|
+
{ "key" => "b" },
|
268
|
+
{ "key" => "c" },
|
269
|
+
{ "key" => "X" },
|
270
|
+
{ "key" => "Y" },
|
271
|
+
{ "key" => "Z" },
|
272
|
+
{ "fake" => "t" },
|
273
|
+
]
|
274
|
+
assert_equal(expectation, @filters.sort_natural(input, "key"))
|
275
|
+
assert_equal(["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"]))
|
177
276
|
end
|
178
277
|
|
179
278
|
def test_sort_empty_array
|
180
|
-
assert_equal
|
279
|
+
assert_equal([], @filters.sort([], "a"))
|
280
|
+
end
|
281
|
+
|
282
|
+
def test_sort_invalid_property
|
283
|
+
foo = [
|
284
|
+
[1],
|
285
|
+
[2],
|
286
|
+
[3],
|
287
|
+
]
|
288
|
+
|
289
|
+
assert_raises(Liquid::ArgumentError) do
|
290
|
+
@filters.sort(foo, "bar")
|
291
|
+
end
|
181
292
|
end
|
182
293
|
|
183
294
|
def test_sort_natural_empty_array
|
184
|
-
assert_equal
|
295
|
+
assert_equal([], @filters.sort_natural([], "a"))
|
296
|
+
end
|
297
|
+
|
298
|
+
def test_sort_natural_invalid_property
|
299
|
+
foo = [
|
300
|
+
[1],
|
301
|
+
[2],
|
302
|
+
[3],
|
303
|
+
]
|
304
|
+
|
305
|
+
assert_raises(Liquid::ArgumentError) do
|
306
|
+
@filters.sort_natural(foo, "bar")
|
307
|
+
end
|
185
308
|
end
|
186
309
|
|
187
310
|
def test_legacy_sort_hash
|
188
|
-
assert_equal
|
311
|
+
assert_equal([{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2))
|
189
312
|
end
|
190
313
|
|
191
314
|
def test_numerical_vs_lexicographical_sort
|
192
|
-
assert_equal
|
193
|
-
assert_equal
|
194
|
-
assert_equal
|
195
|
-
assert_equal
|
315
|
+
assert_equal([2, 10], @filters.sort([10, 2]))
|
316
|
+
assert_equal([{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a"))
|
317
|
+
assert_equal(["10", "2"], @filters.sort(["10", "2"]))
|
318
|
+
assert_equal([{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a"))
|
196
319
|
end
|
197
320
|
|
198
321
|
def test_uniq
|
199
|
-
assert_equal
|
200
|
-
assert_equal
|
201
|
-
assert_equal
|
322
|
+
assert_equal(["foo"], @filters.uniq("foo"))
|
323
|
+
assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))
|
324
|
+
assert_equal([{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
|
202
325
|
testdrop = TestDrop.new
|
203
|
-
assert_equal
|
326
|
+
assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test'))
|
204
327
|
end
|
205
328
|
|
206
329
|
def test_uniq_empty_array
|
207
|
-
assert_equal
|
330
|
+
assert_equal([], @filters.uniq([], "a"))
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_uniq_invalid_property
|
334
|
+
foo = [
|
335
|
+
[1],
|
336
|
+
[2],
|
337
|
+
[3],
|
338
|
+
]
|
339
|
+
|
340
|
+
assert_raises(Liquid::ArgumentError) do
|
341
|
+
@filters.uniq(foo, "bar")
|
342
|
+
end
|
208
343
|
end
|
209
344
|
|
210
345
|
def test_compact_empty_array
|
211
|
-
assert_equal
|
346
|
+
assert_equal([], @filters.compact([], "a"))
|
347
|
+
end
|
348
|
+
|
349
|
+
def test_compact_invalid_property
|
350
|
+
foo = [
|
351
|
+
[1],
|
352
|
+
[2],
|
353
|
+
[3],
|
354
|
+
]
|
355
|
+
|
356
|
+
assert_raises(Liquid::ArgumentError) do
|
357
|
+
@filters.compact(foo, "bar")
|
358
|
+
end
|
212
359
|
end
|
213
360
|
|
214
361
|
def test_reverse
|
215
|
-
assert_equal
|
362
|
+
assert_equal([4, 3, 2, 1], @filters.reverse([1, 2, 3, 4]))
|
216
363
|
end
|
217
364
|
|
218
365
|
def test_legacy_reverse_hash
|
219
|
-
assert_equal
|
366
|
+
assert_equal([{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2))
|
220
367
|
end
|
221
368
|
|
222
369
|
def test_map
|
223
|
-
assert_equal
|
224
|
-
assert_template_result
|
225
|
-
'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
|
370
|
+
assert_equal([1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a'))
|
371
|
+
assert_template_result('abc', "{{ ary | map:'foo' | map:'bar' }}",
|
372
|
+
'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }])
|
226
373
|
end
|
227
374
|
|
228
375
|
def test_map_doesnt_call_arbitrary_stuff
|
229
|
-
assert_template_result
|
230
|
-
assert_template_result
|
376
|
+
assert_template_result("", '{{ "foo" | map: "__id__" }}')
|
377
|
+
assert_template_result("", '{{ "foo" | map: "inspect" }}')
|
231
378
|
end
|
232
379
|
|
233
380
|
def test_map_calls_to_liquid
|
234
381
|
t = TestThing.new
|
235
|
-
assert_template_result
|
382
|
+
assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t])
|
236
383
|
end
|
237
384
|
|
238
385
|
def test_map_on_hashes
|
239
|
-
assert_template_result
|
240
|
-
"thing" => { "foo" => [
|
386
|
+
assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
|
387
|
+
"thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] })
|
241
388
|
end
|
242
389
|
|
243
390
|
def test_legacy_map_on_hashes_with_dynamic_key
|
244
391
|
template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
|
245
|
-
hash
|
246
|
-
assert_template_result
|
392
|
+
hash = { "foo" => { "bar" => 42 } }
|
393
|
+
assert_template_result("42", template, "thing" => hash)
|
247
394
|
end
|
248
395
|
|
249
396
|
def test_sort_calls_to_liquid
|
250
397
|
t = TestThing.new
|
251
398
|
Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
|
252
|
-
assert
|
399
|
+
assert(t.foo > 0)
|
253
400
|
end
|
254
401
|
|
255
402
|
def test_map_over_proc
|
256
|
-
drop
|
257
|
-
p
|
403
|
+
drop = TestDrop.new
|
404
|
+
p = proc { drop }
|
258
405
|
templ = '{{ procs | map: "test" }}'
|
259
|
-
assert_template_result
|
406
|
+
assert_template_result("testfoo", templ, "procs" => [p])
|
260
407
|
end
|
261
408
|
|
262
409
|
def test_map_over_drops_returning_procs
|
263
410
|
drops = [
|
264
411
|
{
|
265
|
-
"proc" => ->{ "foo" },
|
412
|
+
"proc" => -> { "foo" },
|
266
413
|
},
|
267
414
|
{
|
268
|
-
"proc" => ->{ "bar" },
|
415
|
+
"proc" => -> { "bar" },
|
269
416
|
},
|
270
417
|
]
|
271
418
|
templ = '{{ drops | map: "proc" }}'
|
272
|
-
assert_template_result
|
419
|
+
assert_template_result("foobar", templ, "drops" => drops)
|
273
420
|
end
|
274
421
|
|
275
422
|
def test_map_works_on_enumerables
|
276
|
-
assert_template_result
|
423
|
+
assert_template_result("123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new)
|
424
|
+
end
|
425
|
+
|
426
|
+
def test_map_returns_empty_on_2d_input_array
|
427
|
+
foo = [
|
428
|
+
[1],
|
429
|
+
[2],
|
430
|
+
[3],
|
431
|
+
]
|
432
|
+
|
433
|
+
assert_raises(Liquid::ArgumentError) do
|
434
|
+
@filters.map(foo, "bar")
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def test_map_returns_empty_with_no_property
|
439
|
+
foo = [
|
440
|
+
[1],
|
441
|
+
[2],
|
442
|
+
[3],
|
443
|
+
]
|
444
|
+
assert_raises(Liquid::ArgumentError) do
|
445
|
+
@filters.map(foo, nil)
|
446
|
+
end
|
277
447
|
end
|
278
448
|
|
279
449
|
def test_sort_works_on_enumerables
|
280
|
-
assert_template_result
|
450
|
+
assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new)
|
281
451
|
end
|
282
452
|
|
283
453
|
def test_first_and_last_call_to_liquid
|
284
|
-
assert_template_result
|
285
|
-
assert_template_result
|
454
|
+
assert_template_result('foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new])
|
455
|
+
assert_template_result('foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new])
|
286
456
|
end
|
287
457
|
|
288
458
|
def test_truncate_calls_to_liquid
|
289
|
-
assert_template_result
|
459
|
+
assert_template_result("wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new)
|
290
460
|
end
|
291
461
|
|
292
462
|
def test_date
|
293
|
-
assert_equal
|
294
|
-
assert_equal
|
295
|
-
assert_equal
|
463
|
+
assert_equal('May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B"))
|
464
|
+
assert_equal('June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B"))
|
465
|
+
assert_equal('July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B"))
|
296
466
|
|
297
|
-
assert_equal
|
298
|
-
assert_equal
|
299
|
-
assert_equal
|
467
|
+
assert_equal('May', @filters.date("2006-05-05 10:00:00", "%B"))
|
468
|
+
assert_equal('June', @filters.date("2006-06-05 10:00:00", "%B"))
|
469
|
+
assert_equal('July', @filters.date("2006-07-05 10:00:00", "%B"))
|
300
470
|
|
301
|
-
assert_equal
|
302
|
-
assert_equal
|
303
|
-
assert_equal
|
304
|
-
assert_equal
|
471
|
+
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", ""))
|
472
|
+
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", ""))
|
473
|
+
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", ""))
|
474
|
+
assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil))
|
305
475
|
|
306
|
-
assert_equal
|
476
|
+
assert_equal('07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y"))
|
307
477
|
|
308
|
-
assert_equal
|
309
|
-
assert_equal
|
310
|
-
assert_equal
|
311
|
-
assert_equal
|
478
|
+
assert_equal("07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y"))
|
479
|
+
assert_equal(Date.today.year.to_s, @filters.date('now', '%Y'))
|
480
|
+
assert_equal(Date.today.year.to_s, @filters.date('today', '%Y'))
|
481
|
+
assert_equal(Date.today.year.to_s, @filters.date('Today', '%Y'))
|
312
482
|
|
313
|
-
|
483
|
+
assert_nil(@filters.date(nil, "%B"))
|
314
484
|
|
315
|
-
assert_equal
|
485
|
+
assert_equal('', @filters.date('', "%B"))
|
316
486
|
|
317
487
|
with_timezone("UTC") do
|
318
|
-
assert_equal
|
319
|
-
assert_equal
|
488
|
+
assert_equal("07/05/2006", @filters.date(1152098955, "%m/%d/%Y"))
|
489
|
+
assert_equal("07/05/2006", @filters.date("1152098955", "%m/%d/%Y"))
|
320
490
|
end
|
321
491
|
end
|
322
492
|
|
323
493
|
def test_first_last
|
324
|
-
assert_equal
|
325
|
-
assert_equal
|
326
|
-
|
327
|
-
|
494
|
+
assert_equal(1, @filters.first([1, 2, 3]))
|
495
|
+
assert_equal(3, @filters.last([1, 2, 3]))
|
496
|
+
assert_nil(@filters.first([]))
|
497
|
+
assert_nil(@filters.last([]))
|
328
498
|
end
|
329
499
|
|
330
500
|
def test_replace
|
331
|
-
assert_equal
|
332
|
-
assert_equal
|
333
|
-
assert_equal
|
334
|
-
assert_equal
|
335
|
-
assert_template_result
|
501
|
+
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2))
|
502
|
+
assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
|
503
|
+
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2))
|
504
|
+
assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
|
505
|
+
assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
|
336
506
|
end
|
337
507
|
|
338
508
|
def test_remove
|
339
|
-
assert_equal
|
340
|
-
assert_equal
|
341
|
-
assert_equal
|
342
|
-
assert_equal
|
343
|
-
assert_template_result
|
509
|
+
assert_equal(' ', @filters.remove("a a a a", 'a'))
|
510
|
+
assert_equal(' ', @filters.remove("1 1 1 1", 1))
|
511
|
+
assert_equal('a a a', @filters.remove_first("a a a a", 'a '))
|
512
|
+
assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1))
|
513
|
+
assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}")
|
344
514
|
end
|
345
515
|
|
346
516
|
def test_pipes_in_string_arguments
|
347
|
-
assert_template_result
|
517
|
+
assert_template_result('foobar', "{{ 'foo|bar' | remove: '|' }}")
|
348
518
|
end
|
349
519
|
|
350
520
|
def test_strip
|
351
|
-
assert_template_result
|
352
|
-
assert_template_result
|
521
|
+
assert_template_result('ab c', "{{ source | strip }}", 'source' => " ab c ")
|
522
|
+
assert_template_result('ab c', "{{ source | strip }}", 'source' => " \tab c \n \t")
|
353
523
|
end
|
354
524
|
|
355
525
|
def test_lstrip
|
356
|
-
assert_template_result
|
357
|
-
assert_template_result
|
526
|
+
assert_template_result('ab c ', "{{ source | lstrip }}", 'source' => " ab c ")
|
527
|
+
assert_template_result("ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t")
|
358
528
|
end
|
359
529
|
|
360
530
|
def test_rstrip
|
361
|
-
assert_template_result
|
362
|
-
assert_template_result
|
531
|
+
assert_template_result(" ab c", "{{ source | rstrip }}", 'source' => " ab c ")
|
532
|
+
assert_template_result(" \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t")
|
363
533
|
end
|
364
534
|
|
365
535
|
def test_strip_newlines
|
366
|
-
assert_template_result
|
367
|
-
assert_template_result
|
536
|
+
assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc")
|
537
|
+
assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc")
|
368
538
|
end
|
369
539
|
|
370
540
|
def test_newlines_to_br
|
371
|
-
assert_template_result
|
541
|
+
assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
|
372
542
|
end
|
373
543
|
|
374
544
|
def test_plus
|
375
|
-
assert_template_result
|
376
|
-
assert_template_result
|
545
|
+
assert_template_result("2", "{{ 1 | plus:1 }}")
|
546
|
+
assert_template_result("2.0", "{{ '1' | plus:'1.0' }}")
|
377
547
|
|
378
|
-
assert_template_result
|
548
|
+
assert_template_result("5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3))
|
379
549
|
end
|
380
550
|
|
381
551
|
def test_minus
|
382
|
-
assert_template_result
|
383
|
-
assert_template_result
|
552
|
+
assert_template_result("4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1)
|
553
|
+
assert_template_result("2.3", "{{ '4.3' | minus:'2' }}")
|
384
554
|
|
385
|
-
assert_template_result
|
555
|
+
assert_template_result("5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7))
|
386
556
|
end
|
387
557
|
|
388
558
|
def test_abs
|
389
|
-
assert_template_result
|
390
|
-
assert_template_result
|
391
|
-
assert_template_result
|
392
|
-
assert_template_result
|
393
|
-
assert_template_result
|
394
|
-
assert_template_result
|
395
|
-
assert_template_result
|
396
|
-
assert_template_result
|
397
|
-
assert_template_result
|
398
|
-
assert_template_result
|
559
|
+
assert_template_result("17", "{{ 17 | abs }}")
|
560
|
+
assert_template_result("17", "{{ -17 | abs }}")
|
561
|
+
assert_template_result("17", "{{ '17' | abs }}")
|
562
|
+
assert_template_result("17", "{{ '-17' | abs }}")
|
563
|
+
assert_template_result("0", "{{ 0 | abs }}")
|
564
|
+
assert_template_result("0", "{{ '0' | abs }}")
|
565
|
+
assert_template_result("17.42", "{{ 17.42 | abs }}")
|
566
|
+
assert_template_result("17.42", "{{ -17.42 | abs }}")
|
567
|
+
assert_template_result("17.42", "{{ '17.42' | abs }}")
|
568
|
+
assert_template_result("17.42", "{{ '-17.42' | abs }}")
|
399
569
|
end
|
400
570
|
|
401
571
|
def test_times
|
402
|
-
assert_template_result
|
403
|
-
assert_template_result
|
404
|
-
assert_template_result
|
405
|
-
assert_template_result
|
406
|
-
assert_template_result
|
407
|
-
assert_template_result
|
408
|
-
assert_template_result
|
572
|
+
assert_template_result("12", "{{ 3 | times:4 }}")
|
573
|
+
assert_template_result("0", "{{ 'foo' | times:4 }}")
|
574
|
+
assert_template_result("6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}")
|
575
|
+
assert_template_result("7.25", "{{ 0.0725 | times:100 }}")
|
576
|
+
assert_template_result("-7.25", '{{ "-0.0725" | times:100 }}')
|
577
|
+
assert_template_result("7.25", '{{ "-0.0725" | times: -100 }}')
|
578
|
+
assert_template_result("4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2))
|
409
579
|
end
|
410
580
|
|
411
581
|
def test_divided_by
|
412
|
-
assert_template_result
|
413
|
-
assert_template_result
|
582
|
+
assert_template_result("4", "{{ 12 | divided_by:3 }}")
|
583
|
+
assert_template_result("4", "{{ 14 | divided_by:3 }}")
|
414
584
|
|
415
|
-
assert_template_result
|
416
|
-
assert_equal
|
585
|
+
assert_template_result("5", "{{ 15 | divided_by:3 }}")
|
586
|
+
assert_equal("Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render)
|
417
587
|
|
418
|
-
assert_template_result
|
588
|
+
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
|
419
589
|
assert_raises(Liquid::ZeroDivisionError) do
|
420
|
-
assert_template_result
|
590
|
+
assert_template_result("4", "{{ 1 | modulo: 0 }}")
|
421
591
|
end
|
422
592
|
|
423
|
-
assert_template_result
|
593
|
+
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
|
424
594
|
end
|
425
595
|
|
426
596
|
def test_modulo
|
427
|
-
assert_template_result
|
597
|
+
assert_template_result("1", "{{ 3 | modulo:2 }}")
|
428
598
|
assert_raises(Liquid::ZeroDivisionError) do
|
429
|
-
assert_template_result
|
599
|
+
assert_template_result("4", "{{ 1 | modulo: 0 }}")
|
430
600
|
end
|
431
601
|
|
432
|
-
assert_template_result
|
602
|
+
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
|
433
603
|
end
|
434
604
|
|
435
605
|
def test_round
|
436
|
-
assert_template_result
|
437
|
-
assert_template_result
|
438
|
-
assert_template_result
|
606
|
+
assert_template_result("5", "{{ input | round }}", 'input' => 4.6)
|
607
|
+
assert_template_result("4", "{{ '4.3' | round }}")
|
608
|
+
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
|
439
609
|
assert_raises(Liquid::FloatDomainError) do
|
440
|
-
assert_template_result
|
610
|
+
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
|
441
611
|
end
|
442
612
|
|
443
|
-
assert_template_result
|
444
|
-
assert_template_result
|
613
|
+
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
|
614
|
+
assert_template_result("4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3))
|
445
615
|
end
|
446
616
|
|
447
617
|
def test_ceil
|
448
|
-
assert_template_result
|
449
|
-
assert_template_result
|
618
|
+
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
|
619
|
+
assert_template_result("5", "{{ '4.3' | ceil }}")
|
450
620
|
assert_raises(Liquid::FloatDomainError) do
|
451
|
-
assert_template_result
|
621
|
+
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
|
452
622
|
end
|
453
623
|
|
454
|
-
assert_template_result
|
624
|
+
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
|
455
625
|
end
|
456
626
|
|
457
627
|
def test_floor
|
458
|
-
assert_template_result
|
459
|
-
assert_template_result
|
628
|
+
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
|
629
|
+
assert_template_result("4", "{{ '4.3' | floor }}")
|
460
630
|
assert_raises(Liquid::FloatDomainError) do
|
461
|
-
assert_template_result
|
631
|
+
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
|
462
632
|
end
|
463
633
|
|
464
|
-
assert_template_result
|
634
|
+
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
|
635
|
+
end
|
636
|
+
|
637
|
+
def test_at_most
|
638
|
+
assert_template_result("4", "{{ 5 | at_most:4 }}")
|
639
|
+
assert_template_result("5", "{{ 5 | at_most:5 }}")
|
640
|
+
assert_template_result("5", "{{ 5 | at_most:6 }}")
|
641
|
+
|
642
|
+
assert_template_result("4.5", "{{ 4.5 | at_most:5 }}")
|
643
|
+
assert_template_result("5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6))
|
644
|
+
assert_template_result("4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4))
|
645
|
+
assert_template_result("4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4))
|
646
|
+
end
|
647
|
+
|
648
|
+
def test_at_least
|
649
|
+
assert_template_result("5", "{{ 5 | at_least:4 }}")
|
650
|
+
assert_template_result("5", "{{ 5 | at_least:5 }}")
|
651
|
+
assert_template_result("6", "{{ 5 | at_least:6 }}")
|
652
|
+
|
653
|
+
assert_template_result("5", "{{ 4.5 | at_least:5 }}")
|
654
|
+
assert_template_result("6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6))
|
655
|
+
assert_template_result("5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4))
|
656
|
+
assert_template_result("6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6))
|
465
657
|
end
|
466
658
|
|
467
659
|
def test_append
|
@@ -471,9 +663,9 @@ class StandardFiltersTest < Minitest::Test
|
|
471
663
|
end
|
472
664
|
|
473
665
|
def test_concat
|
474
|
-
assert_equal
|
475
|
-
assert_equal
|
476
|
-
assert_equal
|
666
|
+
assert_equal([1, 2, 3, 4], @filters.concat([1, 2], [3, 4]))
|
667
|
+
assert_equal([1, 2, 'a'], @filters.concat([1, 2], ['a']))
|
668
|
+
assert_equal([1, 2, 10], @filters.concat([1, 2], [10]))
|
477
669
|
|
478
670
|
assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
|
479
671
|
@filters.concat([1, 2], 10)
|
@@ -487,12 +679,23 @@ class StandardFiltersTest < Minitest::Test
|
|
487
679
|
end
|
488
680
|
|
489
681
|
def test_default
|
490
|
-
assert_equal
|
491
|
-
assert_equal
|
492
|
-
assert_equal
|
493
|
-
assert_equal
|
494
|
-
assert_equal
|
495
|
-
assert_equal
|
682
|
+
assert_equal("foo", @filters.default("foo", "bar"))
|
683
|
+
assert_equal("bar", @filters.default(nil, "bar"))
|
684
|
+
assert_equal("bar", @filters.default("", "bar"))
|
685
|
+
assert_equal("bar", @filters.default(false, "bar"))
|
686
|
+
assert_equal("bar", @filters.default([], "bar"))
|
687
|
+
assert_equal("bar", @filters.default({}, "bar"))
|
688
|
+
assert_template_result('bar', "{{ false | default: 'bar' }}")
|
689
|
+
end
|
690
|
+
|
691
|
+
def test_default_handle_false
|
692
|
+
assert_equal("foo", @filters.default("foo", "bar", "allow_false" => true))
|
693
|
+
assert_equal("bar", @filters.default(nil, "bar", "allow_false" => true))
|
694
|
+
assert_equal("bar", @filters.default("", "bar", "allow_false" => true))
|
695
|
+
assert_equal(false, @filters.default(false, "bar", "allow_false" => true))
|
696
|
+
assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
|
697
|
+
assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
|
698
|
+
assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
|
496
699
|
end
|
497
700
|
|
498
701
|
def test_cannot_access_private_methods
|
@@ -504,10 +707,125 @@ class StandardFiltersTest < Minitest::Test
|
|
504
707
|
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
505
708
|
end
|
506
709
|
|
710
|
+
def test_where
|
711
|
+
input = [
|
712
|
+
{ "handle" => "alpha", "ok" => true },
|
713
|
+
{ "handle" => "beta", "ok" => false },
|
714
|
+
{ "handle" => "gamma", "ok" => false },
|
715
|
+
{ "handle" => "delta", "ok" => true },
|
716
|
+
]
|
717
|
+
|
718
|
+
expectation = [
|
719
|
+
{ "handle" => "alpha", "ok" => true },
|
720
|
+
{ "handle" => "delta", "ok" => true },
|
721
|
+
]
|
722
|
+
|
723
|
+
assert_equal(expectation, @filters.where(input, "ok", true))
|
724
|
+
assert_equal(expectation, @filters.where(input, "ok"))
|
725
|
+
end
|
726
|
+
|
727
|
+
def test_where_no_key_set
|
728
|
+
input = [
|
729
|
+
{ "handle" => "alpha", "ok" => true },
|
730
|
+
{ "handle" => "beta" },
|
731
|
+
{ "handle" => "gamma" },
|
732
|
+
{ "handle" => "delta", "ok" => true },
|
733
|
+
]
|
734
|
+
|
735
|
+
expectation = [
|
736
|
+
{ "handle" => "alpha", "ok" => true },
|
737
|
+
{ "handle" => "delta", "ok" => true },
|
738
|
+
]
|
739
|
+
|
740
|
+
assert_equal(expectation, @filters.where(input, "ok", true))
|
741
|
+
assert_equal(expectation, @filters.where(input, "ok"))
|
742
|
+
end
|
743
|
+
|
744
|
+
def test_where_non_array_map_input
|
745
|
+
assert_equal([{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok"))
|
746
|
+
assert_equal([], @filters.where({ "a" => "not ok" }, "a", "ok"))
|
747
|
+
end
|
748
|
+
|
749
|
+
def test_where_indexable_but_non_map_value
|
750
|
+
assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) }
|
751
|
+
assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") }
|
752
|
+
end
|
753
|
+
|
754
|
+
def test_where_non_boolean_value
|
755
|
+
input = [
|
756
|
+
{ "message" => "Bonjour!", "language" => "French" },
|
757
|
+
{ "message" => "Hello!", "language" => "English" },
|
758
|
+
{ "message" => "Hallo!", "language" => "German" },
|
759
|
+
]
|
760
|
+
|
761
|
+
assert_equal([{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French"))
|
762
|
+
assert_equal([{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German"))
|
763
|
+
assert_equal([{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English"))
|
764
|
+
end
|
765
|
+
|
766
|
+
def test_where_array_of_only_unindexable_values
|
767
|
+
assert_nil(@filters.where([nil], "ok", true))
|
768
|
+
assert_nil(@filters.where([nil], "ok"))
|
769
|
+
end
|
770
|
+
|
771
|
+
def test_all_filters_never_raise_non_liquid_exception
|
772
|
+
test_drop = TestDrop.new
|
773
|
+
test_drop.context = Context.new
|
774
|
+
test_enum = TestEnumerable.new
|
775
|
+
test_enum.context = Context.new
|
776
|
+
test_types = [
|
777
|
+
"foo",
|
778
|
+
123,
|
779
|
+
0,
|
780
|
+
0.0,
|
781
|
+
-1234.003030303,
|
782
|
+
-99999999,
|
783
|
+
1234.38383000383830003838300,
|
784
|
+
nil,
|
785
|
+
true,
|
786
|
+
false,
|
787
|
+
TestThing.new,
|
788
|
+
test_drop,
|
789
|
+
test_enum,
|
790
|
+
["foo", "bar"],
|
791
|
+
{ "foo" => "bar" },
|
792
|
+
{ foo: "bar" },
|
793
|
+
[{ "foo" => "bar" }, { "foo" => 123 }, { "foo" => nil }, { "foo" => true }, { "foo" => ["foo", "bar"] }],
|
794
|
+
{ 1 => "bar" },
|
795
|
+
["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
|
796
|
+
]
|
797
|
+
test_types.each do |first|
|
798
|
+
test_types.each do |other|
|
799
|
+
(@filters.methods - Object.methods).each do |method|
|
800
|
+
arg_count = @filters.method(method).arity
|
801
|
+
arg_count *= -1 if arg_count < 0
|
802
|
+
inputs = [first]
|
803
|
+
inputs << ([other] * (arg_count - 1)) if arg_count > 1
|
804
|
+
begin
|
805
|
+
@filters.send(method, *inputs)
|
806
|
+
rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
|
807
|
+
nil
|
808
|
+
end
|
809
|
+
end
|
810
|
+
end
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
def test_where_no_target_value
|
815
|
+
input = [
|
816
|
+
{ "foo" => false },
|
817
|
+
{ "foo" => true },
|
818
|
+
{ "foo" => "for sure" },
|
819
|
+
{ "bar" => true },
|
820
|
+
]
|
821
|
+
|
822
|
+
assert_equal([{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo"))
|
823
|
+
end
|
824
|
+
|
507
825
|
private
|
508
826
|
|
509
827
|
def with_timezone(tz)
|
510
|
-
old_tz
|
828
|
+
old_tz = ENV['TZ']
|
511
829
|
ENV['TZ'] = tz
|
512
830
|
yield
|
513
831
|
ensure
|