cherry-pick(#31894): feat(ui mode): ui updates (#31916)

- Update copy to clipboard button.
- Reveal test source in the Source tab instead of external editor.
- New button to reveal in the external editor in the Source tab.
- Move the Pick Locator button next to snapshot tabs.
This commit is contained in:
Dmitry Gozman 2024-07-30 09:23:19 -07:00 committed by GitHub
parent 64e4a9b0eb
commit 468b9b1e7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 108 additions and 92 deletions

View file

@ -55,16 +55,16 @@
overflow: hidden; overflow: hidden;
line-height: 18px; line-height: 18px;
white-space: nowrap; white-space: nowrap;
max-height: 18px;
} }
.call-line .copy-icon { .call-line:not(:hover) .toolbar-button.copy {
display: none; display: none;
margin-left: 5px;
} }
.call-line:hover .copy-icon { .call-line .toolbar-button.copy {
display: block; margin-left: 5px;
cursor: pointer; transform: scale(0.8);
} }
.call-value { .call-value {

View file

@ -15,23 +15,24 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { ToolbarButton } from '@web/components/toolbarButton';
export const CopyToClipboard: React.FunctionComponent<{ export const CopyToClipboard: React.FunctionComponent<{
value: string, value: string,
description?: string, description?: string,
}> = ({ value, description }) => { }> = ({ value, description }) => {
const [iconClassName, setIconClassName] = React.useState('codicon-clippy'); const [icon, setIcon] = React.useState('copy');
const handleCopy = React.useCallback(() => { const handleCopy = React.useCallback(() => {
navigator.clipboard.writeText(value).then(() => { navigator.clipboard.writeText(value).then(() => {
setIconClassName('codicon-check'); setIcon('check');
setTimeout(() => { setTimeout(() => {
setIconClassName('codicon-clippy'); setIcon('copy');
}, 3000); }, 3000);
}, () => { }, () => {
setIconClassName('codicon-close'); setIcon('close');
}); });
}, [value]); }, [value]);
return <span title={description ? description : 'Copy'} className={`copy-icon codicon ${iconClassName}`} onClick={handleCopy}/>; return <ToolbarButton title={description ? description : 'Copy'} icon={icon} onClick={handleCopy}/>;
}; };

View file

@ -29,7 +29,8 @@ const eventsSymbol = Symbol('events');
export type SourceLocation = { export type SourceLocation = {
file: string; file: string;
line: number; line: number;
source: SourceModel; column: number;
source?: SourceModel;
}; };
export type SourceModel = { export type SourceModel = {

View file

@ -28,6 +28,10 @@
background-color: var(--vscode-sideBar-background); background-color: var(--vscode-sideBar-background);
} }
.snapshot-tab .toolbar .pick-locator {
margin: 0 4px;
}
.snapshot-controls { .snapshot-controls {
flex: none; flex: none;
background-color: var(--vscode-sideBar-background); background-color: var(--vscode-sideBar-background);
@ -102,29 +106,6 @@ iframe.snapshot-visible[name=snapshot] {
padding: 50px; padding: 50px;
} }
.popout-icon {
position: absolute;
top: 0;
right: 0;
color: var(--vscode-sideBarTitle-foreground);
font-size: 14px;
z-index: 100;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
}
.popout-icon:not(.popout-disabled):hover {
color: var(--vscode-foreground);
}
.popout-icon.popout-disabled {
opacity: var(--vscode-disabledForeground);
}
.snapshot-tab .cm-wrapper { .snapshot-tab .cm-wrapper {
line-height: 23px; line-height: 23px;
margin-right: 4px; margin-right: 4px;

View file

@ -181,6 +181,7 @@ export const SnapshotTab: React.FunctionComponent<{
iframe={iframeRef1.current} iframe={iframeRef1.current}
iteration={loadingRef.current.iteration} /> iteration={loadingRef.current.iteration} />
<Toolbar> <Toolbar>
<ToolbarButton className='pick-locator' title='Pick locator' icon='target' toggled={isInspecting} onClick={() => setIsInspecting(!isInspecting)} />
{['action', 'before', 'after'].map(tab => { {['action', 'before', 'after'].map(tab => {
return <TabbedPaneTab return <TabbedPaneTab
id={tab} id={tab}

View file

@ -23,21 +23,9 @@
} }
.source-tab-file-name { .source-tab-file-name {
height: 24px; padding-left: 8px;
margin-left: 8px; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
background-color: var(--vscode-breadcrumb-background); flex: 1 1 auto;
box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px;
z-index: 10;
} }
.source-tab-file-name .copy-icon.codicon {
display: block;
cursor: pointer;
}
.source-copy-to-clipboard {
display: block;
padding-left: 4px;
}

View file

@ -24,6 +24,8 @@ import type { SourceHighlight } from '@web/components/codeMirrorWrapper';
import type { SourceLocation, SourceModel } from './modelUtil'; import type { SourceLocation, SourceModel } from './modelUtil';
import type { StackFrame } from '@protocol/channels'; import type { StackFrame } from '@protocol/channels';
import { CopyToClipboard } from './copyToClipboard'; import { CopyToClipboard } from './copyToClipboard';
import { ToolbarButton } from '@web/components/toolbarButton';
import { Toolbar } from '@web/components/toolbar';
export const SourceTab: React.FunctionComponent<{ export const SourceTab: React.FunctionComponent<{
stack: StackFrame[] | undefined, stack: StackFrame[] | undefined,
@ -31,7 +33,8 @@ export const SourceTab: React.FunctionComponent<{
sources: Map<string, SourceModel>, sources: Map<string, SourceModel>,
rootDir?: string, rootDir?: string,
fallbackLocation?: SourceLocation, fallbackLocation?: SourceLocation,
}> = ({ stack, sources, rootDir, fallbackLocation, stackFrameLocation }) => { onOpenExternally?: (location: SourceLocation) => void,
}> = ({ stack, sources, rootDir, fallbackLocation, stackFrameLocation, onOpenExternally }) => {
const [lastStack, setLastStack] = React.useState<StackFrame[] | undefined>(); const [lastStack, setLastStack] = React.useState<StackFrame[] | undefined>();
const [selectedFrame, setSelectedFrame] = React.useState<number>(0); const [selectedFrame, setSelectedFrame] = React.useState<number>(0);
@ -42,7 +45,7 @@ export const SourceTab: React.FunctionComponent<{
} }
}, [stack, lastStack, setLastStack, setSelectedFrame]); }, [stack, lastStack, setLastStack, setSelectedFrame]);
const { source, highlight, targetLine, fileName } = useAsyncMemo<{ source: SourceModel, targetLine?: number, fileName?: string, highlight: SourceHighlight[] }>(async () => { const { source, highlight, targetLine, fileName, location } = useAsyncMemo<{ source: SourceModel, targetLine?: number, fileName?: string, highlight: SourceHighlight[], location?: SourceLocation }>(async () => {
const actionLocation = stack?.[selectedFrame]; const actionLocation = stack?.[selectedFrame];
const shouldUseFallback = !actionLocation?.file; const shouldUseFallback = !actionLocation?.file;
if (shouldUseFallback && !fallbackLocation) if (shouldUseFallback && !fallbackLocation)
@ -56,6 +59,7 @@ export const SourceTab: React.FunctionComponent<{
sources.set(file, source); sources.set(file, source);
} }
const location = shouldUseFallback ? fallbackLocation! : actionLocation;
const targetLine = shouldUseFallback ? fallbackLocation?.line || source.errors[0]?.line || 0 : actionLocation.line; const targetLine = shouldUseFallback ? fallbackLocation?.line || source.errors[0]?.line || 0 : actionLocation.line;
const fileName = rootDir && file.startsWith(rootDir) ? file.substring(rootDir.length + 1) : file; const fileName = rootDir && file.startsWith(rootDir) ? file.substring(rootDir.length + 1) : file;
const highlight: SourceHighlight[] = source.errors.map(e => ({ type: 'error', line: e.line, message: e.message })); const highlight: SourceHighlight[] = source.errors.map(e => ({ type: 'error', line: e.line, message: e.message }));
@ -76,21 +80,29 @@ export const SourceTab: React.FunctionComponent<{
source.content = `<Unable to read "${file}">`; source.content = `<Unable to read "${file}">`;
} }
} }
return { source, highlight, targetLine, fileName }; return { source, highlight, targetLine, fileName, location };
}, [stack, selectedFrame, rootDir, fallbackLocation], { source: { errors: [], content: 'Loading\u2026' }, highlight: [] }); }, [stack, selectedFrame, rootDir, fallbackLocation], { source: { errors: [], content: 'Loading\u2026' }, highlight: [] });
const openExternally = React.useCallback(() => {
if (!location)
return;
if (onOpenExternally) {
onOpenExternally(location);
} else {
// This should open an external protocol handler instead of actually navigating away.
window.location.href = `vscode://file//${location.file}:${location.line}`;
}
}, [onOpenExternally, location]);
const showStackFrames = (stack?.length ?? 0) > 1; const showStackFrames = (stack?.length ?? 0) > 1;
return <SplitView sidebarSize={200} orientation={stackFrameLocation === 'bottom' ? 'vertical' : 'horizontal'} sidebarHidden={!showStackFrames}> return <SplitView sidebarSize={200} orientation={stackFrameLocation === 'bottom' ? 'vertical' : 'horizontal'} sidebarHidden={!showStackFrames}>
<div className='vbox' data-testid='source-code'> <div className='vbox' data-testid='source-code'>
{fileName && ( { fileName && <Toolbar>
<div className='source-tab-file-name'> <span className='source-tab-file-name'>{fileName}</span>
{fileName} <CopyToClipboard description='Copy filename' value={getFileName(fileName, targetLine)}/>
<span className='source-copy-to-clipboard'> {location && <ToolbarButton icon='link-external' title='Open in VS Code' onClick={openExternally}></ToolbarButton>}
<CopyToClipboard description='Copy filename' value={getFileName(fileName, targetLine)}/> </Toolbar> }
</span>
</div>
)}
<CodeMirrorWrapper text={source.content || ''} language='javascript' highlight={highlight} revealLine={targetLine} readOnly={true} lineNumbers={true} /> <CodeMirrorWrapper text={source.content || ''} language='javascript' highlight={highlight} revealLine={targetLine} readOnly={true} lineNumbers={true} />
</div> </div>
<StackTraceView stack={stack} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame} /> <StackTraceView stack={stack} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame} />

View file

@ -47,8 +47,9 @@ export const TestListView: React.FC<{
isLoading?: boolean, isLoading?: boolean,
onItemSelected: (item: { treeItem?: TreeItem, testCase?: reporterTypes.TestCase, testFile?: SourceLocation }) => void, onItemSelected: (item: { treeItem?: TreeItem, testCase?: reporterTypes.TestCase, testFile?: SourceLocation }) => void,
requestedCollapseAllCount: number, requestedCollapseAllCount: number,
setFilterText: (text: string) => void; setFilterText: (text: string) => void,
}> = ({ filterText, testModel, testServerConnection, testTree, runTests, runningState, watchAll, watchedTreeIds, setWatchedTreeIds, isLoading, onItemSelected, requestedCollapseAllCount, setFilterText }) => { onRevealSource: () => void,
}> = ({ filterText, testModel, testServerConnection, testTree, runTests, runningState, watchAll, watchedTreeIds, setWatchedTreeIds, isLoading, onItemSelected, requestedCollapseAllCount, setFilterText, onRevealSource }) => {
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() }); const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>(); const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
const [collapseAllCount, setCollapseAllCount] = React.useState(requestedCollapseAllCount); const [collapseAllCount, setCollapseAllCount] = React.useState(requestedCollapseAllCount);
@ -91,17 +92,7 @@ export const TestListView: React.FC<{
if (!testModel) if (!testModel)
return { selectedTreeItem: undefined }; return { selectedTreeItem: undefined };
const selectedTreeItem = selectedTreeItemId ? testTree.treeItemById(selectedTreeItemId) : undefined; const selectedTreeItem = selectedTreeItemId ? testTree.treeItemById(selectedTreeItemId) : undefined;
let testFile: SourceLocation | undefined; const testFile = itemLocation(selectedTreeItem, testModel);
if (selectedTreeItem) {
testFile = {
file: selectedTreeItem.location.file,
line: selectedTreeItem.location.line,
source: {
errors: testModel.loadErrors.filter(e => e.location?.file === selectedTreeItem.location.file).map(e => ({ line: e.location!.line, message: e.message! })),
content: undefined,
}
};
}
let selectedTest: reporterTypes.TestCase | undefined; let selectedTest: reporterTypes.TestCase | undefined;
if (selectedTreeItem?.kind === 'test') if (selectedTreeItem?.kind === 'test')
selectedTest = selectedTreeItem.test; selectedTest = selectedTreeItem.test;
@ -164,7 +155,7 @@ export const TestListView: React.FC<{
{!!treeItem.duration && treeItem.status !== 'skipped' && <div className='ui-mode-list-item-time'>{msToString(treeItem.duration)}</div>} {!!treeItem.duration && treeItem.status !== 'skipped' && <div className='ui-mode-list-item-time'>{msToString(treeItem.duration)}</div>}
<Toolbar noMinHeight={true} noShadow={true}> <Toolbar noMinHeight={true} noShadow={true}>
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton> <ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton>
<ToolbarButton icon='go-to-file' title='Open in VS Code' onClick={() => testServerConnection?.openNoReply({ location: treeItem.location })} style={(treeItem.kind === 'group' && treeItem.subKind === 'folder') ? { visibility: 'hidden' } : {}}></ToolbarButton> <ToolbarButton icon='go-to-file' title='Show source' onClick={onRevealSource} style={(treeItem.kind === 'group' && treeItem.subKind === 'folder') ? { visibility: 'hidden' } : {}}></ToolbarButton>
{!watchAll && <ToolbarButton icon='eye' title='Watch' onClick={() => { {!watchAll && <ToolbarButton icon='eye' title='Watch' onClick={() => {
if (watchedTreeIds.value.has(treeItem.id)) if (watchedTreeIds.value.has(treeItem.id))
watchedTreeIds.value.delete(treeItem.id); watchedTreeIds.value.delete(treeItem.id);
@ -187,3 +178,17 @@ export const TestListView: React.FC<{
autoExpandDepth={filterText ? 5 : 1} autoExpandDepth={filterText ? 5 : 1}
noItemsMessage={isLoading ? 'Loading\u2026' : 'No tests'} />; noItemsMessage={isLoading ? 'Loading\u2026' : 'No tests'} />;
}; };
function itemLocation(item: TreeItem | undefined, model: TestModel | undefined): SourceLocation | undefined {
if (!item || !model)
return;
return {
file: item.location.file,
line: item.location.line,
column: item.location.column,
source: {
errors: model.loadErrors.filter(e => e.location?.file === item.location.file).map(e => ({ line: e.location!.line, message: e.message! })),
content: undefined,
}
};
}

View file

@ -31,7 +31,9 @@ export const TraceView: React.FC<{
showRouteActionsSetting: Setting<boolean>, showRouteActionsSetting: Setting<boolean>,
item: { treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase }, item: { treeItem?: TreeItem, testFile?: SourceLocation, testCase?: reporterTypes.TestCase },
rootDir?: string, rootDir?: string,
}> = ({ showRouteActionsSetting, item, rootDir }) => { onOpenExternally?: (location: SourceLocation) => void,
revealSource?: boolean,
}> = ({ showRouteActionsSetting, item, rootDir, onOpenExternally, revealSource }) => {
const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean } | undefined>(); const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean } | undefined>();
const [counter, setCounter] = React.useState(0); const [counter, setCounter] = React.useState(0);
const pollTimer = React.useRef<NodeJS.Timeout | null>(null); const pollTimer = React.useRef<NodeJS.Timeout | null>(null);
@ -97,7 +99,10 @@ export const TraceView: React.FC<{
onSelectionChanged={onSelectionChanged} onSelectionChanged={onSelectionChanged}
fallbackLocation={item.testFile} fallbackLocation={item.testFile}
isLive={model?.isLive} isLive={model?.isLive}
status={item.treeItem?.status} />; status={item.treeItem?.status}
onOpenExternally={onOpenExternally}
revealSource={revealSource}
/>;
}; };
const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefined => { const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefined => {

View file

@ -96,6 +96,8 @@ export const UIModeView: React.FC<{}> = ({
const [testServerConnection, setTestServerConnection] = React.useState<TestServerConnection>(); const [testServerConnection, setTestServerConnection] = React.useState<TestServerConnection>();
const [settingsVisible, setSettingsVisible] = React.useState(false); const [settingsVisible, setSettingsVisible] = React.useState(false);
const [testingOptionsVisible, setTestingOptionsVisible] = React.useState(false); const [testingOptionsVisible, setTestingOptionsVisible] = React.useState(false);
const [revealSource, setRevealSource] = React.useState(false);
const onRevealSource = React.useCallback(() => setRevealSource(true), [setRevealSource]);
const [runWorkers, setRunWorkers] = React.useState(queryParams.workers); const [runWorkers, setRunWorkers] = React.useState(queryParams.workers);
const singleWorkerSetting = React.useMemo(() => { const singleWorkerSetting = React.useMemo(() => {
@ -435,7 +437,13 @@ export const UIModeView: React.FC<{}> = ({
<XtermWrapper source={xtermDataSource}></XtermWrapper> <XtermWrapper source={xtermDataSource}></XtermWrapper>
</div> </div>
<div className={'vbox' + (isShowingOutput ? ' hidden' : '')}> <div className={'vbox' + (isShowingOutput ? ' hidden' : '')}>
<TraceView showRouteActionsSetting={showRouteActionsSetting} item={selectedItem} rootDir={testModel?.config?.rootDir} /> <TraceView
showRouteActionsSetting={showRouteActionsSetting}
item={selectedItem}
rootDir={testModel?.config?.rootDir}
revealSource={revealSource}
onOpenExternally={location => testServerConnection?.openNoReply({ location: { file: location.file, line: location.line, column: location.column } })}
/>
</div> </div>
</div> </div>
<div className='vbox ui-mode-sidebar'> <div className='vbox ui-mode-sidebar'>
@ -487,6 +495,7 @@ export const UIModeView: React.FC<{}> = ({
isLoading={isLoading} isLoading={isLoading}
requestedCollapseAllCount={collapseAllCount} requestedCollapseAllCount={collapseAllCount}
setFilterText={setFilterText} setFilterText={setFilterText}
onRevealSource={onRevealSource}
/> />
<Toolbar noShadow={true} noMinHeight={true} className='settings-toolbar' onClick={() => setTestingOptionsVisible(!testingOptionsVisible)}> <Toolbar noShadow={true} noMinHeight={true} className='settings-toolbar' onClick={() => setTestingOptionsVisible(!testingOptionsVisible)}>
<span <span

View file

@ -55,7 +55,9 @@ export const Workbench: React.FunctionComponent<{
inert?: boolean, inert?: boolean,
showRouteActionsSetting?: Setting<boolean>, showRouteActionsSetting?: Setting<boolean>,
openPage?: (url: string, target?: string) => Window | any, openPage?: (url: string, target?: string) => Window | any,
}> = ({ showRouteActionsSetting, model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, inert, openPage }) => { onOpenExternally?: (location: modelUtil.SourceLocation) => void,
revealSource?: boolean,
}> = ({ showRouteActionsSetting, model, showSourcesFirst, rootDir, fallbackLocation, initialSelection, onSelectionChanged, isLive, status, inert, openPage, onOpenExternally, revealSource }) => {
const [selectedAction, setSelectedActionImpl] = React.useState<ActionTraceEventInContext | undefined>(undefined); const [selectedAction, setSelectedActionImpl] = React.useState<ActionTraceEventInContext | undefined>(undefined);
const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined); const [revealedStack, setRevealedStack] = React.useState<StackFrame[] | undefined>(undefined);
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>(); const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEventInContext | undefined>();
@ -63,7 +65,7 @@ export const Workbench: React.FunctionComponent<{
const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState<ConsoleEntry | undefined>(); const [highlightedConsoleMessage, setHighlightedConsoleMessage] = React.useState<ConsoleEntry | undefined>();
const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions'); const [selectedNavigatorTab, setSelectedNavigatorTab] = React.useState<string>('actions');
const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting<string>('propertiesTab', showSourcesFirst ? 'source' : 'call'); const [selectedPropertiesTab, setSelectedPropertiesTab] = useSetting<string>('propertiesTab', showSourcesFirst ? 'source' : 'call');
const [isInspecting, setIsInspecting] = React.useState(false); const [isInspecting, setIsInspectingState] = React.useState(false);
const [highlightedLocator, setHighlightedLocator] = React.useState<string>(''); const [highlightedLocator, setHighlightedLocator] = React.useState<string>('');
const activeAction = model ? highlightedAction || selectedAction : undefined; const activeAction = model ? highlightedAction || selectedAction : undefined;
const [selectedTime, setSelectedTime] = React.useState<Boundaries | undefined>(); const [selectedTime, setSelectedTime] = React.useState<Boundaries | undefined>();
@ -87,6 +89,7 @@ export const Workbench: React.FunctionComponent<{
React.useEffect(() => { React.useEffect(() => {
setSelectedTime(undefined); setSelectedTime(undefined);
setRevealedStack(undefined);
}, [model]); }, [model]);
React.useEffect(() => { React.useEffect(() => {
@ -118,14 +121,25 @@ export const Workbench: React.FunctionComponent<{
const selectPropertiesTab = React.useCallback((tab: string) => { const selectPropertiesTab = React.useCallback((tab: string) => {
setSelectedPropertiesTab(tab); setSelectedPropertiesTab(tab);
if (tab !== 'inspector') if (tab !== 'inspector')
setIsInspecting(false); setIsInspectingState(false);
}, [setSelectedPropertiesTab]); }, [setSelectedPropertiesTab]);
const setIsInspecting = React.useCallback((value: boolean) => {
if (!isInspecting && value)
selectPropertiesTab('inspector');
setIsInspectingState(value);
}, [setIsInspectingState, selectPropertiesTab, isInspecting]);
const locatorPicked = React.useCallback((locator: string) => { const locatorPicked = React.useCallback((locator: string) => {
setHighlightedLocator(locator); setHighlightedLocator(locator);
selectPropertiesTab('inspector'); selectPropertiesTab('inspector');
}, [selectPropertiesTab]); }, [selectPropertiesTab]);
React.useEffect(() => {
if (revealSource)
selectPropertiesTab('source');
}, [revealSource, selectPropertiesTab]);
const consoleModel = useConsoleTabModel(model, selectedTime); const consoleModel = useConsoleTabModel(model, selectedTime);
const networkModel = useNetworkTabModel(model, selectedTime); const networkModel = useNetworkTabModel(model, selectedTime);
const errorsModel = useErrorsTabModel(model); const errorsModel = useErrorsTabModel(model);
@ -174,7 +188,9 @@ export const Workbench: React.FunctionComponent<{
sources={sources} sources={sources}
rootDir={rootDir} rootDir={rootDir}
stackFrameLocation={sidebarLocation === 'bottom' ? 'right' : 'bottom'} stackFrameLocation={sidebarLocation === 'bottom' ? 'right' : 'bottom'}
fallbackLocation={fallbackLocation} /> fallbackLocation={fallbackLocation}
onOpenExternally={onOpenExternally}
/>
}; };
const consoleTab: TabbedPaneTabModel = { const consoleTab: TabbedPaneTabModel = {
id: 'console', id: 'console',
@ -302,13 +318,6 @@ export const Workbench: React.FunctionComponent<{
tabs={tabs} tabs={tabs}
selectedTab={selectedPropertiesTab} selectedTab={selectedPropertiesTab}
setSelectedTab={selectPropertiesTab} setSelectedTab={selectPropertiesTab}
leftToolbar={[
<ToolbarButton title='Pick locator' icon='target' toggled={isInspecting} onClick={() => {
if (!isInspecting)
selectPropertiesTab('inspector');
setIsInspecting(!isInspecting);
}} />
]}
rightToolbar={[ rightToolbar={[
sidebarLocation === 'bottom' ? sidebarLocation === 'bottom' ?
<ToolbarButton title='Dock to right' icon='layout-sidebar-right-off' onClick={() => { <ToolbarButton title='Dock to right' icon='layout-sidebar-right-off' onClick={() => {

View file

@ -26,6 +26,7 @@ export interface ToolbarButtonProps {
onClick: (e: React.MouseEvent) => void, onClick: (e: React.MouseEvent) => void,
style?: React.CSSProperties, style?: React.CSSProperties,
testId?: string, testId?: string,
className?: string,
} }
export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>> = ({ export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>> = ({
@ -37,8 +38,9 @@ export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>
onClick = () => {}, onClick = () => {},
style, style,
testId, testId,
className,
}) => { }) => {
let className = `toolbar-button ${icon}`; className = (className || '') + ` toolbar-button ${icon}`;
if (toggled) if (toggled)
className += ' toggled'; className += ' toggled';
return <button return <button

View file

@ -217,7 +217,8 @@ test('should update test locations', async ({ runUITest, writeFiles }) => {
const passesItemLocator = page.getByRole('listitem').filter({ hasText: 'passes' }); const passesItemLocator = page.getByRole('listitem').filter({ hasText: 'passes' });
await passesItemLocator.hover(); await passesItemLocator.hover();
await passesItemLocator.getByTitle('Open in VS Code').click(); await passesItemLocator.getByTitle('Show source').click();
await page.getByTitle('Open in VS Code').click();
expect(messages).toEqual([{ expect(messages).toEqual([{
method: 'open', method: 'open',
@ -247,7 +248,8 @@ test('should update test locations', async ({ runUITest, writeFiles }) => {
messages.length = 0; messages.length = 0;
await passesItemLocator.hover(); await passesItemLocator.hover();
await passesItemLocator.getByTitle('Open in VS Code').click(); await passesItemLocator.getByTitle('Show source').click();
await page.getByTitle('Open in VS Code').click();
expect(messages).toEqual([{ expect(messages).toEqual([{
method: 'open', method: 'open',