liquid-4-0-2 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +235 -0
  3. data/LICENSE +20 -0
  4. data/README.md +108 -0
  5. data/lib/liquid.rb +80 -0
  6. data/lib/liquid/block.rb +77 -0
  7. data/lib/liquid/block_body.rb +142 -0
  8. data/lib/liquid/condition.rb +151 -0
  9. data/lib/liquid/context.rb +226 -0
  10. data/lib/liquid/document.rb +27 -0
  11. data/lib/liquid/drop.rb +78 -0
  12. data/lib/liquid/errors.rb +56 -0
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +74 -0
  15. data/lib/liquid/file_system.rb +73 -0
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +16 -0
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +485 -0
  30. data/lib/liquid/strainer.rb +66 -0
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +43 -0
  33. data/lib/liquid/tags/assign.rb +59 -0
  34. data/lib/liquid/tags/break.rb +18 -0
  35. data/lib/liquid/tags/capture.rb +38 -0
  36. data/lib/liquid/tags/case.rb +94 -0
  37. data/lib/liquid/tags/comment.rb +16 -0
  38. data/lib/liquid/tags/continue.rb +18 -0
  39. data/lib/liquid/tags/cycle.rb +65 -0
  40. data/lib/liquid/tags/decrement.rb +35 -0
  41. data/lib/liquid/tags/for.rb +203 -0
  42. data/lib/liquid/tags/if.rb +122 -0
  43. data/lib/liquid/tags/ifchanged.rb +18 -0
  44. data/lib/liquid/tags/include.rb +124 -0
  45. data/lib/liquid/tags/increment.rb +31 -0
  46. data/lib/liquid/tags/raw.rb +47 -0
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +30 -0
  49. data/lib/liquid/template.rb +254 -0
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/utils.rb +83 -0
  52. data/lib/liquid/variable.rb +148 -0
  53. data/lib/liquid/variable_lookup.rb +88 -0
  54. data/lib/liquid/version.rb +4 -0
  55. data/test/fixtures/en_locale.yml +9 -0
  56. data/test/integration/assign_test.rb +48 -0
  57. data/test/integration/blank_test.rb +106 -0
  58. data/test/integration/block_test.rb +12 -0
  59. data/test/integration/capture_test.rb +50 -0
  60. data/test/integration/context_test.rb +32 -0
  61. data/test/integration/document_test.rb +19 -0
  62. data/test/integration/drop_test.rb +273 -0
  63. data/test/integration/error_handling_test.rb +260 -0
  64. data/test/integration/filter_test.rb +178 -0
  65. data/test/integration/hash_ordering_test.rb +23 -0
  66. data/test/integration/output_test.rb +123 -0
  67. data/test/integration/parse_tree_visitor_test.rb +247 -0
  68. data/test/integration/parsing_quirks_test.rb +122 -0
  69. data/test/integration/render_profiling_test.rb +154 -0
  70. data/test/integration/security_test.rb +80 -0
  71. data/test/integration/standard_filter_test.rb +698 -0
  72. data/test/integration/tags/break_tag_test.rb +15 -0
  73. data/test/integration/tags/continue_tag_test.rb +15 -0
  74. data/test/integration/tags/for_tag_test.rb +410 -0
  75. data/test/integration/tags/if_else_tag_test.rb +188 -0
  76. data/test/integration/tags/include_tag_test.rb +245 -0
  77. data/test/integration/tags/increment_tag_test.rb +23 -0
  78. data/test/integration/tags/raw_tag_test.rb +31 -0
  79. data/test/integration/tags/standard_tag_test.rb +296 -0
  80. data/test/integration/tags/statements_test.rb +111 -0
  81. data/test/integration/tags/table_row_test.rb +64 -0
  82. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  83. data/test/integration/template_test.rb +332 -0
  84. data/test/integration/trim_mode_test.rb +529 -0
  85. data/test/integration/variable_test.rb +96 -0
  86. data/test/test_helper.rb +116 -0
  87. data/test/unit/block_unit_test.rb +58 -0
  88. data/test/unit/condition_unit_test.rb +166 -0
  89. data/test/unit/context_unit_test.rb +489 -0
  90. data/test/unit/file_system_unit_test.rb +35 -0
  91. data/test/unit/i18n_unit_test.rb +37 -0
  92. data/test/unit/lexer_unit_test.rb +51 -0
  93. data/test/unit/parser_unit_test.rb +82 -0
  94. data/test/unit/regexp_unit_test.rb +44 -0
  95. data/test/unit/strainer_unit_test.rb +164 -0
  96. data/test/unit/tag_unit_test.rb +21 -0
  97. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  98. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  99. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  100. data/test/unit/template_unit_test.rb +78 -0
  101. data/test/unit/tokenizer_unit_test.rb +55 -0
  102. data/test/unit/variable_unit_test.rb +162 -0
  103. metadata +224 -0
@@ -0,0 +1,122 @@
1
+ require 'test_helper'
2
+
3
+ class ParsingQuirksTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_parsing_css
7
+ text = " div { font-weight: bold; } "
8
+ assert_equal text, Template.parse(text).render!
9
+ end
10
+
11
+ def test_raise_on_single_close_bracet
12
+ assert_raises(SyntaxError) do
13
+ Template.parse("text {{method} oh nos!")
14
+ end
15
+ end
16
+
17
+ def test_raise_on_label_and_no_close_bracets
18
+ assert_raises(SyntaxError) do
19
+ Template.parse("TEST {{ ")
20
+ end
21
+ end
22
+
23
+ def test_raise_on_label_and_no_close_bracets_percent
24
+ assert_raises(SyntaxError) do
25
+ Template.parse("TEST {% ")
26
+ end
27
+ end
28
+
29
+ def test_error_on_empty_filter
30
+ assert Template.parse("{{test}}")
31
+
32
+ with_error_mode(:lax) do
33
+ assert Template.parse("{{|test}}")
34
+ end
35
+
36
+ with_error_mode(:strict) do
37
+ assert_raises(SyntaxError) { Template.parse("{{|test}}") }
38
+ assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
39
+ end
40
+ end
41
+
42
+ def test_meaningless_parens_error
43
+ with_error_mode(:strict) do
44
+ assert_raises(SyntaxError) do
45
+ markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
46
+ Template.parse("{% if #{markup} %} YES {% endif %}")
47
+ end
48
+ end
49
+ end
50
+
51
+ def test_unexpected_characters_syntax_error
52
+ with_error_mode(:strict) do
53
+ assert_raises(SyntaxError) do
54
+ markup = "true && false"
55
+ Template.parse("{% if #{markup} %} YES {% endif %}")
56
+ end
57
+ assert_raises(SyntaxError) do
58
+ markup = "false || true"
59
+ Template.parse("{% if #{markup} %} YES {% endif %}")
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_no_error_on_lax_empty_filter
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)
68
+ end
69
+
70
+ def test_meaningless_parens_lax
71
+ with_error_mode(:lax) do
72
+ assigns = { 'b' => 'bar', 'c' => 'baz' }
73
+ markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
74
+ assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
75
+ end
76
+ end
77
+
78
+ def test_unexpected_characters_silently_eat_logic_lax
79
+ with_error_mode(:lax) do
80
+ markup = "true && false"
81
+ assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}")
82
+ markup = "false || true"
83
+ assert_template_result('', "{% if #{markup} %} YES {% endif %}")
84
+ end
85
+ end
86
+
87
+ def test_raise_on_invalid_tag_delimiter
88
+ assert_raises(Liquid::SyntaxError) do
89
+ Template.new.parse('{% end %}')
90
+ end
91
+ end
92
+
93
+ def test_unanchored_filter_arguments
94
+ with_error_mode(:lax) do
95
+ assert_template_result('hi', "{{ 'hi there' | split$$$:' ' | first }}")
96
+
97
+ assert_template_result('x', "{{ 'X' | downcase) }}")
98
+
99
+ # After the messed up quotes a filter without parameters (reverse) should work
100
+ # but one with parameters (remove) shouldn't be detected.
101
+ assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
102
+ assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
103
+ end
104
+ end
105
+
106
+ def test_invalid_variables_work
107
+ with_error_mode(:lax) do
108
+ assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
109
+ assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
110
+ end
111
+ end
112
+
113
+ def test_extra_dots_in_ranges
114
+ with_error_mode(:lax) do
115
+ assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
116
+ end
117
+ end
118
+
119
+ def test_contains_in_id
120
+ assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
121
+ end
122
+ end # ParsingQuirksTest
@@ -0,0 +1,154 @@
1
+ require 'test_helper'
2
+
3
+ class RenderProfilingTest < Minitest::Test
4
+ include Liquid
5
+
6
+ class ProfilingFileSystem
7
+ def read_template_file(template_path)
8
+ "Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
9
+ end
10
+ end
11
+
12
+ def setup
13
+ Liquid::Template.file_system = ProfilingFileSystem.new
14
+ end
15
+
16
+ def test_template_allows_flagging_profiling
17
+ t = Template.parse("{{ 'a string' | upcase }}")
18
+ t.render!
19
+
20
+ assert_nil t.profiler
21
+ end
22
+
23
+ def test_parse_makes_available_simple_profiling
24
+ t = Template.parse("{{ 'a string' | upcase }}", profile: true)
25
+ t.render!
26
+
27
+ assert_equal 1, t.profiler.length
28
+
29
+ node = t.profiler[0]
30
+ assert_equal " 'a string' | upcase ", node.code
31
+ end
32
+
33
+ def test_render_ignores_raw_strings_when_profiling
34
+ t = Template.parse("This is raw string\nstuff\nNewline", profile: true)
35
+ t.render!
36
+
37
+ assert_equal 0, t.profiler.length
38
+ end
39
+
40
+ def test_profiling_includes_line_numbers_of_liquid_nodes
41
+ t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
42
+ t.render!
43
+ assert_equal 2, t.profiler.length
44
+
45
+ # {{ 'a string' | upcase }}
46
+ assert_equal 1, t.profiler[0].line_number
47
+ # {{ increment test }}
48
+ assert_equal 2, t.profiler[1].line_number
49
+ end
50
+
51
+ def test_profiling_includes_line_numbers_of_included_partials
52
+ t = Template.parse("{% include 'a_template' %}", profile: true)
53
+ t.render!
54
+
55
+ included_children = t.profiler[0].children
56
+
57
+ # {% assign template_name = 'a_template' %}
58
+ assert_equal 1, included_children[0].line_number
59
+ # {{ template_name }}
60
+ assert_equal 2, included_children[1].line_number
61
+ end
62
+
63
+ def test_profiling_times_the_rendering_of_tokens
64
+ t = Template.parse("{% include 'a_template' %}", profile: true)
65
+ t.render!
66
+
67
+ node = t.profiler[0]
68
+ refute_nil node.render_time
69
+ end
70
+
71
+ def test_profiling_times_the_entire_render
72
+ t = Template.parse("{% include 'a_template' %}", profile: true)
73
+ t.render!
74
+
75
+ assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
76
+ end
77
+
78
+ def test_profiling_uses_include_to_mark_children
79
+ t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
80
+ t.render!
81
+
82
+ include_node = t.profiler[1]
83
+ assert_equal 2, include_node.children.length
84
+ end
85
+
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)
88
+ t.render!
89
+
90
+ include_node = t.profiler[1]
91
+ include_node.children.each do |child|
92
+ assert_equal "a_template", child.partial
93
+ end
94
+ end
95
+
96
+ def test_profiling_supports_multiple_templates
97
+ t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", profile: true)
98
+ t.render!
99
+
100
+ a_template = t.profiler[1]
101
+ a_template.children.each do |child|
102
+ assert_equal "a_template", child.partial
103
+ end
104
+
105
+ b_template = t.profiler[2]
106
+ b_template.children.each do |child|
107
+ assert_equal "b_template", child.partial
108
+ end
109
+ end
110
+
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)
113
+ t.render!
114
+
115
+ a_template1 = t.profiler[1]
116
+ a_template1.children.each do |child|
117
+ assert_equal "a_template", child.partial
118
+ end
119
+
120
+ a_template2 = t.profiler[2]
121
+ a_template2.children.each do |child|
122
+ assert_equal "a_template", child.partial
123
+ end
124
+ end
125
+
126
+ def test_can_iterate_over_each_profiling_entry
127
+ t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", profile: true)
128
+ t.render!
129
+
130
+ timing_count = 0
131
+ t.profiler.each do |timing|
132
+ timing_count += 1
133
+ end
134
+
135
+ assert_equal 2, timing_count
136
+ end
137
+
138
+ def test_profiling_marks_children_of_if_blocks
139
+ t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", profile: true)
140
+ t.render!
141
+
142
+ assert_equal 1, t.profiler.length
143
+ assert_equal 2, t.profiler[0].children.length
144
+ end
145
+
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"] })
149
+
150
+ assert_equal 1, t.profiler.length
151
+ # Will profile each invocation of the for block
152
+ assert_equal 2, t.profiler[0].children.length
153
+ end
154
+ end
@@ -0,0 +1,80 @@
1
+ require 'test_helper'
2
+
3
+ module SecurityFilter
4
+ def add_one(input)
5
+ "#{input} + 1"
6
+ end
7
+ end
8
+
9
+ class SecurityTest < Minitest::Test
10
+ include Liquid
11
+
12
+ def setup
13
+ @assigns = {}
14
+ end
15
+
16
+ def test_no_instance_eval
17
+ text = %( {{ '1+1' | instance_eval }} )
18
+ expected = %( 1+1 )
19
+
20
+ assert_equal expected, Template.parse(text).render!(@assigns)
21
+ end
22
+
23
+ def test_no_existing_instance_eval
24
+ text = %( {{ '1+1' | __instance_eval__ }} )
25
+ expected = %( 1+1 )
26
+
27
+ assert_equal expected, Template.parse(text).render!(@assigns)
28
+ end
29
+
30
+ def test_no_instance_eval_after_mixing_in_new_filter
31
+ text = %( {{ '1+1' | instance_eval }} )
32
+ expected = %( 1+1 )
33
+
34
+ assert_equal expected, Template.parse(text).render!(@assigns)
35
+ end
36
+
37
+ def test_no_instance_eval_later_in_chain
38
+ text = %( {{ '1+1' | add_one | instance_eval }} )
39
+ expected = %( 1+1 + 1 )
40
+
41
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: SecurityFilter)
42
+ end
43
+
44
+ def test_does_not_add_filters_to_symbol_table
45
+ current_symbols = Symbol.all_symbols
46
+
47
+ test = %( {{ "some_string" | a_bad_filter }} )
48
+
49
+ template = Template.parse(test)
50
+ assert_equal [], (Symbol.all_symbols - current_symbols)
51
+
52
+ template.render!
53
+ assert_equal [], (Symbol.all_symbols - current_symbols)
54
+ end
55
+
56
+ def test_does_not_add_drop_methods_to_symbol_table
57
+ current_symbols = Symbol.all_symbols
58
+
59
+ 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!
63
+
64
+ assert_equal [], (Symbol.all_symbols - current_symbols)
65
+ end
66
+
67
+ def test_max_depth_nested_blocks_does_not_raise_exception
68
+ depth = Liquid::Block::MAX_DEPTH
69
+ code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
70
+ assert_equal "rendered", Template.parse(code).render!
71
+ end
72
+
73
+ def test_more_than_max_depth_nested_blocks_raises_exception
74
+ depth = Liquid::Block::MAX_DEPTH + 1
75
+ code = "{% if true %}" * depth + "rendered" + "{% endif %}" * depth
76
+ assert_raises(Liquid::StackLevelError) do
77
+ Template.parse(code).render!
78
+ end
79
+ end
80
+ end # SecurityTest
@@ -0,0 +1,698 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_helper'
4
+
5
+ class Filters
6
+ include Liquid::StandardFilters
7
+ end
8
+
9
+ class TestThing
10
+ attr_reader :foo
11
+
12
+ def initialize
13
+ @foo = 0
14
+ end
15
+
16
+ def to_s
17
+ "woot: #{@foo}"
18
+ end
19
+
20
+ def [](whatever)
21
+ to_s
22
+ end
23
+
24
+ def to_liquid
25
+ @foo += 1
26
+ self
27
+ end
28
+ end
29
+
30
+ class TestDrop < Liquid::Drop
31
+ def test
32
+ "testfoo"
33
+ end
34
+ end
35
+
36
+ class TestEnumerable < Liquid::Drop
37
+ include Enumerable
38
+
39
+ def each(&block)
40
+ [ { "foo" => 1, "bar" => 2 }, { "foo" => 2, "bar" => 1 }, { "foo" => 3, "bar" => 3 } ].each(&block)
41
+ end
42
+ end
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
+
54
+ class StandardFiltersTest < Minitest::Test
55
+ include Liquid
56
+
57
+ def setup
58
+ @filters = Filters.new
59
+ end
60
+
61
+ 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)
65
+ end
66
+
67
+ def test_downcase
68
+ assert_equal 'testing', @filters.downcase("Testing")
69
+ assert_equal '', @filters.downcase(nil)
70
+ end
71
+
72
+ def test_upcase
73
+ assert_equal 'TESTING', @filters.upcase("Testing")
74
+ assert_equal '', @filters.upcase(nil)
75
+ end
76
+
77
+ 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')
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
96
+ end
97
+
98
+ def test_slice_on_arrays
99
+ 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)
110
+ end
111
+
112
+ 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)
119
+ end
120
+
121
+ 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)
127
+ end
128
+
129
+ def test_escape
130
+ assert_equal '&lt;strong&gt;', @filters.escape('<strong>')
131
+ assert_equal '1', @filters.escape(1)
132
+ assert_equal '2001-02-03', @filters.escape(Date.new(2001, 2, 3))
133
+ assert_nil @filters.escape(nil)
134
+ end
135
+
136
+ def test_h
137
+ assert_equal '&lt;strong&gt;', @filters.h('<strong>')
138
+ assert_equal '1', @filters.h(1)
139
+ assert_equal '2001-02-03', @filters.h(Date.new(2001, 2, 3))
140
+ assert_nil @filters.h(nil)
141
+ end
142
+
143
+ def test_escape_once
144
+ assert_equal '&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>')
145
+ end
146
+
147
+ def test_url_encode
148
+ assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
149
+ assert_equal '1', @filters.url_encode(1)
150
+ assert_equal '2001-02-03', @filters.url_encode(Date.new(2001, 2, 3))
151
+ assert_nil @filters.url_encode(nil)
152
+ end
153
+
154
+ def test_url_decode
155
+ assert_equal 'foo bar', @filters.url_decode('foo+bar')
156
+ assert_equal 'foo bar', @filters.url_decode('foo%20bar')
157
+ assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
158
+ assert_equal '1', @filters.url_decode(1)
159
+ assert_equal '2001-02-03', @filters.url_decode(Date.new(2001, 2, 3))
160
+ assert_nil @filters.url_decode(nil)
161
+ end
162
+
163
+ def test_truncatewords
164
+ assert_equal 'one two three', @filters.truncatewords('one two three', 4)
165
+ assert_equal 'one two...', @filters.truncatewords('one two three', 2)
166
+ assert_equal 'one two three', @filters.truncatewords('one two three')
167
+ 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)
168
+ assert_equal "测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5)
169
+ assert_equal 'one two1', @filters.truncatewords("one two three", 2, 1)
170
+ end
171
+
172
+ def test_strip_html
173
+ assert_equal 'test', @filters.strip_html("<div>test</div>")
174
+ assert_equal 'test', @filters.strip_html("<div id='test'>test</div>")
175
+ assert_equal '', @filters.strip_html("<script type='text/javascript'>document.write('some stuff');</script>")
176
+ assert_equal '', @filters.strip_html("<style type='text/css'>foo bar</style>")
177
+ assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
178
+ assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
179
+ assert_equal '', @filters.strip_html(nil)
180
+ end
181
+
182
+ def test_join
183
+ assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
184
+ assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
185
+ assert_equal '1121314', @filters.join([1, 2, 3, 4], 1)
186
+ end
187
+
188
+ def test_sort
189
+ assert_equal [1, 2, 3, 4], @filters.sort([4, 3, 2, 1])
190
+ assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
191
+ end
192
+
193
+ def test_sort_with_nils
194
+ assert_equal [1, 2, 3, 4, nil], @filters.sort([nil, 4, 3, 2, 1])
195
+ assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }, {}], @filters.sort([{ "a" => 4 }, { "a" => 3 }, {}, { "a" => 1 }, { "a" => 2 }], "a")
196
+ end
197
+
198
+ def test_sort_when_property_is_sometimes_missing_puts_nils_last
199
+ input = [
200
+ { "price" => 4, "handle" => "alpha" },
201
+ { "handle" => "beta" },
202
+ { "price" => 1, "handle" => "gamma" },
203
+ { "handle" => "delta" },
204
+ { "price" => 2, "handle" => "epsilon" }
205
+ ]
206
+ expectation = [
207
+ { "price" => 1, "handle" => "gamma" },
208
+ { "price" => 2, "handle" => "epsilon" },
209
+ { "price" => 4, "handle" => "alpha" },
210
+ { "handle" => "delta" },
211
+ { "handle" => "beta" }
212
+ ]
213
+ assert_equal expectation, @filters.sort(input, "price")
214
+ end
215
+
216
+ def test_sort_natural
217
+ assert_equal ["a", "B", "c", "D"], @filters.sort_natural(["c", "D", "a", "B"])
218
+ assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, { "a" => "a" }, { "a" => "B" }], "a")
219
+ end
220
+
221
+ def test_sort_natural_with_nils
222
+ assert_equal ["a", "B", "c", "D", nil], @filters.sort_natural([nil, "c", "D", "a", "B"])
223
+ assert_equal [{ "a" => "a" }, { "a" => "B" }, { "a" => "c" }, { "a" => "D" }, {}], @filters.sort_natural([{ "a" => "D" }, { "a" => "c" }, {}, { "a" => "a" }, { "a" => "B" }], "a")
224
+ end
225
+
226
+ def test_sort_natural_when_property_is_sometimes_missing_puts_nils_last
227
+ input = [
228
+ { "price" => "4", "handle" => "alpha" },
229
+ { "handle" => "beta" },
230
+ { "price" => "1", "handle" => "gamma" },
231
+ { "handle" => "delta" },
232
+ { "price" => 2, "handle" => "epsilon" }
233
+ ]
234
+ expectation = [
235
+ { "price" => "1", "handle" => "gamma" },
236
+ { "price" => 2, "handle" => "epsilon" },
237
+ { "price" => "4", "handle" => "alpha" },
238
+ { "handle" => "delta" },
239
+ { "handle" => "beta" }
240
+ ]
241
+ assert_equal expectation, @filters.sort_natural(input, "price")
242
+ end
243
+
244
+ def test_sort_natural_case_check
245
+ input = [
246
+ { "key" => "X" },
247
+ { "key" => "Y" },
248
+ { "key" => "Z" },
249
+ { "fake" => "t" },
250
+ { "key" => "a" },
251
+ { "key" => "b" },
252
+ { "key" => "c" }
253
+ ]
254
+ expectation = [
255
+ { "key" => "a" },
256
+ { "key" => "b" },
257
+ { "key" => "c" },
258
+ { "key" => "X" },
259
+ { "key" => "Y" },
260
+ { "key" => "Z" },
261
+ { "fake" => "t" }
262
+ ]
263
+ assert_equal expectation, @filters.sort_natural(input, "key")
264
+ assert_equal ["a", "b", "c", "X", "Y", "Z"], @filters.sort_natural(["X", "Y", "Z", "a", "b", "c"])
265
+ end
266
+
267
+ def test_sort_empty_array
268
+ assert_equal [], @filters.sort([], "a")
269
+ end
270
+
271
+ def test_sort_natural_empty_array
272
+ assert_equal [], @filters.sort_natural([], "a")
273
+ end
274
+
275
+ def test_legacy_sort_hash
276
+ assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
277
+ end
278
+
279
+ def test_numerical_vs_lexicographical_sort
280
+ assert_equal [2, 10], @filters.sort([10, 2])
281
+ assert_equal [{ "a" => 2 }, { "a" => 10 }], @filters.sort([{ "a" => 10 }, { "a" => 2 }], "a")
282
+ assert_equal ["10", "2"], @filters.sort(["10", "2"])
283
+ assert_equal [{ "a" => "10" }, { "a" => "2" }], @filters.sort([{ "a" => "10" }, { "a" => "2" }], "a")
284
+ end
285
+
286
+ def test_uniq
287
+ assert_equal ["foo"], @filters.uniq("foo")
288
+ assert_equal [1, 3, 2, 4], @filters.uniq([1, 1, 3, 2, 3, 1, 4, 3, 2, 1])
289
+ assert_equal [{ "a" => 1 }, { "a" => 3 }, { "a" => 2 }], @filters.uniq([{ "a" => 1 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
290
+ testdrop = TestDrop.new
291
+ assert_equal [testdrop], @filters.uniq([testdrop, TestDrop.new], 'test')
292
+ end
293
+
294
+ def test_uniq_empty_array
295
+ assert_equal [], @filters.uniq([], "a")
296
+ end
297
+
298
+ def test_compact_empty_array
299
+ assert_equal [], @filters.compact([], "a")
300
+ end
301
+
302
+ def test_reverse
303
+ assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
304
+ end
305
+
306
+ def test_legacy_reverse_hash
307
+ assert_equal [{ a: 1, b: 2 }], @filters.reverse(a: 1, b: 2)
308
+ end
309
+
310
+ def test_map
311
+ assert_equal [1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a')
312
+ assert_template_result 'abc', "{{ ary | map:'foo' | map:'bar' }}",
313
+ 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }]
314
+ end
315
+
316
+ def test_map_doesnt_call_arbitrary_stuff
317
+ assert_template_result "", '{{ "foo" | map: "__id__" }}'
318
+ assert_template_result "", '{{ "foo" | map: "inspect" }}'
319
+ end
320
+
321
+ def test_map_calls_to_liquid
322
+ t = TestThing.new
323
+ assert_template_result "woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t]
324
+ end
325
+
326
+ def test_map_on_hashes
327
+ assert_template_result "4217", '{{ thing | map: "foo" | map: "bar" }}',
328
+ "thing" => { "foo" => [ { "bar" => 42 }, { "bar" => 17 } ] }
329
+ end
330
+
331
+ def test_legacy_map_on_hashes_with_dynamic_key
332
+ template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
333
+ hash = { "foo" => { "bar" => 42 } }
334
+ assert_template_result "42", template, "thing" => hash
335
+ end
336
+
337
+ def test_sort_calls_to_liquid
338
+ t = TestThing.new
339
+ Liquid::Template.parse('{{ foo | sort: "whatever" }}').render("foo" => [t])
340
+ assert t.foo > 0
341
+ end
342
+
343
+ def test_map_over_proc
344
+ drop = TestDrop.new
345
+ p = proc{ drop }
346
+ templ = '{{ procs | map: "test" }}'
347
+ assert_template_result "testfoo", templ, "procs" => [p]
348
+ end
349
+
350
+ def test_map_over_drops_returning_procs
351
+ drops = [
352
+ {
353
+ "proc" => ->{ "foo" },
354
+ },
355
+ {
356
+ "proc" => ->{ "bar" },
357
+ },
358
+ ]
359
+ templ = '{{ drops | map: "proc" }}'
360
+ assert_template_result "foobar", templ, "drops" => drops
361
+ end
362
+
363
+ def test_map_works_on_enumerables
364
+ assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
365
+ end
366
+
367
+ def test_sort_works_on_enumerables
368
+ assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
369
+ end
370
+
371
+ def test_first_and_last_call_to_liquid
372
+ assert_template_result 'foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new]
373
+ assert_template_result 'foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new]
374
+ end
375
+
376
+ def test_truncate_calls_to_liquid
377
+ assert_template_result "wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new
378
+ end
379
+
380
+ def test_date
381
+ assert_equal 'May', @filters.date(Time.parse("2006-05-05 10:00:00"), "%B")
382
+ assert_equal 'June', @filters.date(Time.parse("2006-06-05 10:00:00"), "%B")
383
+ assert_equal 'July', @filters.date(Time.parse("2006-07-05 10:00:00"), "%B")
384
+
385
+ assert_equal 'May', @filters.date("2006-05-05 10:00:00", "%B")
386
+ assert_equal 'June', @filters.date("2006-06-05 10:00:00", "%B")
387
+ assert_equal 'July', @filters.date("2006-07-05 10:00:00", "%B")
388
+
389
+ assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
390
+ assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
391
+ assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", "")
392
+ assert_equal '2006-07-05 10:00:00', @filters.date("2006-07-05 10:00:00", nil)
393
+
394
+ assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
395
+
396
+ assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
397
+ assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
398
+ assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
399
+ assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
400
+
401
+ assert_nil @filters.date(nil, "%B")
402
+
403
+ assert_equal '', @filters.date('', "%B")
404
+
405
+ with_timezone("UTC") do
406
+ assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
407
+ assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
408
+ end
409
+ end
410
+
411
+ def test_first_last
412
+ assert_equal 1, @filters.first([1, 2, 3])
413
+ assert_equal 3, @filters.last([1, 2, 3])
414
+ assert_nil @filters.first([])
415
+ assert_nil @filters.last([])
416
+ end
417
+
418
+ def test_replace
419
+ assert_equal '2 2 2 2', @filters.replace('1 1 1 1', '1', 2)
420
+ assert_equal '2 2 2 2', @filters.replace('1 1 1 1', 1, 2)
421
+ assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2)
422
+ assert_equal '2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2)
423
+ assert_template_result '2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}"
424
+ end
425
+
426
+ def test_remove
427
+ assert_equal ' ', @filters.remove("a a a a", 'a')
428
+ assert_equal ' ', @filters.remove("1 1 1 1", 1)
429
+ assert_equal 'a a a', @filters.remove_first("a a a a", 'a ')
430
+ assert_equal ' 1 1 1', @filters.remove_first("1 1 1 1", 1)
431
+ assert_template_result 'a a a', "{{ 'a a a a' | remove_first: 'a ' }}"
432
+ end
433
+
434
+ def test_pipes_in_string_arguments
435
+ assert_template_result 'foobar', "{{ 'foo|bar' | remove: '|' }}"
436
+ end
437
+
438
+ def test_strip
439
+ assert_template_result 'ab c', "{{ source | strip }}", 'source' => " ab c "
440
+ assert_template_result 'ab c', "{{ source | strip }}", 'source' => " \tab c \n \t"
441
+ end
442
+
443
+ def test_lstrip
444
+ assert_template_result 'ab c ', "{{ source | lstrip }}", 'source' => " ab c "
445
+ assert_template_result "ab c \n \t", "{{ source | lstrip }}", 'source' => " \tab c \n \t"
446
+ end
447
+
448
+ def test_rstrip
449
+ assert_template_result " ab c", "{{ source | rstrip }}", 'source' => " ab c "
450
+ assert_template_result " \tab c", "{{ source | rstrip }}", 'source' => " \tab c \n \t"
451
+ end
452
+
453
+ def test_strip_newlines
454
+ assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc"
455
+ assert_template_result 'abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc"
456
+ end
457
+
458
+ def test_newlines_to_br
459
+ assert_template_result "a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc"
460
+ end
461
+
462
+ def test_plus
463
+ assert_template_result "2", "{{ 1 | plus:1 }}"
464
+ assert_template_result "2.0", "{{ '1' | plus:'1.0' }}"
465
+
466
+ assert_template_result "5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3)
467
+ end
468
+
469
+ def test_minus
470
+ assert_template_result "4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1
471
+ assert_template_result "2.3", "{{ '4.3' | minus:'2' }}"
472
+
473
+ assert_template_result "5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7)
474
+ end
475
+
476
+ def test_abs
477
+ assert_template_result "17", "{{ 17 | abs }}"
478
+ assert_template_result "17", "{{ -17 | abs }}"
479
+ assert_template_result "17", "{{ '17' | abs }}"
480
+ assert_template_result "17", "{{ '-17' | abs }}"
481
+ assert_template_result "0", "{{ 0 | abs }}"
482
+ assert_template_result "0", "{{ '0' | abs }}"
483
+ assert_template_result "17.42", "{{ 17.42 | abs }}"
484
+ assert_template_result "17.42", "{{ -17.42 | abs }}"
485
+ assert_template_result "17.42", "{{ '17.42' | abs }}"
486
+ assert_template_result "17.42", "{{ '-17.42' | abs }}"
487
+ end
488
+
489
+ def test_times
490
+ assert_template_result "12", "{{ 3 | times:4 }}"
491
+ assert_template_result "0", "{{ 'foo' | times:4 }}"
492
+ assert_template_result "6", "{{ '2.1' | times:3 | replace: '.','-' | plus:0}}"
493
+ assert_template_result "7.25", "{{ 0.0725 | times:100 }}"
494
+ assert_template_result "-7.25", '{{ "-0.0725" | times:100 }}'
495
+ assert_template_result "7.25", '{{ "-0.0725" | times: -100 }}'
496
+ assert_template_result "4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2)
497
+ end
498
+
499
+ def test_divided_by
500
+ assert_template_result "4", "{{ 12 | divided_by:3 }}"
501
+ assert_template_result "4", "{{ 14 | divided_by:3 }}"
502
+
503
+ assert_template_result "5", "{{ 15 | divided_by:3 }}"
504
+ assert_equal "Liquid error: divided by 0", Template.parse("{{ 5 | divided_by:0 }}").render
505
+
506
+ assert_template_result "0.5", "{{ 2.0 | divided_by:4 }}"
507
+ assert_raises(Liquid::ZeroDivisionError) do
508
+ assert_template_result "4", "{{ 1 | modulo: 0 }}"
509
+ end
510
+
511
+ assert_template_result "5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10)
512
+ end
513
+
514
+ def test_modulo
515
+ assert_template_result "1", "{{ 3 | modulo:2 }}"
516
+ assert_raises(Liquid::ZeroDivisionError) do
517
+ assert_template_result "4", "{{ 1 | modulo: 0 }}"
518
+ end
519
+
520
+ assert_template_result "1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3)
521
+ end
522
+
523
+ def test_round
524
+ assert_template_result "5", "{{ input | round }}", 'input' => 4.6
525
+ assert_template_result "4", "{{ '4.3' | round }}"
526
+ assert_template_result "4.56", "{{ input | round: 2 }}", 'input' => 4.5612
527
+ assert_raises(Liquid::FloatDomainError) do
528
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
529
+ end
530
+
531
+ assert_template_result "5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6)
532
+ assert_template_result "4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3)
533
+ end
534
+
535
+ def test_ceil
536
+ assert_template_result "5", "{{ input | ceil }}", 'input' => 4.6
537
+ assert_template_result "5", "{{ '4.3' | ceil }}"
538
+ assert_raises(Liquid::FloatDomainError) do
539
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
540
+ end
541
+
542
+ assert_template_result "5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6)
543
+ end
544
+
545
+ def test_floor
546
+ assert_template_result "4", "{{ input | floor }}", 'input' => 4.6
547
+ assert_template_result "4", "{{ '4.3' | floor }}"
548
+ assert_raises(Liquid::FloatDomainError) do
549
+ assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
550
+ end
551
+
552
+ assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
553
+ end
554
+
555
+ def test_at_most
556
+ assert_template_result "4", "{{ 5 | at_most:4 }}"
557
+ assert_template_result "5", "{{ 5 | at_most:5 }}"
558
+ assert_template_result "5", "{{ 5 | at_most:6 }}"
559
+
560
+ assert_template_result "4.5", "{{ 4.5 | at_most:5 }}"
561
+ assert_template_result "5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6)
562
+ assert_template_result "4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4)
563
+ assert_template_result "4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4)
564
+ end
565
+
566
+ def test_at_least
567
+ assert_template_result "5", "{{ 5 | at_least:4 }}"
568
+ assert_template_result "5", "{{ 5 | at_least:5 }}"
569
+ assert_template_result "6", "{{ 5 | at_least:6 }}"
570
+
571
+ assert_template_result "5", "{{ 4.5 | at_least:5 }}"
572
+ assert_template_result "6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6)
573
+ assert_template_result "5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4)
574
+ assert_template_result "6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6)
575
+ end
576
+
577
+ def test_append
578
+ assigns = { 'a' => 'bc', 'b' => 'd' }
579
+ assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
580
+ assert_template_result('bcd', "{{ a | append: b}}", assigns)
581
+ end
582
+
583
+ def test_concat
584
+ assert_equal [1, 2, 3, 4], @filters.concat([1, 2], [3, 4])
585
+ assert_equal [1, 2, 'a'], @filters.concat([1, 2], ['a'])
586
+ assert_equal [1, 2, 10], @filters.concat([1, 2], [10])
587
+
588
+ assert_raises(Liquid::ArgumentError, "concat filter requires an array argument") do
589
+ @filters.concat([1, 2], 10)
590
+ end
591
+ end
592
+
593
+ def test_prepend
594
+ assigns = { 'a' => 'bc', 'b' => 'a' }
595
+ assert_template_result('abc', "{{ a | prepend: 'a'}}", assigns)
596
+ assert_template_result('abc', "{{ a | prepend: b}}", assigns)
597
+ end
598
+
599
+ def test_default
600
+ assert_equal "foo", @filters.default("foo", "bar")
601
+ assert_equal "bar", @filters.default(nil, "bar")
602
+ assert_equal "bar", @filters.default("", "bar")
603
+ assert_equal "bar", @filters.default(false, "bar")
604
+ assert_equal "bar", @filters.default([], "bar")
605
+ assert_equal "bar", @filters.default({}, "bar")
606
+ end
607
+
608
+ def test_cannot_access_private_methods
609
+ assert_template_result('a', "{{ 'a' | to_number }}")
610
+ end
611
+
612
+ def test_date_raises_nothing
613
+ assert_template_result('', "{{ '' | date: '%D' }}")
614
+ assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
615
+ end
616
+
617
+ def test_where
618
+ input = [
619
+ { "handle" => "alpha", "ok" => true },
620
+ { "handle" => "beta", "ok" => false },
621
+ { "handle" => "gamma", "ok" => false },
622
+ { "handle" => "delta", "ok" => true }
623
+ ]
624
+
625
+ expectation = [
626
+ { "handle" => "alpha", "ok" => true },
627
+ { "handle" => "delta", "ok" => true }
628
+ ]
629
+
630
+ assert_equal expectation, @filters.where(input, "ok", true)
631
+ assert_equal expectation, @filters.where(input, "ok")
632
+ end
633
+
634
+ def test_where_no_key_set
635
+ input = [
636
+ { "handle" => "alpha", "ok" => true },
637
+ { "handle" => "beta" },
638
+ { "handle" => "gamma" },
639
+ { "handle" => "delta", "ok" => true }
640
+ ]
641
+
642
+ expectation = [
643
+ { "handle" => "alpha", "ok" => true },
644
+ { "handle" => "delta", "ok" => true }
645
+ ]
646
+
647
+ assert_equal expectation, @filters.where(input, "ok", true)
648
+ assert_equal expectation, @filters.where(input, "ok")
649
+ end
650
+
651
+ def test_where_non_array_map_input
652
+ assert_equal [{ "a" => "ok" }], @filters.where({ "a" => "ok" }, "a", "ok")
653
+ assert_equal [], @filters.where({ "a" => "not ok" }, "a", "ok")
654
+ end
655
+
656
+ def test_where_indexable_but_non_map_value
657
+ assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok", true) }
658
+ assert_raises(Liquid::ArgumentError) { @filters.where(1, "ok") }
659
+ end
660
+
661
+ def test_where_non_boolean_value
662
+ input = [
663
+ { "message" => "Bonjour!", "language" => "French" },
664
+ { "message" => "Hello!", "language" => "English" },
665
+ { "message" => "Hallo!", "language" => "German" }
666
+ ]
667
+
668
+ assert_equal [{ "message" => "Bonjour!", "language" => "French" }], @filters.where(input, "language", "French")
669
+ assert_equal [{ "message" => "Hallo!", "language" => "German" }], @filters.where(input, "language", "German")
670
+ assert_equal [{ "message" => "Hello!", "language" => "English" }], @filters.where(input, "language", "English")
671
+ end
672
+
673
+ def test_where_array_of_only_unindexable_values
674
+ assert_nil @filters.where([nil], "ok", true)
675
+ assert_nil @filters.where([nil], "ok")
676
+ end
677
+
678
+ def test_where_no_target_value
679
+ input = [
680
+ { "foo" => false },
681
+ { "foo" => true },
682
+ { "foo" => "for sure" },
683
+ { "bar" => true }
684
+ ]
685
+
686
+ assert_equal [{ "foo" => true }, { "foo" => "for sure" }], @filters.where(input, "foo")
687
+ end
688
+
689
+ private
690
+
691
+ def with_timezone(tz)
692
+ old_tz = ENV['TZ']
693
+ ENV['TZ'] = tz
694
+ yield
695
+ ensure
696
+ ENV['TZ'] = old_tz
697
+ end
698
+ end # StandardFiltersTest