liquid 3.0.6 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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