mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-15 00:40:40 -07:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2186981c06 | ||
|
|
41a77c68c6 | ||
|
|
3d1e2d434e | ||
|
|
1058a7768f | ||
|
|
4b01977067 |
+1
-3
@@ -26,9 +26,7 @@ module Liquid
|
||||
ArgumentSeparator = ','
|
||||
FilterArgumentSeparator = ':'
|
||||
VariableAttributeSeparator = '.'
|
||||
WhitespaceControl = '(?:[-~])'
|
||||
WhitespaceTrim = '-'
|
||||
WhitespaceTrimIndent = '~'
|
||||
WhitespaceControl = '-'
|
||||
TagStart = /\{\%/
|
||||
TagEnd = /\%\}/
|
||||
TagName = /#|\w+/
|
||||
|
||||
@@ -164,7 +164,6 @@ module Liquid
|
||||
token.lstrip!
|
||||
end
|
||||
parse_context.trim_whitespace = false
|
||||
parse_context.strip_trailing = false
|
||||
@nodelist << token
|
||||
@blank &&= token.match?(WhitespaceOrNothing)
|
||||
end
|
||||
@@ -175,8 +174,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def whitespace_handler(token, parse_context)
|
||||
parse_context.indentation = nil
|
||||
if token[2] == WhitespaceTrim
|
||||
if token[2] == WhitespaceControl
|
||||
previous_token = @nodelist.last
|
||||
if previous_token.is_a?(String)
|
||||
first_byte = previous_token.getbyte(0)
|
||||
@@ -185,15 +183,8 @@ module Liquid
|
||||
previous_token << first_byte
|
||||
end
|
||||
end
|
||||
elsif token[2] == WhitespaceTrimIndent
|
||||
previous_token = @nodelist.last
|
||||
if previous_token.is_a?(String)
|
||||
start_of_line = previous_token.rindex("\n") || 0
|
||||
parse_context.indentation = previous_token[start_of_line + 1..]
|
||||
end
|
||||
end
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceTrim)
|
||||
parse_context.strip_trailing = (token[-3] == WhitespaceTrimIndent)
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||
end
|
||||
|
||||
def blank?
|
||||
@@ -257,7 +248,7 @@ module Liquid
|
||||
def create_variable(token, parse_context)
|
||||
if token.end_with?("}}")
|
||||
i = 2
|
||||
i = 3 if token[i] == "-" or token[i] == "~"
|
||||
i = 3 if token[i] == "-"
|
||||
parse_end = token.length - 3
|
||||
parse_end -= 1 if token[parse_end] == "-"
|
||||
markup_end = parse_end - i + 1
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module Liquid
|
||||
class ParseContext
|
||||
attr_accessor :locale, :line_number, :trim_whitespace, :depth, :indentation, :strip_trailing
|
||||
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
||||
attr_reader :partial, :warnings, :error_mode, :environment
|
||||
|
||||
def initialize(options = Const::EMPTY_HASH)
|
||||
|
||||
+102
-28
@@ -378,7 +378,7 @@ module Liquid
|
||||
end
|
||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
||||
begin
|
||||
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
|
||||
ary.sort { |a, b| nil_safe_compare(fetch_property(a, property), fetch_property(b, property)) }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
@@ -407,7 +407,7 @@ module Liquid
|
||||
end
|
||||
elsif ary.all? { |el| el.respond_to?(:[]) }
|
||||
begin
|
||||
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
|
||||
ary.sort { |a, b| nil_safe_casecmp(fetch_property(a, property), fetch_property(b, property)) }
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
end
|
||||
@@ -424,29 +424,59 @@ module Liquid
|
||||
# @liquid_syntax array | where: string, string
|
||||
# @liquid_return [array[untyped]]
|
||||
def where(input, property, target_value = nil)
|
||||
ary = InputIterator.new(input, context)
|
||||
filter_array(input, property, target_value) { |ary, &block| ary.select(&block) }
|
||||
end
|
||||
|
||||
if ary.empty?
|
||||
[]
|
||||
elsif target_value.nil?
|
||||
ary.select do |item|
|
||||
item[property]
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
else
|
||||
ary.select do |item|
|
||||
item[property] == target_value
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Filters an array to exclude items with a specific property value.
|
||||
# @liquid_description
|
||||
# This requires you to provide both the property name and the associated value.
|
||||
# @liquid_syntax array | reject: string, string
|
||||
# @liquid_return [array[untyped]]
|
||||
def reject(input, property, target_value = nil)
|
||||
filter_array(input, property, target_value) { |ary, &block| ary.reject(&block) }
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Tests if any item in an array has a specific property value.
|
||||
# @liquid_description
|
||||
# This requires you to provide both the property name and the associated value.
|
||||
# @liquid_syntax array | some: string, string
|
||||
# @liquid_return [boolean]
|
||||
def has(input, property, target_value = nil)
|
||||
filter_array(input, property, target_value) { |ary, &block| ary.any?(&block) }
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Returns the first item in an array with a specific property value.
|
||||
# @liquid_description
|
||||
# This requires you to provide both the property name and the associated value.
|
||||
# @liquid_syntax array | find: string, string
|
||||
# @liquid_return [untyped]
|
||||
def find(input, property, target_value = nil)
|
||||
filter_array(input, property, target_value) { |ary, &block| ary.find(&block) }
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
# @liquid_type filter
|
||||
# @liquid_category array
|
||||
# @liquid_summary
|
||||
# Returns the index of the first item in an array with a specific property value.
|
||||
# @liquid_description
|
||||
# This requires you to provide both the property name and the associated value.
|
||||
# @liquid_syntax array | find_index: string, string
|
||||
# @liquid_return [number]
|
||||
def find_index(input, property, target_value = nil)
|
||||
filter_array(input, property, target_value) { |ary, &block| ary.find_index(&block) }
|
||||
end
|
||||
|
||||
# @liquid_public_docs
|
||||
@@ -465,7 +495,7 @@ module Liquid
|
||||
[]
|
||||
else
|
||||
ary.uniq do |item|
|
||||
item[property]
|
||||
fetch_property(item, property)
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
@@ -501,7 +531,7 @@ module Liquid
|
||||
if property == "to_liquid"
|
||||
e
|
||||
elsif e.respond_to?(:[])
|
||||
r = e[property]
|
||||
r = fetch_property(e, property)
|
||||
r.is_a?(Proc) ? r.call : r
|
||||
end
|
||||
end
|
||||
@@ -525,7 +555,7 @@ module Liquid
|
||||
[]
|
||||
else
|
||||
ary.reject do |item|
|
||||
item[property].nil?
|
||||
fetch_property(item, property).nil?
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
@@ -899,7 +929,7 @@ module Liquid
|
||||
if property.nil?
|
||||
item
|
||||
elsif item.respond_to?(:[])
|
||||
item[property]
|
||||
fetch_property(item, property)
|
||||
else
|
||||
0
|
||||
end
|
||||
@@ -918,6 +948,50 @@ module Liquid
|
||||
|
||||
attr_reader :context
|
||||
|
||||
def filter_array(input, property, target_value, &block)
|
||||
ary = InputIterator.new(input, context)
|
||||
|
||||
return [] if ary.empty?
|
||||
|
||||
block.call(ary) do |item|
|
||||
if target_value.nil?
|
||||
fetch_property(item, property)
|
||||
else
|
||||
fetch_property(item, property) == target_value
|
||||
end
|
||||
rescue TypeError
|
||||
raise_property_error(property)
|
||||
rescue NoMethodError
|
||||
return nil unless item.respond_to?(:[])
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_property(drop, property_or_keys)
|
||||
##
|
||||
# This keeps backward compatibility by supporting properties containing
|
||||
# dots. This is valid in Liquid syntax and used in some runtimes, such as
|
||||
# Shopify with metafields.
|
||||
#
|
||||
# Using this approach, properties like 'price.value' can be accessed in
|
||||
# both of the following examples:
|
||||
#
|
||||
# ```
|
||||
# [
|
||||
# { 'name' => 'Item 1', 'price.price' => 40000 },
|
||||
# { 'name' => 'Item 2', 'price' => { 'value' => 39900 } }
|
||||
# ]
|
||||
# ```
|
||||
value = drop[property_or_keys]
|
||||
|
||||
return value if !value.nil? || !property_or_keys.is_a?(String)
|
||||
|
||||
keys = property_or_keys.split('.')
|
||||
keys.reduce(drop) do |drop, key|
|
||||
drop.respond_to?(:[]) ? drop[key] : drop
|
||||
end
|
||||
end
|
||||
|
||||
def raise_property_error(property)
|
||||
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
||||
end
|
||||
|
||||
@@ -63,11 +63,10 @@ module Liquid
|
||||
comment_tag_depth -= 1
|
||||
end
|
||||
|
||||
next unless comment_tag_depth.zero?
|
||||
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceTrim) unless tokenizer.for_liquid_tag
|
||||
parse_context.strip_trailing = (token[-3] == WhitespaceTrimIndent) unless tokenizer.for_liquid_tag
|
||||
return false
|
||||
if comment_tag_depth.zero?
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
raise_tag_never_closed(block_name)
|
||||
|
||||
@@ -37,7 +37,6 @@ module Liquid
|
||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||
@template_name_expr = parse_expression(template_name)
|
||||
@attributes = {}
|
||||
@indentation = options.indentation
|
||||
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
@attributes[key] = parse_expression(value)
|
||||
@@ -81,25 +80,15 @@ module Liquid
|
||||
context[key] = context.evaluate(value)
|
||||
end
|
||||
|
||||
partial_output = +''
|
||||
if variable.is_a?(Array)
|
||||
variable.each do |var|
|
||||
context[context_variable_name] = var
|
||||
partial.render_to_output_buffer(context, partial_output)
|
||||
partial.render_to_output_buffer(context, output)
|
||||
end
|
||||
else
|
||||
context[context_variable_name] = variable
|
||||
partial.render_to_output_buffer(context, partial_output)
|
||||
partial.render_to_output_buffer(context, output)
|
||||
end
|
||||
|
||||
if @indentation
|
||||
partial_output = partial_output.lines.map.with_index do |line, i|
|
||||
next line if i == 0
|
||||
@indentation + line
|
||||
end.join
|
||||
end
|
||||
|
||||
output << partial_output
|
||||
end
|
||||
ensure
|
||||
context.template_name = old_template_name
|
||||
|
||||
@@ -25,8 +25,7 @@ module Liquid
|
||||
@body = +''
|
||||
while (token = tokens.shift)
|
||||
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceTrim)
|
||||
parse_context.strip_trailing = (token[-3] == WhitespaceTrimIndent)
|
||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
||||
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
||||
return
|
||||
end
|
||||
|
||||
@@ -46,8 +46,6 @@ module Liquid
|
||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||
@template_name_expr = parse_expression(template_name)
|
||||
@is_for_loop = (with_or_for == FOR)
|
||||
@indentation = options.indentation
|
||||
@strip_trailing = options.strip_trailing
|
||||
|
||||
@attributes = {}
|
||||
markup.scan(TagAttributes) do |key, value|
|
||||
@@ -86,18 +84,7 @@ module Liquid
|
||||
inner_context[key] = context.evaluate(value)
|
||||
end
|
||||
inner_context[context_variable_name] = var unless var.nil?
|
||||
partial_output = +''
|
||||
partial.render_to_output_buffer(inner_context, partial_output)
|
||||
if @indentation
|
||||
partial_output = partial_output.lines.map.with_index do |line, i|
|
||||
next line if i == 0
|
||||
@indentation + line
|
||||
end.join
|
||||
end
|
||||
if @strip_trailing
|
||||
partial_output.rstrip!
|
||||
end
|
||||
output << partial_output
|
||||
partial.render_to_output_buffer(inner_context, output)
|
||||
forloop&.send(:increment!)
|
||||
}
|
||||
|
||||
|
||||
+2
-18
@@ -29,8 +29,6 @@ module Liquid
|
||||
@name = nil
|
||||
@parse_context = parse_context
|
||||
@line_number = parse_context.line_number
|
||||
@indentation = parse_context.indentation
|
||||
@strip_trailing = parse_context.strip_trailing
|
||||
|
||||
strict_parse_with_error_mode_fallback(markup)
|
||||
end
|
||||
@@ -98,27 +96,13 @@ module Liquid
|
||||
def render_to_output_buffer(context, output)
|
||||
obj = render(context)
|
||||
|
||||
obj_output = ''
|
||||
if obj.is_a?(Array)
|
||||
obj_output = obj.join
|
||||
output << obj.join
|
||||
elsif obj.nil?
|
||||
else
|
||||
obj_output = obj.to_s
|
||||
output << obj.to_s
|
||||
end
|
||||
|
||||
if @indentation
|
||||
obj_output = obj_output.lines.map.with_index do |line, i|
|
||||
next line if i == 0
|
||||
@indentation + line
|
||||
end.join
|
||||
end
|
||||
|
||||
if @strip_trailing
|
||||
obj_output.rstrip!
|
||||
end
|
||||
|
||||
output << obj_output
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
|
||||
@@ -54,6 +54,30 @@ class TestEnumerable < Liquid::Drop
|
||||
end
|
||||
end
|
||||
|
||||
class TestDeepEnumerable < Liquid::Drop
|
||||
include Enumerable
|
||||
|
||||
class Product < Liquid::Drop
|
||||
attr_reader :title, :price, :premium
|
||||
|
||||
def initialize(title:, price:, premium: nil)
|
||||
@title = { "content" => title, "language" => "en" }
|
||||
@price = { "value" => price, "unit" => "USD" }
|
||||
@premium = { "category" => premium } if premium
|
||||
end
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
[
|
||||
Product.new(title: "Pro goggles", price: 1299),
|
||||
Product.new(title: "Thermal gloves", price: 1299),
|
||||
Product.new(title: "Alpine jacket", price: 3999, premium: 'Basic'),
|
||||
Product.new(title: "Mountain boots", price: 3899, premium: 'Pro'),
|
||||
Product.new(title: "Safety helmet", price: 1999)
|
||||
].each(&block)
|
||||
end
|
||||
end
|
||||
|
||||
class NumberLikeThing < Liquid::Drop
|
||||
def initialize(amount)
|
||||
@amount = amount
|
||||
@@ -392,6 +416,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_sort_natural_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | sort_natural: 'title.content' | map: 'title.content' | join: ', ' -}}
|
||||
LIQUID
|
||||
expected_output = "Alpine jacket, Mountain boots, Pro goggles, Safety helmet, Thermal gloves"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_legacy_sort_hash
|
||||
assert_equal([{ a: 1, b: 2 }], @filters.sort(a: 1, b: 2))
|
||||
end
|
||||
@@ -428,6 +461,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_uniq_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | uniq: 'price.value' | map: "title.content" | join: ', ' -}}
|
||||
LIQUID
|
||||
expected_output = "Pro goggles, Alpine jacket, Mountain boots, Safety helmet"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_compact_empty_array
|
||||
assert_equal([], @filters.compact([], "a"))
|
||||
end
|
||||
@@ -444,6 +486,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_compact_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | compact: 'premium.category' | map: 'title.content' | join: ', ' -}}
|
||||
LIQUID
|
||||
expected_output = "Alpine jacket, Mountain boots"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_reverse
|
||||
assert_equal([4, 3, 2, 1], @filters.reverse([1, 2, 3, 4]))
|
||||
end
|
||||
@@ -553,6 +604,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', { "foo" => TestEnumerable.new })
|
||||
end
|
||||
|
||||
def test_sort_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | sort: 'price.value' | map: 'title.content' | join: ', ' -}}
|
||||
LIQUID
|
||||
expected_output = "Pro goggles, Thermal gloves, Safety helmet, Mountain boots, Alpine jacket"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_first_and_last_call_to_liquid
|
||||
assert_template_result('foobar', '{{ foo | first }}', { 'foo' => [ThingWithToLiquid.new] })
|
||||
assert_template_result('foobar', '{{ foo | last }}', { 'foo' => [ThingWithToLiquid.new] })
|
||||
@@ -827,21 +887,219 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result('abc', "{{ 'abc' | date: '%D' }}")
|
||||
end
|
||||
|
||||
def test_where
|
||||
input = [
|
||||
def test_reject
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
expectation = [
|
||||
template = "{{ array | reject: 'ok' | map: 'handle' | join: ' ' }}"
|
||||
expected_output = "beta gamma"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_reject_with_value
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
assert_equal(expectation, @filters.where(input, "ok", true))
|
||||
assert_equal(expectation, @filters.where(input, "ok"))
|
||||
template = "{{ array | reject: 'ok', true | map: 'handle' | join: ' ' }}"
|
||||
expected_output = "beta gamma"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_reject_with_false_value
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
template = "{{ array | reject: 'ok', false | map: 'handle' | join: ' ' }}"
|
||||
expected_output = "alpha delta"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_reject_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | reject: 'title.content', 'Pro goggles' | map: 'price.value' | join: ', ' -}}
|
||||
LIQUID
|
||||
expected_output = "1299, 3999, 3899, 1999"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_has
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => false },
|
||||
]
|
||||
|
||||
expected_output = "true"
|
||||
|
||||
assert_template_result(expected_output, "{{ array | has: 'ok' }}", { "array" => array })
|
||||
assert_template_result(expected_output, "{{ array | has: 'ok', true }}", { "array" => array })
|
||||
end
|
||||
|
||||
def test_has_when_does_not_have_it
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => false },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => false },
|
||||
]
|
||||
|
||||
expected_output = "false"
|
||||
|
||||
assert_template_result(expected_output, "{{ array | has: 'ok' }}", { "array" => array })
|
||||
assert_template_result(expected_output, "{{ array | has: 'ok', true }}", { "array" => array })
|
||||
end
|
||||
|
||||
def test_has_with_false_value
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
template = "{{ array | has: 'ok', false }}"
|
||||
expected_output = "true"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_has_with_false_value_when_does_not_have_it
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => true },
|
||||
{ "handle" => "gamma", "ok" => true },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
template = "{{ array | has: 'ok', false }}"
|
||||
expected_output = "false"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_has_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | has: 'title.content', 'Pro goggles' -}},
|
||||
{{- products | has: 'title.content', 'foo' -}}
|
||||
LIQUID
|
||||
expected_output = "true,false"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_find_with_value
|
||||
products = [
|
||||
{ "title" => "Pro goggles", "price" => 1299 },
|
||||
{ "title" => "Thermal gloves", "price" => 1499 },
|
||||
{ "title" => "Alpine jacket", "price" => 3999 },
|
||||
{ "title" => "Mountain boots", "price" => 3899 },
|
||||
{ "title" => "Safety helmet", "price" => 1999 }
|
||||
]
|
||||
|
||||
template = <<~LIQUID
|
||||
{%- assign product = products | find: 'price', 3999 -%}
|
||||
{{- product.title -}}
|
||||
LIQUID
|
||||
expected_output = "Alpine jacket"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => products })
|
||||
end
|
||||
|
||||
def test_find_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{%- assign product = products | find: 'title.content', 'Pro goggles' -%}
|
||||
{{- product.title.content -}}
|
||||
LIQUID
|
||||
expected_output = "Pro goggles"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_find_index_with_value
|
||||
products = [
|
||||
{ "title" => "Pro goggles", "price" => 1299 },
|
||||
{ "title" => "Thermal gloves", "price" => 1499 },
|
||||
{ "title" => "Alpine jacket", "price" => 3999 },
|
||||
{ "title" => "Mountain boots", "price" => 3899 },
|
||||
{ "title" => "Safety helmet", "price" => 1999 }
|
||||
]
|
||||
|
||||
template = <<~LIQUID
|
||||
{%- assign index = products | find_index: 'price', 3999 -%}
|
||||
{{- index -}}
|
||||
LIQUID
|
||||
expected_output = "2"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => products })
|
||||
end
|
||||
|
||||
def test_find_index_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{%- assign index = products | find_index: 'title.content', 'Alpine jacket' -%}
|
||||
{{- index -}}
|
||||
LIQUID
|
||||
expected_output = "2"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_where
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
template = "{{ array | where: 'ok' | map: 'handle' | join: ' ' }}"
|
||||
expected_output = "alpha delta"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_where_with_value
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
template = "{{ array | where: 'ok', true | map: 'handle' | join: ' ' }}"
|
||||
expected_output = "alpha delta"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_where_with_false_value
|
||||
array = [
|
||||
{ "handle" => "alpha", "ok" => true },
|
||||
{ "handle" => "beta", "ok" => false },
|
||||
{ "handle" => "gamma", "ok" => false },
|
||||
{ "handle" => "delta", "ok" => true },
|
||||
]
|
||||
|
||||
template = "{{ array | where: 'ok', false | map: 'handle' | join: ' ' }}"
|
||||
expected_output = "beta gamma"
|
||||
|
||||
assert_template_result(expected_output, template, { "array" => array })
|
||||
end
|
||||
|
||||
def test_where_string_keys
|
||||
@@ -900,6 +1158,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_nil(@filters.where([nil], "ok"))
|
||||
end
|
||||
|
||||
def test_where_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | where: 'title.content', 'Pro goggles' | map: 'price.value' -}}
|
||||
LIQUID
|
||||
expected_output = "1299"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
def test_all_filters_never_raise_non_liquid_exception
|
||||
test_drop = TestDrop.new(value: "test")
|
||||
test_drop.context = Context.new
|
||||
@@ -1051,6 +1318,15 @@ class StandardFiltersTest < Minitest::Test
|
||||
assert_template_result("0", "{{ input | sum: 'subtotal' }}", { "input" => input })
|
||||
end
|
||||
|
||||
def test_sum_with_deep_enumerables
|
||||
template = <<~LIQUID
|
||||
{{- products | sum: 'price.value' -}}
|
||||
LIQUID
|
||||
expected_output = "12495"
|
||||
|
||||
assert_template_result(expected_output, template, { "products" => TestDeepEnumerable.new })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_timezone(tz)
|
||||
|
||||
@@ -555,139 +555,4 @@ class TrimModeTest < Minitest::Test
|
||||
def test_trim_blank
|
||||
assert_template_result('foobar', 'foo {{-}} bar')
|
||||
end
|
||||
|
||||
def test_trim_indent_variable
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{~ 'Hello\nWorld' ~}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_trim_indent_variable_trims_trailing_whitespace
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{{~ 'Hello\nWorld\n' ~}}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
|
||||
def test_trim_indent_tags
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%~ echo 'Hello\nWorld' ~%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text)
|
||||
end
|
||||
|
||||
def test_trim_indent_include
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%~ include "snippet" ~%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text, partials: {"snippet" => "Hello\nWorld"})
|
||||
end
|
||||
|
||||
def test_trim_indent_render
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%~ render "snippet" ~%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text, partials: {"snippet" => "Hello\nWorld"})
|
||||
end
|
||||
|
||||
def test_trim_indent_render_trim_trailing_whitespace
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%~ render "snippet" ~%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text, partials: {"snippet" => "Hello\nWorld\n"})
|
||||
end
|
||||
|
||||
def test_trim_indent_nested_render
|
||||
text = <<-END_TEMPLATE
|
||||
<div>
|
||||
<p>
|
||||
{%~ render "snippet" ~%}
|
||||
</p>
|
||||
</div>
|
||||
END_TEMPLATE
|
||||
expected = <<-END_EXPECTED
|
||||
<div>
|
||||
<p>
|
||||
Hello
|
||||
inside
|
||||
World
|
||||
</p>
|
||||
</div>
|
||||
END_EXPECTED
|
||||
assert_template_result(expected, text, partials: {"snippet" => "Hello\n {%~ render \"snippet2\" ~%}\nWorld", "snippet2" => "inside"})
|
||||
end
|
||||
end # TrimModeTest
|
||||
|
||||
Reference in New Issue
Block a user