Skip to content

Commit ead36c0

Browse files
yzennchevobbe
authored andcommitted
Ensuring long strings get re-rendered when their loaded properties are updated (firefox-devtools#7874) (firefox-devtools#8053)
1 parent 7397cb2 commit ead36c0

File tree

5 files changed

+155
-0
lines changed

5 files changed

+155
-0
lines changed

packages/devtools-components/src/tests/__snapshots__/tree.js.snap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,20 @@ exports[`Tree active item - renders as expected when using keyboard and Space 1`
266266
"
267267
`;
268268

269+
exports[`Tree calls shouldItemUpdate when provided 1`] = `
270+
"
271+
▶︎ A
272+
▶︎ M
273+
"
274+
`;
275+
276+
exports[`Tree calls shouldItemUpdate when provided 2`] = `
277+
"
278+
▶︎ A
279+
▶︎ M
280+
"
281+
`;
282+
269283
exports[`Tree ignores key strokes when pressing modifiers 1`] = `
270284
"
271285
▼ A

packages/devtools-components/src/tests/tree.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,30 @@ describe("Tree", () => {
203203
expect(formatTree(wrapper)).toMatchSnapshot();
204204
});
205205

206+
it("calls shouldItemUpdate when provided", () => {
207+
const shouldItemUpdate = jest.fn((prev, next) => true);
208+
const wrapper = mountTree({
209+
shouldItemUpdate
210+
});
211+
expect(formatTree(wrapper)).toMatchSnapshot();
212+
expect(shouldItemUpdate.mock.calls).toHaveLength(0);
213+
214+
wrapper
215+
.find("Tree")
216+
.first()
217+
.instance()
218+
.forceUpdate();
219+
expect(formatTree(wrapper)).toMatchSnapshot();
220+
expect(shouldItemUpdate.mock.calls).toHaveLength(2);
221+
222+
expect(shouldItemUpdate.mock.calls[0][0]).toBe("A");
223+
expect(shouldItemUpdate.mock.calls[0][1]).toBe("A");
224+
expect(shouldItemUpdate.mock.results[0].value).toBe(true);
225+
expect(shouldItemUpdate.mock.calls[1][0]).toBe("M");
226+
expect(shouldItemUpdate.mock.calls[1][1]).toBe("M");
227+
expect(shouldItemUpdate.mock.results[1].value).toBe(true);
228+
});
229+
206230
it("active item - renders as expected when clicking away", () => {
207231
const wrapper = mountTree({
208232
expanded: new Set("ABCDEFGHIJKLMNO".split("")),

packages/devtools-components/src/tree.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class TreeNode extends Component {
6666
item: PropTypes.any.isRequired,
6767
isExpandable: PropTypes.bool.isRequired,
6868
onClick: PropTypes.func,
69+
shouldItemUpdate: PropTypes.func,
6970
renderItem: PropTypes.func.isRequired
7071
};
7172
}
@@ -96,6 +97,8 @@ class TreeNode extends Component {
9697
shouldComponentUpdate(nextProps) {
9798
return (
9899
this.props.item !== nextProps.item ||
100+
(this.props.shouldItemUpdate &&
101+
this.props.shouldItemUpdate(this.props.item, nextProps.item)) ||
99102
this.props.focused !== nextProps.focused ||
100103
this.props.expanded !== nextProps.expanded
101104
);
@@ -338,6 +341,17 @@ class Tree extends Component {
338341
// getChildren: item => item.children
339342
getChildren: PropTypes.func.isRequired,
340343

344+
// A function to check if the tree node for the item should be updated.
345+
//
346+
// Type: shouldItemUpdate(prevItem: Item, nextItem: Item) -> Boolean
347+
//
348+
// Example:
349+
//
350+
// // This item should be updated if it's type is a long string
351+
// shouldItemUpdate: (prevItem, nextItem) =>
352+
// nextItem.type === "longstring"
353+
shouldItemUpdate: PropTypes.func,
354+
341355
// A function which takes an item and ArrowExpander component instance and
342356
// returns a component, or text, or anything else that React considers
343357
// renderable.
@@ -929,6 +943,7 @@ class Tree extends Component {
929943
index: i,
930944
item,
931945
depth,
946+
shouldItemUpdate: this.props.shouldItemUpdate,
932947
renderItem: this.props.renderItem,
933948
focused: focused === item,
934949
active: active === item,

packages/devtools-reps/src/object-inspector/components/ObjectInspector.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const {
2424
getChildrenWithEvaluations,
2525
getActor,
2626
getParent,
27+
getValue,
2728
nodeIsPrimitive,
2829
nodeHasGetter,
2930
nodeHasSetter
@@ -78,6 +79,7 @@ class ObjectInspector extends Component<Props> {
7879
self.activateItem = this.activateItem.bind(this);
7980
self.getRoots = this.getRoots.bind(this);
8081
self.getNodeKey = this.getNodeKey.bind(this);
82+
self.shouldItemUpdate = this.shouldItemUpdate.bind(this);
8183
}
8284

8385
componentWillMount() {
@@ -236,6 +238,13 @@ class ObjectInspector extends Component<Props> {
236238
}
237239
}
238240

241+
shouldItemUpdate(prevItem: Node, nextItem: Node) {
242+
const value = getValue(nextItem);
243+
// Long string should always update because fullText loading will not
244+
// trigger item re-render.
245+
return value && value.type === "longString";
246+
}
247+
239248
render() {
240249
const {
241250
autoExpandAll = true,
@@ -271,6 +280,7 @@ class ObjectInspector extends Component<Props> {
271280
onFocus: focusable ? this.focusItem : null,
272281
onActivate: focusable ? this.activateItem : null,
273282

283+
shouldItemUpdate: this.shouldItemUpdate,
274284
renderItem: (item, depth, focused, arrow, expanded) =>
275285
ObjectInspectorItem({
276286
...this.props,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
4+
5+
/* global jest */
6+
7+
const { mountObjectInspector } = require("../test-utils");
8+
const ObjectClient = require("../__mocks__/object-client");
9+
const LongStringClient = require("../__mocks__/long-string-client");
10+
11+
const repsPath = "../../../reps";
12+
const longStringStubs = require(`${repsPath}/stubs/long-string`);
13+
const gripStubs = require(`${repsPath}/stubs/grip`);
14+
15+
function mount(stub) {
16+
const root = {
17+
path: "root",
18+
contents: {
19+
value: stub
20+
}
21+
};
22+
23+
const { wrapper } = mountObjectInspector({
24+
client: {
25+
createObjectClient: grip => ObjectClient(grip),
26+
createLongStringClient: grip => LongStringClient(grip)
27+
},
28+
props: {
29+
roots: [root]
30+
}
31+
});
32+
33+
return { wrapper, root };
34+
}
35+
36+
describe("shouldItemUpdate", () => {
37+
it("for longStrings", () => {
38+
shouldItemUpdateCheck(longStringStubs.get("testUnloadedFullText"), true, 2);
39+
});
40+
41+
it("for basic object", () => {
42+
shouldItemUpdateCheck(gripStubs.get("testBasic"), false, 1);
43+
});
44+
});
45+
46+
function shouldItemUpdateCheck(
47+
stub,
48+
shouldItemUpdateResult,
49+
renderCallsLength
50+
) {
51+
const { root, wrapper } = mount(stub);
52+
53+
const shouldItemUpdateSpy = getShouldItemUpdateSpy(wrapper);
54+
const treeNodeRenderSpy = getTreeNodeRenderSpy(wrapper);
55+
56+
updateObjectInspectorTree(wrapper);
57+
58+
checkShouldItemUpdate(shouldItemUpdateSpy, root, shouldItemUpdateResult);
59+
expect(treeNodeRenderSpy.mock.calls).toHaveLength(renderCallsLength);
60+
}
61+
62+
function checkShouldItemUpdate(spy, item, result) {
63+
expect(spy.mock.calls).toHaveLength(1);
64+
expect(spy.mock.calls[0][0]).toBe(item);
65+
expect(spy.mock.calls[0][1]).toBe(item);
66+
expect(spy.mock.results[0].value).toBe(result);
67+
}
68+
69+
function getInstance(wrapper, selector) {
70+
return wrapper
71+
.find(selector)
72+
.first()
73+
.instance();
74+
}
75+
76+
function getShouldItemUpdateSpy(wrapper) {
77+
return jest.spyOn(
78+
getInstance(wrapper, "ObjectInspector"),
79+
"shouldItemUpdate"
80+
);
81+
}
82+
83+
function getTreeNodeRenderSpy(wrapper) {
84+
return jest.spyOn(getInstance(wrapper, "TreeNode"), "render");
85+
}
86+
87+
function updateObjectInspectorTree(wrapper) {
88+
// Update the ObjectInspector first to propagate its updated options to the
89+
// Tree component.
90+
getInstance(wrapper, "ObjectInspector").forceUpdate();
91+
getInstance(wrapper, "Tree").forceUpdate();
92+
}

0 commit comments

Comments
 (0)