From 46823e3b7c4499236bc5498e8b790121259d21df Mon Sep 17 00:00:00 2001 From: Dave McNelis Date: Mon, 30 Oct 2017 17:00:50 -0400 Subject: [PATCH] Add options interfaces to enable sub-filter matching for nested records Add missing dependency to fix min/max rules Add jasmine unit tests (from anglerfish) --- .gitignore | 1 + index.js | 62 +--- package.json | 7 +- tests/index-spec.js | 743 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 759 insertions(+), 54 deletions(-) create mode 100644 tests/index-spec.js diff --git a/.gitignore b/.gitignore index a126b31..33717f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.sublime-* +node_modules/ diff --git a/index.js b/index.js index 00c769c..c88f6ae 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,7 @@ */ var znFilterMatcher = (function() { + var BigNumber = require('bignumber.js'); var ruleFunctionMap = { '': 'ruleEquals', 'not': 'ruleDoesNotEqual', @@ -155,52 +156,6 @@ var znFilterMatcher = (function() { } }; - /** - * Is var an object? - * - * @param x - * @returns {Boolean} - * @author David McNelis - * @since 1.1.0 - */ - function isObject(x) { - return (!!x) && (x.constructor === Object); - }; - - /** - * Is var an empty object? - * - * @param x - * @returns {Boolean} - * @author David McNelis - * @since 1.1.0 - */ - function noKeys(x) { - return isObject(x) && (Object.keys(x).length === 0); - }; - - /** - * Do any object keys match a pattern - * - * @param x - * @param pattern - * @returns {Boolean} - * @author David McNelis - * @since 1.1.0 - */ - function anyKeysLike(x, pattern) { - if (!isObject(x)) { - return false; - } - var keys = Object.keys(x); - for (var i = 0; i < keys.length; i++) { - if (pattern.test(keys[i])) { - return true; - } - } - return false; - } - /** * Determine whether the given record matches the given filter * @@ -210,7 +165,8 @@ var znFilterMatcher = (function() { * @author Paul W. Smith * @since 0.5.75 */ - function recordMatchesFilter(record, filter) { + function recordMatchesFilter(record, filter, options) { + options = typeof options !== 'undefined' ? options : {}; var currentOperator = Object.keys(filter)[0]; if (filter[currentOperator].length === 0) { // Empty filter / no rules - considered a "match all" @@ -218,7 +174,7 @@ var znFilterMatcher = (function() { } for (var i in filter[currentOperator]) { - var match = recordMatchesRule(record, filter[currentOperator][i]); + var match = recordMatchesRule(record, filter[currentOperator][i], options); if (currentOperator == 'or' && match) { return true; } @@ -240,7 +196,8 @@ var znFilterMatcher = (function() { * @author Paul W. Smith * @since 0.5.75 */ - function recordMatchesRule(record, rule) { + function recordMatchesRule(record, rule, options) { + options = typeof options !== 'undefined' ? options : {}; var operators = ["and", "or"]; if (operators.indexOf(Object.keys(rule)[0]) !== -1) { @@ -248,16 +205,15 @@ var znFilterMatcher = (function() { return recordMatchesFilter(record, rule); } if (rule.filter !== undefined) { - var subRecord = record[rule.attribute]; - if (subRecord && (noKeys(subRecord) || anyKeysLike(subRecord, /^field/))) { - if (!recordMatchesFilter(subRecord, rule.filter)) { + if (options.subfiltering) { + var subRecord = record[rule.attribute]; + if (!recordMatchesFilter(subRecord, rule.filter, options)) { return false; } return true; } else { throw new Error("Subfilter matching is not supported"); } - } if (typeof rule.value === 'string' && rule.value.split('|').indexOf('logged-in-user') !== -1) { diff --git a/package.json b/package.json index 02b816c..11c09ef 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,10 @@ "bugs": { "url": "https://github.com/ZengineHQ/zn-filter-matcher/issues" }, - "homepage": "https://github.com/ZengineHQ/zn-filter-matcher#readme" + "homepage": "https://github.com/ZengineHQ/zn-filter-matcher#readme", + "dependencies": { + "bignumber.js": "2.4.0" + }, + "devDependencies": {} + } diff --git a/tests/index-spec.js b/tests/index-spec.js new file mode 100644 index 0000000..06c6005 --- /dev/null +++ b/tests/index-spec.js @@ -0,0 +1,743 @@ +/** + * Unit tests for znFilterMatcher + * + * Copyright (c) WizeHive - http://www.wizehive.com + * + * @author Wes DeMoney + * @since 0.5.84 + */ +describe('znFilterMatcher', function() { + + var znFilterMatcher = require('../index.js'); + + beforeEach(function() { + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('recordMatchesFilter', function() { + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleEquals', function() { + + var record, + filter; + + beforeEach(function() { + + record = { + field1: 'abc' + }; + + filter = { + and: [ + { + prefix: '', + attribute: 'field1', + value: 'abc' + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field1 = 'def'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false with null value', function() { + + record.field1 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleDoesNotEqual', function() { + + var record, + filter; + + beforeEach(function() { + + record = { + field1: 'abc' + }; + + filter = { + and: [ + { + prefix: 'not', + attribute: 'field1', + value: 'def' + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field1 = 'def'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return true with null value', function() { + + record.field1 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleMinimum', function() { + + var record, + filter; + + beforeEach(function() { + + record = { + field2: 6 + }; + + filter = { + and: [ + { + prefix: 'min', + attribute: 'field2', + value: 5 + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return true - stripped commas', function() { + + record.field2 = '$9,999.123'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false - rounding', function() { + + record.field2 = '999999999.99999999'; + + filter.and[0].value = '1000000000'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false', function() { + + record.field2 = 4; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false with null value', function() { + + record.field2 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleMaximum', function() { + + var record, + filter; + + beforeEach(function() { + + record = { + field3: 4 + }; + + filter = { + and: [ + { + prefix: 'max', + attribute: 'field3', + value: 5 + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field3 = 6; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false with null value', function() { + + record.field3 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleContains', function() { + + var record, + filter; + + describe('non array value', function() { + + beforeEach(function() { + + record = { + field4: 'abc' + }; + + filter = { + and: [ + { + prefix: 'contains', + attribute: 'field4', + value: 'bc' + } + ] + }; + + }); + + it ('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it ('should return false', function() { + + filter.and[0].value = 'cd'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false with null value ', function() { + + record.field4 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + describe('array value', function() { + + beforeEach(function() { + + record = { + field4: ['field1'] + }; + + filter = { + and: [ + { + prefix: 'contains', + attribute: 'field4', + value: ['field1'] + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('non array filter value should return true', function() { + + filter['and'][0]['value'] = 'field1'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('multi value array should return true', function() { + + record.field4 = ['field1', 'field2', 'field3']; + + filter.and[0].value = ['field1', 'field3']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('multi value array should return true', function() { + + record.field4 = ['field1', 'field2', 'field3']; + + filter.and[0].value = ['field1']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field4 = ['field2']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('non array filter value should return false', function() { + + filter['and'][0]['value'] = 'field2'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('multi value array should return false', function() { + + record.field4 = ['field1', 'field2']; + + filter.and[0].value = ['field1', 'field3']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('multi value array should return false', function() { + + record.field4 = ['field1', 'field2']; + + filter.and[0].value = ['field3']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleDoesNotContain', function() { + + var record, + filter; + + describe('non array value', function() { + + beforeEach(function() { + + record = { + field4: 'abc' + }; + + filter = { + and: [ + { + prefix: 'not-contains', + attribute: 'field4', + value: 'cd' + } + ] + }; + + }); + + it ('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it ('should return false', function() { + + filter.and[0].value = 'bc'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return true with null value', function() { + + record.field4 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + }); + + describe('array value', function() { + + beforeEach(function() { + + record = { + field4: ['field2'] + }; + + filter = { + and: [ + { + prefix: 'not-contains', + attribute: 'field4', + value: ['field1'] + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('non array filter value should return true', function() { + + filter['and'][0]['value'] = 'field1'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('multi value array should return true', function() { + + record.field4 = ['field1', 'field2', 'field3']; + + filter.and[0].value = ['field1', 'field4']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('multi value array should return true', function() { + + record.field4 = ['field1', 'field2', 'field3']; + + filter.and[0].value = ['field4']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field4 = ['field1']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('non array filter value should return false', function() { + + filter['and'][0]['value'] = 'field2'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('multi value array should return false', function() { + + record.field4 = ['field1', 'field2']; + + filter.and[0].value = ['field1', 'field2']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('multi value array should return false', function() { + + record.field4 = ['field1', 'field2']; + + filter.and[0].value = ['field1']; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleStartsWith', function() { + + var record, + filter; + + beforeEach(function() { + + record = { + field5: 'abcdef' + }; + + filter = { + and: [ + { + prefix: 'starts-with', + attribute: 'field5', + value: 'abc' + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field5 = 'def'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false with null value', function() { + + record.field5 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleEndsWith', function() { + + var record, + filter; + + beforeEach(function() { + + record = { + field6: 'abcdef' + }; + + filter = { + and: [ + { + prefix: 'ends-with', + attribute: 'field6', + value: 'def' + } + ] + }; + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(true); + + }); + + it('should return false', function() { + + record.field6 = 'abc'; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + it('should return false with null value', function() { + + record.field6 = null; + + expect(znFilterMatcher.recordMatchesFilter(record, filter)).toBe(false); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleSubfilter option enabled', function() { + + var record, + filter, + options; + + beforeEach(function() { + + record = { + field6: { + field11: 'abcdef' + } + }; + + filter = { + and: [ + { + prefix: '', + attribute: 'field6', + filter: { + and: [ + { + prefix: '', + attribute: 'field11', + value: 'abcdef' + } + ] + } + } + ] + }; + + options = { + subfiltering: true + } + + }); + + it('should return true', function() { + + expect(znFilterMatcher.recordMatchesFilter(record, filter, options)).toBe(true); + + }); + + }); + + /** + * @author Wes DeMoney + * @since 0.5.84 + */ + describe('ruleSubfilter option not enabled', function() { + + var record, + filter, + options; + + beforeEach(function() { + + record = { + field6: { + field11: 'abcdef' + } + }; + + filter = { + and: [ + { + prefix: '', + attribute: 'field6', + filter: { + and: [ + { + prefix: '', + attribute: 'field11', + value: 'abcdef' + } + ] + } + } + ] + }; + + options = { + } + + }); + + it('should throw error', function() { + + expect(function () { znFilterMatcher.recordMatchesFilter(record, filter, options) } ).toThrow(new Error("Subfilter matching is not supported")); + + }); + + }); + }); + + + +});