Skip to content

Commit

Permalink
feat: Array sum filter (#661)
Browse files Browse the repository at this point in the history
* feat(filters): add array sum filter

* docs(filters): array sum filter

* docs: update versoin number in source/filters/sum.md

---------

Co-authored-by: Jun Yang <harttleharttle@gmail.com>
  • Loading branch information
brunodccarvalho and harttle committed Dec 19, 2023
1 parent fe978c8 commit 629d958
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/source/_data/sidebar.yml
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
@@ -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
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
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

0 comments on commit 629d958

Please sign in to comment.