liquid 4.0.0 → 4.0.1
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.
- checksums.yaml +5 -5
- data/History.md +5 -2
- data/README.md +2 -0
- data/lib/liquid/block.rb +22 -12
- data/lib/liquid/block_body.rb +62 -59
- data/lib/liquid/condition.rb +20 -10
- data/lib/liquid/context.rb +10 -8
- data/lib/liquid/expression.rb +10 -6
- data/lib/liquid/extensions.rb +6 -0
- data/lib/liquid/lexer.rb +5 -3
- data/lib/liquid/locales/en.yml +1 -1
- data/lib/liquid/parse_context.rb +2 -1
- data/lib/liquid/profiler/hooks.rb +4 -4
- data/lib/liquid/standardfilters.rb +47 -11
- data/lib/liquid/strainer.rb +1 -1
- data/lib/liquid/tags/cycle.rb +2 -2
- data/lib/liquid/tags/for.rb +6 -3
- data/lib/liquid/tags/if.rb +8 -5
- data/lib/liquid/tags/include.rb +1 -1
- data/lib/liquid/tags/table_row.rb +1 -1
- data/lib/liquid/utils.rb +2 -2
- data/lib/liquid/variable.rb +10 -4
- data/lib/liquid/version.rb +1 -1
- data/test/integration/block_test.rb +12 -0
- data/test/integration/parsing_quirks_test.rb +4 -0
- data/test/integration/security_test.rb +14 -0
- data/test/integration/standard_filter_test.rb +97 -6
- data/test/integration/tags/for_tag_test.rb +2 -2
- data/test/integration/tags/include_tag_test.rb +8 -1
- data/test/integration/template_test.rb +9 -0
- data/test/integration/trim_mode_test.rb +4 -0
- data/test/integration/variable_test.rb +4 -0
- data/test/test_helper.rb +0 -1
- data/test/unit/condition_unit_test.rb +8 -0
- data/test/unit/context_unit_test.rb +23 -17
- data/test/unit/lexer_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +16 -0
- metadata +42 -40
data/lib/liquid/parse_context.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Liquid
|
2
2
|
class ParseContext
|
3
|
-
attr_accessor :locale, :line_number, :trim_whitespace
|
3
|
+
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
4
4
|
attr_reader :partial, :warnings, :error_mode
|
5
5
|
|
6
6
|
def initialize(options = {})
|
7
7
|
@template_options = options ? options.dup : {}
|
8
8
|
@locale = @template_options[:locale] ||= I18n.new
|
9
9
|
@warnings = []
|
10
|
+
self.depth = 0
|
10
11
|
self.partial = false
|
11
12
|
end
|
12
13
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Liquid
|
2
2
|
class BlockBody
|
3
|
-
def render_node_with_profiling(node, context)
|
3
|
+
def render_node_with_profiling(node, output, context, skip_output = false)
|
4
4
|
Profiler.profile_node_render(node) do
|
5
|
-
render_node_without_profiling(node, context)
|
5
|
+
render_node_without_profiling(node, output, context, skip_output)
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
alias_method :render_node_without_profiling, :
|
10
|
-
alias_method :
|
9
|
+
alias_method :render_node_without_profiling, :render_node_to_output
|
10
|
+
alias_method :render_node_to_output, :render_node_with_profiling
|
11
11
|
end
|
12
12
|
|
13
13
|
class Include < Tag
|
@@ -33,7 +33,7 @@ module Liquid
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def escape(input)
|
36
|
-
CGI.escapeHTML(input).untaint unless input.nil?
|
36
|
+
CGI.escapeHTML(input.to_s).untaint unless input.nil?
|
37
37
|
end
|
38
38
|
alias_method :h, :escape
|
39
39
|
|
@@ -42,11 +42,11 @@ module Liquid
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def url_encode(input)
|
45
|
-
CGI.escape(input) unless input.nil?
|
45
|
+
CGI.escape(input.to_s) unless input.nil?
|
46
46
|
end
|
47
47
|
|
48
48
|
def url_decode(input)
|
49
|
-
CGI.unescape(input) unless input.nil?
|
49
|
+
CGI.unescape(input.to_s) unless input.nil?
|
50
50
|
end
|
51
51
|
|
52
52
|
def slice(input, offset, length = nil)
|
@@ -121,17 +121,23 @@ module Liquid
|
|
121
121
|
def sort(input, property = nil)
|
122
122
|
ary = InputIterator.new(input)
|
123
123
|
if property.nil?
|
124
|
-
ary.sort
|
124
|
+
ary.sort do |a, b|
|
125
|
+
if !a.nil? && !b.nil?
|
126
|
+
a <=> b
|
127
|
+
else
|
128
|
+
a.nil? ? 1 : -1
|
129
|
+
end
|
130
|
+
end
|
125
131
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
126
132
|
[]
|
127
|
-
elsif ary.
|
133
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
128
134
|
ary.sort do |a, b|
|
129
135
|
a = a[property]
|
130
136
|
b = b[property]
|
131
|
-
if a && b
|
137
|
+
if !a.nil? && !b.nil?
|
132
138
|
a <=> b
|
133
139
|
else
|
134
|
-
a ?
|
140
|
+
a.nil? ? 1 : -1
|
135
141
|
end
|
136
142
|
end
|
137
143
|
end
|
@@ -143,11 +149,25 @@ module Liquid
|
|
143
149
|
ary = InputIterator.new(input)
|
144
150
|
|
145
151
|
if property.nil?
|
146
|
-
ary.sort
|
152
|
+
ary.sort do |a, b|
|
153
|
+
if !a.nil? && !b.nil?
|
154
|
+
a.to_s.casecmp(b.to_s)
|
155
|
+
else
|
156
|
+
a.nil? ? 1 : -1
|
157
|
+
end
|
158
|
+
end
|
147
159
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
148
160
|
[]
|
149
|
-
elsif ary.
|
150
|
-
ary.sort
|
161
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
162
|
+
ary.sort do |a, b|
|
163
|
+
a = a[property]
|
164
|
+
b = b[property]
|
165
|
+
if !a.nil? && !b.nil?
|
166
|
+
a.to_s.casecmp(b.to_s)
|
167
|
+
else
|
168
|
+
a.nil? ? 1 : -1
|
169
|
+
end
|
170
|
+
end
|
151
171
|
end
|
152
172
|
end
|
153
173
|
|
@@ -353,6 +373,22 @@ module Liquid
|
|
353
373
|
raise Liquid::FloatDomainError, e.message
|
354
374
|
end
|
355
375
|
|
376
|
+
def at_least(input, n)
|
377
|
+
min_value = Utils.to_number(n)
|
378
|
+
|
379
|
+
result = Utils.to_number(input)
|
380
|
+
result = min_value if min_value > result
|
381
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
382
|
+
end
|
383
|
+
|
384
|
+
def at_most(input, n)
|
385
|
+
max_value = Utils.to_number(n)
|
386
|
+
|
387
|
+
result = Utils.to_number(input)
|
388
|
+
result = max_value if max_value < result
|
389
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
390
|
+
end
|
391
|
+
|
356
392
|
def default(input, default_value = ''.freeze)
|
357
393
|
if !input || input.respond_to?(:empty?) && input.empty?
|
358
394
|
default_value
|
@@ -384,7 +420,7 @@ module Liquid
|
|
384
420
|
end
|
385
421
|
|
386
422
|
def join(glue)
|
387
|
-
to_a.join(glue)
|
423
|
+
to_a.join(glue.to_s)
|
388
424
|
end
|
389
425
|
|
390
426
|
def concat(args)
|
data/lib/liquid/strainer.rb
CHANGED
@@ -27,7 +27,7 @@ module Liquid
|
|
27
27
|
|
28
28
|
def self.add_filter(filter)
|
29
29
|
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
30
|
-
unless self.
|
30
|
+
unless self.include?(filter)
|
31
31
|
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
32
32
|
if invokable_non_public_methods.any?
|
33
33
|
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
data/lib/liquid/tags/cycle.rb
CHANGED
@@ -30,11 +30,11 @@ module Liquid
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def render(context)
|
33
|
-
context.registers[:cycle] ||=
|
33
|
+
context.registers[:cycle] ||= {}
|
34
34
|
|
35
35
|
context.stack do
|
36
36
|
key = context.evaluate(@name)
|
37
|
-
iteration = context.registers[:cycle][key]
|
37
|
+
iteration = context.registers[:cycle][key].to_i
|
38
38
|
result = context.evaluate(@variables[iteration])
|
39
39
|
iteration += 1
|
40
40
|
iteration = 0 if iteration >= @variables.size
|
data/lib/liquid/tags/for.rb
CHANGED
@@ -23,7 +23,7 @@ module Liquid
|
|
23
23
|
# {{ item.name }}
|
24
24
|
# {% end %}
|
25
25
|
#
|
26
|
-
# To reverse the for loop simply use {% for item in collection reversed %}
|
26
|
+
# To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
|
27
27
|
#
|
28
28
|
# == Available variables:
|
29
29
|
#
|
@@ -46,6 +46,9 @@ module Liquid
|
|
46
46
|
class For < Block
|
47
47
|
Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
|
48
48
|
|
49
|
+
attr_reader :collection_name
|
50
|
+
attr_reader :variable_name
|
51
|
+
|
49
52
|
def initialize(tag_name, markup, options)
|
50
53
|
super
|
51
54
|
@from = @limit = nil
|
@@ -117,7 +120,7 @@ module Liquid
|
|
117
120
|
private
|
118
121
|
|
119
122
|
def collection_segment(context)
|
120
|
-
offsets = context.registers[:for] ||=
|
123
|
+
offsets = context.registers[:for] ||= {}
|
121
124
|
|
122
125
|
from = if @from == :continue
|
123
126
|
offsets[@name].to_i
|
@@ -153,7 +156,7 @@ module Liquid
|
|
153
156
|
begin
|
154
157
|
context['forloop'.freeze] = loop_vars
|
155
158
|
|
156
|
-
segment.
|
159
|
+
segment.each do |item|
|
157
160
|
context[@variable_name] = item
|
158
161
|
result << @for_block.render(context)
|
159
162
|
loop_vars.send(:increment!)
|
data/lib/liquid/tags/if.rb
CHANGED
@@ -83,17 +83,20 @@ module Liquid
|
|
83
83
|
|
84
84
|
def strict_parse(markup)
|
85
85
|
p = Parser.new(markup)
|
86
|
-
condition =
|
86
|
+
condition = parse_binary_comparisons(p)
|
87
87
|
p.consume(:end_of_string)
|
88
88
|
condition
|
89
89
|
end
|
90
90
|
|
91
|
-
def
|
91
|
+
def parse_binary_comparisons(p)
|
92
92
|
condition = parse_comparison(p)
|
93
|
-
|
94
|
-
|
93
|
+
first_condition = condition
|
94
|
+
while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
|
95
|
+
child_condition = parse_comparison(p)
|
96
|
+
condition.send(op, child_condition)
|
97
|
+
condition = child_condition
|
95
98
|
end
|
96
|
-
|
99
|
+
first_condition
|
97
100
|
end
|
98
101
|
|
99
102
|
def parse_comparison(p)
|
data/lib/liquid/tags/include.rb
CHANGED
@@ -50,7 +50,7 @@ module Liquid
|
|
50
50
|
variable = if @variable_name_expr
|
51
51
|
context.evaluate(@variable_name_expr)
|
52
52
|
else
|
53
|
-
context.find_variable(template_name)
|
53
|
+
context.find_variable(template_name, raise_on_not_found: false)
|
54
54
|
end
|
55
55
|
|
56
56
|
old_template_name = context.template_name
|
@@ -33,7 +33,7 @@ module Liquid
|
|
33
33
|
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
|
34
34
|
context['tablerowloop'.freeze] = tablerowloop
|
35
35
|
|
36
|
-
collection.
|
36
|
+
collection.each do |item|
|
37
37
|
context[@variable_name] = item
|
38
38
|
|
39
39
|
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
|
data/lib/liquid/utils.rb
CHANGED
@@ -46,11 +46,11 @@ module Liquid
|
|
46
46
|
def self.to_number(obj)
|
47
47
|
case obj
|
48
48
|
when Float
|
49
|
-
BigDecimal
|
49
|
+
BigDecimal(obj.to_s)
|
50
50
|
when Numeric
|
51
51
|
obj
|
52
52
|
when String
|
53
|
-
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal
|
53
|
+
(obj.strip =~ /\A-?\d+\.\d+\z/) ? BigDecimal(obj) : obj.to_i
|
54
54
|
else
|
55
55
|
if obj.respond_to?(:to_number)
|
56
56
|
obj.to_number
|
data/lib/liquid/variable.rb
CHANGED
@@ -10,10 +10,16 @@ module Liquid
|
|
10
10
|
# {{ user | link }}
|
11
11
|
#
|
12
12
|
class Variable
|
13
|
+
FilterMarkupRegex = /#{FilterSeparator}\s*(.*)/om
|
13
14
|
FilterParser = /(?:\s+|#{QuotedFragment}|#{ArgumentSeparator})+/o
|
15
|
+
FilterArgsRegex = /(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*((?:\w+\s*\:\s*)?#{QuotedFragment})/o
|
16
|
+
JustTagAttributes = /\A#{TagAttributes}\z/o
|
17
|
+
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
|
18
|
+
|
14
19
|
attr_accessor :filters, :name, :line_number
|
15
20
|
attr_reader :parse_context
|
16
21
|
alias_method :options, :parse_context
|
22
|
+
|
17
23
|
include ParserSwitching
|
18
24
|
|
19
25
|
def initialize(markup, parse_context)
|
@@ -35,17 +41,17 @@ module Liquid
|
|
35
41
|
|
36
42
|
def lax_parse(markup)
|
37
43
|
@filters = []
|
38
|
-
return unless markup =~
|
44
|
+
return unless markup =~ MarkupWithQuotedFragment
|
39
45
|
|
40
46
|
name_markup = $1
|
41
47
|
filter_markup = $2
|
42
48
|
@name = Expression.parse(name_markup)
|
43
|
-
if filter_markup =~
|
49
|
+
if filter_markup =~ FilterMarkupRegex
|
44
50
|
filters = $1.scan(FilterParser)
|
45
51
|
filters.each do |f|
|
46
52
|
next unless f =~ /\w+/
|
47
53
|
filtername = Regexp.last_match(0)
|
48
|
-
filterargs = f.scan(
|
54
|
+
filterargs = f.scan(FilterArgsRegex).flatten
|
49
55
|
@filters << parse_filter_expressions(filtername, filterargs)
|
50
56
|
end
|
51
57
|
end
|
@@ -91,7 +97,7 @@ module Liquid
|
|
91
97
|
filter_args = []
|
92
98
|
keyword_args = {}
|
93
99
|
unparsed_args.each do |a|
|
94
|
-
if matches = a.match(
|
100
|
+
if matches = a.match(JustTagAttributes)
|
95
101
|
keyword_args[matches[1]] = Expression.parse(matches[2])
|
96
102
|
else
|
97
103
|
filter_args << Expression.parse(a)
|
data/lib/liquid/version.rb
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BlockTest < Minitest::Test
|
4
|
+
include Liquid
|
5
|
+
|
6
|
+
def test_unexpected_end_tag
|
7
|
+
exc = assert_raises(SyntaxError) do
|
8
|
+
Template.parse("{% if true %}{% endunless %}")
|
9
|
+
end
|
10
|
+
assert_equal exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif"
|
11
|
+
end
|
12
|
+
end
|
@@ -115,4 +115,8 @@ class ParsingQuirksTest < Minitest::Test
|
|
115
115
|
assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
|
116
116
|
end
|
117
117
|
end
|
118
|
+
|
119
|
+
def test_contains_in_id
|
120
|
+
assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
|
121
|
+
end
|
118
122
|
end # ParsingQuirksTest
|
@@ -63,4 +63,18 @@ class SecurityTest < Minitest::Test
|
|
63
63
|
|
64
64
|
assert_equal [], (Symbol.all_symbols - current_symbols)
|
65
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
|
66
80
|
end # SecurityTest
|
@@ -128,8 +128,16 @@ class StandardFiltersTest < Minitest::Test
|
|
128
128
|
|
129
129
|
def test_escape
|
130
130
|
assert_equal '<strong>', @filters.escape('<strong>')
|
131
|
-
assert_equal
|
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
|
132
137
|
assert_equal '<strong>', @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)
|
133
141
|
end
|
134
142
|
|
135
143
|
def test_escape_once
|
@@ -138,14 +146,18 @@ class StandardFiltersTest < Minitest::Test
|
|
138
146
|
|
139
147
|
def test_url_encode
|
140
148
|
assert_equal 'foo%2B1%40example.com', @filters.url_encode('foo+1@example.com')
|
141
|
-
assert_equal
|
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)
|
142
152
|
end
|
143
153
|
|
144
154
|
def test_url_decode
|
145
155
|
assert_equal 'foo bar', @filters.url_decode('foo+bar')
|
146
156
|
assert_equal 'foo bar', @filters.url_decode('foo%20bar')
|
147
157
|
assert_equal 'foo+1@example.com', @filters.url_decode('foo%2B1%40example.com')
|
148
|
-
assert_equal
|
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)
|
149
161
|
end
|
150
162
|
|
151
163
|
def test_truncatewords
|
@@ -170,6 +182,7 @@ class StandardFiltersTest < Minitest::Test
|
|
170
182
|
def test_join
|
171
183
|
assert_equal '1 2 3 4', @filters.join([1, 2, 3, 4])
|
172
184
|
assert_equal '1 - 2 - 3 - 4', @filters.join([1, 2, 3, 4], ' - ')
|
185
|
+
assert_equal '1121314', @filters.join([1, 2, 3, 4], 1)
|
173
186
|
end
|
174
187
|
|
175
188
|
def test_sort
|
@@ -177,6 +190,11 @@ class StandardFiltersTest < Minitest::Test
|
|
177
190
|
assert_equal [{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], @filters.sort([{ "a" => 4 }, { "a" => 3 }, { "a" => 1 }, { "a" => 2 }], "a")
|
178
191
|
end
|
179
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
|
+
|
180
198
|
def test_sort_when_property_is_sometimes_missing_puts_nils_last
|
181
199
|
input = [
|
182
200
|
{ "price" => 4, "handle" => "alpha" },
|
@@ -195,6 +213,57 @@ class StandardFiltersTest < Minitest::Test
|
|
195
213
|
assert_equal expectation, @filters.sort(input, "price")
|
196
214
|
end
|
197
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
|
+
|
198
267
|
def test_sort_empty_array
|
199
268
|
assert_equal [], @filters.sort([], "a")
|
200
269
|
end
|
@@ -329,7 +398,7 @@ class StandardFiltersTest < Minitest::Test
|
|
329
398
|
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
|
330
399
|
assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
|
331
400
|
|
332
|
-
|
401
|
+
assert_nil @filters.date(nil, "%B")
|
333
402
|
|
334
403
|
assert_equal '', @filters.date('', "%B")
|
335
404
|
|
@@ -342,8 +411,8 @@ class StandardFiltersTest < Minitest::Test
|
|
342
411
|
def test_first_last
|
343
412
|
assert_equal 1, @filters.first([1, 2, 3])
|
344
413
|
assert_equal 3, @filters.last([1, 2, 3])
|
345
|
-
|
346
|
-
|
414
|
+
assert_nil @filters.first([])
|
415
|
+
assert_nil @filters.last([])
|
347
416
|
end
|
348
417
|
|
349
418
|
def test_replace
|
@@ -483,6 +552,28 @@ class StandardFiltersTest < Minitest::Test
|
|
483
552
|
assert_template_result "5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4)
|
484
553
|
end
|
485
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
|
+
|
486
577
|
def test_append
|
487
578
|
assigns = { 'a' => 'bc', 'b' => 'd' }
|
488
579
|
assert_template_result('bcd', "{{ a | append: 'd'}}", assigns)
|