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.
Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +93 -2
  3. data/README.md +8 -0
  4. data/lib/liquid.rb +18 -5
  5. data/lib/liquid/block.rb +47 -20
  6. data/lib/liquid/block_body.rb +190 -76
  7. data/lib/liquid/condition.rb +69 -29
  8. data/lib/liquid/context.rb +122 -76
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -25
  12. data/lib/liquid/expression.rb +30 -31
  13. data/lib/liquid/extensions.rb +8 -0
  14. data/lib/liquid/file_system.rb +6 -4
  15. data/lib/liquid/forloop_drop.rb +11 -4
  16. data/lib/liquid/i18n.rb +5 -3
  17. data/lib/liquid/interrupts.rb +3 -1
  18. data/lib/liquid/lexer.rb +35 -26
  19. data/lib/liquid/locales/en.yml +4 -2
  20. data/lib/liquid/parse_context.rb +17 -4
  21. data/lib/liquid/parse_tree_visitor.rb +42 -0
  22. data/lib/liquid/parser.rb +30 -18
  23. data/lib/liquid/parser_switching.rb +17 -3
  24. data/lib/liquid/partial_cache.rb +24 -0
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/profiler/hooks.rb +26 -14
  27. data/lib/liquid/range_lookup.rb +5 -3
  28. data/lib/liquid/register.rb +6 -0
  29. data/lib/liquid/resource_limits.rb +47 -8
  30. data/lib/liquid/standardfilters.rb +171 -57
  31. data/lib/liquid/static_registers.rb +44 -0
  32. data/lib/liquid/strainer_factory.rb +36 -0
  33. data/lib/liquid/strainer_template.rb +53 -0
  34. data/lib/liquid/tablerowloop_drop.rb +6 -4
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tag/disableable.rb +22 -0
  37. data/lib/liquid/tag/disabler.rb +21 -0
  38. data/lib/liquid/tags/assign.rb +32 -10
  39. data/lib/liquid/tags/break.rb +8 -3
  40. data/lib/liquid/tags/capture.rb +11 -8
  41. data/lib/liquid/tags/case.rb +41 -27
  42. data/lib/liquid/tags/comment.rb +5 -3
  43. data/lib/liquid/tags/continue.rb +8 -3
  44. data/lib/liquid/tags/cycle.rb +35 -16
  45. data/lib/liquid/tags/decrement.rb +6 -3
  46. data/lib/liquid/tags/echo.rb +26 -0
  47. data/lib/liquid/tags/for.rb +79 -47
  48. data/lib/liquid/tags/if.rb +53 -30
  49. data/lib/liquid/tags/ifchanged.rb +11 -10
  50. data/lib/liquid/tags/include.rb +42 -44
  51. data/lib/liquid/tags/increment.rb +7 -3
  52. data/lib/liquid/tags/raw.rb +14 -11
  53. data/lib/liquid/tags/render.rb +84 -0
  54. data/lib/liquid/tags/table_row.rb +32 -20
  55. data/lib/liquid/tags/unless.rb +15 -15
  56. data/lib/liquid/template.rb +60 -71
  57. data/lib/liquid/template_factory.rb +9 -0
  58. data/lib/liquid/tokenizer.rb +17 -9
  59. data/lib/liquid/usage.rb +8 -0
  60. data/lib/liquid/utils.rb +6 -4
  61. data/lib/liquid/variable.rb +55 -38
  62. data/lib/liquid/variable_lookup.rb +14 -6
  63. data/lib/liquid/version.rb +3 -1
  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 +58 -0
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +608 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +90 -60
  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 +24 -8
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +41 -18
  79. data/test/integration/standard_filter_test.rb +523 -205
  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 +109 -53
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +83 -52
  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 +128 -121
  97. data/test/integration/trim_mode_test.rb +82 -44
  98. data/test/integration/variable_test.rb +46 -31
  99. data/test/test_helper.rb +75 -23
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +82 -72
  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 +12 -10
  105. data/test/unit/parse_tree_visitor_test.rb +247 -0
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +19 -17
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +83 -50
  121. data/lib/liquid/strainer.rb +0 -65
  122. data/test/unit/context_unit_test.rb +0 -483
  123. 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 RenderProfilingTest < Minitest::Test
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 t.profiler
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 1, t.profiler.length
29
+ assert_equal(1, t.profiler.length)
28
30
 
29
31
  node = t.profiler[0]
30
- assert_equal " 'a string' | upcase ", node.code
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 0, t.profiler.length
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 2, t.profiler.length
45
+ assert_equal(2, t.profiler.length)
44
46
 
45
47
  # {{ 'a string' | upcase }}
46
- assert_equal 1, t.profiler[0].line_number
48
+ assert_equal(1, t.profiler[0].line_number)
47
49
  # {{ increment test }}
48
- assert_equal 2, t.profiler[1].line_number
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 1, included_children[0].line_number
60
+ assert_equal(1, included_children[0].line_number)
59
61
  # {{ template_name }}
60
- assert_equal 2, included_children[1].line_number
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 node.render_time
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 t.profiler.total_render_time >= 0, "Total render time was not calculated"
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 2, include_node.children.length
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 "a_template", child.partial
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 "a_template", child.partial
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 "b_template", child.partial
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 "a_template", child.partial
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 "a_template", child.partial
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 |timing|
175
+ t.profiler.each do |_timing|
132
176
  timing_count += 1
133
177
  end
134
178
 
135
- assert_equal 2, timing_count
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 1, t.profiler.length
143
- assert_equal 2, t.profiler[0].children.length
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!({ "collection" => ["one", "two"] })
192
+ t.render!("collection" => ["one", "two"])
149
193
 
150
- assert_equal 1, t.profiler.length
194
+ assert_equal(1, t.profiler.length)
151
195
  # Will profile each invocation of the for block
152
- assert_equal 2, t.profiler[0].children.length
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 = %( {{ '1+1' | instance_eval }} )
19
+ text = %( {{ '1+1' | instance_eval }} )
18
20
  expected = %( 1+1 )
19
21
 
20
- assert_equal expected, Template.parse(text).render!(@assigns)
22
+ assert_equal(expected, Template.parse(text).render!(@assigns))
21
23
  end
22
24
 
23
25
  def test_no_existing_instance_eval
24
- text = %( {{ '1+1' | __instance_eval__ }} )
26
+ text = %( {{ '1+1' | __instance_eval__ }} )
25
27
  expected = %( 1+1 )
26
28
 
27
- assert_equal expected, Template.parse(text).render!(@assigns)
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 = %( {{ '1+1' | instance_eval }} )
33
+ text = %( {{ '1+1' | instance_eval }} )
32
34
  expected = %( 1+1 )
33
35
 
34
- assert_equal expected, Template.parse(text).render!(@assigns)
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 = %( {{ '1+1' | add_one | instance_eval }} )
40
+ text = %( {{ '1+1' | add_one | instance_eval }} )
39
41
  expected = %( 1+1 + 1 )
40
42
 
41
- assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
43
+ assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
42
44
  end
43
45
 
44
- def test_does_not_add_filters_to_symbol_table
46
+ def test_does_not_permanently_add_filters_to_symbol_table
45
47
  current_symbols = Symbol.all_symbols
46
48
 
47
- test = %( {{ "some_string" | a_bad_filter }} )
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
- template = Template.parse(test)
50
- assert_equal [], (Symbol.all_symbols - current_symbols)
60
+ GC.start
51
61
 
52
- template.render!
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 "", Template.parse("{{ drop.custom_method_1 }}", assigns).render!
61
- assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render!
62
- assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render!
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
- assert_equal [], (Symbol.all_symbols - current_symbols)
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 [](whatever)
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
- [ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
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 3, @filters.size([1, 2, 3])
63
- assert_equal 0, @filters.size([])
64
- assert_equal 0, @filters.size(nil)
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 'testing', @filters.downcase("Testing")
69
- assert_equal '', @filters.downcase(nil)
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 'TESTING', @filters.upcase("Testing")
74
- assert_equal '', @filters.upcase(nil)
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 'oob', @filters.slice('foobar', 1, 3)
79
- assert_equal 'oobar', @filters.slice('foobar', 1, 1000)
80
- assert_equal '', @filters.slice('foobar', 1, 0)
81
- assert_equal 'o', @filters.slice('foobar', 1, 1)
82
- assert_equal 'bar', @filters.slice('foobar', 3, 3)
83
- assert_equal 'ar', @filters.slice('foobar', -2, 2)
84
- assert_equal 'ar', @filters.slice('foobar', -2, 1000)
85
- assert_equal 'r', @filters.slice('foobar', -1)
86
- assert_equal '', @filters.slice(nil, 0)
87
- assert_equal '', @filters.slice('foobar', 100, 10)
88
- assert_equal '', @filters.slice('foobar', -100, 10)
89
- assert_equal 'oob', @filters.slice('foobar', '1', '3')
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 %w(o o b), @filters.slice(input, 1, 3)
101
- assert_equal %w(o o b a r), @filters.slice(input, 1, 1000)
102
- assert_equal %w(), @filters.slice(input, 1, 0)
103
- assert_equal %w(o), @filters.slice(input, 1, 1)
104
- assert_equal %w(b a r), @filters.slice(input, 3, 3)
105
- assert_equal %w(a r), @filters.slice(input, -2, 2)
106
- assert_equal %w(a r), @filters.slice(input, -2, 1000)
107
- assert_equal %w(r), @filters.slice(input, -1)
108
- assert_equal %w(), @filters.slice(input, 100, 10)
109
- assert_equal %w(), @filters.slice(input, -100, 10)
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 '1234...', @filters.truncate('1234567890', 7)
114
- assert_equal '1234567890', @filters.truncate('1234567890', 20)
115
- assert_equal '...', @filters.truncate('1234567890', 0)
116
- assert_equal '1234567890', @filters.truncate('1234567890')
117
- assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
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 ['12', '34'], @filters.split('12~34', '~')
122
- assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
123
- assert_equal ['A?Z'], @filters.split('A?Z', '~')
124
- # Regexp works although Liquid does not support.
125
- assert_equal ['A', 'Z'], @filters.split('AxZ', /x/)
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 '&lt;strong&gt;', @filters.escape('<strong>')
131
- assert_equal nil, @filters.escape(nil)
132
- assert_equal '&lt;strong&gt;', @filters.h('<strong>')
131
+ assert_equal('&lt;strong&gt;', @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('&lt;strong&gt;', @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 '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
145
+ assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))
137
146
  end
138
147
 
139
148
  def test_url_encode
140
- assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
141
- assert_equal nil, @filters.url_encode(nil)
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 'foo bar', @filters.url_decode('foo+bar')
146
- assert_equal 'foo bar', @filters.url_decode('foo%20bar')
147
- assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
148
- assert_equal nil, @filters.url_decode(nil)
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 'one two three', @filters.truncatewords('one two three', 4)
153
- assert_equal 'one two...', @filters.truncatewords('one two three', 2)
154
- assert_equal 'one two three', @filters.truncatewords('one two three')
155
- assert_equal 'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...', @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
156
- assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
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&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
174
+ @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; 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 'test', @filters.strip_html("<div>test</div>")
161
- assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
162
- assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
163
- assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
164
- assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
165
- assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
166
- assert_equal '', @filters.strip_html(nil)
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 '1 2 3 4', @filters.join([1, 2, 3, 4])
171
- assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
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 [1, 2, 3, 4], @filters.sort([4, 3, 2, 1])
176
- assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
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 [], @filters.sort([], "a")
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 [], @filters.sort_natural([], "a")
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 [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
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 [2, 10], @filters.sort([10, 2])
193
- assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
194
- assert_equal ["10", "2"], @filters.sort(["10", "2"])
195
- assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
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 ["foo"], @filters.uniq("foo")
200
- assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
201
- assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
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 [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
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 [], @filters.uniq([], "a")
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 [], @filters.compact([], "a")
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 [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
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 [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
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 [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
224
- assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
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 "", '{{ "foo" | map: "__id__" }}'
230
- assert_template_result "", '{{ "foo" | map: "inspect" }}'
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 "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
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 "4217", '{{ thing | map: "foo" | map: "bar" }}',
240
- "thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
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 = { "foo" => { "bar" => 42 } }
246
- assert_template_result "42", template, "thing" => hash
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 t.foo > 0
399
+ assert(t.foo > 0)
253
400
  end
254
401
 
255
402
  def test_map_over_proc
256
- drop = TestDrop.new
257
- p = proc{ drop }
403
+ drop = TestDrop.new
404
+ p = proc { drop }
258
405
  templ = '{{ procs | map: "test" }}'
259
- assert_template_result "testfoo", templ, "procs" => [p]
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 "foobar", templ, "drops" => drops
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 "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
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 "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
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 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
285
- assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
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 "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
459
+ assert_template_result("wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new)
290
460
  end
291
461
 
292
462
  def test_date
293
- assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
294
- assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
295
- assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")
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 'May', @filters.date("2006-05-05 10:00:00", "%B")
298
- assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B")
299
- assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B")
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 '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
302
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
303
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
304
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)
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 '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
476
+ assert_equal('07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y"))
307
477
 
308
- assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
309
- assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
310
- assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
311
- assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
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
- assert_equal nil, @filters.date(nil, "%B")
483
+ assert_nil(@filters.date(nil, "%B"))
314
484
 
315
- assert_equal '', @filters.date('', "%B")
485
+ assert_equal('', @filters.date('', "%B"))
316
486
 
317
487
  with_timezone("UTC") do
318
- assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
319
- assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
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 1, @filters.first([1, 2, 3])
325
- assert_equal 3, @filters.last([1, 2, 3])
326
- assert_equal nil, @filters.first([])
327
- assert_equal nil, @filters.last([])
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 '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
332
- assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
333
- assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
334
- assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
335
- assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
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 ' ', @filters.remove("a a a a", 'a')
340
- assert_equal ' ', @filters.remove("1 1 1 1", 1)
341
- assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
342
- assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
343
- assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
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 'foobar', "{{ 'foo|bar' | remove: '|' }}"
517
+ assert_template_result('foobar', "{{ 'foo|bar' | remove: '|' }}")
348
518
  end
349
519
 
350
520
  def test_strip
351
- assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c "
352
- assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t"
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 'ab c ', "{{ source | lstrip }}", 'source' => " ab c "
357
- assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t"
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 " ab c", "{{ source | rstrip }}", 'source' => " ab c "
362
- assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t"
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 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
367
- assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
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 "a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
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 "2", "{{ 1 | plus:1 }}"
376
- assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
545
+ assert_template_result("2", "{{ 1 | plus:1 }}")
546
+ assert_template_result("2.0", "{{ '1' | plus:'1.0' }}")
377
547
 
378
- assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
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 "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
383
- assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
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 "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
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 "17", "{{ 17 | abs }}"
390
- assert_template_result "17", "{{ -17 | abs }}"
391
- assert_template_result "17", "{{ '17' | abs }}"
392
- assert_template_result "17", "{{ '-17' | abs }}"
393
- assert_template_result "0", "{{ 0 | abs }}"
394
- assert_template_result "0", "{{ '0' | abs }}"
395
- assert_template_result "17.42", "{{ 17.42 | abs }}"
396
- assert_template_result "17.42", "{{ -17.42 | abs }}"
397
- assert_template_result "17.42", "{{ '17.42' | abs }}"
398
- assert_template_result "17.42", "{{ '-17.42' | abs }}"
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 "12", "{{ 3 | times:4 }}"
403
- assert_template_result "0", "{{ 'foo' | times:4 }}"
404
- assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
405
- assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
406
- assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
407
- assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
408
- assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
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 "4", "{{ 12 | divided_by:3 }}"
413
- assert_template_result "4", "{{ 14 | divided_by:3 }}"
582
+ assert_template_result("4", "{{ 12 | divided_by:3 }}")
583
+ assert_template_result("4", "{{ 14 | divided_by:3 }}")
414
584
 
415
- assert_template_result "5", "{{ 15 | divided_by:3 }}"
416
- assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
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 "0.5", "{{ 2.0 | divided_by:4 }}"
588
+ assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
419
589
  assert_raises(Liquid::ZeroDivisionError) do
420
- assert_template_result "4", "{{ 1 | modulo: 0 }}"
590
+ assert_template_result("4", "{{ 1 | modulo: 0 }}")
421
591
  end
422
592
 
423
- assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
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 "1", "{{ 3 | modulo:2 }}"
597
+ assert_template_result("1", "{{ 3 | modulo:2 }}")
428
598
  assert_raises(Liquid::ZeroDivisionError) do
429
- assert_template_result "4", "{{ 1 | modulo: 0 }}"
599
+ assert_template_result("4", "{{ 1 | modulo: 0 }}")
430
600
  end
431
601
 
432
- assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
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 "5", "{{ input | round }}", 'input' => 4.6
437
- assert_template_result "4", "{{ '4.3' | round }}"
438
- assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
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 "4", "{{ 1.0 | divided_by: 0.0 | round }}"
610
+ assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
441
611
  end
442
612
 
443
- assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
444
- assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
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 "5", "{{ input | ceil }}", 'input' => 4.6
449
- assert_template_result "5", "{{ '4.3' | ceil }}"
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 "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
621
+ assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
452
622
  end
453
623
 
454
- assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
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 "4", "{{ input | floor }}", 'input' => 4.6
459
- assert_template_result "4", "{{ '4.3' | floor }}"
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 "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
631
+ assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
462
632
  end
463
633
 
464
- assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
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 [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
475
- assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
476
- assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
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 "foo", @filters.default("foo", "bar")
491
- assert_equal "bar", @filters.default(nil, "bar")
492
- assert_equal "bar", @filters.default("", "bar")
493
- assert_equal "bar", @filters.default(false, "bar")
494
- assert_equal "bar", @filters.default([], "bar")
495
- assert_equal "bar", @filters.default({}, "bar")
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 = ENV['TZ']
828
+ old_tz = ENV['TZ']
511
829
  ENV['TZ'] = tz
512
830
  yield
513
831
  ensure