mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-14 11:50:40 -07:00
feat: add find_index, has, and reject filters (#799)
* feat: add find_index, has, and reject filters * Minor tweaks * Change semantics of jekyllStyle, add more tests * Some docs improvements
This commit is contained in:
@@ -51,10 +51,14 @@ filters:
|
||||
escape_once: escape_once.html
|
||||
find: find.html
|
||||
find_exp: find_exp.html
|
||||
find_index: find_index.html
|
||||
find_index_exp: find_index_exp.html
|
||||
first: first.html
|
||||
floor: floor.html
|
||||
group_by: group_by.html
|
||||
group_by_exp: group_by_exp.html
|
||||
has: has.html
|
||||
has_exp: has_exp.html
|
||||
inspect: inspect.html
|
||||
join: join.html
|
||||
json: json.html
|
||||
@@ -72,6 +76,8 @@ filters:
|
||||
push: push.html
|
||||
prepend: prepend.html
|
||||
raw: raw.html
|
||||
reject: reject.html
|
||||
reject_exp: reject_exp.html
|
||||
remove: remove.html
|
||||
remove_first: remove_first.html
|
||||
remove_last: remove_last.html
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: find_index
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return the 0-based index of the first object in an array for which the queried attribute has the given value or return `nil` if no item in the array satisfies the given criteria. For the following `members` array:
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | find_index: "graduation_year", 2014 | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: find_index_exp
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return the 0-based index of the first object in an array for which the given expression evaluates to true or return `nil` if no item in the array satisfies the evaluated expression.
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | find_index_exp: "item", "item.graduation_year == 2014" | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
1
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: has
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return `true` if the array includes an item for which the queried attribute has the given value or return `false` if no item in the array satisfies the given criteria. For the following `members` array:
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | has: "graduation_year", 2014 | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
true
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: has_exp
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Return `true` if an item exists in an array for which the given expression evaluates to true or return `false` if no item in the array satisfies the evaluated expression.
|
||||
|
||||
```javascript
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{{ members | has_exp: "item", "item.graduation_year == 2014" | json }}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
true
|
||||
```
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: reject
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Creates an array excluding the objects with a given property value, or excluding [truthy][truthy] values by default when a property is not given.
|
||||
|
||||
In this example, assume you have a list of products and you want to filter out kitchen products. Using `reject`, you can create an array excluding only the products that have a `"type"` of `"kitchen"`.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
All products:
|
||||
{% for product in products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% assign non_kitchen_products = products | reject: "type", "kitchen" %}
|
||||
|
||||
Kitchen products:
|
||||
{% for product in non_kitchen_products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
All products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
|
||||
Kitchen products:
|
||||
- Vacuum
|
||||
- Television
|
||||
```
|
||||
|
||||
Say instead you have a list of products and you want to exclude taxable products. You can `reject` with a property name but no target value to reject all products with a [truthy][truthy] `"taxable"` value.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
All products:
|
||||
{% for product in products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% assign not_taxed_products = products | reject: "taxable" %}
|
||||
|
||||
Available products:
|
||||
{% for product in not_taxed_products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
All products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
|
||||
Available products:
|
||||
- Spatula
|
||||
- Television
|
||||
```
|
||||
|
||||
Additionally, `property` can be any valid Liquid variable expression as used in output syntax, except that the scope of this expression is within each item. For the following `products` array:
|
||||
|
||||
```javascript
|
||||
const products = [
|
||||
{ meta: { details: { class: 'A' } }, order: 1 },
|
||||
{ meta: { details: { class: 'B' } }, order: 2 },
|
||||
{ meta: { details: { class: 'B' } }, order: 3 }
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign selected = products | reject: 'meta.details["class"]', "B" %}
|
||||
{% for item in selected -%}
|
||||
- {{ item.order }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
- 1
|
||||
```
|
||||
|
||||
## Jekyll style
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for excluding tags.
|
||||
|
||||
```javascript
|
||||
const pages = [
|
||||
{ tags: ["cat", "food"], title: 'Cat Food' },
|
||||
{ tags: ["dog", "food"], title: 'Dog Food' },
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign selected = pages | reject: 'tags', "cat" %}
|
||||
{% for item in selected -%}
|
||||
- {{ item.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Dog Food
|
||||
```
|
||||
|
||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: reject_exp
|
||||
---
|
||||
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
Select all the objects in an array where the expression is false. In this example, assume you have a list of products and you want to hide your kitchen products. Using `reject_exp`, you can create an array that omits only the products that have a `"type"` of `"kitchen"`.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
All products:
|
||||
{% for product in products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% assign non_kitchen_products = products | reject_exp: "item", "item.type == 'kitchen'" %}
|
||||
|
||||
Kitchen products:
|
||||
{% for product in non_kitchen_products %}
|
||||
- {{ product.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
All products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
|
||||
Kitchen products:
|
||||
- Vacuum
|
||||
- Television
|
||||
```
|
||||
|
||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||
@@ -37,6 +37,7 @@ Kitchen products:
|
||||
```
|
||||
|
||||
Say instead you have a list of products and you only want to show those that are available to buy. You can `where` with a property name but no target value to include all products with a [truthy][truthy] `"available"` value.
|
||||
As a special case, the same will happen if the target value is given but evaluates to `undefined`.
|
||||
|
||||
Input
|
||||
```liquid
|
||||
@@ -70,7 +71,6 @@ The `where` filter can also be used to find a single object in an array when com
|
||||
Input
|
||||
```liquid
|
||||
{% assign new_shirt = products | where: "type", "shirt" | first %}
|
||||
|
||||
Featured product: {{ new_shirt.title }}
|
||||
```
|
||||
|
||||
@@ -105,9 +105,11 @@ Output
|
||||
|
||||
## Jekyll style
|
||||
|
||||
{% since %}v10.19.0{% endsince %}
|
||||
{% since %}v10.21.0{% endsince %}
|
||||
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for filtering tags.
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for filtering tags. Additionally, a target value of `undefined` is treated normally, entries matched are exactly those which are themselves `undefined`.
|
||||
|
||||
This option affects other array selection filters as well, such as `reject` and `find`.
|
||||
|
||||
```javascript
|
||||
const pages = [
|
||||
|
||||
+70
-18
@@ -117,7 +117,17 @@ export function slice<T> (this: FilterImpl, v: T[] | string, begin: number, leng
|
||||
return v.slice(begin, begin + length)
|
||||
}
|
||||
|
||||
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
function expectedMatcher (this: FilterImpl, expected: any): (v: any) => boolean {
|
||||
if (this.context.opts.jekyllWhere) {
|
||||
return (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
||||
} else if (expected === undefined) {
|
||||
return (v: any) => isTruthy(v, this.context)
|
||||
} else {
|
||||
return (v: any) => equals(v, expected)
|
||||
}
|
||||
}
|
||||
|
||||
function * filter<T extends object> (this: FilterImpl, include: boolean, arr: T[], property: string, expected: any): IterableIterator<unknown> {
|
||||
const values: unknown[] = []
|
||||
arr = toArray(arr)
|
||||
this.context.memoryLimit.use(arr.length)
|
||||
@@ -125,16 +135,11 @@ export function * where<T extends object> (this: FilterImpl, arr: T[], property:
|
||||
for (const item of arr) {
|
||||
values.push(yield evalToken(token, this.context.spawn(item)))
|
||||
}
|
||||
const matcher = this.context.opts.jekyllWhere
|
||||
? (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
||||
: (v: any) => equals(v, expected)
|
||||
return arr.filter((_, i) => {
|
||||
if (expected === undefined) return isTruthy(values[i], this.context)
|
||||
return matcher(values[i])
|
||||
})
|
||||
const matcher = expectedMatcher.call(this, expected)
|
||||
return arr.filter((_, i) => matcher(values[i]) === include)
|
||||
}
|
||||
|
||||
export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
function * filter_exp<T extends object> (this: FilterImpl, include: boolean, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const filtered: unknown[] = []
|
||||
const keyTemplate = new Value(stringify(exp), this.liquid)
|
||||
const array = toArray(arr)
|
||||
@@ -143,11 +148,27 @@ export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemN
|
||||
this.context.push({ [itemName]: item })
|
||||
const value = yield keyTemplate.value(this.context)
|
||||
this.context.pop()
|
||||
if (value) filtered.push(item)
|
||||
if (value === include) filtered.push(item)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
return yield * filter.call(this, true, arr, property, expected)
|
||||
}
|
||||
|
||||
export function * reject<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
return yield * filter.call(this, false, arr, property, expected)
|
||||
}
|
||||
|
||||
export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
return yield * filter_exp.call(this, true, arr, itemName, exp)
|
||||
}
|
||||
|
||||
export function * reject_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
return yield * filter_exp.call(this, false, arr, itemName, exp)
|
||||
}
|
||||
|
||||
export function * group_by<T extends object> (this: FilterImpl, arr: T[], property: string): IterableIterator<unknown> {
|
||||
const map = new Map()
|
||||
arr = toEnumerable(arr)
|
||||
@@ -176,26 +197,57 @@ export function * group_by_exp<T extends object> (this: FilterImpl, arr: T[], it
|
||||
return [...map.entries()].map(([name, items]) => ({ name, items }))
|
||||
}
|
||||
|
||||
export function * find<T extends object> (this: FilterImpl, arr: T[], property: string, expected: string): IterableIterator<unknown> {
|
||||
function * search<T extends object> (this: FilterImpl, arr: T[], property: string, expected: string): IterableIterator<unknown> {
|
||||
const token = new Tokenizer(stringify(property)).readScopeValue()
|
||||
const array = toArray(arr)
|
||||
for (const item of array) {
|
||||
const value = yield evalToken(token, this.context.spawn(item))
|
||||
if (equals(value, expected)) return item
|
||||
const matcher = expectedMatcher.call(this, expected)
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
const value = yield evalToken(token, this.context.spawn(array[index]))
|
||||
if (matcher(value)) return [index, array[index]]
|
||||
}
|
||||
}
|
||||
|
||||
export function * find_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
function * search_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const predicate = new Value(stringify(exp), this.liquid)
|
||||
const array = toArray(arr)
|
||||
for (const item of array) {
|
||||
this.context.push({ [itemName]: item })
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
this.context.push({ [itemName]: array[index] })
|
||||
const value = yield predicate.value(this.context)
|
||||
this.context.pop()
|
||||
if (value) return item
|
||||
if (value) return [index, array[index]]
|
||||
}
|
||||
}
|
||||
|
||||
export function * has<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
const result = yield * search.call(this, arr, property, expected)
|
||||
return !!result
|
||||
}
|
||||
|
||||
export function * has_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const result = yield * search_exp.call(this, arr, itemName, exp)
|
||||
return !!result
|
||||
}
|
||||
|
||||
export function * find_index<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
const result = yield * search.call(this, arr, property, expected)
|
||||
return result ? result[0] : undefined
|
||||
}
|
||||
|
||||
export function * find_index_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const result = yield * search_exp.call(this, arr, itemName, exp)
|
||||
return result ? result[0] : undefined
|
||||
}
|
||||
|
||||
export function * find<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||
const result = yield * search.call(this, arr, property, expected)
|
||||
return result ? result[1] : undefined
|
||||
}
|
||||
|
||||
export function * find_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
|
||||
const result = yield * search_exp.call(this, arr, itemName, exp)
|
||||
return result ? result[1] : undefined
|
||||
}
|
||||
|
||||
export function uniq<T> (this: FilterImpl, arr: T[]): T[] {
|
||||
arr = toArray(arr)
|
||||
this.context.memoryLimit.use(arr.length)
|
||||
|
||||
@@ -417,6 +417,32 @@ describe('filters/array', function () {
|
||||
- Boring sneakers
|
||||
`)
|
||||
})
|
||||
it('should support filter with undefined target', function () {
|
||||
return test(`{% assign typed_products = products | where: "type", notdefined %}
|
||||
Typed products:
|
||||
{% for product in typed_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Typed products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
`)
|
||||
})
|
||||
it('should support no target', function () {
|
||||
return test(`{% assign typed_products = products | where: "type" %}
|
||||
Typed products:
|
||||
{% for product in typed_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Typed products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
`)
|
||||
})
|
||||
it('should support nested property', async function () {
|
||||
const authors = [
|
||||
{ name: 'Alice', books: { year: 2019 } },
|
||||
@@ -491,7 +517,7 @@ describe('filters/array', function () {
|
||||
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[]')
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should not match string with array', async () => {
|
||||
it('should filter arrays by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[{"foo":["FOO","bar"]}]', { jekyllWhere: true })
|
||||
})
|
||||
@@ -499,6 +525,14 @@ describe('filters/array', function () {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]', { jekyllWhere: true })
|
||||
})
|
||||
it('should filter by undefined when target is omitted', async () => {
|
||||
await test('{{products | where: "type" | map: "title" | join: ","}}', { products },
|
||||
'Coffee mug,Limited edition sneakers,Boring sneakers', { jekyllWhere: true })
|
||||
})
|
||||
it('should filter plainly when target is undefined', async () => {
|
||||
await test('{{products | where: "type", notdefined | map: "title" | join: ","}}', { products },
|
||||
'Coffee mug,Limited edition sneakers,Boring sneakers', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('where_exp', function () {
|
||||
@@ -537,6 +571,89 @@ describe('filters/array', function () {
|
||||
return test(tpl, scope, html)
|
||||
})
|
||||
})
|
||||
describe('reject', function () {
|
||||
const products = [
|
||||
{ title: 'Vacuum', type: 'living room' },
|
||||
{ title: 'Spatula', type: 'kitchen' },
|
||||
{ title: 'Television', type: 'living room' },
|
||||
{ title: 'Garlic press', type: 'kitchen' },
|
||||
{ title: 'Coffee mug', available: true },
|
||||
{ title: 'Limited edition sneakers', available: false },
|
||||
{ title: 'Boring sneakers', available: true }
|
||||
]
|
||||
it('should support reject by property value', function () {
|
||||
return test(`{% assign kitchen_products = products | reject: "type", "kitchen" %}
|
||||
Kitchen products:
|
||||
{% for product in kitchen_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Kitchen products:
|
||||
- Vacuum
|
||||
- Television
|
||||
- Coffee mug
|
||||
- Limited edition sneakers
|
||||
- Boring sneakers
|
||||
`)
|
||||
})
|
||||
it('should support reject truthy property', function () {
|
||||
return test(`{% assign unavailable_products = products | reject: "available" %}
|
||||
Unavailable products:
|
||||
{% for product in unavailable_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Unavailable products:
|
||||
- Vacuum
|
||||
- Spatula
|
||||
- Television
|
||||
- Garlic press
|
||||
- Limited edition sneakers
|
||||
`)
|
||||
})
|
||||
it('should support reject by string property', function () {
|
||||
return test(`{% assign untyped_products = products | reject: "type" %}
|
||||
Untyped products:
|
||||
{% for product in untyped_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Untyped products:
|
||||
- Coffee mug
|
||||
- Limited edition sneakers
|
||||
- Boring sneakers
|
||||
`)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should filter arrays by exclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }, { foo: ['bar', 'baz'] }, { foo: ['FOO'] }] }
|
||||
await test('{{objs | reject: "foo", "FOO" | json}}', scope, '[{"foo":["bar","baz"]}]', { jekyllWhere: true })
|
||||
})
|
||||
it('should filter by undefined when target is omitted', async () => {
|
||||
await test('{{products | reject: "type" | map: "title" | join: ","}}', { products },
|
||||
'Vacuum,Spatula,Television,Garlic press', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('reject_exp', function () {
|
||||
const products = [
|
||||
{ title: 'Vacuum', type: 'living room' },
|
||||
{ title: 'Spatula', type: 'kitchen' },
|
||||
{ title: 'Television', type: 'living room' },
|
||||
{ title: 'Garlic press', type: 'kitchen' },
|
||||
{ title: 'Coffee mug', available: true },
|
||||
{ title: 'Limited edition sneakers', available: false },
|
||||
{ title: 'Boring sneakers', available: true }
|
||||
]
|
||||
it('should support reject by exp', function () {
|
||||
return test(`{% assign kitchen_products = products | reject_exp: "item", "item.type != 'kitchen'" %}
|
||||
Kitchen products:
|
||||
{% for product in kitchen_products -%}
|
||||
- {{ product.title }}
|
||||
{% endfor %}`, { products }, `
|
||||
Kitchen products:
|
||||
- Spatula
|
||||
- Garlic press
|
||||
`)
|
||||
})
|
||||
})
|
||||
describe('group_by', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2003, name: 'Jay' },
|
||||
@@ -616,12 +733,76 @@ describe('filters/array', function () {
|
||||
JSON.stringify(expected))
|
||||
})
|
||||
})
|
||||
describe('find', function () {
|
||||
describe('has', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack', age: 13 }
|
||||
]
|
||||
it('should support has with no value', function () {
|
||||
return test(
|
||||
`{{ members | has: "age" | json }}, {{ members | has: "height" | json }}`,
|
||||
{ members },
|
||||
`true, false`)
|
||||
})
|
||||
it('should support has by property', function () {
|
||||
return test(
|
||||
`{{ members | has: "graduation_year", 2014 | json }}`,
|
||||
{ members },
|
||||
`true`)
|
||||
})
|
||||
it('should return false if not found', function () {
|
||||
return test(
|
||||
`{{ members | has: "graduation_year", 2018 | json }}`,
|
||||
{ members },
|
||||
`false`)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should select array by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | has: "foo", "FOO" | json}}', scope, 'true', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | has: "tags", empty | json}}', scope, 'true', { jekyllWhere: true })
|
||||
})
|
||||
it('should search plainly when target is undefined', async () => {
|
||||
await test('{{members | has: "age", notdefined}}', { members }, 'true', { jekyllWhere: true })
|
||||
await test('{{members | has: "name", notdefined}}', { members }, 'false', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('has_exp', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
it('should support has by expression', function () {
|
||||
return test(
|
||||
`{{ members | has_exp: "item", "item.graduation_year == 2014" | json }}`,
|
||||
{ members },
|
||||
`true`)
|
||||
})
|
||||
it('should return false if not found', function () {
|
||||
return test(
|
||||
`{{ members | has_exp: "item", "item.graduation_year == 2018" | json }}`,
|
||||
{ members },
|
||||
`false`)
|
||||
})
|
||||
})
|
||||
describe('find', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack', age: 13 }
|
||||
]
|
||||
it('should support find with no value', function () {
|
||||
return test(
|
||||
`{{ members | find: "age" | json }}`,
|
||||
{ members },
|
||||
`{"graduation_year":2014,"name":"Jack","age":13}`)
|
||||
})
|
||||
it('should support find by property', function () {
|
||||
return test(
|
||||
`{{ members | find: "graduation_year", 2014 | json }}`,
|
||||
@@ -634,6 +815,24 @@ describe('filters/array', function () {
|
||||
{ members },
|
||||
``)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should select array by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | find: "foo", "FOO" | json}}', scope, '{"foo":["FOO","bar"]}', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | find: "tags", empty | json}}', scope, '{"tags":[]}', { jekyllWhere: true })
|
||||
})
|
||||
it('should search plainly when target is undefined', async () => {
|
||||
await test(
|
||||
'{{members | find: "age", notdefined | json}}',
|
||||
{ members },
|
||||
'{"graduation_year":2013,"name":"Jay"}',
|
||||
{ jekyllWhere: true })
|
||||
await test('{{members | find: "name", notdefined | json}}', { members }, '', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('find_exp', function () {
|
||||
const members = [
|
||||
@@ -654,4 +853,62 @@ describe('filters/array', function () {
|
||||
``)
|
||||
})
|
||||
})
|
||||
describe('find_index', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack', age: 13 }
|
||||
]
|
||||
it('should support find_index with no value', function () {
|
||||
return test(
|
||||
`{{ members | find_index: "age" | json }}`,
|
||||
{ members },
|
||||
`2`)
|
||||
})
|
||||
it('should support find_index by property', function () {
|
||||
return test(
|
||||
`{{ members | find_index: "graduation_year", 2014 | json }}`,
|
||||
{ members },
|
||||
`1`)
|
||||
})
|
||||
it('should render none if not found', function () {
|
||||
return test(
|
||||
`{{ members | find_index: "graduation_year", 2018 | json }}`,
|
||||
{ members },
|
||||
``)
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should select array by inclusion', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | find_index: "foo", "FOO" | json}}', scope, '0', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | find_index: "tags", empty | json}}', scope, '1', { jekyllWhere: true })
|
||||
})
|
||||
it('should search plainly when target is undefined', async () => {
|
||||
await test('{{members | find_index: "age", notdefined | json}}', { members }, '0', { jekyllWhere: true })
|
||||
await test('{{members | find_index: "name", notdefined | json}}', { members }, '', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('find_index_exp', function () {
|
||||
const members = [
|
||||
{ graduation_year: 2013, name: 'Jay' },
|
||||
{ graduation_year: 2014, name: 'John' },
|
||||
{ graduation_year: 2014, name: 'Jack' }
|
||||
]
|
||||
it('should support find_index by expression', function () {
|
||||
return test(
|
||||
`{{ members | find_index_exp: "item", "item.graduation_year == 2014" | json }}`,
|
||||
{ members },
|
||||
`1`)
|
||||
})
|
||||
it('should render none if not found', function () {
|
||||
return test(
|
||||
`{{ members | find_index_exp: "item", "item.graduation_year == 2018" | json }}`,
|
||||
{ members },
|
||||
``)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user