liquid 3.0.6 → 4.0.0.rc1

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +89 -58
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/lib/liquid.rb +7 -6
  5. data/lib/liquid/block.rb +31 -124
  6. data/lib/liquid/block_body.rb +54 -57
  7. data/lib/liquid/condition.rb +23 -22
  8. data/lib/liquid/context.rb +50 -42
  9. data/lib/liquid/document.rb +19 -9
  10. data/lib/liquid/drop.rb +12 -13
  11. data/lib/liquid/errors.rb +16 -17
  12. data/lib/liquid/expression.rb +15 -3
  13. data/lib/liquid/extensions.rb +7 -7
  14. data/lib/liquid/file_system.rb +3 -3
  15. data/lib/liquid/forloop_drop.rb +42 -0
  16. data/lib/liquid/i18n.rb +5 -5
  17. data/lib/liquid/interrupts.rb +1 -2
  18. data/lib/liquid/lexer.rb +6 -4
  19. data/lib/liquid/locales/en.yml +3 -1
  20. data/lib/liquid/parse_context.rb +37 -0
  21. data/lib/liquid/parser_switching.rb +4 -4
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/profiler/hooks.rb +7 -7
  24. data/lib/liquid/range_lookup.rb +16 -1
  25. data/lib/liquid/resource_limits.rb +23 -0
  26. data/lib/liquid/standardfilters.rb +101 -56
  27. data/lib/liquid/strainer.rb +4 -5
  28. data/lib/liquid/tablerowloop_drop.rb +62 -0
  29. data/lib/liquid/tag.rb +9 -8
  30. data/lib/liquid/tags/assign.rb +5 -4
  31. data/lib/liquid/tags/break.rb +0 -3
  32. data/lib/liquid/tags/capture.rb +1 -1
  33. data/lib/liquid/tags/case.rb +19 -12
  34. data/lib/liquid/tags/comment.rb +2 -2
  35. data/lib/liquid/tags/cycle.rb +6 -6
  36. data/lib/liquid/tags/decrement.rb +1 -4
  37. data/lib/liquid/tags/for.rb +93 -75
  38. data/lib/liquid/tags/if.rb +49 -44
  39. data/lib/liquid/tags/ifchanged.rb +0 -2
  40. data/lib/liquid/tags/include.rb +60 -52
  41. data/lib/liquid/tags/raw.rb +26 -4
  42. data/lib/liquid/tags/table_row.rb +12 -30
  43. data/lib/liquid/tags/unless.rb +3 -4
  44. data/lib/liquid/template.rb +23 -50
  45. data/lib/liquid/tokenizer.rb +31 -0
  46. data/lib/liquid/utils.rb +48 -8
  47. data/lib/liquid/variable.rb +46 -45
  48. data/lib/liquid/variable_lookup.rb +3 -3
  49. data/lib/liquid/version.rb +1 -1
  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 +64 -45
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/output_test.rb +26 -27
  58. data/test/integration/parsing_quirks_test.rb +15 -13
  59. data/test/integration/render_profiling_test.rb +20 -20
  60. data/test/integration/security_test.rb +5 -7
  61. data/test/integration/standard_filter_test.rb +119 -37
  62. data/test/integration/tags/break_tag_test.rb +1 -2
  63. data/test/integration/tags/continue_tag_test.rb +0 -1
  64. data/test/integration/tags/for_tag_test.rb +133 -98
  65. data/test/integration/tags/if_else_tag_test.rb +75 -77
  66. data/test/integration/tags/include_tag_test.rb +23 -30
  67. data/test/integration/tags/increment_tag_test.rb +10 -11
  68. data/test/integration/tags/raw_tag_test.rb +7 -1
  69. data/test/integration/tags/standard_tag_test.rb +121 -122
  70. data/test/integration/tags/statements_test.rb +3 -5
  71. data/test/integration/tags/table_row_test.rb +20 -19
  72. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  73. data/test/integration/template_test.rb +91 -45
  74. data/test/integration/variable_test.rb +23 -13
  75. data/test/test_helper.rb +33 -5
  76. data/test/unit/block_unit_test.rb +6 -5
  77. data/test/unit/condition_unit_test.rb +82 -77
  78. data/test/unit/context_unit_test.rb +48 -57
  79. data/test/unit/file_system_unit_test.rb +3 -3
  80. data/test/unit/i18n_unit_test.rb +2 -2
  81. data/test/unit/lexer_unit_test.rb +11 -8
  82. data/test/unit/parser_unit_test.rb +2 -2
  83. data/test/unit/regexp_unit_test.rb +1 -1
  84. data/test/unit/strainer_unit_test.rb +13 -2
  85. data/test/unit/tag_unit_test.rb +7 -2
  86. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  87. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  88. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  89. data/test/unit/template_unit_test.rb +6 -5
  90. data/test/unit/tokenizer_unit_test.rb +24 -7
  91. data/test/unit/variable_unit_test.rb +60 -43
  92. metadata +44 -41
  93. data/lib/liquid/module_ex.rb +0 -62
  94. data/lib/liquid/token.rb +0 -18
  95. data/test/unit/module_ex_unit_test.rb +0 -87
@@ -28,11 +28,14 @@ class ParsingQuirksTest < Minitest::Test
28
28
 
29
29
  def test_error_on_empty_filter
30
30
  assert Template.parse("{{test}}")
31
- assert Template.parse("{{|test}}")
31
+
32
+ with_error_mode(:lax) do
33
+ assert Template.parse("{{|test}}")
34
+ end
35
+
32
36
  with_error_mode(:strict) do
33
- assert_raises(SyntaxError) do
34
- Template.parse("{{test |a|b|}}")
35
- end
37
+ assert_raises(SyntaxError) { Template.parse("{{|test}}") }
38
+ assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
36
39
  end
37
40
  end
38
41
 
@@ -59,25 +62,25 @@ class ParsingQuirksTest < Minitest::Test
59
62
  end
60
63
 
61
64
  def test_no_error_on_lax_empty_filter
62
- assert Template.parse("{{test |a|b|}}", :error_mode => :lax)
63
- assert Template.parse("{{test}}", :error_mode => :lax)
64
- assert Template.parse("{{|test|}}", :error_mode => :lax)
65
+ assert Template.parse("{{test |a|b|}}", error_mode: :lax)
66
+ assert Template.parse("{{test}}", error_mode: :lax)
67
+ assert Template.parse("{{|test|}}", error_mode: :lax)
65
68
  end
66
69
 
67
70
  def test_meaningless_parens_lax
68
71
  with_error_mode(:lax) do
69
- assigns = {'b' => 'bar', 'c' => 'baz'}
72
+ assigns = { 'b' => 'bar', 'c' => 'baz' }
70
73
  markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
71
- assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
74
+ assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
72
75
  end
73
76
  end
74
77
 
75
78
  def test_unexpected_characters_silently_eat_logic_lax
76
79
  with_error_mode(:lax) do
77
80
  markup = "true && false"
78
- assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
81
+ assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}")
79
82
  markup = "false || true"
80
- assert_template_result('',"{% if #{markup} %} YES {% endif %}")
83
+ assert_template_result('', "{% if #{markup} %} YES {% endif %}")
81
84
  end
82
85
  end
83
86
 
@@ -89,7 +92,7 @@ class ParsingQuirksTest < Minitest::Test
89
92
 
90
93
  def test_unanchored_filter_arguments
91
94
  with_error_mode(:lax) do
92
- assert_template_result('hi',"{{ 'hi there' | split$$$:' ' | first }}")
95
+ assert_template_result('hi', "{{ 'hi there' | split$$$:' ' | first }}")
93
96
 
94
97
  assert_template_result('x', "{{ 'X' | downcase) }}")
95
98
 
@@ -112,5 +115,4 @@ class ParsingQuirksTest < Minitest::Test
112
115
  assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
113
116
  end
114
117
  end
115
-
116
118
  end # ParsingQuirksTest
@@ -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
@@ -11,32 +11,30 @@ class SecurityTest < Minitest::Test
11
11
 
12
12
  def test_no_instance_eval
13
13
  text = %( {{ '1+1' | instance_eval }} )
14
- expected = %| 1+1 |
14
+ expected = %( 1+1 )
15
15
 
16
16
  assert_equal expected, Template.parse(text).render!(@assigns)
17
17
  end
18
18
 
19
19
  def test_no_existing_instance_eval
20
20
  text = %( {{ '1+1' | __instance_eval__ }} )
21
- expected = %| 1+1 |
21
+ expected = %( 1+1 )
22
22
 
23
23
  assert_equal expected, Template.parse(text).render!(@assigns)
24
24
  end
25
25
 
26
-
27
26
  def test_no_instance_eval_after_mixing_in_new_filter
28
27
  text = %( {{ '1+1' | instance_eval }} )
29
- expected = %| 1+1 |
28
+ expected = %( 1+1 )
30
29
 
31
30
  assert_equal expected, Template.parse(text).render!(@assigns)
32
31
  end
33
32
 
34
-
35
33
  def test_no_instance_eval_later_in_chain
36
34
  text = %( {{ '1+1' | add_one | instance_eval }} )
37
- expected = %| 1+1 + 1 |
35
+ expected = %( 1+1 + 1 )
38
36
 
39
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
37
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
40
38
  end
41
39
 
42
40
  def test_does_not_add_filters_to_symbol_table
@@ -49,7 +49,7 @@ class StandardFiltersTest < Minitest::Test
49
49
  end
50
50
 
51
51
  def test_size
52
- assert_equal 3, @filters.size([1,2,3])
52
+ assert_equal 3, @filters.size([1, 2, 3])
53
53
  assert_equal 0, @filters.size([])
54
54
  assert_equal 0, @filters.size(nil)
55
55
  end
@@ -76,20 +76,27 @@ class StandardFiltersTest < Minitest::Test
76
76
  assert_equal '', @filters.slice(nil, 0)
77
77
  assert_equal '', @filters.slice('foobar', 100, 10)
78
78
  assert_equal '', @filters.slice('foobar', -100, 10)
79
+ assert_equal 'oob', @filters.slice('foobar', '1', '3')
80
+ assert_raises(Liquid::ArgumentError) do
81
+ @filters.slice('foobar', nil)
82
+ end
83
+ assert_raises(Liquid::ArgumentError) do
84
+ @filters.slice('foobar', 0, "")
85
+ end
79
86
  end
80
87
 
81
88
  def test_slice_on_arrays
82
89
  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)
90
+ assert_equal %w(o o b), @filters.slice(input, 1, 3)
91
+ assert_equal %w(o o b a r), @filters.slice(input, 1, 1000)
92
+ assert_equal %w(), @filters.slice(input, 1, 0)
93
+ assert_equal %w(o), @filters.slice(input, 1, 1)
94
+ assert_equal %w(b a r), @filters.slice(input, 3, 3)
95
+ assert_equal %w(a r), @filters.slice(input, -2, 2)
96
+ assert_equal %w(a r), @filters.slice(input, -2, 1000)
97
+ assert_equal %w(r), @filters.slice(input, -1)
98
+ assert_equal %w(), @filters.slice(input, 100, 10)
99
+ assert_equal %w(), @filters.slice(input, -100, 10)
93
100
  end
94
101
 
95
102
  def test_truncate
@@ -101,16 +108,17 @@ class StandardFiltersTest < Minitest::Test
101
108
  end
102
109
 
103
110
  def test_split
104
- assert_equal ['12','34'], @filters.split('12~34', '~')
105
- assert_equal ['A? ',' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
111
+ assert_equal ['12', '34'], @filters.split('12~34', '~')
112
+ assert_equal ['A? ', ' ,Z'], @filters.split('A? ~ ~ ~ ,Z', '~ ~ ~')
106
113
  assert_equal ['A?Z'], @filters.split('A?Z', '~')
107
114
  # Regexp works although Liquid does not support.
108
- assert_equal ['A','Z'], @filters.split('AxZ', /x/)
115
+ assert_equal ['A', 'Z'], @filters.split('AxZ', /x/)
109
116
  assert_equal [], @filters.split(nil, ' ')
110
117
  end
111
118
 
112
119
  def test_escape
113
120
  assert_equal '&lt;strong&gt;', @filters.escape('<strong>')
121
+ assert_equal nil, @filters.escape(nil)
114
122
  assert_equal '&lt;strong&gt;', @filters.h('<strong>')
115
123
  end
116
124
 
@@ -123,6 +131,13 @@ class StandardFiltersTest < Minitest::Test
123
131
  assert_equal nil, @filters.url_encode(nil)
124
132
  end
125
133
 
134
+ def test_url_decode
135
+ assert_equal 'foo bar', @filters.url_decode('foo+bar')
136
+ assert_equal 'foo bar', @filters.url_decode('foo%20bar')
137
+ assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
138
+ assert_equal nil, @filters.url_decode(nil)
139
+ end
140
+
126
141
  def test_truncatewords
127
142
  assert_equal 'one two three', @filters.truncatewords('one two three', 4)
128
143
  assert_equal 'one two...', @filters.truncatewords('one two three', 2)
@@ -142,45 +157,62 @@ class StandardFiltersTest < Minitest::Test
142
157
  end
143
158
 
144
159
  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], ' - ')
160
+ assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
161
+ assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
147
162
  end
148
163
 
149
164
  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")
165
+ assert_equal [1, 2, 3, 4], @filters.sort([4, 3, 2, 1])
166
+ assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
167
+ end
168
+
169
+ def test_sort_empty_array
170
+ assert_equal [], @filters.sort([], "a")
171
+ end
172
+
173
+ def test_sort_natural_empty_array
174
+ assert_equal [], @filters.sort_natural([], "a")
152
175
  end
153
176
 
154
177
  def test_legacy_sort_hash
155
- assert_equal [{a:1, b:2}], @filters.sort({a:1, b:2})
178
+ assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
156
179
  end
157
180
 
158
181
  def test_numerical_vs_lexicographical_sort
159
182
  assert_equal [2, 10], @filters.sort([10, 2])
160
- assert_equal [{"a" => 2}, {"a" => 10}], @filters.sort([{"a" => 10}, {"a" => 2}], "a")
183
+ assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
161
184
  assert_equal ["10", "2"], @filters.sort(["10", "2"])
162
- assert_equal [{"a" => "10"}, {"a" => "2"}], @filters.sort([{"a" => "10"}, {"a" => "2"}], "a")
185
+ assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
163
186
  end
164
187
 
165
188
  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")
189
+ assert_equal ["foo"], @filters.uniq("foo")
190
+ assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
191
+ assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
168
192
  testdrop = TestDrop.new
169
193
  assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
170
194
  end
171
195
 
196
+ def test_uniq_empty_array
197
+ assert_equal [], @filters.uniq([], "a")
198
+ end
199
+
200
+ def test_compact_empty_array
201
+ assert_equal [], @filters.compact([], "a")
202
+ end
203
+
172
204
  def test_reverse
173
- assert_equal [4,3,2,1], @filters.reverse([1,2,3,4])
205
+ assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
174
206
  end
175
207
 
176
208
  def test_legacy_reverse_hash
177
- assert_equal [{a:1, b:2}], @filters.reverse(a:1, b:2)
209
+ assert_equal [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
178
210
  end
179
211
 
180
212
  def test_map
181
- assert_equal [1,2,3,4], @filters.map([{"a" => 1}, {"a" => 2}, {"a" => 3}, {"a" => 4}], 'a')
213
+ assert_equal [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
182
214
  assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
183
- 'ary' => [{'foo' => {'bar' => 'a'}}, {'foo' => {'bar' => 'b'}}, {'foo' => {'bar' => 'c'}}]
215
+ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
184
216
  end
185
217
 
186
218
  def test_map_doesnt_call_arbitrary_stuff
@@ -212,11 +244,24 @@ class StandardFiltersTest < Minitest::Test
212
244
 
213
245
  def test_map_over_proc
214
246
  drop = TestDrop.new
215
- p = Proc.new{ drop }
247
+ p = proc{ drop }
216
248
  templ = '{{ procs | map: "test" }}'
217
249
  assert_template_result "testfoo", templ, "procs" => [p]
218
250
  end
219
251
 
252
+ def test_map_over_drops_returning_procs
253
+ drops = [
254
+ {
255
+ "proc" => ->{ "foo" },
256
+ },
257
+ {
258
+ "proc" => ->{ "bar" },
259
+ },
260
+ ]
261
+ templ = '{{ drops | map: "proc" }}'
262
+ assert_template_result "foobar", templ, "drops" => drops
263
+ end
264
+
220
265
  def test_map_works_on_enumerables
221
266
  assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
222
267
  end
@@ -230,6 +275,10 @@ class StandardFiltersTest < Minitest::Test
230
275
  assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
231
276
  end
232
277
 
278
+ def test_truncate_calls_to_liquid
279
+ assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
280
+ end
281
+
233
282
  def test_date
234
283
  assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
235
284
  assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
@@ -249,9 +298,12 @@ class StandardFiltersTest < Minitest::Test
249
298
  assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
250
299
  assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
251
300
  assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
301
+ assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
252
302
 
253
303
  assert_equal nil, @filters.date(nil, "%B")
254
304
 
305
+ assert_equal '', @filters.date('', "%B")
306
+
255
307
  with_timezone("UTC") do
256
308
  assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
257
309
  assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
@@ -259,21 +311,25 @@ class StandardFiltersTest < Minitest::Test
259
311
  end
260
312
 
261
313
  def test_first_last
262
- assert_equal 1, @filters.first([1,2,3])
263
- assert_equal 3, @filters.last([1,2,3])
314
+ assert_equal 1, @filters.first([1, 2, 3])
315
+ assert_equal 3, @filters.last([1, 2, 3])
264
316
  assert_equal nil, @filters.first([])
265
317
  assert_equal nil, @filters.last([])
266
318
  end
267
319
 
268
320
  def test_replace
269
321
  assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
322
+ assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
270
323
  assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
324
+ assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
271
325
  assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
272
326
  end
273
327
 
274
328
  def test_remove
275
329
  assert_equal ' ', @filters.remove("a a a a", 'a')
330
+ assert_equal ' ', @filters.remove("1 1 1 1", 1)
276
331
  assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
332
+ assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
277
333
  assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
278
334
  end
279
335
 
@@ -332,38 +388,64 @@ class StandardFiltersTest < Minitest::Test
332
388
  assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
333
389
 
334
390
  assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
391
+ assert_raises(Liquid::ZeroDivisionError) do
392
+ assert_template_result "4", "{{ 1 | modulo: 0 }}"
393
+ end
335
394
  end
336
395
 
337
396
  def test_modulo
338
397
  assert_template_result "1", "{{ 3 | modulo:2 }}"
398
+ assert_raises(Liquid::ZeroDivisionError) do
399
+ assert_template_result "4", "{{ 1 | modulo: 0 }}"
400
+ end
339
401
  end
340
402
 
341
403
  def test_round
342
404
  assert_template_result "5", "{{ input | round }}", 'input' => 4.6
343
405
  assert_template_result "4", "{{ '4.3' | round }}"
344
406
  assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
407
+ assert_raises(Liquid::FloatDomainError) do
408
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
409
+ end
345
410
  end
346
411
 
347
412
  def test_ceil
348
413
  assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
349
414
  assert_template_result "5", "{{ '4.3' | ceil }}"
415
+ assert_raises(Liquid::FloatDomainError) do
416
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
417
+ end
350
418
  end
351
419
 
352
420
  def test_floor
353
421
  assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
354
422
  assert_template_result "4", "{{ '4.3' | floor }}"
423
+ assert_raises(Liquid::FloatDomainError) do
424
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
425
+ end
355
426
  end
356
427
 
357
428
  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)
429
+ assigns = { 'a' => 'bc', 'b' => 'd' }
430
+ assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
431
+ assert_template_result('bcd', "{{ a | append: b}}", assigns)
432
+ end
433
+
434
+ def test_concat
435
+ assert_equal [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
436
+ assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
437
+ assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
438
+
439
+ assert_raises(TypeError) do
440
+ # no implicit conversion of Fixnum into Array
441
+ @filters.concat([1, 2], 10)
442
+ end
361
443
  end
362
444
 
363
445
  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)
446
+ assigns = { 'a' => 'bc', 'b' => 'a' }
447
+ assert_template_result('abc', "{{ a | prepend: 'a'}}", assigns)
448
+ assert_template_result('abc', "{{ a | prepend: b}}", assigns)
367
449
  end
368
450
 
369
451
  def test_default
@@ -376,7 +458,7 @@ class StandardFiltersTest < Minitest::Test
376
458
  end
377
459
 
378
460
  def test_cannot_access_private_methods
379
- assert_template_result('a',"{{ 'a' | to_number }}")
461
+ assert_template_result('a', "{{ 'a' | to_number }}")
380
462
  end
381
463
 
382
464
  def test_date_raises_nothing