diff --git a/src/SearchParameters/RefinementList.js b/src/SearchParameters/RefinementList.js index c44002dd9..9c39f3557 100644 --- a/src/SearchParameters/RefinementList.js +++ b/src/SearchParameters/RefinementList.js @@ -15,8 +15,8 @@ var isUndefined = require('lodash/isUndefined'); var isFunction = require('lodash/isFunction'); var isEmpty = require('lodash/isEmpty'); -var defaults = require('lodash/defaults'); +var defaultsPure = require('../functions/defaultsPure'); var omit = require('../functions/omit'); var lib = { @@ -42,7 +42,7 @@ var lib = { mod[attribute] = facetRefinement; - return defaults({}, mod, refinementList); + return defaultsPure({}, mod, refinementList); }, /** * Removes refinement(s) for an attribute: diff --git a/src/SearchParameters/index.js b/src/SearchParameters/index.js index ad42c819c..c46b9915c 100644 --- a/src/SearchParameters/index.js +++ b/src/SearchParameters/index.js @@ -7,9 +7,9 @@ var isEqual = require('lodash/isEqual'); var isUndefined = require('lodash/isUndefined'); var isFunction = require('lodash/isFunction'); -var defaults = require('lodash/defaults'); var merge = require('lodash/merge'); +var defaultsPure = require('../functions/defaultsPure'); var find = require('../functions/find'); var valToNumber = require('../functions/valToNumber'); var omit = require('../functions/omit'); @@ -1020,7 +1020,7 @@ SearchParameters.prototype = { } return this.setQueryParameters({ - hierarchicalFacetsRefinements: defaults({}, mod, this.hierarchicalFacetsRefinements) + hierarchicalFacetsRefinements: defaultsPure({}, mod, this.hierarchicalFacetsRefinements) }); }, @@ -1038,7 +1038,7 @@ SearchParameters.prototype = { var mod = {}; mod[facet] = [path]; return this.setQueryParameters({ - hierarchicalFacetsRefinements: defaults({}, mod, this.hierarchicalFacetsRefinements) + hierarchicalFacetsRefinements: defaultsPure({}, mod, this.hierarchicalFacetsRefinements) }); }, @@ -1055,7 +1055,7 @@ SearchParameters.prototype = { var mod = {}; mod[facet] = []; return this.setQueryParameters({ - hierarchicalFacetsRefinements: defaults({}, mod, this.hierarchicalFacetsRefinements) + hierarchicalFacetsRefinements: defaultsPure({}, mod, this.hierarchicalFacetsRefinements) }); }, /** diff --git a/src/SearchResults/index.js b/src/SearchResults/index.js index 45fd059e6..ded126d8c 100644 --- a/src/SearchResults/index.js +++ b/src/SearchResults/index.js @@ -2,7 +2,6 @@ var orderBy = require('lodash/orderBy'); -var defaults = require('lodash/defaults'); var merge = require('lodash/merge'); var isFunction = require('lodash/isFunction'); @@ -10,6 +9,7 @@ var isFunction = require('lodash/isFunction'); var partial = require('lodash/partial'); var partialRight = require('lodash/partialRight'); +var defaultsPure = require('../functions/defaultsPure'); var compact = require('../functions/compact'); var find = require('../functions/find'); var findIndex = require('../functions/findIndex'); @@ -462,7 +462,7 @@ function SearchResults(state, results) { self.disjunctiveFacets[position] = { name: dfacet, - data: defaults({}, facetResults, dataFromMainRequest), + data: defaultsPure({}, facetResults, dataFromMainRequest), exhaustive: result.exhaustiveFacetsCount }; assignFacetStats(self.disjunctiveFacets[position], result.facets_stats, dfacet); @@ -526,7 +526,7 @@ function SearchResults(state, results) { defaultData[root] = self.hierarchicalFacets[position][attributeIndex].data[root]; } - self.hierarchicalFacets[position][attributeIndex].data = defaults( + self.hierarchicalFacets[position][attributeIndex].data = defaultsPure( defaultData, facetResults, self.hierarchicalFacets[position][attributeIndex].data @@ -697,7 +697,7 @@ SearchResults.prototype.getFacetValues = function(attribute, opts) { var facetValues = extractNormalizedFacetValues(this, attribute); if (!facetValues) throw new Error(attribute + ' is not a retrieved facet.'); - var options = defaults({}, opts, {sortBy: SearchResults.DEFAULT_SORT}); + var options = defaultsPure({}, opts, {sortBy: SearchResults.DEFAULT_SORT}); if (Array.isArray(options.sortBy)) { var order = formatSort(options.sortBy, SearchResults.DEFAULT_SORT); diff --git a/src/functions/defaultsPure.js b/src/functions/defaultsPure.js new file mode 100644 index 000000000..cb93246dd --- /dev/null +++ b/src/functions/defaultsPure.js @@ -0,0 +1,14 @@ +'use strict'; + +// NOTE: this behaves like lodash/defaults, but doesn't mutate the target +module.exports = function defaultsPure() { + const sources = Array.prototype.slice.call(arguments); + return sources.reduceRight(function(acc, source) { + Object.keys(Object(source)).forEach(function(key) { + if (source[key] !== undefined) { + acc[key] = source[key]; + } + }); + return acc; + }, {}); +}; diff --git a/test/spec/functions/defaultsPure.js b/test/spec/functions/defaultsPure.js new file mode 100644 index 000000000..ca13ece14 --- /dev/null +++ b/test/spec/functions/defaultsPure.js @@ -0,0 +1,66 @@ +'use strict'; + +var clone = require('lodash/clone'); +var defaults = require('../../../src/functions/defaultsPure'); + +// tests modified from lodash source + +it('should assign source properties if missing on `object`', function() { + var actual = defaults({'a': 1}, {'a': 2, 'b': 2}); + expect(actual).toEqual({'a': 1, 'b': 2}); +}); + +it('should accept multiple sources', function() { + var expected = {'a': 1, 'b': 2, 'c': 3}; + var actual = defaults({'a': 1, 'b': 2}, {'b': 3}, {'c': 3}); + + expect(actual).toEqual(expected); + + actual = defaults({'a': 1, 'b': 2}, {'b': 3, 'c': 3}, {'c': 2}); + expect(actual).toEqual(expected); +}); + +it('should not overwrite `null` values', function() { + var actual = defaults({'a': null}, {'a': 1}); + expect(actual.a).toBe(null); +}); + +it('should overwrite `undefined` values', function() { + var actual = defaults({'a': undefined}, {'a': 1}); + expect(actual.a).toBe(1); +}); + +it('should assign `undefined` values', function() { + var source = {'a': undefined, 'b': 1}; + var actual = defaults({}, source); + + expect(actual).toEqual({'a': undefined, 'b': 1}); +}); + +it('should assign properties that shadow those on `Object.prototype`', function() { + var object = { + 'constructor': Object.prototype.constructor, + 'hasOwnProperty': Object.prototype.hasOwnProperty, + 'isPrototypeOf': Object.prototype.isPrototypeOf, + 'propertyIsEnumerable': Object.prototype.propertyIsEnumerable, + 'toLocaleString': Object.prototype.toLocaleString, + 'toString': Object.prototype.toString, + 'valueOf': Object.prototype.valueOf + }; + + var source = { + 'constructor': 1, + 'hasOwnProperty': 2, + 'isPrototypeOf': 3, + 'propertyIsEnumerable': 4, + 'toLocaleString': 5, + 'toString': 6, + 'valueOf': 7 + }; + + var expected = clone(source); + expect(defaults({}, source)).toEqual(expected); + + expected = clone(object); + expect(defaults({}, object, source)).toEqual(expected); +});