Repository: mozilla/nunjucks (js)

Issue:
Implement select/reject
These exists in jinja and is a pretty good way to exclude something from array iteration. I'm working on a patch.


Pull request:
Add `reject` filter
## Summary

Proposed change:

Add [`reject` filter](https://jinja.palletsprojects.com/en/2.11.x/templates/#reject) to match Jinja2 behaviour.

Closes #282.


## Checklist

I've completed the checklist below to ensure I didn't forget anything. This makes reviewing this PR as easy as possible for the maintainers. And it gets this change released as soon as possible.

* [x] Proposed change helps towards [*purpose of this project*](https://github.com/mozilla/nunjucks/blob/master/CONTRIBUTING.md#purpose).
* [x] [*Documentation*](https://github.com/mozilla/nunjucks/tree/master/docs/) is added / updated to describe proposed change.
* [x] [*Tests*](https://github.com/mozilla/nunjucks/tree/master/tests) are added / updated to cover proposed change.
* [x] [*Changelog*](https://github.com/mozilla/nunjucks/blob/master/CHANGELOG.md) has an entry for proposed change (if user-facing fix or feature).

<!-- Tick of items by replacing `[ ]` by `[x]` -->

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca2c3b8..54aa11f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,16 @@
 Changelog
 =========
 
-* Add [`select` filter](https://mozilla.github.io/nunjucks/templating.html#select).
+3.2.2 (unreleased)
+------------------
+
+* Add [`select`](https://mozilla.github.io/nunjucks/templating.html#select) and
+  [`reject`](https://mozilla.github.io/nunjucks/templating.html#reject) filters.
+  Merge of [#1278](https://github.com/mozilla/nunjucks/pull/1278) and
+  [#1279](https://github.com/mozilla/nunjucks/pull/1279); fixes
+  [#282](https://github.com/mozilla/nunjucks/issues/282). Thanks
+  [ogonkov](https://github.com/ogonkovv)!
+  
 
 3.2.1 (Mar 17 2020)
 -------------------
diff --git a/docs/templating.md b/docs/templating.md
index fcae50b..5c21301 100644
--- a/docs/templating.md
+++ b/docs/templating.md
@@ -1389,6 +1389,32 @@ Select a random value from an array.
 
 A random value between 1-9 (inclusive).
 
+### reject
+
+Filters a sequence of objects by applying a test to each object, and rejecting
+the objects with the test succeeding.
+
+If no test is specified, each object will be evaluated as a boolean.
+
+**Input**
+
+```jinja
+{% set numbers=[0, 1, 2, 3, 4, 5] %}
+
+{{ numbers | reject("odd") | join }}
+{{ numbers | reject("even") | join }}
+{{ numbers | reject("divisibleby", 3) | join }}
+{{ numbers | reject() | join }}
+```
+
+**Output**
+
+```jinja
+024
+135
+1245
+0
+```
 
 ### rejectattr (only the single-argument form)
 
diff --git a/nunjucks/src/filters.js b/nunjucks/src/filters.js
index 5875dff..b14c298 100644
--- a/nunjucks/src/filters.js
+++ b/nunjucks/src/filters.js
@@ -266,22 +266,34 @@ function random(arr) {
 
 exports.random = random;
 
+/**
+ * Construct select or reject filter
+ *
+ * @param {boolean} expectedTestResult
+ * @returns {function(array, string, *): array}
+ */
+function getSelectOrReject(expectedTestResult) {
+  function filter(arr, testName = 'truthy', secondArg) {
+    const context = this;
+    const test = context.env.getTest(testName);
+
+    return lib.toArray(arr).filter(function examineTestResult(item) {
+      return test.call(context, item, secondArg) === expectedTestResult;
+    });
+  }
+
+  return filter;
+}
+
+exports.reject = getSelectOrReject(false);
+
 function rejectattr(arr, attr) {
   return arr.filter((item) => !item[attr]);
 }
 
 exports.rejectattr = rejectattr;
 
-function select(arr, testName = 'truthy', secondArg) {
-  const context = this;
-  const test = context.env.getTest(testName);
-
-  return arr.filter(function applyToTest(item) {
-    return test.call(context, item, secondArg);
-  });
-}
-
-exports.select = select;
+exports.select = getSelectOrReject(true);
 
 function selectattr(arr, attr) {
   return arr.filter((item) => !!item[attr]);


Test patch:
diff --git a/tests/filters.js b/tests/filters.js
index c21900d..26c610f 100644
--- a/tests/filters.js
+++ b/tests/filters.js
@@ -490,6 +490,22 @@
       finish(done);
     });
 
+    it('reject', function(done) {
+      var context = {
+        numbers: [0, 1, 2, 3, 4, 5]
+      };
+
+      equal('{{ numbers | reject("odd") | join }}', context, '024');
+
+      equal('{{ numbers | reject("even") | join }}', context, '135');
+
+      equal('{{ numbers | reject("divisibleby", 3) | join }}', context, '1245');
+
+      equal('{{ numbers | reject() | join }}', context, '0');
+
+      finish(done);
+    });
+
     it('rejectattr', function(done) {
       var foods = [{
         tasty: true