Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
100 changes: 100 additions & 0 deletions packages/react-devtools-inline/__tests__/__e2e__/profiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,104 @@ test.describe('Profiler', () => {
'3 / 3'
);
});

test('should allow searching for a component within the selected commit', async () => {
runOnlyForReactRange('>=16.5');

async function waitForSearchResultsCount(expectedText) {
return await page.waitForFunction(expected => {
const {createTestNameSelector, findAllNodes} =
window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

const indexInput = findAllNodes(container, [
createTestNameSelector('ProfilerSearchInput-ResultIndexInput'),
])[0];
const resultsCount = findAllNodes(container, [
createTestNameSelector('ProfilerSearchInput-ResultsCount'),
])[0];
if (indexInput === undefined || resultsCount === undefined) {
return false;
}
const totalCount = resultsCount.innerText.replace(/[^0-9]/g, '');
return `${indexInput.value} | ${totalCount}` === expected;
}, expectedText);
}

async function focusProfilerSearch() {
await page.evaluate(() => {
const {createTestNameSelector, focusWithin} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');

focusWithin(container, [
createTestNameSelector('ProfilerSearchInput-Input'),
]);
});
}

await devToolsUtils.clickButton(page, 'ProfilerToggleButton');
await listAppUtils.addItem(page, 'four');
await listAppUtils.addItem(page, 'five');
await listAppUtils.addItem(page, 'six');
await devToolsUtils.clickButton(page, 'ProfilerToggleButton');

await page.waitForFunction(() => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');
return (
findAllNodes(container, [
createTestNameSelector('SnapshotSelector-Input'),
]).length === 1
);
});

await devToolsUtils.clickButton(page, 'ProfilerSearchButton');
await page.waitForFunction(() => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');
return (
findAllNodes(container, [
createTestNameSelector('ProfilerSearchInput-Input'),
]).length === 1
);
});

await focusProfilerSearch();
await page.keyboard.insertText('ListItem');
await waitForSearchResultsCount('1 | 4');

await devToolsUtils.clickButton(page, 'SnapshotSelector-NextButton');
await waitForSearchResultsCount('1 | 5');
await devToolsUtils.clickButton(page, 'SnapshotSelector-NextButton');
await waitForSearchResultsCount('1 | 6');
await devToolsUtils.clickButton(page, 'SnapshotSelector-PreviousButton');
await waitForSearchResultsCount('1 | 5');
await devToolsUtils.clickButton(page, 'SnapshotSelector-PreviousButton');
await waitForSearchResultsCount('1 | 4');

await page.keyboard.press('Enter');
await waitForSearchResultsCount('2 | 4');
await page.keyboard.press('Enter');
await waitForSearchResultsCount('3 | 4');
await page.keyboard.press('Enter');
await waitForSearchResultsCount('4 | 4');
await page.keyboard.press('Enter');
await waitForSearchResultsCount('1 | 4');
await page.keyboard.press('Shift+Enter');
await waitForSearchResultsCount('4 | 4');

await page.keyboard.insertText('zzz');
await waitForSearchResultsCount('0 | 0');

await devToolsUtils.clickButton(page, 'ProfilerSearchInput-CloseButton');
await page.waitForFunction(() => {
const {createTestNameSelector, findAllNodes} = window.REACT_DOM_DEVTOOLS;
const container = document.getElementById('devtools');
return (
findAllNodes(container, [
createTestNameSelector('ProfilerSearchInput-Input'),
]).length === 0
);
});
});
});
15 changes: 10 additions & 5 deletions packages/react-devtools-shared/src/devtools/views/ButtonIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type IconType =
| 'expanded'
| 'export'
| 'filter'
| 'find'
| 'import'
| 'log-data'
| 'more'
Expand Down Expand Up @@ -129,6 +130,10 @@ export default function ButtonIcon({className = '', type}: Props): React.Node {
case 'search':
pathData = PATH_SEARCH;
break;
case 'find':
pathData = PATH_FIND;
viewBox = '0 0 16 16';
break;
case 'settings':
pathData = PATH_SETTINGS;
break;
Expand Down Expand Up @@ -211,11 +216,7 @@ export default function ButtonIcon({className = '', type}: Props): React.Node {
height="24"
viewBox={viewBox}>
<path d="M0 0h24v24H0z" fill="none" />
{typeof pathData === 'string' ? (
<path fill="currentColor" d={pathData} />
) : (
pathData
)}
<path fill="currentColor" d={pathData} />
</svg>
);
}
Expand Down Expand Up @@ -300,6 +301,10 @@ const PATH_SEARCH = `
M23,13.9l-4.6,3.6l4.6,4.6l-1.1,1.1l-4.7-4.4l-3.3,4.4l-3.2-12.3L23,13.9z
`;

const PATH_FIND =
'M6.5 0.5a6 6 0 1 0 0 12 6 6 0 0 0 0-12zm0 1.5a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z' +
'M11.17 10.03l3.7 3.7a0.8 0.8 0 0 1-1.14 1.14l-3.7-3.7z';

const PATH_SETTINGS = `
M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49
1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38
Expand Down
9 changes: 9 additions & 0 deletions packages/react-devtools-shared/src/devtools/views/Icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type IconType =
| 'copy'
| 'error'
| 'facebook'
| 'find'
| 'flame-chart'
| 'profiler'
| 'ranked-chart'
Expand Down Expand Up @@ -77,6 +78,10 @@ export default function Icon({
case 'search':
pathData = PATH_SEARCH;
break;
case 'find':
pathData = PATH_FIND;
viewBox = '0 0 16 16';
break;
case 'settings':
pathData = PATH_SETTINGS;
break;
Expand Down Expand Up @@ -161,6 +166,10 @@ const PATH_SEARCH = `
5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z
`;

const PATH_FIND =
'M6.5 0.5a6 6 0 1 0 0 12 6 6 0 0 0 0-12zm0 1.5a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z' +
'M11.17 10.03l3.7 3.7a0.8 0.8 0 0 1-1.14 1.14l-3.7-3.7z';

const PATH_RANKED_CHART = 'M3 5h18v3H3zM3 10.5h13v3H3zM3 16h8v3H3z';

const PATH_SETTINGS = `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
transition: all ease-in-out 250ms;
}

.Highlight {
border-radius: 0.125rem;
background-color: var(--color-search-match);
}
.CurrentHighlight {
border-radius: 0.125rem;
background-color: var(--color-search-match-current);
}

.Div {
pointer-events: none;
white-space: nowrap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ import typeof {SyntheticMouseEvent} from 'react-dom-bindings/src/events/Syntheti
type Props = {
color: string,
height: number,
isCurrentSearchMatch?: boolean,
isDimmed?: boolean,
isSearchMatch?: boolean,
label: string,
onClick: (event: SyntheticMouseEvent) => mixed,
onDoubleClick?: (event: SyntheticMouseEvent) => mixed,
onMouseEnter: (event: SyntheticMouseEvent) => mixed,
onMouseLeave: (event: SyntheticMouseEvent) => mixed,
placeLabelAboveNode?: boolean,
searchRegExp?: RegExp | null,
textStyle?: Object,
width: number,
x: number,
Expand All @@ -30,20 +33,54 @@ type Props = {

const minWidthToDisplay = 35;

// Wrap the matched substring of `label` in a highlight, like the Components
// panel search does (see IndexableDisplayName).
function highlightLabel(
label: string,
searchRegExp: RegExp,
isCurrentSearchMatch: boolean,
): React.Node {
const match = searchRegExp.exec(label);
if (match === null) {
return label;
}
const start = match.index;
const stop = start + match[0].length;
return (
<>
{start > 0 ? label.slice(0, start) : null}
<mark
className={
isCurrentSearchMatch ? styles.CurrentHighlight : styles.Highlight
}>
{label.slice(start, stop)}
</mark>
{stop < label.length ? label.slice(stop) : null}
</>
);
}

export default function ChartNode({
color,
height,
isCurrentSearchMatch = false,
isDimmed = false,
isSearchMatch = false,
label,
onClick,
onMouseEnter,
onMouseLeave,
onDoubleClick,
searchRegExp,
textStyle,
width,
x,
y,
}: Props): React.Node {
const content =
isSearchMatch && searchRegExp != null
? highlightLabel(label, searchRegExp, isCurrentSearchMatch)
: label;
return (
<g className={styles.Group} transform={`translate(${x},${y})`}>
<rect
Expand Down Expand Up @@ -71,7 +108,7 @@ export default function ChartNode({
}}
y={0}>
<div className={styles.Div} style={textStyle}>
{label}
{content}
</div>
</foreignObject>
)}
Expand Down
Loading
Loading