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