From e7f00afc77d66093f2bedb29ba64cf2aae3e4b1f Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 23 Sep 2016 00:58:19 +0900 Subject: [PATCH 1/2] support circular structure for logger --- src/plugins/logger.js | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/plugins/logger.js b/src/plugins/logger.js index e1d27f7a0..9120f3ef0 100644 --- a/src/plugins/logger.js +++ b/src/plugins/logger.js @@ -6,13 +6,13 @@ export default function createLogger ({ mutationTransformer = mut => mut } = {}) { return store => { - let prevState = JSON.parse(JSON.stringify(store.state)) + let prevState = deepCopy(store.state) store.subscribe((mutation, state) => { if (typeof console === 'undefined') { return } - const nextState = JSON.parse(JSON.stringify(state)) + const nextState = deepCopy(state) const time = new Date() const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` const formattedMutation = mutationTransformer(mutation) @@ -50,3 +50,39 @@ function repeat (str, times) { function pad (num, maxLength) { return repeat('0', maxLength - num.toString().length) + num } + +function find (list, f) { + return list.filter(f)[0] +} + +/** + * Deep copy the given object considering circular structure. + * This function caches all nested objects and its copies. + * If it detects circular structure, use cached copy to avoid infinite loop. + */ +function deepCopy (obj, cache = []) { + // just return if obj is immutable value + if (obj === null || typeof obj !== 'object') { + return obj + } + + // if obj is hit, it is in circular structure + const hit = find(cache, c => c.original === obj) + if (hit) { + return hit.copy + } + + const copy = Array.isArray(obj) ? [] : {} + // put the copy into cache at first + // because we want to refer it in recursive deepCopy + cache.push({ + original: obj, + copy + }) + + Object.keys(obj).forEach(key => { + copy[key] = deepCopy(obj[key], cache) + }) + + return copy +} From 143a71e235f3f47961120b503a6d9a8d42ef477b Mon Sep 17 00:00:00 2001 From: ktsn Date: Fri, 23 Sep 2016 02:10:36 +0900 Subject: [PATCH 2/2] add deepCopy unit test --- package.json | 2 +- src/plugins/logger.js | 38 ++-------------------------------- src/util.js | 48 +++++++++++++++++++++++++++++++++++++++++++ test/unit/util.js | 42 +++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 37 deletions(-) create mode 100644 test/unit/util.js diff --git a/package.json b/package.json index cfb04836c..594923d6f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "chat": "cd examples/chat && webpack-dev-server --inline --hot --config ../webpack.shared.config.js", "build": "node build/build.js", "build-examples": "BABEL_ENV=development webpack --config examples/webpack.build-all.config.js", - "unit": "BABEL_ENV=development mocha test/unit/test.js --compilers js:babel-core/register", + "unit": "BABEL_ENV=development mocha test/unit/*.js --compilers js:babel-core/register", "pree2e": "npm run build-examples", "e2e": "casperjs test --concise ./test/e2e", "test": "eslint src && npm run unit && npm run e2e", diff --git a/src/plugins/logger.js b/src/plugins/logger.js index 9120f3ef0..1bef55cc7 100644 --- a/src/plugins/logger.js +++ b/src/plugins/logger.js @@ -1,5 +1,7 @@ // Credits: borrowed code from fcomb/redux-logger +import { deepCopy } from '../util' + export default function createLogger ({ collapsed = true, transformer = state => state, @@ -50,39 +52,3 @@ function repeat (str, times) { function pad (num, maxLength) { return repeat('0', maxLength - num.toString().length) + num } - -function find (list, f) { - return list.filter(f)[0] -} - -/** - * Deep copy the given object considering circular structure. - * This function caches all nested objects and its copies. - * If it detects circular structure, use cached copy to avoid infinite loop. - */ -function deepCopy (obj, cache = []) { - // just return if obj is immutable value - if (obj === null || typeof obj !== 'object') { - return obj - } - - // if obj is hit, it is in circular structure - const hit = find(cache, c => c.original === obj) - if (hit) { - return hit.copy - } - - const copy = Array.isArray(obj) ? [] : {} - // put the copy into cache at first - // because we want to refer it in recursive deepCopy - cache.push({ - original: obj, - copy - }) - - Object.keys(obj).forEach(key => { - copy[key] = deepCopy(obj[key], cache) - }) - - return copy -} diff --git a/src/util.js b/src/util.js index fea7d5e46..c8729791a 100644 --- a/src/util.js +++ b/src/util.js @@ -25,6 +25,54 @@ export function mergeObjects (arr) { }, {}) } +/** + * Get the first item that pass the test + * by second argument function + * + * @param {Array} list + * @param {Function} f + * @return {*} + */ +function find (list, f) { + return list.filter(f)[0] +} + +/** + * Deep copy the given object considering circular structure. + * This function caches all nested objects and its copies. + * If it detects circular structure, use cached copy to avoid infinite loop. + * + * @param {*} obj + * @param {Array} cache + * @return {*} + */ +export function deepCopy (obj, cache = []) { + // just return if obj is immutable value + if (obj === null || typeof obj !== 'object') { + return obj + } + + // if obj is hit, it is in circular structure + const hit = find(cache, c => c.original === obj) + if (hit) { + return hit.copy + } + + const copy = Array.isArray(obj) ? [] : {} + // put the copy into cache at first + // because we want to refer it in recursive deepCopy + cache.push({ + original: obj, + copy + }) + + Object.keys(obj).forEach(key => { + copy[key] = deepCopy(obj[key], cache) + }) + + return copy +} + /** * Check whether the given value is Object or not * diff --git a/test/unit/util.js b/test/unit/util.js new file mode 100644 index 000000000..1ce62fea3 --- /dev/null +++ b/test/unit/util.js @@ -0,0 +1,42 @@ +import { expect } from 'chai' +import { deepCopy } from '../../src/util' + +describe('util', () => { + it('deepCopy: nornal structure', () => { + const original = { + a: 1, + b: 'string', + c: true, + d: null, + e: undefined + } + const copy = deepCopy(original) + + expect(copy).to.deep.equal(original) + }) + + it('deepCopy: nested structure', () => { + const original = { + a: { + b: 1, + c: [2, 3, { + d: 4 + }] + } + } + const copy = deepCopy(original) + + expect(copy).to.deep.equal(original) + }) + + it('deepCopy: circular structure', () => { + const original = { + a: 1 + } + original.circular = original + + const copy = deepCopy(original) + + expect(copy).to.deep.equal(original) + }) +})