From 9ba84d98327d6f108a673136dae9f69ccc0798df Mon Sep 17 00:00:00 2001 From: Nils Lundquist Date: Tue, 11 Apr 2017 15:44:12 -0600 Subject: [PATCH 1/6] Adding dotNotation property and test --- src/prop_tests/dotNotation_test.js | 31 ++++++++++++++++++ src/props.js | 51 ++++++++++++++++++++++++++++++ src/props_test.js | 1 + 3 files changed, 83 insertions(+) create mode 100644 src/prop_tests/dotNotation_test.js diff --git a/src/prop_tests/dotNotation_test.js b/src/prop_tests/dotNotation_test.js new file mode 100644 index 0000000..5a6c714 --- /dev/null +++ b/src/prop_tests/dotNotation_test.js @@ -0,0 +1,31 @@ +var QUnit = require("steal-qunit"); + +var set = require('../set-core'), + props = require("../props"); + +QUnit.module("can-set props.boolean"); + +/* + * For the dotNotation prop, we define sets like so: + * + * For a property 'n.p', with value 'IL' + * x ∈ X | x.n.p = 'IL' + * + */ +test('dotNotation set membership', function() { + /* + * For a property 'n.p', with value 'IL' + * x ∈ X | x.n.p == 'IL' + */ + var prop = props.dotNotation('n.p'), + alg = new set.Algebra(prop), + res = alg.has({'n.p': 'IL'}, {n:{p:'IL'}}); + ok(res, "object with nested property is member of set using dotNotation"); + + /* + * For a property 'n.p', with value 'IL' + * x ∉ X | x.n.p != 'IL' + */ + res = alg.has({'n.p': 'IL'}, {n:{p:'MI'}}); + notOk(res, "object with nested property not a member of set using dotNotation"); +}); \ No newline at end of file diff --git a/src/props.js b/src/props.js index 8c99026..edeee49 100644 --- a/src/props.js +++ b/src/props.js @@ -483,4 +483,55 @@ props.rangeInclusive = function(startIndexProperty, endIndexProperty){ }); }; +/** + * @function can-set.props.dotNotation dotNotation + * @parent can-set.props + * + * @description Supports MongoDB-style 'dot notation' properties. + * + * @signature `set.props.dotNotation(dotProperty)` + * + * Defines a property that specifies a MongoDB-style nested property match. + * For example, a set property of "address.city" matches against the value of the nested `{address: {city}}` value. + * + * ``` + * set.props.dotNotation("address.city") + * ``` + * + * @param {String} dotProperty The MongoDB-style nested property name + * @return {can-set.compares} Returns a comparator used to build a set algebra + */ +props.dotNotation = function(dotProperty){ + var compares = new clause.Where({}); + + compares[dotProperty] = function(aVal, bVal, a, b, propertyName) { + var modelVal, propertyVal; + + // one of the value arguments being defined implies its the can-set definition prop, since a set member itself wont + // have obj['nested.property.name'] defined + if (aVal != undefined) { + propertyVal = aVal; + modelVal = nestedLookup(b, propertyName.split('.')); + } else if (bVal != undefined) { + propertyVal = bVal; + modelVal = nestedLookup(a, propertyName.split('.')); + } + + return propertyVal === modelVal; + }; + + return compares; +}; +var nestedLookup = function(obj, propNameArray) { + if (obj === undefined) { + return undefined; + } + + if (propNameArray.length == 1) { + return obj[propNameArray[0]]; + } else { + return nestedLookup(obj[propNameArray[0]], propNameArray.slice(1)); + } +}; + module.exports = props; diff --git a/src/props_test.js b/src/props_test.js index a29751e..39d200b 100644 --- a/src/props_test.js +++ b/src/props_test.js @@ -7,3 +7,4 @@ require('./prop_tests/rangeInclusive_test'); require('./prop_tests/offsetLimit_test'); require('./prop_tests/boolean_test'); require('./prop_tests/enum_test'); +require('./prop_tests/dotNotation_test'); From ef49df7d75853aec4c89d82c42761a6470cbaa27 Mon Sep 17 00:00:00 2001 From: Nils Lundquist Date: Tue, 11 Apr 2017 15:48:20 -0600 Subject: [PATCH 2/6] JSHint fixes --- src/props.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/props.js b/src/props.js index edeee49..c412c7f 100644 --- a/src/props.js +++ b/src/props.js @@ -483,6 +483,17 @@ props.rangeInclusive = function(startIndexProperty, endIndexProperty){ }); }; +var nestedLookup = function(obj, propNameArray) { + if (obj === undefined) { + return undefined; + } + + if (propNameArray.length === 1) { + return obj[propNameArray[0]]; + } else { + return nestedLookup(obj[propNameArray[0]], propNameArray.slice(1)); + } +}; /** * @function can-set.props.dotNotation dotNotation * @parent can-set.props @@ -509,10 +520,10 @@ props.dotNotation = function(dotProperty){ // one of the value arguments being defined implies its the can-set definition prop, since a set member itself wont // have obj['nested.property.name'] defined - if (aVal != undefined) { + if (aVal !== undefined) { propertyVal = aVal; modelVal = nestedLookup(b, propertyName.split('.')); - } else if (bVal != undefined) { + } else if (bVal !== undefined) { propertyVal = bVal; modelVal = nestedLookup(a, propertyName.split('.')); } @@ -522,16 +533,5 @@ props.dotNotation = function(dotProperty){ return compares; }; -var nestedLookup = function(obj, propNameArray) { - if (obj === undefined) { - return undefined; - } - - if (propNameArray.length == 1) { - return obj[propNameArray[0]]; - } else { - return nestedLookup(obj[propNameArray[0]], propNameArray.slice(1)); - } -}; module.exports = props; From afc72c15a250216c4feff3a5b677f542fa209650 Mon Sep 17 00:00:00 2001 From: Nils Lundquist Date: Tue, 11 Apr 2017 15:54:35 -0600 Subject: [PATCH 3/6] Avoid using QUnit notOk. Not a defined global in jshint checking. --- src/prop_tests/dotNotation_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prop_tests/dotNotation_test.js b/src/prop_tests/dotNotation_test.js index 5a6c714..29a503f 100644 --- a/src/prop_tests/dotNotation_test.js +++ b/src/prop_tests/dotNotation_test.js @@ -27,5 +27,5 @@ test('dotNotation set membership', function() { * x ∉ X | x.n.p != 'IL' */ res = alg.has({'n.p': 'IL'}, {n:{p:'MI'}}); - notOk(res, "object with nested property not a member of set using dotNotation"); + ok(res === false, "object with nested property not a member of set using dotNotation"); }); \ No newline at end of file From 1af1a818c48585465ac23694d53a5fdc262d9401 Mon Sep 17 00:00:00 2001 From: Nils Lundquist Date: Wed, 12 Apr 2017 14:55:41 -0600 Subject: [PATCH 4/6] Add deep nested property case --- src/prop_tests/dotNotation_test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/prop_tests/dotNotation_test.js b/src/prop_tests/dotNotation_test.js index 29a503f..0ab12eb 100644 --- a/src/prop_tests/dotNotation_test.js +++ b/src/prop_tests/dotNotation_test.js @@ -28,4 +28,13 @@ test('dotNotation set membership', function() { */ res = alg.has({'n.p': 'IL'}, {n:{p:'MI'}}); ok(res === false, "object with nested property not a member of set using dotNotation"); + + /* + * For a property 'n.p.s', with value 'IL' + * x ∈ X | x.n.p.s == 'IL' + */ + prop = props.dotNotation('n.p.s'); + alg = new set.Algebra(prop); + res = alg.has({'n.p.s': 'IL'}, {n:{p:{s:'IL'}}}); + ok(res, "object with deep nested property is member of set using dotNotation"); }); \ No newline at end of file From 4fa19ca40ff4f1fd54e448aab53435a79ece6277 Mon Sep 17 00:00:00 2001 From: Nils Lundquist Date: Thu, 13 Apr 2017 11:17:13 -0600 Subject: [PATCH 5/6] Changed dotNotation prop to try to do a dotNotation lookup on a parent whenever a provided value is undefined. Needed when comparing two dot notation sets. Added set equality and subset tests. --- src/prop_tests/dotNotation_test.js | 60 ++++++++++++++++++++++++++++-- src/props.js | 18 ++++----- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/prop_tests/dotNotation_test.js b/src/prop_tests/dotNotation_test.js index 0ab12eb..126608a 100644 --- a/src/prop_tests/dotNotation_test.js +++ b/src/prop_tests/dotNotation_test.js @@ -3,7 +3,7 @@ var QUnit = require("steal-qunit"); var set = require('../set-core'), props = require("../props"); -QUnit.module("can-set props.boolean"); +QUnit.module("can-set props.dotNotation"); /* * For the dotNotation prop, we define sets like so: @@ -18,8 +18,8 @@ test('dotNotation set membership', function() { * x ∈ X | x.n.p == 'IL' */ var prop = props.dotNotation('n.p'), - alg = new set.Algebra(prop), - res = alg.has({'n.p': 'IL'}, {n:{p:'IL'}}); + alg = new set.Algebra(prop), + res = alg.has({'n.p': 'IL'}, {n:{p:'IL'}}); ok(res, "object with nested property is member of set using dotNotation"); /* @@ -37,4 +37,58 @@ test('dotNotation set membership', function() { alg = new set.Algebra(prop); res = alg.has({'n.p.s': 'IL'}, {n:{p:{s:'IL'}}}); ok(res, "object with deep nested property is member of set using dotNotation"); +}); + +test('dotNotation set equality', function() { + var prop = props.dotNotation('n.p'), + alg = new set.Algebra(prop), + set1 = {'n.p': 'IL'}, + set2 = {'n.p': 'IL'}, + set3 = {'n.p': 'MI'}, + set4 = {n:{p:'MI'}}; + + /* + * {x | x ∈ X, x.n.p == 'IL'} = {y | y ∈ Y, y.n.p == 'IL'} + */ + ok(alg.equal(set1, set2) && alg.equal(set2, set1), "sets with dotNotation properties are equivalent"); + + /* + * {x | x ∈ X, x.n.p == 'IL'} != {y | y ∈ Y, y.n.p == 'MI'} + */ + ok(alg.equal(set1, set3) === false, "sets with dotNotation properties are not equivalent"); + + /* + * {x | x ∈ X, x.n.p == 'MI'} = {y | y ∈ Y, y.n.p == 'MI'} + */ + ok(alg.equal(set4, set3) === false, "sets with dotNotation properties are equivalent to sets with nested properties"); +}); + +test('dotNotation set subset', function() { + var alg = new set.Algebra( + props.dotNotation('address.state'), + props.dotNotation('address.city') + ), + set1 = {'address.state': 'IL'}, + set2 = {'address.state': 'IL', 'address.city': 'Chicago'}, + set3 = {address: {state: 'IL', city: 'Chicago'}}; + + /* + * {x | x ∈ X, x.address.state = 'IL', x.address.city = 'Chicago'} ⊆ {y | y ∈ Y, y.address.state == 'IL'} + */ + ok(alg.subset(set2, set1), "sets with dotNotation property is a subset of another dotNotation set"); + + /* + * {x | x ∈ X, x.address.state = 'IL', x.address.city = 'Chicago'} ⊆ {y | y ∈ Y, y.address.state == 'IL'} + */ + ok(alg.subset(set3, set1), "sets with nested property notation is a subset of a dotNotation set"); + + /* + * {y | y ∈ Y, y.address.state == 'IL'} ⊆ ξ + */ + ok(alg.subset(set1, {}), "sets with dotNotation properties are subsets of the universal set"); + + /* + * ξ ⊄ {y | y ∈ Y, y.address.state == 'IL'} + */ + ok(alg.subset({}, set1) === false, "the universal set is not a subset of a set with dotNotation"); }); \ No newline at end of file diff --git a/src/props.js b/src/props.js index c412c7f..cac7b9d 100644 --- a/src/props.js +++ b/src/props.js @@ -516,19 +516,15 @@ props.dotNotation = function(dotProperty){ var compares = new clause.Where({}); compares[dotProperty] = function(aVal, bVal, a, b, propertyName) { - var modelVal, propertyVal; - - // one of the value arguments being defined implies its the can-set definition prop, since a set member itself wont - // have obj['nested.property.name'] defined - if (aVal !== undefined) { - propertyVal = aVal; - modelVal = nestedLookup(b, propertyName.split('.')); - } else if (bVal !== undefined) { - propertyVal = bVal; - modelVal = nestedLookup(a, propertyName.split('.')); + // if the value wasn't picked out from the parent try a dotNotation lookup + if (aVal == undefined) { + aVal = nestedLookup(a, propertyName.split('.')); + } + if (bVal == undefined) { + bVal = nestedLookup(b, propertyName.split('.')); } - return propertyVal === modelVal; + return aVal === bVal; }; return compares; From dd8deeb1570798b5582d692a6ce16abbc3f95513 Mon Sep 17 00:00:00 2001 From: Nils Lundquist Date: Thu, 13 Apr 2017 11:23:33 -0600 Subject: [PATCH 6/6] Use '=== undefined' --- src/props.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/props.js b/src/props.js index cac7b9d..380f026 100644 --- a/src/props.js +++ b/src/props.js @@ -517,10 +517,10 @@ props.dotNotation = function(dotProperty){ compares[dotProperty] = function(aVal, bVal, a, b, propertyName) { // if the value wasn't picked out from the parent try a dotNotation lookup - if (aVal == undefined) { + if (aVal === undefined) { aVal = nestedLookup(a, propertyName.split('.')); } - if (bVal == undefined) { + if (bVal === undefined) { bVal = nestedLookup(b, propertyName.split('.')); }