mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-15 08:50:45 -07:00
Add find, find_index, has, and reject filters to arrays (#1869)
* Add reject filter #1573 * Add deep search for filter taking in properties #1749 * Update branch with main * Add `find`, `find_index`, `has`, and `reject` filters to arrays * Refactor: avoid usage of public_send --------- Co-authored-by: Anders Søgaard <[email protected]> Co-authored-by: Anders Søgaard <[email protected]>
This commit is contained in:
co-authored by
Anders Søgaard
Anders Søgaard
parent
cd9971579f
commit
6909570f8e
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user