liquid 4.0.0 → 5.0.1

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 +101 -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 +192 -76
  7. data/lib/liquid/condition.rb +69 -29
  8. data/lib/liquid/context.rb +110 -53
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -18
  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 +21 -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 +170 -63
  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 +34 -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 +53 -72
  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 +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +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 +513 -210
  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 +123 -120
  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 +254 -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 +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +79 -46
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/test/unit/context_unit_test.rb +0 -483
  123. data/test/unit/strainer_unit_test.rb +0 -148
@@ -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,390 +98,566 @@ 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)
118
- assert_equal '12341', @filters.truncate("1234567890", 5, 1)
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))
119
120
  end
120
121
 
121
122
  def test_split
122
- assert_equal ['12', '34'], @filters.split('12~34', '~')
123
- assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
124
- assert_equal ['A?Z'], @filters.split('A?Z', '~')
125
- assert_equal [], @filters.split(nil, ' ')
126
- assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
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)
157
- assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
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))
178
+ assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
179
+ assert_equal('one two...', @filters.truncatewords("one two three four", 2))
180
+ assert_equal('one...', @filters.truncatewords("one two three four", 0))
158
181
  end
159
182
 
160
183
  def test_strip_html
161
- assert_equal 'test', @filters.strip_html("<div>test</div>")
162
- assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
163
- assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
164
- assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
165
- assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
166
- assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
167
- assert_equal '', @filters.strip_html(nil)
184
+ assert_equal('test', @filters.strip_html("<div>test</div>"))
185
+ assert_equal('test', @filters.strip_html("<div id='test'>test</div>"))
186
+ assert_equal('', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>"))
187
+ assert_equal('', @filters.strip_html("<style type='text/css'>foo bar</style>"))
188
+ assert_equal('test', @filters.strip_html("<div\nclass='multiline'>test</div>"))
189
+ assert_equal('test', @filters.strip_html("<!-- foo bar \n test -->test"))
190
+ assert_equal('', @filters.strip_html(nil))
191
+
192
+ # Quirk of the existing implementation
193
+ assert_equal('foo;', @filters.strip_html("<<<script </script>script>foo;</script>"))
168
194
  end
169
195
 
170
196
  def test_join
171
- assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
172
- assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
197
+ assert_equal('1 2 3 4', @filters.join([1, 2, 3, 4]))
198
+ assert_equal('1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - '))
199
+ assert_equal('1121314', @filters.join([1, 2, 3, 4], 1))
173
200
  end
174
201
 
175
202
  def test_sort
176
- assert_equal [1, 2, 3, 4], @filters.sort([4, 3, 2, 1])
177
- assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
203
+ assert_equal([1, 2, 3, 4], @filters.sort([4, 3, 2, 1]))
204
+ assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
205
+ end
206
+
207
+ def test_sort_with_nils
208
+ assert_equal([1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1]))
209
+ assert_equal([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a"))
178
210
  end
179
211
 
180
212
  def test_sort_when_property_is_sometimes_missing_puts_nils_last
181
- input = [
213
+ input = [
182
214
  { "price" => 4, "handle" => "alpha" },
183
215
  { "handle" => "beta" },
184
216
  { "price" => 1, "handle" => "gamma" },
185
217
  { "handle" => "delta" },
186
- { "price" => 2, "handle" => "epsilon" }
218
+ { "price" => 2, "handle" => "epsilon" },
187
219
  ]
188
220
  expectation = [
189
221
  { "price" => 1, "handle" => "gamma" },
190
222
  { "price" => 2, "handle" => "epsilon" },
191
223
  { "price" => 4, "handle" => "alpha" },
192
224
  { "handle" => "delta" },
193
- { "handle" => "beta" }
225
+ { "handle" => "beta" },
194
226
  ]
195
- assert_equal expectation, @filters.sort(input, "price")
227
+ assert_equal(expectation, @filters.sort(input, "price"))
228
+ end
229
+
230
+ def test_sort_natural
231
+ assert_equal(["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"]))
232
+ assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a"))
233
+ end
234
+
235
+ def test_sort_natural_with_nils
236
+ assert_equal(["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"]))
237
+ assert_equal([{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a"))
238
+ end
239
+
240
+ def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
241
+ input = [
242
+ { "price" => "4", "handle" => "alpha" },
243
+ { "handle" => "beta" },
244
+ { "price" => "1", "handle" => "gamma" },
245
+ { "handle" => "delta" },
246
+ { "price" => 2, "handle" => "epsilon" },
247
+ ]
248
+ expectation = [
249
+ { "price" => "1", "handle" => "gamma" },
250
+ { "price" => 2, "handle" => "epsilon" },
251
+ { "price" => "4", "handle" => "alpha" },
252
+ { "handle" => "delta" },
253
+ { "handle" => "beta" },
254
+ ]
255
+ assert_equal(expectation, @filters.sort_natural(input, "price"))
256
+ end
257
+
258
+ def test_sort_natural_case_check
259
+ input = [
260
+ { "key" => "X" },
261
+ { "key" => "Y" },
262
+ { "key" => "Z" },
263
+ { "fake" => "t" },
264
+ { "key" => "a" },
265
+ { "key" => "b" },
266
+ { "key" => "c" },
267
+ ]
268
+ expectation = [
269
+ { "key" => "a" },
270
+ { "key" => "b" },
271
+ { "key" => "c" },
272
+ { "key" => "X" },
273
+ { "key" => "Y" },
274
+ { "key" => "Z" },
275
+ { "fake" => "t" },
276
+ ]
277
+ assert_equal(expectation, @filters.sort_natural(input, "key"))
278
+ assert_equal(["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"]))
196
279
  end
197
280
 
198
281
  def test_sort_empty_array
199
- assert_equal [], @filters.sort([], "a")
282
+ assert_equal([], @filters.sort([], "a"))
283
+ end
284
+
285
+ def test_sort_invalid_property
286
+ foo = [
287
+ [1],
288
+ [2],
289
+ [3],
290
+ ]
291
+
292
+ assert_raises(Liquid::ArgumentError) do
293
+ @filters.sort(foo, "bar")
294
+ end
200
295
  end
201
296
 
202
297
  def test_sort_natural_empty_array
203
- assert_equal [], @filters.sort_natural([], "a")
298
+ assert_equal([], @filters.sort_natural([], "a"))
299
+ end
300
+
301
+ def test_sort_natural_invalid_property
302
+ foo = [
303
+ [1],
304
+ [2],
305
+ [3],
306
+ ]
307
+
308
+ assert_raises(Liquid::ArgumentError) do
309
+ @filters.sort_natural(foo, "bar")
310
+ end
204
311
  end
205
312
 
206
313
  def test_legacy_sort_hash
207
- assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
314
+ assert_equal([{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2))
208
315
  end
209
316
 
210
317
  def test_numerical_vs_lexicographical_sort
211
- assert_equal [2, 10], @filters.sort([10, 2])
212
- assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
213
- assert_equal ["10", "2"], @filters.sort(["10", "2"])
214
- assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
318
+ assert_equal([2, 10], @filters.sort([10, 2]))
319
+ assert_equal([{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a"))
320
+ assert_equal(["10", "2"], @filters.sort(["10", "2"]))
321
+ assert_equal([{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a"))
215
322
  end
216
323
 
217
324
  def test_uniq
218
- assert_equal ["foo"], @filters.uniq("foo")
219
- assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
220
- assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
325
+ assert_equal(["foo"], @filters.uniq("foo"))
326
+ assert_equal([1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1]))
327
+ assert_equal([{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a"))
221
328
  testdrop = TestDrop.new
222
- assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
329
+ assert_equal([testdrop], @filters.uniq([testdrop, TestDrop.new], 'test'))
223
330
  end
224
331
 
225
332
  def test_uniq_empty_array
226
- assert_equal [], @filters.uniq([], "a")
333
+ assert_equal([], @filters.uniq([], "a"))
334
+ end
335
+
336
+ def test_uniq_invalid_property
337
+ foo = [
338
+ [1],
339
+ [2],
340
+ [3],
341
+ ]
342
+
343
+ assert_raises(Liquid::ArgumentError) do
344
+ @filters.uniq(foo, "bar")
345
+ end
227
346
  end
228
347
 
229
348
  def test_compact_empty_array
230
- assert_equal [], @filters.compact([], "a")
349
+ assert_equal([], @filters.compact([], "a"))
350
+ end
351
+
352
+ def test_compact_invalid_property
353
+ foo = [
354
+ [1],
355
+ [2],
356
+ [3],
357
+ ]
358
+
359
+ assert_raises(Liquid::ArgumentError) do
360
+ @filters.compact(foo, "bar")
361
+ end
231
362
  end
232
363
 
233
364
  def test_reverse
234
- assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
365
+ assert_equal([4, 3, 2, 1], @filters.reverse([1, 2, 3, 4]))
235
366
  end
236
367
 
237
368
  def test_legacy_reverse_hash
238
- assert_equal [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
369
+ assert_equal([{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2))
239
370
  end
240
371
 
241
372
  def test_map
242
- assert_equal [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
243
- assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
244
- 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
373
+ assert_equal([1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a'))
374
+ assert_template_result('abc', "{{ ary | map:'foo' | map:'bar' }}",
375
+ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }])
245
376
  end
246
377
 
247
378
  def test_map_doesnt_call_arbitrary_stuff
248
- assert_template_result "", '{{ "foo" | map: "__id__" }}'
249
- assert_template_result "", '{{ "foo" | map: "inspect" }}'
379
+ assert_template_result("", '{{ "foo" | map: "__id__" }}')
380
+ assert_template_result("", '{{ "foo" | map: "inspect" }}')
250
381
  end
251
382
 
252
383
  def test_map_calls_to_liquid
253
384
  t = TestThing.new
254
- assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
385
+ assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t])
255
386
  end
256
387
 
257
388
  def test_map_on_hashes
258
- assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
259
- "thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
389
+ assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
390
+ "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] })
260
391
  end
261
392
 
262
393
  def test_legacy_map_on_hashes_with_dynamic_key
263
394
  template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
264
- hash = { "foo" => { "bar" => 42 } }
265
- assert_template_result "42", template, "thing" => hash
395
+ hash = { "foo" => { "bar" => 42 } }
396
+ assert_template_result("42", template, "thing" => hash)
266
397
  end
267
398
 
268
399
  def test_sort_calls_to_liquid
269
400
  t = TestThing.new
270
401
  Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
271
- assert t.foo > 0
402
+ assert(t.foo > 0)
272
403
  end
273
404
 
274
405
  def test_map_over_proc
275
- drop = TestDrop.new
276
- p = proc{ drop }
406
+ drop = TestDrop.new
407
+ p = proc { drop }
277
408
  templ = '{{ procs | map: "test" }}'
278
- assert_template_result "testfoo", templ, "procs" => [p]
409
+ assert_template_result("testfoo", templ, "procs" => [p])
279
410
  end
280
411
 
281
412
  def test_map_over_drops_returning_procs
282
413
  drops = [
283
414
  {
284
- "proc" => ->{ "foo" },
415
+ "proc" => -> { "foo" },
285
416
  },
286
417
  {
287
- "proc" => ->{ "bar" },
418
+ "proc" => -> { "bar" },
288
419
  },
289
420
  ]
290
421
  templ = '{{ drops | map: "proc" }}'
291
- assert_template_result "foobar", templ, "drops" => drops
422
+ assert_template_result("foobar", templ, "drops" => drops)
292
423
  end
293
424
 
294
425
  def test_map_works_on_enumerables
295
- assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
426
+ assert_template_result("123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new)
427
+ end
428
+
429
+ def test_map_returns_empty_on_2d_input_array
430
+ foo = [
431
+ [1],
432
+ [2],
433
+ [3],
434
+ ]
435
+
436
+ assert_raises(Liquid::ArgumentError) do
437
+ @filters.map(foo, "bar")
438
+ end
439
+ end
440
+
441
+ def test_map_returns_empty_with_no_property
442
+ foo = [
443
+ [1],
444
+ [2],
445
+ [3],
446
+ ]
447
+ assert_raises(Liquid::ArgumentError) do
448
+ @filters.map(foo, nil)
449
+ end
296
450
  end
297
451
 
298
452
  def test_sort_works_on_enumerables
299
- assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
453
+ assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new)
300
454
  end
301
455
 
302
456
  def test_first_and_last_call_to_liquid
303
- assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
304
- assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
457
+ assert_template_result('foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new])
458
+ assert_template_result('foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new])
305
459
  end
306
460
 
307
461
  def test_truncate_calls_to_liquid
308
- assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
462
+ assert_template_result("wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new)
309
463
  end
310
464
 
311
465
  def test_date
312
- assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
313
- assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
314
- assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")
466
+ assert_equal('May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B"))
467
+ assert_equal('June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B"))
468
+ assert_equal('July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B"))
315
469
 
316
- assert_equal 'May', @filters.date("2006-05-05 10:00:00", "%B")
317
- assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B")
318
- assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B")
470
+ assert_equal('May', @filters.date("2006-05-05 10:00:00", "%B"))
471
+ assert_equal('June', @filters.date("2006-06-05 10:00:00", "%B"))
472
+ assert_equal('July', @filters.date("2006-07-05 10:00:00", "%B"))
319
473
 
320
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
321
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
322
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
323
- assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)
474
+ assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", ""))
475
+ assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", ""))
476
+ assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", ""))
477
+ assert_equal('2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil))
324
478
 
325
- assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
479
+ assert_equal('07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y"))
326
480
 
327
- assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
328
- assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
329
- assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
330
- assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
481
+ assert_equal("07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y"))
482
+ assert_equal(Date.today.year.to_s, @filters.date('now', '%Y'))
483
+ assert_equal(Date.today.year.to_s, @filters.date('today', '%Y'))
484
+ assert_equal(Date.today.year.to_s, @filters.date('Today', '%Y'))
331
485
 
332
- assert_equal nil, @filters.date(nil, "%B")
486
+ assert_nil(@filters.date(nil, "%B"))
333
487
 
334
- assert_equal '', @filters.date('', "%B")
488
+ assert_equal('', @filters.date('', "%B"))
335
489
 
336
490
  with_timezone("UTC") do
337
- assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
338
- assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
491
+ assert_equal("07/05/2006", @filters.date(1152098955, "%m/%d/%Y"))
492
+ assert_equal("07/05/2006", @filters.date("1152098955", "%m/%d/%Y"))
339
493
  end
340
494
  end
341
495
 
342
496
  def test_first_last
343
- assert_equal 1, @filters.first([1, 2, 3])
344
- assert_equal 3, @filters.last([1, 2, 3])
345
- assert_equal nil, @filters.first([])
346
- assert_equal nil, @filters.last([])
497
+ assert_equal(1, @filters.first([1, 2, 3]))
498
+ assert_equal(3, @filters.last([1, 2, 3]))
499
+ assert_nil(@filters.first([]))
500
+ assert_nil(@filters.last([]))
347
501
  end
348
502
 
349
503
  def test_replace
350
- assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
351
- assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
352
- assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
353
- assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
354
- assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
504
+ assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2))
505
+ assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
506
+ assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2))
507
+ assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
508
+ assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
355
509
  end
356
510
 
357
511
  def test_remove
358
- assert_equal ' ', @filters.remove("a a a a", 'a')
359
- assert_equal ' ', @filters.remove("1 1 1 1", 1)
360
- assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
361
- assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
362
- assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
512
+ assert_equal(' ', @filters.remove("a a a a", 'a'))
513
+ assert_equal(' ', @filters.remove("1 1 1 1", 1))
514
+ assert_equal('a a a', @filters.remove_first("a a a a", 'a '))
515
+ assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1))
516
+ assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}")
363
517
  end
364
518
 
365
519
  def test_pipes_in_string_arguments
366
- assert_template_result 'foobar', "{{ 'foo|bar' | remove: '|' }}"
520
+ assert_template_result('foobar', "{{ 'foo|bar' | remove: '|' }}")
367
521
  end
368
522
 
369
523
  def test_strip
370
- assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c "
371
- assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t"
524
+ assert_template_result('ab c', "{{ source | strip }}", 'source' => " ab c ")
525
+ assert_template_result('ab c', "{{ source | strip }}", 'source' => " \tab c \n \t")
372
526
  end
373
527
 
374
528
  def test_lstrip
375
- assert_template_result 'ab c ', "{{ source | lstrip }}", 'source' => " ab c "
376
- assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t"
529
+ assert_template_result('ab c ', "{{ source | lstrip }}", 'source' => " ab c ")
530
+ assert_template_result("ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t")
377
531
  end
378
532
 
379
533
  def test_rstrip
380
- assert_template_result " ab c", "{{ source | rstrip }}", 'source' => " ab c "
381
- assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t"
534
+ assert_template_result(" ab c", "{{ source | rstrip }}", 'source' => " ab c ")
535
+ assert_template_result(" \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t")
382
536
  end
383
537
 
384
538
  def test_strip_newlines
385
- assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
386
- assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
539
+ assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc")
540
+ assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc")
387
541
  end
388
542
 
389
543
  def test_newlines_to_br
390
- assert_template_result "a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
544
+ assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
545
+ assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
391
546
  end
392
547
 
393
548
  def test_plus
394
- assert_template_result "2", "{{ 1 | plus:1 }}"
395
- assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
549
+ assert_template_result("2", "{{ 1 | plus:1 }}")
550
+ assert_template_result("2.0", "{{ '1' | plus:'1.0' }}")
396
551
 
397
- assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
552
+ assert_template_result("5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3))
398
553
  end
399
554
 
400
555
  def test_minus
401
- assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
402
- assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
556
+ assert_template_result("4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1)
557
+ assert_template_result("2.3", "{{ '4.3' | minus:'2' }}")
403
558
 
404
- assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
559
+ assert_template_result("5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7))
405
560
  end
406
561
 
407
562
  def test_abs
408
- assert_template_result "17", "{{ 17 | abs }}"
409
- assert_template_result "17", "{{ -17 | abs }}"
410
- assert_template_result "17", "{{ '17' | abs }}"
411
- assert_template_result "17", "{{ '-17' | abs }}"
412
- assert_template_result "0", "{{ 0 | abs }}"
413
- assert_template_result "0", "{{ '0' | abs }}"
414
- assert_template_result "17.42", "{{ 17.42 | abs }}"
415
- assert_template_result "17.42", "{{ -17.42 | abs }}"
416
- assert_template_result "17.42", "{{ '17.42' | abs }}"
417
- assert_template_result "17.42", "{{ '-17.42' | abs }}"
563
+ assert_template_result("17", "{{ 17 | abs }}")
564
+ assert_template_result("17", "{{ -17 | abs }}")
565
+ assert_template_result("17", "{{ '17' | abs }}")
566
+ assert_template_result("17", "{{ '-17' | abs }}")
567
+ assert_template_result("0", "{{ 0 | abs }}")
568
+ assert_template_result("0", "{{ '0' | abs }}")
569
+ assert_template_result("17.42", "{{ 17.42 | abs }}")
570
+ assert_template_result("17.42", "{{ -17.42 | abs }}")
571
+ assert_template_result("17.42", "{{ '17.42' | abs }}")
572
+ assert_template_result("17.42", "{{ '-17.42' | abs }}")
418
573
  end
419
574
 
420
575
  def test_times
421
- assert_template_result "12", "{{ 3 | times:4 }}"
422
- assert_template_result "0", "{{ 'foo' | times:4 }}"
423
- assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
424
- assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
425
- assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
426
- assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
427
- assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
576
+ assert_template_result("12", "{{ 3 | times:4 }}")
577
+ assert_template_result("0", "{{ 'foo' | times:4 }}")
578
+ assert_template_result("6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}")
579
+ assert_template_result("7.25", "{{ 0.0725 | times:100 }}")
580
+ assert_template_result("-7.25", '{{ "-0.0725" | times:100 }}')
581
+ assert_template_result("7.25", '{{ "-0.0725" | times: -100 }}')
582
+ assert_template_result("4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2))
428
583
  end
429
584
 
430
585
  def test_divided_by
431
- assert_template_result "4", "{{ 12 | divided_by:3 }}"
432
- assert_template_result "4", "{{ 14 | divided_by:3 }}"
586
+ assert_template_result("4", "{{ 12 | divided_by:3 }}")
587
+ assert_template_result("4", "{{ 14 | divided_by:3 }}")
433
588
 
434
- assert_template_result "5", "{{ 15 | divided_by:3 }}"
435
- assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
589
+ assert_template_result("5", "{{ 15 | divided_by:3 }}")
590
+ assert_equal("Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render)
436
591
 
437
- assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
592
+ assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
438
593
  assert_raises(Liquid::ZeroDivisionError) do
439
- assert_template_result "4", "{{ 1 | modulo: 0 }}"
594
+ assert_template_result("4", "{{ 1 | modulo: 0 }}")
440
595
  end
441
596
 
442
- assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
597
+ assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
443
598
  end
444
599
 
445
600
  def test_modulo
446
- assert_template_result "1", "{{ 3 | modulo:2 }}"
601
+ assert_template_result("1", "{{ 3 | modulo:2 }}")
447
602
  assert_raises(Liquid::ZeroDivisionError) do
448
- assert_template_result "4", "{{ 1 | modulo: 0 }}"
603
+ assert_template_result("4", "{{ 1 | modulo: 0 }}")
449
604
  end
450
605
 
451
- assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
606
+ assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
452
607
  end
453
608
 
454
609
  def test_round
455
- assert_template_result "5", "{{ input | round }}", 'input' => 4.6
456
- assert_template_result "4", "{{ '4.3' | round }}"
457
- assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
610
+ assert_template_result("5", "{{ input | round }}", 'input' => 4.6)
611
+ assert_template_result("4", "{{ '4.3' | round }}")
612
+ assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
458
613
  assert_raises(Liquid::FloatDomainError) do
459
- assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
614
+ assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
460
615
  end
461
616
 
462
- assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
463
- assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
617
+ assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
618
+ assert_template_result("4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3))
464
619
  end
465
620
 
466
621
  def test_ceil
467
- assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
468
- assert_template_result "5", "{{ '4.3' | ceil }}"
622
+ assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
623
+ assert_template_result("5", "{{ '4.3' | ceil }}")
469
624
  assert_raises(Liquid::FloatDomainError) do
470
- assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
625
+ assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
471
626
  end
472
627
 
473
- assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
628
+ assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
474
629
  end
475
630
 
476
631
  def test_floor
477
- assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
478
- assert_template_result "4", "{{ '4.3' | floor }}"
632
+ assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
633
+ assert_template_result("4", "{{ '4.3' | floor }}")
479
634
  assert_raises(Liquid::FloatDomainError) do
480
- assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
635
+ assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
481
636
  end
482
637
 
483
- assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
638
+ assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
639
+ end
640
+
641
+ def test_at_most
642
+ assert_template_result("4", "{{ 5 | at_most:4 }}")
643
+ assert_template_result("5", "{{ 5 | at_most:5 }}")
644
+ assert_template_result("5", "{{ 5 | at_most:6 }}")
645
+
646
+ assert_template_result("4.5", "{{ 4.5 | at_most:5 }}")
647
+ assert_template_result("5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6))
648
+ assert_template_result("4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4))
649
+ assert_template_result("4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4))
650
+ end
651
+
652
+ def test_at_least
653
+ assert_template_result("5", "{{ 5 | at_least:4 }}")
654
+ assert_template_result("5", "{{ 5 | at_least:5 }}")
655
+ assert_template_result("6", "{{ 5 | at_least:6 }}")
656
+
657
+ assert_template_result("5", "{{ 4.5 | at_least:5 }}")
658
+ assert_template_result("6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6))
659
+ assert_template_result("5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4))
660
+ assert_template_result("6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6))
484
661
  end
485
662
 
486
663
  def test_append
@@ -490,9 +667,9 @@ class StandardFiltersTest < Minitest::Test
490
667
  end
491
668
 
492
669
  def test_concat
493
- assert_equal [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
494
- assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
495
- assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
670
+ assert_equal([1, 2, 3, 4], @filters.concat([1, 2], [3, 4]))
671
+ assert_equal([1, 2, 'a'], @filters.concat([1, 2], ['a']))
672
+ assert_equal([1, 2, 10], @filters.concat([1, 2], [10]))
496
673
 
497
674
  assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
498
675
  @filters.concat([1, 2], 10)
@@ -506,12 +683,23 @@ class StandardFiltersTest < Minitest::Test
506
683
  end
507
684
 
508
685
  def test_default
509
- assert_equal "foo", @filters.default("foo", "bar")
510
- assert_equal "bar", @filters.default(nil, "bar")
511
- assert_equal "bar", @filters.default("", "bar")
512
- assert_equal "bar", @filters.default(false, "bar")
513
- assert_equal "bar", @filters.default([], "bar")
514
- assert_equal "bar", @filters.default({}, "bar")
686
+ assert_equal("foo", @filters.default("foo", "bar"))
687
+ assert_equal("bar", @filters.default(nil, "bar"))
688
+ assert_equal("bar", @filters.default("", "bar"))
689
+ assert_equal("bar", @filters.default(false, "bar"))
690
+ assert_equal("bar", @filters.default([], "bar"))
691
+ assert_equal("bar", @filters.default({}, "bar"))
692
+ assert_template_result('bar', "{{ false | default: 'bar' }}")
693
+ end
694
+
695
+ def test_default_handle_false
696
+ assert_equal("foo", @filters.default("foo", "bar", "allow_false" => true))
697
+ assert_equal("bar", @filters.default(nil, "bar", "allow_false" => true))
698
+ assert_equal("bar", @filters.default("", "bar", "allow_false" => true))
699
+ assert_equal(false, @filters.default(false, "bar", "allow_false" => true))
700
+ assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
701
+ assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
702
+ assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
515
703
  end
516
704
 
517
705
  def test_cannot_access_private_methods
@@ -523,10 +711,125 @@ class StandardFiltersTest < Minitest::Test
523
711
  assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
524
712
  end
525
713
 
714
+ def test_where
715
+ input = [
716
+ { "handle" => "alpha", "ok" => true },
717
+ { "handle" => "beta", "ok" => false },
718
+ { "handle" => "gamma", "ok" => false },
719
+ { "handle" => "delta", "ok" => true },
720
+ ]
721
+
722
+ expectation = [
723
+ { "handle" => "alpha", "ok" => true },
724
+ { "handle" => "delta", "ok" => true },
725
+ ]
726
+
727
+ assert_equal(expectation, @filters.where(input, "ok", true))
728
+ assert_equal(expectation, @filters.where(input, "ok"))
729
+ end
730
+
731
+ def test_where_no_key_set
732
+ input = [
733
+ { "handle" => "alpha", "ok" => true },
734
+ { "handle" => "beta" },
735
+ { "handle" => "gamma" },
736
+ { "handle" => "delta", "ok" => true },
737
+ ]
738
+
739
+ expectation = [
740
+ { "handle" => "alpha", "ok" => true },
741
+ { "handle" => "delta", "ok" => true },
742
+ ]
743
+
744
+ assert_equal(expectation, @filters.where(input, "ok", true))
745
+ assert_equal(expectation, @filters.where(input, "ok"))
746
+ end
747
+
748
+ def test_where_non_array_map_input
749
+ assert_equal([{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok"))
750
+ assert_equal([], @filters.where({ "a" => "not ok" }, "a", "ok"))
751
+ end
752
+
753
+ def test_where_indexable_but_non_map_value
754
+ assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) }
755
+ assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") }
756
+ end
757
+
758
+ def test_where_non_boolean_value
759
+ input = [
760
+ { "message" => "Bonjour!", "language" => "French" },
761
+ { "message" => "Hello!", "language" => "English" },
762
+ { "message" => "Hallo!", "language" => "German" },
763
+ ]
764
+
765
+ assert_equal([{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French"))
766
+ assert_equal([{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German"))
767
+ assert_equal([{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English"))
768
+ end
769
+
770
+ def test_where_array_of_only_unindexable_values
771
+ assert_nil(@filters.where([nil], "ok", true))
772
+ assert_nil(@filters.where([nil], "ok"))
773
+ end
774
+
775
+ def test_all_filters_never_raise_non_liquid_exception
776
+ test_drop = TestDrop.new
777
+ test_drop.context = Context.new
778
+ test_enum = TestEnumerable.new
779
+ test_enum.context = Context.new
780
+ test_types = [
781
+ "foo",
782
+ 123,
783
+ 0,
784
+ 0.0,
785
+ -1234.003030303,
786
+ -99999999,
787
+ 1234.38383000383830003838300,
788
+ nil,
789
+ true,
790
+ false,
791
+ TestThing.new,
792
+ test_drop,
793
+ test_enum,
794
+ ["foo", "bar"],
795
+ { "foo" => "bar" },
796
+ { foo: "bar" },
797
+ [{ "foo" => "bar" }, { "foo" => 123 }, { "foo" => nil }, { "foo" => true }, { "foo" => ["foo", "bar"] }],
798
+ { 1 => "bar" },
799
+ ["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
800
+ ]
801
+ test_types.each do |first|
802
+ test_types.each do |other|
803
+ (@filters.methods - Object.methods).each do |method|
804
+ arg_count = @filters.method(method).arity
805
+ arg_count *= -1 if arg_count < 0
806
+ inputs = [first]
807
+ inputs << ([other] * (arg_count - 1)) if arg_count > 1
808
+ begin
809
+ @filters.send(method, *inputs)
810
+ rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
811
+ nil
812
+ end
813
+ end
814
+ end
815
+ end
816
+ end
817
+
818
+ def test_where_no_target_value
819
+ input = [
820
+ { "foo" => false },
821
+ { "foo" => true },
822
+ { "foo" => "for sure" },
823
+ { "bar" => true },
824
+ ]
825
+
826
+ assert_equal([{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo"))
827
+ end
828
+
526
829
  private
527
830
 
528
831
  def with_timezone(tz)
529
- old_tz = ENV['TZ']
832
+ old_tz = ENV['TZ']
530
833
  ENV['TZ'] = tz
531
834
  yield
532
835
  ensure