liquid 3.0.6 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +98 -58
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +50 -46
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser_switching.rb +4 -4
  21. data/lib/liquid/profiler/hooks.rb +7 -7
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/range_lookup.rb +16 -1
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +121 -61
  26. data/lib/liquid/strainer.rb +14 -7
  27. data/lib/liquid/tablerowloop_drop.rb +62 -0
  28. data/lib/liquid/tag.rb +9 -8
  29. data/lib/liquid/tags/assign.rb +17 -4
  30. data/lib/liquid/tags/break.rb +0 -3
  31. data/lib/liquid/tags/capture.rb +1 -1
  32. data/lib/liquid/tags/case.rb +19 -12
  33. data/lib/liquid/tags/comment.rb +2 -2
  34. data/lib/liquid/tags/cycle.rb +6 -6
  35. data/lib/liquid/tags/decrement.rb +1 -4
  36. data/lib/liquid/tags/for.rb +95 -75
  37. data/lib/liquid/tags/if.rb +49 -44
  38. data/lib/liquid/tags/ifchanged.rb +0 -2
  39. data/lib/liquid/tags/include.rb +61 -52
  40. data/lib/liquid/tags/raw.rb +32 -4
  41. data/lib/liquid/tags/table_row.rb +12 -30
  42. data/lib/liquid/tags/unless.rb +3 -4
  43. data/lib/liquid/template.rb +42 -54
  44. data/lib/liquid/tokenizer.rb +31 -0
  45. data/lib/liquid/utils.rb +52 -8
  46. data/lib/liquid/variable.rb +46 -45
  47. data/lib/liquid/variable_lookup.rb +7 -5
  48. data/lib/liquid/version.rb +1 -1
  49. data/lib/liquid.rb +9 -7
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +99 -46
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/hash_ordering_test.rb +9 -9
  58. data/test/integration/output_test.rb +26 -27
  59. data/test/integration/parsing_quirks_test.rb +15 -13
  60. data/test/integration/render_profiling_test.rb +20 -20
  61. data/test/integration/security_test.rb +9 -7
  62. data/test/integration/standard_filter_test.rb +179 -40
  63. data/test/integration/tags/break_tag_test.rb +1 -2
  64. data/test/integration/tags/continue_tag_test.rb +0 -1
  65. data/test/integration/tags/for_tag_test.rb +133 -98
  66. data/test/integration/tags/if_else_tag_test.rb +75 -77
  67. data/test/integration/tags/include_tag_test.rb +34 -30
  68. data/test/integration/tags/increment_tag_test.rb +10 -11
  69. data/test/integration/tags/raw_tag_test.rb +7 -1
  70. data/test/integration/tags/standard_tag_test.rb +121 -122
  71. data/test/integration/tags/statements_test.rb +3 -5
  72. data/test/integration/tags/table_row_test.rb +20 -19
  73. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  74. data/test/integration/template_test.rb +190 -49
  75. data/test/integration/trim_mode_test.rb +525 -0
  76. data/test/integration/variable_test.rb +23 -13
  77. data/test/test_helper.rb +33 -5
  78. data/test/unit/block_unit_test.rb +8 -5
  79. data/test/unit/condition_unit_test.rb +86 -77
  80. data/test/unit/context_unit_test.rb +48 -57
  81. data/test/unit/file_system_unit_test.rb +3 -3
  82. data/test/unit/i18n_unit_test.rb +2 -2
  83. data/test/unit/lexer_unit_test.rb +11 -8
  84. data/test/unit/parser_unit_test.rb +2 -2
  85. data/test/unit/regexp_unit_test.rb +1 -1
  86. data/test/unit/strainer_unit_test.rb +80 -1
  87. data/test/unit/tag_unit_test.rb +7 -2
  88. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  89. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  90. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  91. data/test/unit/template_unit_test.rb +14 -5
  92. data/test/unit/tokenizer_unit_test.rb +24 -7
  93. data/test/unit/variable_unit_test.rb +60 -43
  94. metadata +19 -14
  95. data/lib/liquid/module_ex.rb +0 -62
  96. data/lib/liquid/token.rb +0 -18
  97. data/test/unit/module_ex_unit_test.rb +0 -87
  98. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -4,7 +4,7 @@ class RenderProfilingTest < Minitest::Test
4
4
  include Liquid
5
5
 
6
6
  class ProfilingFileSystem
7
- def read_template_file(template_path, context)
7
+ def read_template_file(template_path)
8
8
  "Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
9
9
  end
10
10
  end
@@ -21,7 +21,7 @@ class RenderProfilingTest < Minitest::Test
21
21
  end
22
22
 
23
23
  def test_parse_makes_available_simple_profiling
24
- t = Template.parse("{{ 'a string' | upcase }}", :profile => true)
24
+ t = Template.parse("{{ 'a string' | upcase }}", profile: true)
25
25
  t.render!
26
26
 
27
27
  assert_equal 1, t.profiler.length
@@ -31,14 +31,14 @@ class RenderProfilingTest < Minitest::Test
31
31
  end
32
32
 
33
33
  def test_render_ignores_raw_strings_when_profiling
34
- t = Template.parse("This is raw string\nstuff\nNewline", :profile => true)
34
+ t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
35
35
  t.render!
36
36
 
37
37
  assert_equal 0, t.profiler.length
38
38
  end
39
39
 
40
40
  def test_profiling_includes_line_numbers_of_liquid_nodes
41
- t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
41
+ t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
42
42
  t.render!
43
43
  assert_equal 2, t.profiler.length
44
44
 
@@ -49,7 +49,7 @@ class RenderProfilingTest < Minitest::Test
49
49
  end
50
50
 
51
51
  def test_profiling_includes_line_numbers_of_included_partials
52
- t = Template.parse("{% include 'a_template' %}", :profile => true)
52
+ t = Template.parse("{% include 'a_template' %}", profile: true)
53
53
  t.render!
54
54
 
55
55
  included_children = t.profiler[0].children
@@ -61,7 +61,7 @@ class RenderProfilingTest < Minitest::Test
61
61
  end
62
62
 
63
63
  def test_profiling_times_the_rendering_of_tokens
64
- t = Template.parse("{% include 'a_template' %}", :profile => true)
64
+ t = Template.parse("{% include 'a_template' %}", profile: true)
65
65
  t.render!
66
66
 
67
67
  node = t.profiler[0]
@@ -69,14 +69,14 @@ class RenderProfilingTest < Minitest::Test
69
69
  end
70
70
 
71
71
  def test_profiling_times_the_entire_render
72
- t = Template.parse("{% include 'a_template' %}", :profile => true)
72
+ t = Template.parse("{% include 'a_template' %}", profile: true)
73
73
  t.render!
74
74
 
75
75
  assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
76
76
  end
77
77
 
78
78
  def test_profiling_uses_include_to_mark_children
79
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
79
+ t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
80
80
  t.render!
81
81
 
82
82
  include_node = t.profiler[1]
@@ -84,47 +84,47 @@ class RenderProfilingTest < Minitest::Test
84
84
  end
85
85
 
86
86
  def test_profiling_marks_children_with_the_name_of_included_partial
87
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
87
+ t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
88
88
  t.render!
89
89
 
90
90
  include_node = t.profiler[1]
91
91
  include_node.children.each do |child|
92
- assert_equal "'a_template'", child.partial
92
+ assert_equal "a_template", child.partial
93
93
  end
94
94
  end
95
95
 
96
96
  def test_profiling_supports_multiple_templates
97
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true)
97
+ t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", profile: true)
98
98
  t.render!
99
99
 
100
100
  a_template = t.profiler[1]
101
101
  a_template.children.each do |child|
102
- assert_equal "'a_template'", child.partial
102
+ assert_equal "a_template", child.partial
103
103
  end
104
104
 
105
105
  b_template = t.profiler[2]
106
106
  b_template.children.each do |child|
107
- assert_equal "'b_template'", child.partial
107
+ assert_equal "b_template", child.partial
108
108
  end
109
109
  end
110
110
 
111
111
  def test_profiling_supports_rendering_the_same_partial_multiple_times
112
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true)
112
+ t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", profile: true)
113
113
  t.render!
114
114
 
115
115
  a_template1 = t.profiler[1]
116
116
  a_template1.children.each do |child|
117
- assert_equal "'a_template'", child.partial
117
+ assert_equal "a_template", child.partial
118
118
  end
119
119
 
120
120
  a_template2 = t.profiler[2]
121
121
  a_template2.children.each do |child|
122
- assert_equal "'a_template'", child.partial
122
+ assert_equal "a_template", child.partial
123
123
  end
124
124
  end
125
125
 
126
126
  def test_can_iterate_over_each_profiling_entry
127
- t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
127
+ t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
128
128
  t.render!
129
129
 
130
130
  timing_count = 0
@@ -136,7 +136,7 @@ class RenderProfilingTest < Minitest::Test
136
136
  end
137
137
 
138
138
  def test_profiling_marks_children_of_if_blocks
139
- t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true)
139
+ t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
140
140
  t.render!
141
141
 
142
142
  assert_equal 1, t.profiler.length
@@ -144,8 +144,8 @@ class RenderProfilingTest < Minitest::Test
144
144
  end
145
145
 
146
146
  def test_profiling_marks_children_of_for_blocks
147
- t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true)
148
- t.render!({"collection" => ["one", "two"]})
147
+ t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", profile: true)
148
+ t.render!({ "collection" => ["one", "two"] })
149
149
 
150
150
  assert_equal 1, t.profiler.length
151
151
  # Will profile each invocation of the for block
@@ -9,34 +9,36 @@ end
9
9
  class SecurityTest < Minitest::Test
10
10
  include Liquid
11
11
 
12
+ def setup
13
+ @assigns = {}
14
+ end
15
+
12
16
  def test_no_instance_eval
13
17
  text = %( {{ '1+1' | instance_eval }} )
14
- expected = %| 1+1 |
18
+ expected = %( 1+1 )
15
19
 
16
20
  assert_equal expected, Template.parse(text).render!(@assigns)
17
21
  end
18
22
 
19
23
  def test_no_existing_instance_eval
20
24
  text = %( {{ '1+1' | __instance_eval__ }} )
21
- expected = %| 1+1 |
25
+ expected = %( 1+1 )
22
26
 
23
27
  assert_equal expected, Template.parse(text).render!(@assigns)
24
28
  end
25
29
 
26
-
27
30
  def test_no_instance_eval_after_mixing_in_new_filter
28
31
  text = %( {{ '1+1' | instance_eval }} )
29
- expected = %| 1+1 |
32
+ expected = %( 1+1 )
30
33
 
31
34
  assert_equal expected, Template.parse(text).render!(@assigns)
32
35
  end
33
36
 
34
-
35
37
  def test_no_instance_eval_later_in_chain
36
38
  text = %( {{ '1+1' | add_one | instance_eval }} )
37
- expected = %| 1+1 + 1 |
39
+ expected = %( 1+1 + 1 )
38
40
 
39
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
41
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
40
42
  end
41
43
 
42
44
  def test_does_not_add_filters_to_symbol_table
@@ -41,6 +41,16 @@ class TestEnumerable < Liquid::Drop
41
41
  end
42
42
  end
43
43
 
44
+ class NumberLikeThing < Liquid::Drop
45
+ def initialize(amount)
46
+ @amount = amount
47
+ end
48
+
49
+ def to_number
50
+ @amount
51
+ end
52
+ end
53
+
44
54
  class StandardFiltersTest < Minitest::Test
45
55
  include Liquid
46
56
 
@@ -49,7 +59,7 @@ class StandardFiltersTest < Minitest::Test
49
59
  end
50
60
 
51
61
  def test_size
52
- assert_equal 3, @filters.size([1,2,3])
62
+ assert_equal 3, @filters.size([1, 2, 3])
53
63
  assert_equal 0, @filters.size([])
54
64
  assert_equal 0, @filters.size(nil)
55
65
  end
@@ -76,20 +86,27 @@ class StandardFiltersTest < Minitest::Test
76
86
  assert_equal '', @filters.slice(nil, 0)
77
87
  assert_equal '', @filters.slice('foobar', 100, 10)
78
88
  assert_equal '', @filters.slice('foobar', -100, 10)
89
+ assert_equal 'oob', @filters.slice('foobar', '1', '3')
90
+ assert_raises(Liquid::ArgumentError) do
91
+ @filters.slice('foobar', nil)
92
+ end
93
+ assert_raises(Liquid::ArgumentError) do
94
+ @filters.slice('foobar', 0, "")
95
+ end
79
96
  end
80
97
 
81
98
  def test_slice_on_arrays
82
99
  input = 'foobar'.split(//)
83
- assert_equal %w{o o b}, @filters.slice(input, 1, 3)
84
- assert_equal %w{o o b a r}, @filters.slice(input, 1, 1000)
85
- assert_equal %w{}, @filters.slice(input, 1, 0)
86
- assert_equal %w{o}, @filters.slice(input, 1, 1)
87
- assert_equal %w{b a r}, @filters.slice(input, 3, 3)
88
- assert_equal %w{a r}, @filters.slice(input, -2, 2)
89
- assert_equal %w{a r}, @filters.slice(input, -2, 1000)
90
- assert_equal %w{r}, @filters.slice(input, -1)
91
- assert_equal %w{}, @filters.slice(input, 100, 10)
92
- assert_equal %w{}, @filters.slice(input, -100, 10)
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)
93
110
  end
94
111
 
95
112
  def test_truncate
@@ -98,19 +115,20 @@ class StandardFiltersTest < Minitest::Test
98
115
  assert_equal '...', @filters.truncate('1234567890', 0)
99
116
  assert_equal '1234567890', @filters.truncate('1234567890')
100
117
  assert_equal "测试...", @filters.truncate("测试测试测试测试", 5)
118
+ assert_equal '12341', @filters.truncate("1234567890", 5, 1)
101
119
  end
102
120
 
103
121
  def test_split
104
- assert_equal ['12','34'], @filters.split('12~34', '~')
105
- assert_equal ['A? ',' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
122
+ assert_equal ['12', '34'], @filters.split('12~34', '~')
123
+ assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
106
124
  assert_equal ['A?Z'], @filters.split('A?Z', '~')
107
- # Regexp works although Liquid does not support.
108
- assert_equal ['A','Z'], @filters.split('AxZ', /x/)
109
125
  assert_equal [], @filters.split(nil, ' ')
126
+ assert_equal ['A', 'Z'], @filters.split('A1Z', 1)
110
127
  end
111
128
 
112
129
  def test_escape
113
130
  assert_equal '&lt;strong&gt;', @filters.escape('<strong>')
131
+ assert_equal nil, @filters.escape(nil)
114
132
  assert_equal '&lt;strong&gt;', @filters.h('<strong>')
115
133
  end
116
134
 
@@ -123,12 +141,20 @@ class StandardFiltersTest < Minitest::Test
123
141
  assert_equal nil, @filters.url_encode(nil)
124
142
  end
125
143
 
144
+ 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)
149
+ end
150
+
126
151
  def test_truncatewords
127
152
  assert_equal 'one two three', @filters.truncatewords('one two three', 4)
128
153
  assert_equal 'one two...', @filters.truncatewords('one two three', 2)
129
154
  assert_equal 'one two three', @filters.truncatewords('one two three')
130
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)
131
156
  assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
157
+ assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
132
158
  end
133
159
 
134
160
  def test_strip_html
@@ -142,45 +168,80 @@ class StandardFiltersTest < Minitest::Test
142
168
  end
143
169
 
144
170
  def test_join
145
- assert_equal '1 2 3 4', @filters.join([1,2,3,4])
146
- 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])
172
+ assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
147
173
  end
148
174
 
149
175
  def test_sort
150
- assert_equal [1,2,3,4], @filters.sort([4,3,2,1])
151
- assert_equal [{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], @filters.sort([{"a" => 4}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
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")
178
+ end
179
+
180
+ def test_sort_when_property_is_sometimes_missing_puts_nils_last
181
+ input = [
182
+ { "price" => 4, "handle" => "alpha" },
183
+ { "handle" => "beta" },
184
+ { "price" => 1, "handle" => "gamma" },
185
+ { "handle" => "delta" },
186
+ { "price" => 2, "handle" => "epsilon" }
187
+ ]
188
+ expectation = [
189
+ { "price" => 1, "handle" => "gamma" },
190
+ { "price" => 2, "handle" => "epsilon" },
191
+ { "price" => 4, "handle" => "alpha" },
192
+ { "handle" => "delta" },
193
+ { "handle" => "beta" }
194
+ ]
195
+ assert_equal expectation, @filters.sort(input, "price")
196
+ end
197
+
198
+ def test_sort_empty_array
199
+ assert_equal [], @filters.sort([], "a")
200
+ end
201
+
202
+ def test_sort_natural_empty_array
203
+ assert_equal [], @filters.sort_natural([], "a")
152
204
  end
153
205
 
154
206
  def test_legacy_sort_hash
155
- assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2})
207
+ assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
156
208
  end
157
209
 
158
210
  def test_numerical_vs_lexicographical_sort
159
211
  assert_equal [2, 10], @filters.sort([10, 2])
160
- assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
212
+ assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
161
213
  assert_equal ["10", "2"], @filters.sort(["10", "2"])
162
- assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a")
214
+ assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
163
215
  end
164
216
 
165
217
  def test_uniq
166
- assert_equal [1,3,2,4], @filters.uniq([1,1,3,2,3,1,4,3,2,1])
167
- assert_equal [{"a" => 1}, {"a" => 3}, {"a" => 2}], @filters.uniq([{"a" => 1}, {"a" => 3}, {"a" => 1}, {"a" => 2}], "a")
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")
168
221
  testdrop = TestDrop.new
169
222
  assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
170
223
  end
171
224
 
225
+ def test_uniq_empty_array
226
+ assert_equal [], @filters.uniq([], "a")
227
+ end
228
+
229
+ def test_compact_empty_array
230
+ assert_equal [], @filters.compact([], "a")
231
+ end
232
+
172
233
  def test_reverse
173
- assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
234
+ assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
174
235
  end
175
236
 
176
237
  def test_legacy_reverse_hash
177
- assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2)
238
+ assert_equal [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
178
239
  end
179
240
 
180
241
  def test_map
181
- assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
242
+ assert_equal [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
182
243
  assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
183
- 'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
244
+ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
184
245
  end
185
246
 
186
247
  def test_map_doesnt_call_arbitrary_stuff
@@ -212,11 +273,24 @@ class StandardFiltersTest < Minitest::Test
212
273
 
213
274
  def test_map_over_proc
214
275
  drop = TestDrop.new
215
- p = Proc.new{ drop }
276
+ p = proc{ drop }
216
277
  templ = '{{ procs | map: "test" }}'
217
278
  assert_template_result "testfoo", templ, "procs" => [p]
218
279
  end
219
280
 
281
+ def test_map_over_drops_returning_procs
282
+ drops = [
283
+ {
284
+ "proc" => ->{ "foo" },
285
+ },
286
+ {
287
+ "proc" => ->{ "bar" },
288
+ },
289
+ ]
290
+ templ = '{{ drops | map: "proc" }}'
291
+ assert_template_result "foobar", templ, "drops" => drops
292
+ end
293
+
220
294
  def test_map_works_on_enumerables
221
295
  assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
222
296
  end
@@ -230,6 +304,10 @@ class StandardFiltersTest < Minitest::Test
230
304
  assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
231
305
  end
232
306
 
307
+ def test_truncate_calls_to_liquid
308
+ assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
309
+ end
310
+
233
311
  def test_date
234
312
  assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
235
313
  assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
@@ -249,9 +327,12 @@ class StandardFiltersTest < Minitest::Test
249
327
  assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
250
328
  assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
251
329
  assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
330
+ assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
252
331
 
253
332
  assert_equal nil, @filters.date(nil, "%B")
254
333
 
334
+ assert_equal '', @filters.date('', "%B")
335
+
255
336
  with_timezone("UTC") do
256
337
  assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
257
338
  assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
@@ -259,21 +340,25 @@ class StandardFiltersTest < Minitest::Test
259
340
  end
260
341
 
261
342
  def test_first_last
262
- assert_equal 1, @filters.first([1,2,3])
263
- assert_equal 3, @filters.last([1,2,3])
343
+ assert_equal 1, @filters.first([1, 2, 3])
344
+ assert_equal 3, @filters.last([1, 2, 3])
264
345
  assert_equal nil, @filters.first([])
265
346
  assert_equal nil, @filters.last([])
266
347
  end
267
348
 
268
349
  def test_replace
269
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)
270
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)
271
354
  assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
272
355
  end
273
356
 
274
357
  def test_remove
275
358
  assert_equal ' ', @filters.remove("a a a a", 'a')
359
+ assert_equal ' ', @filters.remove("1 1 1 1", 1)
276
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)
277
362
  assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
278
363
  end
279
364
 
@@ -308,20 +393,38 @@ class StandardFiltersTest < Minitest::Test
308
393
  def test_plus
309
394
  assert_template_result "2", "{{ 1 | plus:1 }}"
310
395
  assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
396
+
397
+ assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
311
398
  end
312
399
 
313
400
  def test_minus
314
401
  assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
315
402
  assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
403
+
404
+ assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
405
+ end
406
+
407
+ 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 }}"
316
418
  end
317
419
 
318
420
  def test_times
319
421
  assert_template_result "12", "{{ 3 | times:4 }}"
320
422
  assert_template_result "0", "{{ 'foo' | times:4 }}"
321
-
322
423
  assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
323
-
324
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)
325
428
  end
326
429
 
327
430
  def test_divided_by
@@ -332,38 +435,74 @@ class StandardFiltersTest < Minitest::Test
332
435
  assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
333
436
 
334
437
  assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
438
+ assert_raises(Liquid::ZeroDivisionError) do
439
+ assert_template_result "4", "{{ 1 | modulo: 0 }}"
440
+ end
441
+
442
+ assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
335
443
  end
336
444
 
337
445
  def test_modulo
338
446
  assert_template_result "1", "{{ 3 | modulo:2 }}"
447
+ assert_raises(Liquid::ZeroDivisionError) do
448
+ assert_template_result "4", "{{ 1 | modulo: 0 }}"
449
+ end
450
+
451
+ assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
339
452
  end
340
453
 
341
454
  def test_round
342
455
  assert_template_result "5", "{{ input | round }}", 'input' => 4.6
343
456
  assert_template_result "4", "{{ '4.3' | round }}"
344
457
  assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
458
+ assert_raises(Liquid::FloatDomainError) do
459
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
460
+ end
461
+
462
+ assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
463
+ assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
345
464
  end
346
465
 
347
466
  def test_ceil
348
467
  assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
349
468
  assert_template_result "5", "{{ '4.3' | ceil }}"
469
+ assert_raises(Liquid::FloatDomainError) do
470
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
471
+ end
472
+
473
+ assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
350
474
  end
351
475
 
352
476
  def test_floor
353
477
  assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
354
478
  assert_template_result "4", "{{ '4.3' | floor }}"
479
+ assert_raises(Liquid::FloatDomainError) do
480
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
481
+ end
482
+
483
+ assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
355
484
  end
356
485
 
357
486
  def test_append
358
- assigns = {'a' => 'bc', 'b' => 'd' }
359
- assert_template_result('bcd',"{{ a | append: 'd'}}",assigns)
360
- assert_template_result('bcd',"{{ a | append: b}}",assigns)
487
+ assigns = { 'a' => 'bc', 'b' => 'd' }
488
+ assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
489
+ assert_template_result('bcd', "{{ a | append: b}}", assigns)
490
+ end
491
+
492
+ 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])
496
+
497
+ assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
498
+ @filters.concat([1, 2], 10)
499
+ end
361
500
  end
362
501
 
363
502
  def test_prepend
364
- assigns = {'a' => 'bc', 'b' => 'a' }
365
- assert_template_result('abc',"{{ a | prepend: 'a'}}",assigns)
366
- assert_template_result('abc',"{{ a | prepend: b}}",assigns)
503
+ assigns = { 'a' => 'bc', 'b' => 'a' }
504
+ assert_template_result('abc', "{{ a | prepend: 'a'}}", assigns)
505
+ assert_template_result('abc', "{{ a | prepend: b}}", assigns)
367
506
  end
368
507
 
369
508
  def test_default
@@ -376,7 +515,7 @@ class StandardFiltersTest < Minitest::Test
376
515
  end
377
516
 
378
517
  def test_cannot_access_private_methods
379
- assert_template_result('a',"{{ 'a' | to_number }}")
518
+ assert_template_result('a', "{{ 'a' | to_number }}")
380
519
  end
381
520
 
382
521
  def test_date_raises_nothing
@@ -6,11 +6,10 @@ class BreakTagTest < Minitest::Test
6
6
  # tests that no weird errors are raised if break is called outside of a
7
7
  # block
8
8
  def test_break_with_no_block
9
- assigns = {'i' => 1}
9
+ assigns = { 'i' => 1 }
10
10
  markup = '{% break %}'
11
11
  expected = ''
12
12
 
13
13
  assert_template_result(expected, markup, assigns)
14
14
  end
15
-
16
15
  end
@@ -12,5 +12,4 @@ class ContinueTagTest < Minitest::Test
12
12
 
13
13
  assert_template_result(expected, markup, assigns)
14
14
  end
15
-
16
15
  end