liquid-4-0-2 4.0.2

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 (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