Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DevTools] Show component names while highlighting renders #31577

Merged
merged 17 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
96829c1
fix(trace-updates): add component name display to canvas
piotrski Nov 18, 2024
c8de4d5
fix(trace-updates): improve canvas rendering on high-DPI screens
piotrski Nov 18, 2024
273287a
refactor(trace-updates): simplify `iterateNodes` function and improve…
piotrski Nov 18, 2024
48d5341
feat(trace-updates): display update count on component labels
piotrski Nov 18, 2024
2982ccc
feat(trace-updates): group overlapping updates and merge labels
piotrski Nov 18, 2024
4aa2dc5
feat(trace-updates): sort component update labels by maximum update c…
piotrski Nov 18, 2024
c4b71d7
feat(trace-updates): add setting to show component names during trace…
piotrski Nov 18, 2024
2f5c31e
refactor(trace-updates): improve draw logic and modularize canvas ren…
piotrski Nov 18, 2024
547f497
test(trace-updates): add tests for node grouping and sorting logic
piotrski Nov 19, 2024
57900dd
fix: remove console log
piotrski Dec 11, 2024
36b5798
fix(trace-updates): conditionally get component names based on setting
piotrski Dec 11, 2024
4516046
refactor(trace-updates): replace spread operator with explicit object…
piotrski Dec 11, 2024
9e84c54
feat(trace-updates): add grouped trace updates with names for React N…
piotrski Dec 11, 2024
4ba16b0
feat(trace-updates): extract HOC names and add markers to display names
piotrski Dec 12, 2024
4633a85
fix: add missing return type
piotrski Dec 13, 2024
bea90c4
fix[utils]: lint
hoxyq Dec 13, 2024
ee3b328
fix[TraceUpdates]: update Forget marker prefix
hoxyq Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 269 additions & 0 deletions packages/react-devtools-shared/src/__tests__/traceUpdates-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import {groupAndSortNodes} from 'react-devtools-shared/src/backend/views/TraceUpdates/canvas';

describe('Trace updates group and sort nodes', () => {
test('should group nodes by position without changing order within group', () => {
const nodeToData = new Map([
[
{id: 1},
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#80b393',
displayName: 'Node1',
count: 3,
},
],
[
{id: 2},
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#63b19e',
displayName: 'Node2',
count: 2,
},
],
]);

const result = groupAndSortNodes(nodeToData);

expect(result).toEqual([
[
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#80b393',
displayName: 'Node1',
count: 3,
},
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#63b19e',
displayName: 'Node2',
count: 2,
},
],
]);
});

test('should sort groups by lowest count in each group', () => {
const nodeToData = new Map([
[
{id: 1},
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#97b488',
displayName: 'Group1',
count: 4,
},
],
[
{id: 2},
{
rect: {left: 100, top: 0, width: 100, height: 100},
color: '#37afa9',
displayName: 'Group2',
count: 1,
},
],
[
{id: 3},
{
rect: {left: 200, top: 0, width: 100, height: 100},
color: '#63b19e',
displayName: 'Group3',
count: 2,
},
],
]);

const result = groupAndSortNodes(nodeToData);

expect(result).toEqual([
[
{
rect: {left: 100, top: 0, width: 100, height: 100},
color: '#37afa9',
displayName: 'Group2',
count: 1,
},
],
[
{
rect: {left: 200, top: 0, width: 100, height: 100},
color: '#63b19e',
displayName: 'Group3',
count: 2,
},
],
[
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#97b488',
displayName: 'Group1',
count: 4,
},
],
]);
});

test('should maintain order within groups while sorting groups by lowest count', () => {
const nodeToData = new Map([
[
{id: 1},
{
rect: {left: 0, top: 0, width: 50, height: 50},
color: '#97b488',
displayName: 'Pos1Node1',
count: 4,
},
],
[
{id: 2},
{
rect: {left: 0, top: 0, width: 60, height: 60},
color: '#63b19e',
displayName: 'Pos1Node2',
count: 2,
},
],
[
{id: 3},
{
rect: {left: 100, top: 0, width: 70, height: 70},
color: '#80b393',
displayName: 'Pos2Node1',
count: 3,
},
],
[
{id: 4},
{
rect: {left: 100, top: 0, width: 80, height: 80},
color: '#37afa9',
displayName: 'Pos2Node2',
count: 1,
},
],
]);

const result = groupAndSortNodes(nodeToData);

expect(result).toEqual([
[
{
rect: {left: 100, top: 0, width: 70, height: 70},
color: '#80b393',
displayName: 'Pos2Node1',
count: 3,
},
{
rect: {left: 100, top: 0, width: 80, height: 80},
color: '#37afa9',
displayName: 'Pos2Node2',
count: 1,
},
],
[
{
rect: {left: 0, top: 0, width: 50, height: 50},
color: '#97b488',
displayName: 'Pos1Node1',
count: 4,
},
{
rect: {left: 0, top: 0, width: 60, height: 60},
color: '#63b19e',
displayName: 'Pos1Node2',
count: 2,
},
],
]);
});

test('should handle multiple groups with same minimum count', () => {
const nodeToData = new Map([
[
{id: 1},
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#37afa9',
displayName: 'Group1Node1',
count: 1,
},
],
[
{id: 2},
{
rect: {left: 100, top: 0, width: 100, height: 100},
color: '#37afa9',
displayName: 'Group2Node1',
count: 1,
},
],
]);

const result = groupAndSortNodes(nodeToData);

expect(result).toEqual([
[
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#37afa9',
displayName: 'Group1Node1',
count: 1,
},
],
[
{
rect: {left: 100, top: 0, width: 100, height: 100},
color: '#37afa9',
displayName: 'Group2Node1',
count: 1,
},
],
]);
});

test('should filter out nodes without rect property', () => {
const nodeToData = new Map([
[
{id: 1},
{
rect: null,
color: '#37afa9',
displayName: 'NoRectNode',
count: 1,
},
],
[
{id: 2},
{
rect: undefined,
color: '#63b19e',
displayName: 'UndefinedRectNode',
count: 2,
},
],
[
{id: 3},
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#80b393',
displayName: 'ValidNode',
count: 3,
},
],
]);

const result = groupAndSortNodes(nodeToData);

expect(result).toEqual([
[
{
rect: {left: 0, top: 0, width: 100, height: 100},
color: '#80b393',
displayName: 'ValidNode',
count: 3,
},
],
]);
});
});
14 changes: 14 additions & 0 deletions packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
DevToolsHookSettings,
} from './types';
import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types';
import type {GroupItem} from './views/TraceUpdates/canvas';
import {isReactNativeEnvironment} from './utils';
import {
sessionStorageGetItem,
Expand Down Expand Up @@ -142,10 +143,12 @@ export default class Agent extends EventEmitter<{
shutdown: [],
traceUpdates: [Set<HostInstance>],
drawTraceUpdates: [Array<HostInstance>],
drawGroupedTraceUpdatesWithNames: [Array<Array<GroupItem>>],
disableTraceUpdates: [],
getIfHasUnsupportedRendererVersion: [],
updateHookSettings: [$ReadOnly<DevToolsHookSettings>],
getHookSettings: [],
showNamesWhenTracing: [boolean],
}> {
_bridge: BackendBridge;
_isProfiling: boolean = false;
Expand All @@ -156,6 +159,7 @@ export default class Agent extends EventEmitter<{
_onReloadAndProfile:
| ((recordChangeDescriptions: boolean, recordTimeline: boolean) => void)
| void;
_showNamesWhenTracing: boolean = true;

constructor(
bridge: BackendBridge,
Expand Down Expand Up @@ -200,6 +204,7 @@ export default class Agent extends EventEmitter<{
bridge.addListener('reloadAndProfile', this.reloadAndProfile);
bridge.addListener('renamePath', this.renamePath);
bridge.addListener('setTraceUpdatesEnabled', this.setTraceUpdatesEnabled);
bridge.addListener('setShowNamesWhenTracing', this.setShowNamesWhenTracing);
bridge.addListener('startProfiling', this.startProfiling);
bridge.addListener('stopProfiling', this.stopProfiling);
bridge.addListener('storeAsGlobal', this.storeAsGlobal);
Expand Down Expand Up @@ -722,6 +727,7 @@ export default class Agent extends EventEmitter<{
this._traceUpdatesEnabled = traceUpdatesEnabled;

setTraceUpdatesEnabled(traceUpdatesEnabled);
this.emit('showNamesWhenTracing', this._showNamesWhenTracing);

for (const rendererID in this._rendererInterfaces) {
const renderer = ((this._rendererInterfaces[
Expand All @@ -731,6 +737,14 @@ export default class Agent extends EventEmitter<{
}
};

setShowNamesWhenTracing: (show: boolean) => void = show => {
if (this._showNamesWhenTracing === show) {
return;
}
this._showNamesWhenTracing = show;
this.emit('showNamesWhenTracing', show);
};

syncSelectionFromBuiltinElementsPanel: () => void = () => {
const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
if (target == null) {
Expand Down
Loading
Loading