From 0538dc0c9fd71d058ce04c3586ecbe4ea2e87c55 Mon Sep 17 00:00:00 2001 From: Shuhei Kagawa Date: Wed, 10 Dec 2014 20:41:52 +0900 Subject: [PATCH 1/2] fix(filterFilter): filter deep object by string Enable filterFilter with string expression to filter objects with deep properties. It used to work like this before #9757 and v1.3.6. --- src/ng/filter/filter.js | 11 ++++++----- test/ng/filter/filterSpec.js | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ng/filter/filter.js b/src/ng/filter/filter.js index a739a1ca21c7..70a24a5e7700 100644 --- a/src/ng/filter/filter.js +++ b/src/ng/filter/filter.js @@ -169,17 +169,18 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) { return predicateFn; } -function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { +function deepCompare(actual, expected, comparator, matchAgainstAnyProp, temporaryAnyProp) { var actualType = typeof actual; var expectedType = typeof expected; + var nextMatchAgainstAnyProp = matchAgainstAnyProp && !temporaryAnyProp; if ((expectedType === 'string') && (expected.charAt(0) === '!')) { - return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); + return !deepCompare(actual, expected.substring(1), comparator, nextMatchAgainstAnyProp); } else if (actualType === 'array') { // In case `actual` is an array, consider it a match // if ANY of it's items matches `expected` return actual.some(function(item) { - return deepCompare(item, expected, comparator, matchAgainstAnyProp); + return deepCompare(item, expected, comparator, nextMatchAgainstAnyProp); }); } @@ -188,7 +189,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { var key; if (matchAgainstAnyProp) { for (key in actual) { - if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) { + if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, nextMatchAgainstAnyProp)) { return true; } } @@ -202,7 +203,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { var keyIsDollar = key === '$'; var actualVal = keyIsDollar ? actual : actual[key]; - if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) { + if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar, keyIsDollar)) { return false; } } diff --git a/test/ng/filter/filterSpec.js b/test/ng/filter/filterSpec.js index 7e14f5f567f4..3d62e56895b2 100644 --- a/test/ng/filter/filterSpec.js +++ b/test/ng/filter/filterSpec.js @@ -28,6 +28,13 @@ describe('Filter: filter', function() { expect(filter(items, "I don't exist").length).toBe(0); }); + it('should filter deep object by string', function() { + var items = [{person: {name: 'Annet', email: 'annet@example.com'}}, + {person: {name: 'Billy', email: 'me@billy.com'}}, + {person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}]; + expect(filter(items, 'me@joan').length).toBe(1); + expect(filter(items, 'joan@example').length).toBe(1); + }); it('should not read $ properties', function() { expect(''.charAt(0)).toBe(''); // assumption From 16a941937495a7b6161ebc0d6fcc7b20e4f95854 Mon Sep 17 00:00:00 2001 From: Shuhei Kagawa Date: Thu, 11 Dec 2014 00:30:36 +0900 Subject: [PATCH 2/2] fix(filterFilter) filter the same level or deeper of $ property. --- src/ng/filter/filter.js | 62 ++++++++++++++++++++++-------------- test/ng/filter/filterSpec.js | 6 ++-- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/ng/filter/filter.js b/src/ng/filter/filter.js index 70a24a5e7700..aeb4839d6b3e 100644 --- a/src/ng/filter/filter.js +++ b/src/ng/filter/filter.js @@ -120,7 +120,6 @@ function filterFilter() { if (!isArray(array)) return array; var predicateFn; - var matchAgainstAnyProp; switch (typeof expression) { case 'function': @@ -129,11 +128,8 @@ function filterFilter() { case 'boolean': case 'number': case 'string': - matchAgainstAnyProp = true; - //jshint -W086 case 'object': - //jshint +W086 - predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); + predicateFn = createPredicateFn(expression, comparator); break; default: return array; @@ -144,7 +140,7 @@ function filterFilter() { } // Helper functions for `filterFilter` -function createPredicateFn(expression, comparator, matchAgainstAnyProp) { +function createPredicateFn(expression, comparator) { var predicateFn; if (comparator === true) { @@ -163,38 +159,32 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) { } predicateFn = function(item) { - return deepCompare(item, expression, comparator, matchAgainstAnyProp); + return deepCompare(item, expression, comparator); }; return predicateFn; } -function deepCompare(actual, expected, comparator, matchAgainstAnyProp, temporaryAnyProp) { +function deepCompare(actual, expected, comparator) { var actualType = typeof actual; var expectedType = typeof expected; - var nextMatchAgainstAnyProp = matchAgainstAnyProp && !temporaryAnyProp; if ((expectedType === 'string') && (expected.charAt(0) === '!')) { - return !deepCompare(actual, expected.substring(1), comparator, nextMatchAgainstAnyProp); - } else if (actualType === 'array') { + return !deepCompare(actual, expected.substring(1), comparator); + } + + if (actualType === 'array') { // In case `actual` is an array, consider it a match // if ANY of it's items matches `expected` return actual.some(function(item) { - return deepCompare(item, expected, comparator, nextMatchAgainstAnyProp); + return deepCompare(item, expected, comparator); }); } switch (actualType) { case 'object': var key; - if (matchAgainstAnyProp) { - for (key in actual) { - if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, nextMatchAgainstAnyProp)) { - return true; - } - } - return false; - } else if (expectedType === 'object') { + if (expectedType === 'object') { for (key in expected) { var expectedVal = expected[key]; if (isFunction(expectedVal)) { @@ -202,14 +192,25 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, temporar } var keyIsDollar = key === '$'; - var actualVal = keyIsDollar ? actual : actual[key]; - if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar, keyIsDollar)) { - return false; + if (keyIsDollar) { + if (!matchAnyProp(actual, expectedVal, comparator)) { + return false; + } + } else { + var actualVal = actual[key]; + if (!deepCompare(actualVal, expectedVal, comparator)) { + return false; + } } } return true; } else { - return comparator(actual, expected); + for (key in actual) { + if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) { + return true; + } + } + return false; } break; case 'function': @@ -218,3 +219,16 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, temporar return comparator(actual, expected); } } + +function matchAnyProp(actual, expected, comparator) { + if (typeof actual !== 'object') { + throw new Error('actual must be an object but was ' + typeof actual); + } + + for (var key in actual) { + if (deepCompare(actual[key], expected, comparator)) { + return true; + } + } + return false; +} diff --git a/test/ng/filter/filterSpec.js b/test/ng/filter/filterSpec.js index 3d62e56895b2..864f1205e31c 100644 --- a/test/ng/filter/filterSpec.js +++ b/test/ng/filter/filterSpec.js @@ -143,14 +143,14 @@ describe('Filter: filter', function() { }); - it('should respect the depth level of a "$" property', function() { + it('should match the same level and deeper of a "$" property', function() { var items = [{person: {name: 'Annet', email: 'annet@example.com'}}, {person: {name: 'Billy', email: 'me@billy.com'}}, {person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}]; var expr = {person: {$: 'net'}}; - expect(filter(items, expr).length).toBe(1); - expect(filter(items, expr)).toEqual([items[0]]); + expect(filter(items, expr).length).toBe(2); + expect(filter(items, expr)).toEqual([items[0], items[2]]); });