diff --git a/src/ng/filter/filter.js b/src/ng/filter/filter.js index a739a1ca21c7..47c17b9df7b0 100644 --- a/src/ng/filter/filter.js +++ b/src/ng/filter/filter.js @@ -14,19 +14,26 @@ * * Can be one of: * - * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against - * the contents of the `array`. All strings or objects with string properties in `array` that contain this string - * will be returned. The predicate can be negated by prefixing the string with `!`. + * - `string`: The string is used for matching against the contents of the `array`. All strings or + * objects with string properties in `array` that match this string will be returned. This also + * applies to nested object properties. + * The predicate can be negated by prefixing the string with `!`. * * - `Object`: A pattern object can be used to filter specific properties on objects contained * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items * which have property `name` containing "M" and property `phone` containing "1". A special * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object. That's equivalent to the simple substring match with a `string` - * as described above. The predicate can be negated by prefixing the string with `!`. - * For Example `{name: "!M"}` predicate will return an array of items which have property `name` + * property of the object or its nested object properties. That's equivalent to the simple + * substring match with a `string` as described above. The predicate can be negated by prefixing + * the string with `!`. + * For example `{name: "!M"}` predicate will return an array of items which have property `name` * not containing "M". * + * Note that a named property will match properties on the same level only, while the special + * `$` property will match properties on the same level or deeper. E.g. an array item like + * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but + * **will** be matched by `{$: 'John'}`. + * * - `function(value, index)`: A predicate function can be used to write arbitrary filters. The * function is called for each element of `array`. The final result is an array of those * elements that the predicate returned true for. @@ -39,10 +46,10 @@ * * - `function(actual, expected)`: * The function will be given the object value and the predicate value to compare and - * should return true if the item should be included in filtered result. + * should return true if both values should be considered equal. * - * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. - * this is essentially strict comparison of expected and actual. + * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. + * This is essentially strict comparison of expected and actual. * * - `false|undefined`: A short hand for a function which will look for a substring match in case * insensitive way. @@ -169,7 +176,7 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) { return predicateFn; } -function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { +function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { var actualType = typeof actual; var expectedType = typeof expected; @@ -188,11 +195,11 @@ 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, true)) { return true; } } - return false; + return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); } else if (expectedType === 'object') { for (key in expected) { var expectedVal = expected[key]; @@ -200,9 +207,9 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) { continue; } - var keyIsDollar = key === '$'; - var actualVal = keyIsDollar ? actual : actual[key]; - if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) { + var matchAnyProperty = key === '$'; + var actualVal = matchAnyProperty ? actual : actual[key]; + if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { return false; } } diff --git a/test/ng/filter/filterSpec.js b/test/ng/filter/filterSpec.js index 7e14f5f567f4..7ec3bcd1205b 100644 --- a/test/ng/filter/filterSpec.js +++ b/test/ng/filter/filterSpec.js @@ -136,14 +136,30 @@ describe('Filter: filter', function() { }); - it('should respect the depth level 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'}}; + it('should match named properties only against named properties on the same level', function() { + var expr = {person: {name: 'John'}}; + var items = [{person: 'John'}, // No match (1 level higher) + {person: {name: 'John'}}, // Match (same level) + {person: {name: {first: 'John', last: 'Doe'}}}]; // No match (1 level deeper) expect(filter(items, expr).length).toBe(1); - expect(filter(items, expr)).toEqual([items[0]]); + expect(filter(items, expr)).toEqual([items[1]]); + }); + + + it('should match any properties on same or deeper level for given "$" property', function() { + var items = [{level1: 'test', foo1: 'bar1'}, + {level1: {level2: 'test', foo2:'bar2'}, foo1: 'bar1'}, + {level1: {level2: {level3: 'test', foo3: 'bar3'}, foo2: 'bar2'}, foo1: 'bar1'}]; + + expect(filter(items, {$: 'ES'}).length).toBe(3); + expect(filter(items, {$: 'ES'})).toEqual([items[0], items[1], items[2]]); + + expect(filter(items, {level1: {$: 'ES'}}).length).toBe(2); + expect(filter(items, {level1: {$: 'ES'}})).toEqual([items[1], items[2]]); + + expect(filter(items, {level1: {level2: {$: 'ES'}}}).length).toBe(1); + expect(filter(items, {level1: {level2: {$: 'ES'}}})).toEqual([items[2]]); });