summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Slaughter <pslaughter@gitlab.com>2018-09-13 16:00:56 -0500
committerPaul Slaughter <pslaughter@gitlab.com>2018-09-13 17:46:45 -0500
commit578efd5fe7320e9a160236d40d1cdd4ca0aec0da (patch)
tree7cf9d511f78cc85a6c792f6a2ea09cf187868f9c
parent32b96bfd81ff254142dbd9c73e1a494308213cb3 (diff)
downloadgitlab-ce-stash-deep-merge-util.tar.gz
Create deep_merge javascript utilstash-deep-merge-util
- This will help simplify `replaceState()` calls in tests - We could've used a third-party package for this, but this is a pretty small utility and might not be worth the headache of a whole other dependency. Update deep_merge
-rw-r--r--app/assets/javascripts/lib/utils/deep_merge.js51
-rw-r--r--spec/javascripts/lib/utils/deep_merge_spec.js78
2 files changed, 129 insertions, 0 deletions
diff --git a/app/assets/javascripts/lib/utils/deep_merge.js b/app/assets/javascripts/lib/utils/deep_merge.js
new file mode 100644
index 00000000000..82595ddd6b4
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/deep_merge.js
@@ -0,0 +1,51 @@
+import _ from 'underscore';
+
+function isMergeable(obj) {
+ return obj && _.isObject(obj);
+}
+
+function deepMergeInto(dest, source) {
+ return Object.keys(source)
+ .reduce((acc, key) => Object.assign(acc, {
+ [key]: isMergeable(acc[key]) && isMergeable(source[key])
+ ? deepMergeInto({ ...acc[key] }, source[key])
+ : source[key],
+ }), dest);
+}
+
+/**
+ * Return a deeply merge of the given objects
+ *
+ * Rules:
+ * - Objects are merged from left to right.
+ * - Values are merged only if they are mergeable,
+ * otherwise the left value is replaced with the right value.
+ * - Immutable.
+ *
+ * Example:
+ ```javascript
+ const a = {
+ foo: { val: 1 },
+ bar: 'abc',
+ zoo: {},
+ };
+ const b = {
+ foo: { reason: 'math' },
+ bar: 'abc/abc',
+ zoo: null,
+ };
+ const result = deepMerge(a, b);
+
+ // result is
+ // {
+ // foo: { val: 1, reason: 'math' },
+ // bar: 'abc/abc',
+ // zoo: null,
+ // }
+ ```
+ *
+ * @param {...any} args
+ */
+export default function deepMerge(...objs) {
+ return objs.reduce(deepMergeInto, {});
+}
diff --git a/spec/javascripts/lib/utils/deep_merge_spec.js b/spec/javascripts/lib/utils/deep_merge_spec.js
new file mode 100644
index 00000000000..f1ccad20082
--- /dev/null
+++ b/spec/javascripts/lib/utils/deep_merge_spec.js
@@ -0,0 +1,78 @@
+import deepMerge from '~/lib/utils/deep_merge';
+
+/**
+ * This array of objects is used to test the following cases:
+ * - [x] merging more than 2 objects
+ * - [x] merging shallow properties
+ * - [x] merging deep properties
+ * - [x] overwriting when the source property is not mergeable
+ * - [x] overwriting when the target property is not mergeable
+ */
+const getTestArgs = () => [
+ {
+ foo: {
+ author: 'Franz',
+ chapter: {
+ page: 3,
+ },
+ },
+ bar: null,
+ },
+ {
+ foo: {
+ author: 'Franz Kafka',
+ title: 'The Trial',
+ },
+ bar: {
+ zoo: 'ny',
+ animal: 'monkey',
+ },
+ },
+ {
+ foo: {
+ chapter: {
+ page: 3,
+ title: 'The First Chapter',
+ },
+ },
+ bar: {
+ zoo: 'la',
+ },
+ car: 'fast',
+ },
+];
+
+const TEST_RESULT = {
+ foo: {
+ author: 'Franz Kafka',
+ chapter: {
+ page: 3,
+ title: 'The First Chapter',
+ },
+ title: 'The Trial',
+ },
+ bar: {
+ zoo: 'la',
+ animal: 'monkey',
+ },
+ car: 'fast',
+};
+
+describe('deepMerge', () => {
+ it('merges objects deeply', () => {
+ const args = getTestArgs();
+
+ const result = deepMerge(...args);
+
+ expect(result).toEqual(TEST_RESULT);
+ });
+
+ it('does not mutate objects', () => {
+ const args = getTestArgs();
+ const origJSON = JSON.stringify(args);
+
+ deepMerge(...args);
+
+ expect(JSON.stringify(args)).toEqual(origJSON);
+ });
+});