From a2877ae86fb80eb1ebdf0af639e7e916cc64c037 Mon Sep 17 00:00:00 2001 From: Igor ZInovev Date: Sun, 26 Feb 2017 21:38:42 +0300 Subject: [PATCH 1/9] Deferred object getters evaluation --- agent/Agent.js | 9 ++++++++ agent/dehydrate.js | 17 +++++++++++++- frontend/DataView/DataView.js | 4 ++++ frontend/DataView/Getter.js | 43 +++++++++++++++++++++++++++++++++++ frontend/PropState.js | 7 ++++++ frontend/Store.js | 15 ++++++++++++ test/example/target.js | 24 ++++++++++++++++++- 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 frontend/DataView/Getter.js diff --git a/agent/Agent.js b/agent/Agent.js index cefed2aac6..d7d2b0f1be 100644 --- a/agent/Agent.js +++ b/agent/Agent.js @@ -79,6 +79,7 @@ import type Bridge from './Bridge'; * - mount * - update * - unmount + * - sendGetterValue */ class Agent extends EventEmitter { // the window or global -> used to "make a value available in the console" @@ -148,6 +149,7 @@ class Agent extends EventEmitter { bridge.on('setState', this._setState.bind(this)); bridge.on('setProps', this._setProps.bind(this)); bridge.on('setContext', this._setContext.bind(this)); + bridge.on('evalGetter', this._evalGetter.bind(this)); bridge.on('makeGlobal', this._makeGlobal.bind(this)); bridge.on('highlight', id => this.highlight(id)); bridge.on('highlightMany', id => this.highlightMany(id)); @@ -202,6 +204,7 @@ class Agent extends EventEmitter { bridge.forget(id); }); this.on('setSelection', data => bridge.send('select', data)); + this.on('sendGetterValue', (data) => bridge.send('evalgetterresult', data)); } scrollToNode(id: ElementID): void { @@ -321,6 +324,12 @@ class Agent extends EventEmitter { } } + _evalGetter({id, path}: {id: ElementID, path: Array}) { + var obj = this.elementData.get(id).props; + var value = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); + this.emit('sendGetterValue', {id, path, value}); + } + _makeGlobal({id, path}: {id: ElementID, path: Array}) { var data = this.elementData.get(id); if (!data) { diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 0fddfac5a1..8145304915 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -84,9 +84,24 @@ function dehydrate(data: Object, cleaned: Array>, path?: Array ); complex = false; + } else if (data[consts.type] === 'getter') { + preview = ; + complex = false; } else { preview = previewComplex(data); } diff --git a/frontend/DataView/Getter.js b/frontend/DataView/Getter.js new file mode 100644 index 0000000000..e85632b609 --- /dev/null +++ b/frontend/DataView/Getter.js @@ -0,0 +1,43 @@ +/** + * 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. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +'use strict'; +var React = require('react'); + +class Getter extends React.Component { + + constructor(props: Object) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + this.context.onEvalGetter(this.props.path); + } + + render() { + return (
(...)
); + } +} + +const style = { + 'cursor': 'pointer', +}; + +Getter.propTypes = { + data: React.PropTypes.any, + path: React.PropTypes.array, +}; + +Getter.contextTypes = { + onEvalGetter: React.PropTypes.func, +}; + +module.exports = Getter; diff --git a/frontend/PropState.js b/frontend/PropState.js index 043567de73..ef51f85e97 100644 --- a/frontend/PropState.js +++ b/frontend/PropState.js @@ -27,6 +27,9 @@ class PropState extends React.Component { onChange: (path, val) => { this.props.onChange(path, val); }, + onEvalGetter: (path) => { + this.props.onEvalGetter(path); + }, }; } @@ -156,6 +159,7 @@ class PropState extends React.Component { PropState.childContextTypes = { onChange: React.PropTypes.func, + onEvalGetter: React.PropTypes.func, }; var WrappedPropState = decorate({ @@ -186,6 +190,9 @@ var WrappedPropState = decorate({ showMenu(e, val, path, name) { store.showContextMenu('attr', e, store.selected, node, val, path, name); }, + onEvalGetter(path) { + store.evalGetter(store.selected, path.slice(1)); + }, inspect: store.inspect.bind(store, store.selected), }; }, diff --git a/frontend/Store.js b/frontend/Store.js index e447d01517..1d9e80b648 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -64,6 +64,7 @@ const DEFAULT_PLACEHOLDER = 'Search by Component Name'; * - selectFirstSearchResult * - toggleCollapse * - setProps/State/Context + * - evalGetter * - makeGlobal(id, path) * - setHover(id, isHovered) * - selectTop(id) @@ -151,6 +152,7 @@ class Store extends EventEmitter { this._bridge.on('mount', (data) => this._mountComponent(data)); this._bridge.on('update', (data) => this._updateComponent(data)); this._bridge.on('unmount', id => this._unmountComponent(id)); + this._bridge.on('evalgetterresult', (data) => this._setGetterValue(data)); this._bridge.on('select', ({id, quiet}) => { this._revealDeep(id); this.selectTop(this.skipWrapper(id), quiet); @@ -352,6 +354,10 @@ class Store extends EventEmitter { this._bridge.send('setContext', {id, path, value}); } + evalGetter(id: ElementID, path: Array) { + this._bridge.send('evalGetter', {id, path}); + } + makeGlobal(id: ElementID, path: Array) { this._bridge.send('makeGlobal', {id, path}); } @@ -614,6 +620,15 @@ class Store extends EventEmitter { this._nodesByName = this._nodesByName.set(node.get('name'), this._nodesByName.get(node.get('name')).delete(id)); } } + + _setGetterValue(data: DataType) { + var node = this._nodes.get(data.id); + var props = node.get('props'); + var last = data.path.length - 1; + var propObj = data.path.slice(0, last).reduce((obj_, attr) => obj_ ? obj_[attr] : null, props); + propObj[data.path[last]] = data.value; + this.emit(data.id); + } } module.exports = Store; diff --git a/test/example/target.js b/test/example/target.js index d673e82763..2f0d97d09c 100644 --- a/test/example/target.js +++ b/test/example/target.js @@ -309,6 +309,28 @@ class Something { var someVal = new Something(); someVal.awesome = 2; +var protoWithGetter = { + get upper() { + return this.name.toUpperCase(); + }, +}; + +var withProtoWithGetter = Object.create(protoWithGetter); +withProtoWithGetter.name = 'Foo'; + +var funWithGetters = { + name: 'foo', + get sideEffect() { + alert('Wow!'); + }, + get simple() { + return this.name.toUpperCase(); + }, + get object() { + return {foo: 'bar'}; + }, +}; + class Wrap extends React.Component { render() { return ( @@ -325,7 +347,7 @@ class Wrap extends React.Component {
<
*/} - + ); } From e9f2c028265d0af1e08b344406b7b7ea5c3bb988 Mon Sep 17 00:00:00 2001 From: Igor Zinoviev Date: Thu, 2 Mar 2017 17:03:18 +0300 Subject: [PATCH 2/9] Fix flow errors --- agent/Agent.js | 6 +++++- frontend/DataView/Getter.js | 10 ++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/agent/Agent.js b/agent/Agent.js index d7d2b0f1be..670bd2fe6b 100644 --- a/agent/Agent.js +++ b/agent/Agent.js @@ -325,7 +325,11 @@ class Agent extends EventEmitter { } _evalGetter({id, path}: {id: ElementID, path: Array}) { - var obj = this.elementData.get(id).props; + var data = this.elementData.get(id); + if (!data) { + return; + } + var obj = data.props; var value = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); this.emit('sendGetterValue', {id, path, value}); } diff --git a/frontend/DataView/Getter.js b/frontend/DataView/Getter.js index e85632b609..2695ff20b5 100644 --- a/frontend/DataView/Getter.js +++ b/frontend/DataView/Getter.js @@ -12,18 +12,16 @@ var React = require('react'); class Getter extends React.Component { + handleClick() { + this.context.onEvalGetter(this.props.path); + } constructor(props: Object) { super(props); - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - this.context.onEvalGetter(this.props.path); } render() { - return (
(...)
); + return (
(…)
); } } From 8fffed52655776d281d603ae9bdb9f2b22aab311 Mon Sep 17 00:00:00 2001 From: Igor ZInovev Date: Thu, 16 Mar 2017 01:47:14 +0300 Subject: [PATCH 3/9] show evaluated getter in TreeView --- frontend/DataView/Getter.js | 2 +- frontend/PropVal.js | 3 +++ frontend/Store.js | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/DataView/Getter.js b/frontend/DataView/Getter.js index 2695ff20b5..9ee6259634 100644 --- a/frontend/DataView/Getter.js +++ b/frontend/DataView/Getter.js @@ -21,7 +21,7 @@ class Getter extends React.Component { } render() { - return (
(…)
); + return
(…)
; } } diff --git a/frontend/PropVal.js b/frontend/PropVal.js index 529e2c5c1f..f0faac1534 100644 --- a/frontend/PropVal.js +++ b/frontend/PropVal.js @@ -90,6 +90,9 @@ function previewProp(val: any, nested: boolean) { // the name is "Symbol(something)" return {val[consts.name]}; } + if (type === 'getter') { + return (…); + } } if (nested) { return {'{…}'}; diff --git a/frontend/Store.js b/frontend/Store.js index 1d9e80b648..2dd21fbda9 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -627,6 +627,8 @@ class Store extends EventEmitter { var last = data.path.length - 1; var propObj = data.path.slice(0, last).reduce((obj_, attr) => obj_ ? obj_[attr] : null, props); propObj[data.path[last]] = data.value; + var newProps = assign({}, props); + this._nodes = this._nodes.setIn([data.id, 'props'], newProps); this.emit(data.id); } } From 71d9c3d1fa02c4e45a6b5287019374a219f7a82f Mon Sep 17 00:00:00 2001 From: Igor Zinoviev Date: Thu, 4 May 2017 18:50:57 +0300 Subject: [PATCH 4/9] Respect property descriptors on editing prop values --- backend/__tests__/copyWithSet-test.js | 12 ++++++++++++ backend/copyWithSet.js | 21 +++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/backend/__tests__/copyWithSet-test.js b/backend/__tests__/copyWithSet-test.js index 2589fa7184..c1bf944d2c 100644 --- a/backend/__tests__/copyWithSet-test.js +++ b/backend/__tests__/copyWithSet-test.js @@ -37,4 +37,16 @@ describe('copyWithSet', function() { var res = copyWithSet([0, 1, {2: {3: [4, 5, {6: {7: 8}}, 9], 10: 11}}, 12], [2, '2', '3', 2, '6', '7'], 'moose'); expect(res).toEqual([0, 1, {2: {3: [4, 5, {6: {7: 'moose'}}, 9], 10: 11}}, 12]); }); + + it('must copy descriptors', function() { + var obj = { + _name: 'foo', + get name() { + return this._name + 'bar'; + }, + }; + + var res = copyWithSet(obj, ['a'], 'b'); + expect(res.name).toEqual('foobar'); + }); }); diff --git a/backend/copyWithSet.js b/backend/copyWithSet.js index 8c04739614..ceebf6a55c 100644 --- a/backend/copyWithSet.js +++ b/backend/copyWithSet.js @@ -15,9 +15,10 @@ function copyWithSetImpl(obj, path, idx, value) { return value; } var key = path[idx]; - var updated = Array.isArray(obj) ? obj.slice() : {...obj}; + var updated = Array.isArray(obj) ? obj.slice() : assignWithDescriptors(obj); // $FlowFixMe number or string is fine here - updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value); + var copy = copyWithSetImpl(obj[key], path, idx + 1, value); + updated[key] = copy; return updated; } @@ -25,4 +26,20 @@ function copyWithSet(obj: Object | Array, path: Array, val return copyWithSetImpl(obj, path, 0, value); } +function assignWithDescriptors(source) { + /* eslint-disable no-proto */ + var target = Object.create(source.__proto__); + /* eslint-enable no-proto */ + + Object.defineProperties(target, Object.keys(source).reduce((descriptors, key) => { + var descriptor = Object.getOwnPropertyDescriptor(source, key); + if (descriptor.hasOwnProperty('writable')) { + descriptor.writable = true; + } + descriptors[key] = descriptor; + return descriptors; + }, {})); + return target; +} + module.exports = copyWithSet; From ce60b1cd7ba03a66d89ac3bbebd960075bb23209 Mon Sep 17 00:00:00 2001 From: Igor Zinovev Date: Wed, 31 May 2017 14:09:22 +0300 Subject: [PATCH 5/9] Fix merge errors, add test samples --- frontend/PropVal.js | 16 +++++++++------- test/example/target.js | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/frontend/PropVal.js b/frontend/PropVal.js index bf7ac7313e..5bd7133e3e 100644 --- a/frontend/PropVal.js +++ b/frontend/PropVal.js @@ -122,27 +122,29 @@ function previewProp(val: any, nested: boolean, inverted: boolean, theme: Theme) return {`${val[consts.name]}[${val[consts.meta].length}]`}; } case 'iterator': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base05, + style = { + color: inverted ? getInvertedWeak(theme.state02) : theme.base05, }; return {val[consts.name] + '(…)'}; } case 'symbol': { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base05, + style = { + color: inverted ? getInvertedWeak(theme.state02) : theme.base05, }; // the name is "Symbol(something)" return {val[consts.name]}; } case 'getter': { - const style = inverted ? invertedStyle : valueStyles.object; + style = { + color: inverted ? getInvertedWeak(theme.state02) : theme.base05, + }; return (…); } } if (nested) { - style = { - color: inverted ? getInvertedWeak(theme.state02) : theme.base05, + style = { + color: inverted ? getInvertedWeak(theme.state02) : theme.base05, }; return {'{…}'}; } diff --git a/test/example/target.js b/test/example/target.js index eba3d26b69..66c19d0632 100644 --- a/test/example/target.js +++ b/test/example/target.js @@ -398,6 +398,27 @@ for (var mCount = 200; mCount--;) { } +var protoWithGetter = { + get upperName() { + return this.name.toUpperCase(); + }, +}; + +var getterOnProtoProp = Object.create(protoWithGetter); +getterOnProtoProp.name = 'Foo'; + +let setterTestProp = { + _value: 'hello', + _editsCounter: 0, + get lol() { + return this._value.toUpperCase(); + }, + set lol(val) { + this._editsCounter ++; + this._value = val; + }, +}; + class Wrap extends React.Component { render() { return ( @@ -421,6 +442,7 @@ class Wrap extends React.Component { + ); } From b4cd08f2867b0a7f4afc9c732eb32774c8207193 Mon Sep 17 00:00:00 2001 From: Igor Zinovev Date: Thu, 8 Jun 2017 01:59:36 +0300 Subject: [PATCH 6/9] Naive implementation --- frontend/Store.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frontend/Store.js b/frontend/Store.js index 4666c86e90..1254975738 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -90,6 +90,7 @@ class Store extends EventEmitter { _nodes: Map; _parents: Map; _nodesByName: Map; + _gettersCache: Map; _eventQueue: Array; _eventTimer: ?number; @@ -128,6 +129,7 @@ class Store extends EventEmitter { this._nodes = new Map(); this._parents = new Map(); this._nodesByName = new Map(); + this._gettersCache = new Map(); this._bridge = bridge; // Public state @@ -414,6 +416,10 @@ class Store extends EventEmitter { } setProps(id: ElementID, path: Array, value: any) { + if (this._gettersCache.has(id)) { + this._gettersCache = this._gettersCache.deleteIn([id, path[0]]); + } + this._gettersCache = this._gettersCache.remove(path[0]); this._bridge.send('setProps', {id, path, value}); } @@ -535,6 +541,9 @@ class Store extends EventEmitter { inspect(id: ElementID, path: Array, cb: () => void) { invariant(path[0] === 'props' || path[0] === 'state' || path[0] === 'context', 'Inspected path must be one of props, state, or context'); + + this._gettersCache.clear(); + this._bridge.inspect(id, path, value => { var base = this.get(id).get(path[0]); var inspected = path.slice(1).reduce((obj, attr) => obj ? obj[attr] : null, base); @@ -647,6 +656,16 @@ class Store extends EventEmitter { return; } data.renders = node.get('renders') + 1; + if (this._gettersCache.has(data.id)) { + var props = data.props; + for (var prop of this._gettersCache.get(data.id).keys()) { + var evaledValue = this._gettersCache.getIn([data.id, prop]); + var path = evaledValue.path; + var propObj = path.slice(0, path.length - 1).reduce( (obj, attr) => obj ? obj[attr] : null, props); + propObj[path[path.length-1]] = evaledValue.value; + } + + } this._nodes = this._nodes.mergeIn([data.id], Map(data)); if (data.children && data.children.forEach) { data.children.forEach(cid => { @@ -713,6 +732,7 @@ class Store extends EventEmitter { propObj[data.path[last]] = data.value; var newProps = assign({}, props); this._nodes = this._nodes.setIn([data.id, 'props'], newProps); + this._gettersCache = this._gettersCache.setIn([data.id, data.path[0]], { path: data.path, value: data.value }); this.emit(data.id); } } From b59ebf589a5e9af72fd1b42550084f10fb4f665d Mon Sep 17 00:00:00 2001 From: Igor Zinovev Date: Fri, 16 Jun 2017 17:26:49 +0300 Subject: [PATCH 7/9] =?UTF-8?q?Don=E2=80=99t=20collapse=20getters=20outsid?= =?UTF-8?q?e=20edit=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/DataView/DataView.js | 10 ++++++++++ frontend/PropState.js | 8 ++++++++ frontend/Store.js | 26 ++++++++------------------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/frontend/DataView/DataView.js b/frontend/DataView/DataView.js index 3a2141d1c5..08b1fc48b3 100644 --- a/frontend/DataView/DataView.js +++ b/frontend/DataView/DataView.js @@ -143,6 +143,7 @@ DataView.contextTypes = { class DataItem extends React.Component { context: { onChange: (path: Array, checked: boolean) => void, + isPropertyFrozen: (path: Array) => boolean, theme: Theme, }; props: { @@ -175,6 +176,14 @@ class DataItem extends React.Component { } } + shouldComponentUpdate(nextProps) { + if (nextProps.value[consts.type] === 'getter') { + return !this.context.isPropertyFrozen(nextProps.path); + } else { + return true; + } + } + inspect() { this.setState({loading: true, open: true}); this.props.inspect(this.props.path, () => { @@ -298,6 +307,7 @@ class DataItem extends React.Component { DataItem.contextTypes = { onChange: React.PropTypes.func, + isPropertyFrozen: React.PropTypes.func, theme: React.PropTypes.object.isRequired, }; diff --git a/frontend/PropState.js b/frontend/PropState.js index 7b5aa236fc..2b01cbdfa9 100644 --- a/frontend/PropState.js +++ b/frontend/PropState.js @@ -37,6 +37,9 @@ class PropState extends React.Component { onEvalGetter: (path) => { this.props.onEvalGetter(path); }, + isPropertyFrozen: (path) => { + return this.props.isPathFrozen(path); + }, }; } @@ -180,6 +183,8 @@ PropState.contextTypes = { PropState.childContextTypes = { onChange: React.PropTypes.func, onEvalGetter: React.PropTypes.func, + isPropertyFrozen: React.PropTypes.func, + }; var WrappedPropState = decorate({ @@ -193,6 +198,9 @@ var WrappedPropState = decorate({ id: store.selected, node, canEditTextContent: store.capabilities.editTextContent, + isPathFrozen(path) { + return store.isPathFrozen(path); + }, onChangeText(text) { store.changeTextContent(store.selected, text); }, diff --git a/frontend/Store.js b/frontend/Store.js index 1254975738..ca7ded7a7e 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -90,7 +90,6 @@ class Store extends EventEmitter { _nodes: Map; _parents: Map; _nodesByName: Map; - _gettersCache: Map; _eventQueue: Array; _eventTimer: ?number; @@ -129,7 +128,7 @@ class Store extends EventEmitter { this._nodes = new Map(); this._parents = new Map(); this._nodesByName = new Map(); - this._gettersCache = new Map(); + this._frozenPaths = new Map(); this._bridge = bridge; // Public state @@ -416,10 +415,7 @@ class Store extends EventEmitter { } setProps(id: ElementID, path: Array, value: any) { - if (this._gettersCache.has(id)) { - this._gettersCache = this._gettersCache.deleteIn([id, path[0]]); - } - this._gettersCache = this._gettersCache.remove(path[0]); + this._frozenPaths = this._frozenPaths.deleteIn([id, ...path.slice(0, path.length-1)]); this._bridge.send('setProps', {id, path, value}); } @@ -490,6 +486,7 @@ class Store extends EventEmitter { select(id: ?ElementID, noHighlight?: boolean, keepBreadcrumb?: boolean) { var oldSel = this.selected; this.selected = id; + this._frozenPaths.clear(); if (oldSel) { this.emit(oldSel); } @@ -542,8 +539,6 @@ class Store extends EventEmitter { invariant(path[0] === 'props' || path[0] === 'state' || path[0] === 'context', 'Inspected path must be one of props, state, or context'); - this._gettersCache.clear(); - this._bridge.inspect(id, path, value => { var base = this.get(id).get(path[0]); var inspected = path.slice(1).reduce((obj, attr) => obj ? obj[attr] : null, base); @@ -555,6 +550,10 @@ class Store extends EventEmitter { }); } + isPathFrozen(path: Array) { + return this._frozenPaths.getIn( [this.selected, ...path.slice(1, path.length)]) === true; + } + changeTraceUpdates(state: ControlState) { this.traceupdatesState = state; this.emit('traceupdatesstatechange'); @@ -656,16 +655,7 @@ class Store extends EventEmitter { return; } data.renders = node.get('renders') + 1; - if (this._gettersCache.has(data.id)) { - var props = data.props; - for (var prop of this._gettersCache.get(data.id).keys()) { - var evaledValue = this._gettersCache.getIn([data.id, prop]); - var path = evaledValue.path; - var propObj = path.slice(0, path.length - 1).reduce( (obj, attr) => obj ? obj[attr] : null, props); - propObj[path[path.length-1]] = evaledValue.value; - } - } this._nodes = this._nodes.mergeIn([data.id], Map(data)); if (data.children && data.children.forEach) { data.children.forEach(cid => { @@ -732,7 +722,7 @@ class Store extends EventEmitter { propObj[data.path[last]] = data.value; var newProps = assign({}, props); this._nodes = this._nodes.setIn([data.id, 'props'], newProps); - this._gettersCache = this._gettersCache.setIn([data.id, data.path[0]], { path: data.path, value: data.value }); + this._frozenPaths = this._frozenPaths.setIn([data.id, ...data.path], true ); this.emit(data.id); } } From a64d1c9523d9251e4c275060b1db6a09085683ee Mon Sep 17 00:00:00 2001 From: Igor Zinovev Date: Fri, 16 Jun 2017 19:12:40 +0300 Subject: [PATCH 8/9] Find getter inside prototype chain --- agent/__tests__/dehydrate-test.js | 14 ++++++++++++++ agent/dehydrate.js | 5 ++++- frontend/Store.js | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/agent/__tests__/dehydrate-test.js b/agent/__tests__/dehydrate-test.js index c889aa4d3d..ad206cf9a5 100644 --- a/agent/__tests__/dehydrate-test.js +++ b/agent/__tests__/dehydrate-test.js @@ -63,4 +63,18 @@ describe('dehydrate', () => { var result = dehydrate(object, cleaned); expect(result.a).toEqual({type: 'date', name: d.toString(), meta: {uninspectable: true}}); }); + + it('cleans getters', () => { + var grand = { + get getter() { + return 'a'; + }, + }; + + var parent = Object.create(grand); + var object = Object.create(parent); + var cleaned = []; + var result = dehydrate(object, cleaned); + expect(result.getter).toEqual( { name: 'getter', type: 'getter' }); + }); }); diff --git a/agent/dehydrate.js b/agent/dehydrate.js index 0eba869460..a7ea21ee57 100644 --- a/agent/dehydrate.js +++ b/agent/dehydrate.js @@ -175,7 +175,10 @@ function dehydrate(data: Object, cleaned: Array>, path?: Array; _eventTimer: ?number; From 0d2565ab6511b6c7600d01d12c7989fb8ff62a9e Mon Sep 17 00:00:00 2001 From: Igor Zinovev Date: Sun, 9 Jul 2017 11:57:20 +0300 Subject: [PATCH 9/9] Make getters work for context and state --- agent/Agent.js | 3 +-- frontend/PropState.js | 2 +- frontend/Store.js | 14 ++++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/agent/Agent.js b/agent/Agent.js index fc678e0580..19762756c2 100644 --- a/agent/Agent.js +++ b/agent/Agent.js @@ -346,8 +346,7 @@ class Agent extends EventEmitter { if (!data) { return; } - var obj = data.props; - var value = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); + var value = path.reduce((obj_, attr) => obj_ ? obj_[attr] : null, data); this.emit('sendGetterValue', {id, path, value}); } diff --git a/frontend/PropState.js b/frontend/PropState.js index 2b01cbdfa9..7fdb7c8791 100644 --- a/frontend/PropState.js +++ b/frontend/PropState.js @@ -219,7 +219,7 @@ var WrappedPropState = decorate({ store.showContextMenu('attr', e, store.selected, node, val, path, name); }, onEvalGetter(path) { - store.evalGetter(store.selected, path.slice(1)); + store.evalGetter(store.selected, path); }, inspect: store.inspect.bind(store, store.selected), }; diff --git a/frontend/Store.js b/frontend/Store.js index e252ad6998..59121f3d45 100644 --- a/frontend/Store.js +++ b/frontend/Store.js @@ -401,15 +401,17 @@ class Store extends EventEmitter { } setProps(id: ElementID, path: Array, value: any) { - this._frozenPaths = this._frozenPaths.deleteIn([id, ...path.slice(0, path.length-1)]); + this._frozenPaths = this._frozenPaths.deleteIn([id, 'props', ...path.slice(0, path.length-1)]); this._bridge.send('setProps', {id, path, value}); } setState(id: ElementID, path: Array, value: any) { + this._frozenPaths = this._frozenPaths.deleteIn([id, 'state', ...path.slice(0, path.length-1)]); this._bridge.send('setState', {id, path, value}); } setContext(id: ElementID, path: Array, value: any) { + this._frozenPaths = this._frozenPaths.deleteIn([id, 'context', ...path.slice(0, path.length-1)]); this._bridge.send('setContext', {id, path, value}); } @@ -552,7 +554,7 @@ class Store extends EventEmitter { } isPathFrozen(path: Array) { - return this._frozenPaths.getIn( [this.selected, ...path.slice(1, path.length)]) === true; + return this._frozenPaths.getIn( [this.selected, ...path]) === true; } changeTraceUpdates(state: ControlState) { @@ -716,12 +718,12 @@ class Store extends EventEmitter { _setGetterValue(data: DataType) { var node = this._nodes.get(data.id); - var props = node.get('props'); + var obj = node.get(data.path[0]); var last = data.path.length - 1; - var propObj = data.path.slice(0, last).reduce((obj_, attr) => obj_ ? obj_[attr] : null, props); + var propObj = data.path.slice(1, last).reduce((obj_, attr) => obj_ ? obj_[attr] : null, obj); propObj[data.path[last]] = data.value; - var newProps = assign({}, props); - this._nodes = this._nodes.setIn([data.id, 'props'], newProps); + var newProps = assign({}, obj); + this._nodes = this._nodes.setIn([data.id, data.path[0]], newProps); this._frozenPaths = this._frozenPaths.setIn([data.id, ...data.path], true ); this.emit(data.id); }