Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Array sum filter #661

Merged
merged 3 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/_data/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ filters:
strip: strip.html
strip_html: strip_html.html
strip_newlines: strip_newlines.html
sum: sum.html
times: times.html
truncate: truncate.html
truncatewords: truncatewords.html
Expand Down
22 changes: 22 additions & 0 deletions docs/source/filters/sum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: sum
---

{% since %}v10.10.0{% endsince %}

Computes the sum of all the numbers in an array.
An optional argument specifies which property of the array's items to sum up.

In this example, assume the object `cart.products` contains an array of all products in the cart of a website.
Assume each cart product has a `qty` property that gives the count of that product instance in the cart.
Using the `sum` filter we can calculate the total number of products in the cart.

Input
```liquid
The cart has {{ order.products | sum: "qty" }} products.
```

Output
```text
The cart has 7 products.
```
9 changes: 9 additions & 0 deletions src/filters/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export function * map (this: FilterImpl, arr: Scope[], property: string): Iterab
return results
}

export function * sum (this: FilterImpl, arr: Scope[], property?: string): IterableIterator<unknown> {
let sum = 0
for (const item of toArray(toValue(arr))) {
const data = Number(property ? yield this.context._getFromScope(item, stringify(property), false) : item)
sum += Number.isNaN(data) ? 0 : data
}
return sum
}

export function compact<T> (this: FilterImpl, arr: T[]) {
arr = toValue(arr)
return toArray(arr).filter(x => !isNil(toValue(x)))
Expand Down
38 changes: 38 additions & 0 deletions test/integration/filters/array.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,44 @@ describe('filters/array', function () {
return test(tpl, { arr: [a, b, c] }, 'Alice Bob Carol')
})
})
describe('sum', () => {
it('should support sum with no args', function () {
const ages = [21, null, -4, '4.5', 13.25, undefined, 0]
return test('{{ages | sum}}', { ages }, '34.75')
})
it('should support sum with property', function () {
const ages = [21, null, -4, '4.5', 13.25, undefined, 0].map(x => ({ age: x }))
return test('{{ages | sum: "age"}}', { ages }, '34.75')
})
it('should support sum with nested property', function () {
const ages = [21, null, -4, '4.5', 13.25, undefined, 0].map(x => ({ age: { first: x } }))
return test('{{ages | sum: "age.first"}}', { ages }, '34.75')
})
it('should support non-array input', function () {
const age = 21.5
return test('{{age | sum}}', { age }, '21.5')
})
it('should coerce missing property to zero', function () {
const ages = [{ qty: 1 }, { qty: 2, cnt: 3 }, { cnt: 4 }]
return test('{{ages | sum}} {{ages | sum: "cnt"}} {{ages | sum: "other"}}', { ages }, '0 7 0')
})
it('should coerce indexable non-map values to zero', function () {
const input = [1, 'foo', { quantity: 3 }]
return test('{{input | sum}}', { input }, '1')
})
it('should coerce unindexable values to zero', function () {
const input = [1, null, { quantity: 2 }]
return test('{{input | sum}}', { input }, '1')
})
it('should coerce true to 1', function () {
const input = [1, true, null, { quantity: 2 }]
return test('{{input | sum}}', { input }, '2')
})
it('should not support nested arrays', function () {
const ages = [1, [2, [3, 4]]]
return test('{{ages | sum}}', { ages }, '1')
})
})
describe('compact', () => {
it('should compact array', function () {
const posts = [{ category: 'foo' }, { category: 'bar' }, { foo: 'bar' }]
Expand Down