diff --git a/agent/Agent.js b/agent/Agent.js index 778e8ce063..127691097a 100644 --- a/agent/Agent.js +++ b/agent/Agent.js @@ -12,6 +12,7 @@ var {EventEmitter} = require('events'); var assign = require('object-assign'); +var immutableUtils = require('./immutableUtils'); import type {RendererID, DataType, OpaqueNodeHandle, NativeType, Helpers} from '../backend/types'; @@ -378,7 +379,11 @@ function randid() { function getIn(base, path) { return path.reduce((obj, attr) => { - return obj ? obj[attr] : null; + var alt; + if (obj !== null && immutableUtils.isImmutable(obj) && obj.get) { + alt = obj.get(attr); + } + return obj ? alt || obj[attr] : null; }, base); } diff --git a/agent/Bridge.js b/agent/Bridge.js index b80844402e..3124b67606 100644 --- a/agent/Bridge.js +++ b/agent/Bridge.js @@ -13,6 +13,7 @@ var consts = require('./consts'); var hydrate = require('./hydrate'); var dehydrate = require('./dehydrate'); +var immutableUtils = require('../agent/immutableUtils'); type AnyFn = (...x: any) => any; export type Wall = { @@ -103,6 +104,7 @@ type PayloadType = { * determined that an object is no longer needed, call `.forget(id)` to clean * up. */ + class Bridge { _buffer: Array<{evt: string, data: any}>; _cbs: Map; @@ -318,6 +320,11 @@ class Bridge { if (val) { var protod = false; var isFn = typeof val === 'function'; + + if (immutableUtils.isImmutable(val)) { + val = immutableUtils.shallowToJS(val); + } + Object.getOwnPropertyNames(val).forEach(name => { if (name === '__proto__') { protod = true; @@ -354,7 +361,11 @@ class Bridge { function getIn(base, path) { return path.reduce((obj, attr) => { - return obj ? obj[attr] : null; + var alt; + if (obj !== null && immutableUtils.isImmutable(obj) && obj.get) { + alt = obj.get(attr); + } + return obj ? alt || obj[attr] : null; }, base); } diff --git a/agent/__tests__/dehydrate-test.js b/agent/__tests__/dehydrate-test.js index 37690400d4..73d12d0227 100644 --- a/agent/__tests__/dehydrate-test.js +++ b/agent/__tests__/dehydrate-test.js @@ -10,7 +10,10 @@ 'use strict'; jest.dontMock('../dehydrate'); -var dehydrate = require('../dehydrate'); +jest.dontMock('../immutableUtils'); +jest.dontMock('immutable'); +var dehydrate = require('../dehydrate'), + Immutable = require('immutable'); describe('dehydrate', () => { it('leaves an empty object alone', () => { @@ -55,4 +58,26 @@ describe('dehydrate', () => { expect(result.a.b.c).toEqual({type: 'array', name: 'Array', meta: {length: 2}}); expect(result.a.b.d).toEqual({type: 'object', name: 'Something', meta: null}); }); + + it('cleans a shallowly nested Immutable objects with the appropriate name', function() { + var object = {c: Immutable.List([1, 3]), d: Immutable.Map(), e: Immutable.Set()}; + var cleaned = []; + var result = dehydrate(object, cleaned); + + expect(cleaned).toEqual([['c'], ['d'], ['e']]); + expect(result.c).toEqual({type: 'object', name: 'Immutable.List'}); + expect(result.d).toEqual({type: 'object', name: 'Immutable.Map'}); + expect(result.e).toEqual({type: 'object', name: 'Immutable.Set'}); + }); + + it('cleans a deeply nested Immutable objects with the appropriate name', function() { + var object = {a: {b: {c: Immutable.List([1, 3]), d: Immutable.Map(), e: Immutable.Set()}}}; + var cleaned = []; + var result = dehydrate(object, cleaned); + + expect(cleaned).toEqual([['a', 'b', 'c'], ['a', 'b', 'd'], ['a', 'b', 'e']]); + expect(result.a.b.c).toEqual({type: 'object', name: 'Immutable.List', meta: null}); + expect(result.a.b.d).toEqual({type: 'object', name: 'Immutable.Map', meta: null}); + expect(result.a.b.e).toEqual({type: 'object', name: 'Immutable.Set', meta: null}); + }); }); diff --git a/agent/__tests__/immutableUtils-test.js b/agent/__tests__/immutableUtils-test.js new file mode 100644 index 0000000000..356e683d6e --- /dev/null +++ b/agent/__tests__/immutableUtils-test.js @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. a additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ +'use strict'; + +jest.dontMock('../immutableUtils'); +jest.dontMock('immutable'); +var immutableUtils = require('../immutableUtils'), + Immutable = require('immutable'); + +describe('immutableUtils', () => { + + describe('getImmutableName()', function() { + it('generates the right name for an OrderedMap', () => { + var name = immutableUtils.getImmutableName(Immutable.OrderedMap()); + expect(name).toBe('Immutable.OrderedMap'); + }); + + it('generates the right name for an OrderedSet', () => { + var name = immutableUtils.getImmutableName(Immutable.OrderedSet()); + expect(name).toBe('Immutable.OrderedSet'); + }); + + it('generates the right name for a Map', () => { + var name = immutableUtils.getImmutableName(Immutable.Map()); + expect(name).toBe('Immutable.Map'); + }); + + it('generates the right name for a Set', () => { + var name = immutableUtils.getImmutableName(Immutable.Set()); + expect(name).toBe('Immutable.Set'); + }); + + it('generates the right name for a List', () => { + var name = immutableUtils.getImmutableName(Immutable.List()); + expect(name).toBe('Immutable.List'); + }); + + it('generates the right name for a Stack', () => { + var name = immutableUtils.getImmutableName(Immutable.Stack()); + expect(name).toBe('Immutable.Stack'); + }); + + it('generates the right name for a Seq', () => { + var name = immutableUtils.getImmutableName(Immutable.Seq()); + expect(name).toBe('Immutable.Seq'); + }); + }); + + describe('shallowToJS()', function() { + it('converts a Map correctly', function() { + var map = Immutable.Map({ + a: 1, + b: 2, + c: Immutable.Map() + }); + var result = immutableUtils.shallowToJS(map); + expect(result.a).toBe(1); + expect(result.b).toBe(2); + expect(Immutable.Map.isMap(result.c)).toBe(true); + }); + + it('converts a List correctly', function() { + var list = Immutable.List(['a', 'b', Immutable.Map()]); + var result = immutableUtils.shallowToJS(list); + expect(result[0]).toBe('a'); + expect(result[1]).toBe('b'); + expect(Immutable.Map.isMap(result[2])).toBe(true); + }); + + it('converts a Set correctly', function() { + var set = Immutable.Set([1, 2, 3, Immutable.List()]); + var result = immutableUtils.shallowToJS(set); + + expect(Array.isArray(result)).toBe(true); + expect(result.filter(Immutable.List.isList).length > 0).toBe(true); + }); + }); + +}); diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 209f5719c5..39bb232b78 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -30,6 +30,9 @@ * } * and cleaned = [["some", "attr"], ["other"]] */ + +var immutableUtils = require('./immutableUtils'); + function dehydrate(data: Object, cleaned: Array>, path?: Array, level?: number): string | Object { level = level || 0; path = path || []; @@ -60,11 +63,16 @@ function dehydrate(data: Object, cleaned: Array>, path?: Array 2) { cleaned.push(path); return { type: Array.isArray(data) ? 'array' : 'object', - name: data.constructor.name === 'Object' ? '' : data.constructor.name, + name: data.constructor.name === 'Object' ? '' : name, meta: Array.isArray(data) ? { length: data.length, } : null, @@ -78,8 +86,8 @@ function dehydrate(data: Object, cleaned: Array>, path?: Array