chore: implement tree w/o list (#33167)
This commit is contained in:
parent
aa952c1b03
commit
623a8916f9
|
|
@ -14,28 +14,28 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.ui-mode-list-item {
|
.ui-mode-tree-item {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-mode-list-item-title {
|
.ui-mode-tree-item-title {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-mode-list-item-time {
|
.ui-mode-tree-item-time {
|
||||||
flex: none;
|
flex: none;
|
||||||
color: var(--vscode-editorCodeLens-foreground);
|
color: var(--vscode-editorCodeLens-foreground);
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tests-list-view .list-view-entry.selected .ui-mode-list-item-time,
|
.tests-tree-view .tree-view-entry.selected .ui-mode-tree-item-time,
|
||||||
.tests-list-view .list-view-entry.highlighted .ui-mode-list-item-time {
|
.tests-tree-view .tree-view-entry.highlighted .ui-mode-tree-item-time {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tests-list-view .list-view-entry:not(.highlighted):not(.selected) .toolbar-button:not(.toggled) {
|
.tests-tree-view .tree-view-entry:not(.highlighted):not(.selected) .toolbar-button:not(.toggled) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,12 +159,12 @@ export const TestListView: React.FC<{
|
||||||
rootItem={testTree.rootItem}
|
rootItem={testTree.rootItem}
|
||||||
dataTestId='test-tree'
|
dataTestId='test-tree'
|
||||||
render={treeItem => {
|
render={treeItem => {
|
||||||
return <div className='hbox ui-mode-list-item'>
|
return <div className='hbox ui-mode-tree-item'>
|
||||||
<div className='ui-mode-list-item-title'>
|
<div className='ui-mode-tree-item-title'>
|
||||||
<span title={treeItem.title}>{treeItem.title}</span>
|
<span title={treeItem.title}>{treeItem.title}</span>
|
||||||
{treeItem.kind === 'case' ? treeItem.tags.map(tag => <TagView key={tag} tag={tag.slice(1)} onClick={e => handleTagClick(e, tag)} />) : null}
|
{treeItem.kind === 'case' ? treeItem.tags.map(tag => <TagView key={tag} tag={tag.slice(1)} onClick={e => handleTagClick(e, tag)} />) : null}
|
||||||
</div>
|
</div>
|
||||||
{!!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-tree-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 && !runningState.completed}></ToolbarButton>
|
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState && !runningState.completed}></ToolbarButton>
|
||||||
<ToolbarButton icon='go-to-file' title='Show source' onClick={onRevealSource} 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>
|
||||||
|
|
|
||||||
|
|
@ -110,15 +110,12 @@ export function GridView<T>(model: GridViewProps<T>) {
|
||||||
</>;
|
</>;
|
||||||
}}
|
}}
|
||||||
icon={model.icon}
|
icon={model.icon}
|
||||||
indent={model.indent}
|
|
||||||
isError={model.isError}
|
isError={model.isError}
|
||||||
isWarning={model.isWarning}
|
isWarning={model.isWarning}
|
||||||
isInfo={model.isInfo}
|
isInfo={model.isInfo}
|
||||||
selectedItem={model.selectedItem}
|
selectedItem={model.selectedItem}
|
||||||
onAccepted={model.onAccepted}
|
onAccepted={model.onAccepted}
|
||||||
onSelected={model.onSelected}
|
onSelected={model.onSelected}
|
||||||
onLeftArrow={model.onLeftArrow}
|
|
||||||
onRightArrow={model.onRightArrow}
|
|
||||||
onHighlighted={model.onHighlighted}
|
onHighlighted={model.onHighlighted}
|
||||||
onIconClicked={model.onIconClicked}
|
onIconClicked={model.onIconClicked}
|
||||||
noItemsMessage={model.noItemsMessage}
|
noItemsMessage={model.noItemsMessage}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './listView.css';
|
import './listView.css';
|
||||||
import { clsx } from '@web/uiUtils';
|
import { clsx, scrollIntoViewIfNeeded } from '@web/uiUtils';
|
||||||
|
|
||||||
export type ListViewProps<T> = {
|
export type ListViewProps<T> = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -24,15 +24,12 @@ export type ListViewProps<T> = {
|
||||||
id?: (item: T, index: number) => string,
|
id?: (item: T, index: number) => string,
|
||||||
render: (item: T, index: number) => React.ReactNode,
|
render: (item: T, index: number) => React.ReactNode,
|
||||||
icon?: (item: T, index: number) => string | undefined,
|
icon?: (item: T, index: number) => string | undefined,
|
||||||
indent?: (item: T, index: number) => number | undefined,
|
|
||||||
isError?: (item: T, index: number) => boolean,
|
isError?: (item: T, index: number) => boolean,
|
||||||
isWarning?: (item: T, index: number) => boolean,
|
isWarning?: (item: T, index: number) => boolean,
|
||||||
isInfo?: (item: T, index: number) => boolean,
|
isInfo?: (item: T, index: number) => boolean,
|
||||||
selectedItem?: T,
|
selectedItem?: T,
|
||||||
onAccepted?: (item: T, index: number) => void,
|
onAccepted?: (item: T, index: number) => void,
|
||||||
onSelected?: (item: T, index: number) => void,
|
onSelected?: (item: T, index: number) => void,
|
||||||
onLeftArrow?: (item: T, index: number) => void,
|
|
||||||
onRightArrow?: (item: T, index: number) => void,
|
|
||||||
onHighlighted?: (item: T | undefined) => void,
|
onHighlighted?: (item: T | undefined) => void,
|
||||||
onIconClicked?: (item: T, index: number) => void,
|
onIconClicked?: (item: T, index: number) => void,
|
||||||
noItemsMessage?: string,
|
noItemsMessage?: string,
|
||||||
|
|
@ -51,12 +48,9 @@ export function ListView<T>({
|
||||||
isError,
|
isError,
|
||||||
isWarning,
|
isWarning,
|
||||||
isInfo,
|
isInfo,
|
||||||
indent,
|
|
||||||
selectedItem,
|
selectedItem,
|
||||||
onAccepted,
|
onAccepted,
|
||||||
onSelected,
|
onSelected,
|
||||||
onLeftArrow,
|
|
||||||
onRightArrow,
|
|
||||||
onHighlighted,
|
onHighlighted,
|
||||||
onIconClicked,
|
onIconClicked,
|
||||||
noItemsMessage,
|
noItemsMessage,
|
||||||
|
|
@ -95,21 +89,12 @@ export function ListView<T>({
|
||||||
onAccepted?.(selectedItem, items.indexOf(selectedItem));
|
onAccepted?.(selectedItem, items.indexOf(selectedItem));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
|
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (selectedItem && event.key === 'ArrowLeft') {
|
|
||||||
onLeftArrow?.(selectedItem, items.indexOf(selectedItem));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectedItem && event.key === 'ArrowRight') {
|
|
||||||
onRightArrow?.(selectedItem, items.indexOf(selectedItem));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = selectedItem ? items.indexOf(selectedItem) : -1;
|
const index = selectedItem ? items.indexOf(selectedItem) : -1;
|
||||||
let newIndex = index;
|
let newIndex = index;
|
||||||
if (event.key === 'ArrowDown') {
|
if (event.key === 'ArrowDown') {
|
||||||
|
|
@ -135,7 +120,6 @@ export function ListView<T>({
|
||||||
>
|
>
|
||||||
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
|
{noItemsMessage && items.length === 0 && <div className='list-view-empty'>{noItemsMessage}</div>}
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
const indentation = indent?.(item, index) || 0;
|
|
||||||
const rendered = render(item, index);
|
const rendered = render(item, index);
|
||||||
return <div
|
return <div
|
||||||
key={id?.(item, index) || index}
|
key={id?.(item, index) || index}
|
||||||
|
|
@ -152,8 +136,6 @@ export function ListView<T>({
|
||||||
onMouseEnter={() => setHighlightedItem(item)}
|
onMouseEnter={() => setHighlightedItem(item)}
|
||||||
onMouseLeave={() => setHighlightedItem(undefined)}
|
onMouseLeave={() => setHighlightedItem(undefined)}
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line react/jsx-key */}
|
|
||||||
{indentation ? new Array(indentation).fill(0).map(() => <div className='list-view-indent'></div>) : undefined}
|
|
||||||
{icon && <div
|
{icon && <div
|
||||||
className={'codicon ' + (icon(item, index) || 'codicon-blank')}
|
className={'codicon ' + (icon(item, index) || 'codicon-blank')}
|
||||||
style={{ minWidth: 16, marginRight: 4 }}
|
style={{ minWidth: 16, marginRight: 4 }}
|
||||||
|
|
@ -173,12 +155,3 @@ export function ListView<T>({
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollIntoViewIfNeeded(element: Element | undefined) {
|
|
||||||
if (!element)
|
|
||||||
return;
|
|
||||||
if ((element as any)?.scrollIntoViewIfNeeded)
|
|
||||||
(element as any).scrollIntoViewIfNeeded(false);
|
|
||||||
else
|
|
||||||
element?.scrollIntoView();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
91
packages/web/src/components/treeView.css
Normal file
91
packages/web/src/components/treeView.css
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.tree-view-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: auto;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden auto;
|
||||||
|
outline: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-entry {
|
||||||
|
display: flex;
|
||||||
|
flex: none;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 28px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-content.not-selectable > .tree-view-entry {
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-entry.highlighted:not(.selected) {
|
||||||
|
background-color: var(--vscode-list-inactiveSelectionBackground) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-entry.selected {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-indent {
|
||||||
|
min-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-content:focus .tree-view-entry.selected {
|
||||||
|
background-color: var(--vscode-list-activeSelectionBackground);
|
||||||
|
color: var(--vscode-list-activeSelectionForeground);
|
||||||
|
outline: 1px solid var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-content .tree-view-entry.selected {
|
||||||
|
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-content:focus .tree-view-entry.selected * {
|
||||||
|
color: var(--vscode-list-activeSelectionForeground) !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-content:focus .tree-view-entry.selected .codicon {
|
||||||
|
color: var(--vscode-list-activeSelectionForeground) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-empty {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-entry.error {
|
||||||
|
color: var(--vscode-list-errorForeground);
|
||||||
|
background-color: var(--vscode-inputValidation-errorBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-entry.warning {
|
||||||
|
color: var(--vscode-list-warningForeground);
|
||||||
|
background-color: var(--vscode-inputValidation-warningBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view-entry.info {
|
||||||
|
background-color: var(--vscode-inputValidation-infoBackground);
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ListView } from './listView';
|
import { clsx, scrollIntoViewIfNeeded } from '@web/uiUtils';
|
||||||
|
import './treeView.css';
|
||||||
|
|
||||||
export type TreeItem = {
|
export type TreeItem = {
|
||||||
id: string,
|
id: string,
|
||||||
|
|
@ -45,7 +46,7 @@ export type TreeViewProps<T> = {
|
||||||
autoExpandDepth?: number,
|
autoExpandDepth?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeListView = ListView<TreeItem>;
|
const scrollPositions = new Map<string, number>();
|
||||||
|
|
||||||
export function TreeView<T extends TreeItem>({
|
export function TreeView<T extends TreeItem>({
|
||||||
name,
|
name,
|
||||||
|
|
@ -97,61 +98,185 @@ export function TreeView<T extends TreeItem>({
|
||||||
return result;
|
return result;
|
||||||
}, [treeItems, isVisible]);
|
}, [treeItems, isVisible]);
|
||||||
|
|
||||||
return <TreeListView
|
const itemListRef = React.useRef<HTMLDivElement>(null);
|
||||||
name={name}
|
const [highlightedItem, setHighlightedItem] = React.useState<any>();
|
||||||
items={visibleItems}
|
|
||||||
id={item => item.id}
|
React.useEffect(() => {
|
||||||
dataTestId={dataTestId || (name + '-tree')}
|
onHighlighted?.(highlightedItem);
|
||||||
render={item => {
|
}, [onHighlighted, highlightedItem]);
|
||||||
const rendered = render(item as T);
|
|
||||||
return <>
|
React.useEffect(() => {
|
||||||
{icon && <div className={'codicon ' + (icon(item as T) || 'blank')} style={{ minWidth: 16, marginRight: 4 }}></div>}
|
const treeElem = itemListRef.current;
|
||||||
{typeof rendered === 'string' ? <div style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>{rendered}</div> : rendered}
|
if (!treeElem)
|
||||||
</>;
|
return;
|
||||||
}}
|
const saveScrollPosition = () => {
|
||||||
icon={item => {
|
scrollPositions.set(name, treeElem.scrollTop);
|
||||||
const expanded = treeItems.get(item as T)!.expanded;
|
};
|
||||||
if (typeof expanded === 'boolean')
|
treeElem.addEventListener('scroll', saveScrollPosition, { passive: true });
|
||||||
return expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
|
return () => treeElem.removeEventListener('scroll', saveScrollPosition);
|
||||||
}}
|
}, [name]);
|
||||||
isError={item => isError?.(item as T) || false}
|
|
||||||
indent={item => treeItems.get(item as T)!.depth}
|
React.useEffect(() => {
|
||||||
selectedItem={selectedItem}
|
if (itemListRef.current)
|
||||||
onAccepted={item => onAccepted?.(item as T)}
|
itemListRef.current.scrollTop = scrollPositions.get(name) || 0;
|
||||||
onSelected={item => onSelected?.(item as T)}
|
}, [name]);
|
||||||
onHighlighted={item => onHighlighted?.(item as T)}
|
|
||||||
onLeftArrow={item => {
|
const toggleExpanded = React.useCallback((item: T) => {
|
||||||
const { expanded, parent } = treeItems.get(item as T)!;
|
const { expanded } = treeItems.get(item)!;
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
treeState.expandedItems.set(item.id, false);
|
// Move nested selection up.
|
||||||
setTreeState({ ...treeState });
|
for (let i: TreeItem | undefined = selectedItem; i; i = i.parent) {
|
||||||
} else if (parent) {
|
if (i === item) {
|
||||||
onSelected?.(parent as T);
|
onSelected?.(item as T);
|
||||||
}
|
break;
|
||||||
}}
|
|
||||||
onRightArrow={item => {
|
|
||||||
if (item.children.length) {
|
|
||||||
treeState.expandedItems.set(item.id, true);
|
|
||||||
setTreeState({ ...treeState });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onIconClicked={item => {
|
|
||||||
const { expanded } = treeItems.get(item as T)!;
|
|
||||||
if (expanded) {
|
|
||||||
// Move nested selection up.
|
|
||||||
for (let i: TreeItem | undefined = selectedItem; i; i = i.parent) {
|
|
||||||
if (i === item) {
|
|
||||||
onSelected?.(item as T);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
treeState.expandedItems.set(item.id, false);
|
|
||||||
} else {
|
|
||||||
treeState.expandedItems.set(item.id, true);
|
|
||||||
}
|
}
|
||||||
setTreeState({ ...treeState });
|
treeState.expandedItems.set(item.id, false);
|
||||||
}}
|
} else {
|
||||||
noItemsMessage={noItemsMessage} />;
|
treeState.expandedItems.set(item.id, true);
|
||||||
|
}
|
||||||
|
setTreeState({ ...treeState });
|
||||||
|
}, [treeItems, selectedItem, onSelected, treeState, setTreeState]);
|
||||||
|
|
||||||
|
return <div className={clsx(`tree-view vbox`, name + '-tree-view')} role={'tree'} data-testid={dataTestId || (name + '-tree')}>
|
||||||
|
<div
|
||||||
|
className={clsx('tree-view-content')}
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={event => {
|
||||||
|
if (selectedItem && event.key === 'Enter') {
|
||||||
|
onAccepted?.(selectedItem);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (selectedItem && event.key === 'ArrowLeft') {
|
||||||
|
const { expanded, parent } = treeItems.get(selectedItem)!;
|
||||||
|
if (expanded) {
|
||||||
|
treeState.expandedItems.set(selectedItem.id, false);
|
||||||
|
setTreeState({ ...treeState });
|
||||||
|
} else if (parent) {
|
||||||
|
onSelected?.(parent as T);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedItem && event.key === 'ArrowRight') {
|
||||||
|
if (selectedItem.children.length) {
|
||||||
|
treeState.expandedItems.set(selectedItem.id, true);
|
||||||
|
setTreeState({ ...treeState });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = selectedItem ? visibleItems.indexOf(selectedItem) : -1;
|
||||||
|
let newIndex = index;
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
if (index === -1)
|
||||||
|
newIndex = 0;
|
||||||
|
else
|
||||||
|
newIndex = Math.min(index + 1, visibleItems.length - 1);
|
||||||
|
}
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
if (index === -1)
|
||||||
|
newIndex = visibleItems.length - 1;
|
||||||
|
else
|
||||||
|
newIndex = Math.max(index - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = itemListRef.current?.children.item(newIndex);
|
||||||
|
scrollIntoViewIfNeeded(element || undefined);
|
||||||
|
onHighlighted?.(undefined);
|
||||||
|
onSelected?.(visibleItems[newIndex]);
|
||||||
|
setHighlightedItem(undefined);
|
||||||
|
}}
|
||||||
|
ref={itemListRef}
|
||||||
|
>
|
||||||
|
{noItemsMessage && visibleItems.length === 0 && <div className='tree-view-empty'>{noItemsMessage}</div>}
|
||||||
|
{visibleItems.map(item => {
|
||||||
|
return <div key={item.id} role='treeitem' aria-selected={item === selectedItem}>
|
||||||
|
<TreeItemHeader
|
||||||
|
item={item}
|
||||||
|
itemData={treeItems.get(item)!}
|
||||||
|
selectedItem={selectedItem}
|
||||||
|
onSelected={onSelected}
|
||||||
|
onAccepted={onAccepted}
|
||||||
|
isError={isError}
|
||||||
|
toggleExpanded={toggleExpanded}
|
||||||
|
highlightedItem={highlightedItem}
|
||||||
|
setHighlightedItem={setHighlightedItem}
|
||||||
|
render={render}
|
||||||
|
icon={icon} />
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeItemHeaderProps<T> = {
|
||||||
|
item: T,
|
||||||
|
itemData: TreeItemData,
|
||||||
|
selectedItem: T | undefined,
|
||||||
|
onSelected?: (item: T) => void,
|
||||||
|
toggleExpanded: (item: T) => void,
|
||||||
|
highlightedItem: T | undefined,
|
||||||
|
isError?: (item: T) => boolean,
|
||||||
|
onAccepted?: (item: T) => void,
|
||||||
|
setHighlightedItem: (item: T | undefined) => void,
|
||||||
|
render: (item: T) => React.ReactNode,
|
||||||
|
icon?: (item: T) => string | undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TreeItemHeader<T extends TreeItem>({
|
||||||
|
item,
|
||||||
|
itemData,
|
||||||
|
selectedItem,
|
||||||
|
onSelected,
|
||||||
|
highlightedItem,
|
||||||
|
setHighlightedItem,
|
||||||
|
isError,
|
||||||
|
onAccepted,
|
||||||
|
toggleExpanded,
|
||||||
|
render,
|
||||||
|
icon }: TreeItemHeaderProps<T>) {
|
||||||
|
|
||||||
|
const indentation = itemData.depth;
|
||||||
|
const expanded = itemData.expanded;
|
||||||
|
let expandIcon = 'codicon-blank';
|
||||||
|
if (typeof expanded === 'boolean')
|
||||||
|
expandIcon = expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
|
||||||
|
const rendered = render(item);
|
||||||
|
|
||||||
|
return <div
|
||||||
|
onDoubleClick={() => onAccepted?.(item)}
|
||||||
|
className={clsx(
|
||||||
|
'tree-view-entry',
|
||||||
|
selectedItem === item && 'selected',
|
||||||
|
highlightedItem === item && 'highlighted',
|
||||||
|
isError?.(item) && 'error')}
|
||||||
|
onClick={() => onSelected?.(item)}
|
||||||
|
onMouseEnter={() => setHighlightedItem(item)}
|
||||||
|
onMouseLeave={() => setHighlightedItem(undefined)}
|
||||||
|
>
|
||||||
|
{indentation ? new Array(indentation).fill(0).map((_, i) => <div key={'indent-' + i} className='tree-view-indent'></div>) : undefined}
|
||||||
|
<div
|
||||||
|
className={'codicon ' + expandIcon}
|
||||||
|
style={{ minWidth: 16, marginRight: 4 }}
|
||||||
|
onDoubleClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
toggleExpanded(item);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{icon && <div className={'codicon ' + (icon(item) || 'codicon-blank')} style={{ minWidth: 16, marginRight: 4 }}></div>}
|
||||||
|
{typeof rendered === 'string' ? <div style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>{rendered}</div> : rendered}
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TreeItemData = {
|
type TreeItemData = {
|
||||||
|
|
@ -160,7 +285,12 @@ type TreeItemData = {
|
||||||
parent: TreeItem | null,
|
parent: TreeItem | null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function flattenTree<T extends TreeItem>(rootItem: T, selectedItem: T | undefined, expandedItems: Map<string, boolean | undefined>, autoExpandDepth: number): Map<T, TreeItemData> {
|
function flattenTree<T extends TreeItem>(
|
||||||
|
rootItem: T,
|
||||||
|
selectedItem: T | undefined,
|
||||||
|
expandedItems: Map<string, boolean | undefined>,
|
||||||
|
autoExpandDepth: number): Map<T, TreeItemData> {
|
||||||
|
|
||||||
const result = new Map<T, TreeItemData>();
|
const result = new Map<T, TreeItemData>();
|
||||||
const temporaryExpanded = new Set<string>();
|
const temporaryExpanded = new Set<string>();
|
||||||
for (let item: TreeItem | undefined = selectedItem?.parent; item; item = item.parent)
|
for (let item: TreeItem | undefined = selectedItem?.parent; item; item = item.parent)
|
||||||
|
|
|
||||||
|
|
@ -208,5 +208,14 @@ export async function sha1(str: string): Promise<string> {
|
||||||
return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer))).map(b => b.toString(16).padStart(2, '0')).join('');
|
return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer))).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function scrollIntoViewIfNeeded(element: Element | undefined) {
|
||||||
|
if (!element)
|
||||||
|
return;
|
||||||
|
if ((element as any)?.scrollIntoViewIfNeeded)
|
||||||
|
(element as any).scrollIntoViewIfNeeded(false);
|
||||||
|
else
|
||||||
|
element?.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
const kControlCodesRe = '\\u0000-\\u0020\\u007f-\\u009f';
|
const kControlCodesRe = '\\u0000-\\u0020\\u007f-\\u009f';
|
||||||
export const kWebLinkRe = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|www\\.)[^\\s' + kControlCodesRe + '"]{2,}[^\\s' + kControlCodesRe + '"\')}\\],:;.!?]', 'ug');
|
export const kWebLinkRe = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|www\\.)[^\\s' + kControlCodesRe + '"]{2,}[^\\s' + kControlCodesRe + '"\')}\\],:;.!?]', 'ug');
|
||||||
|
|
|
||||||
|
|
@ -62,13 +62,13 @@ class TraceViewerPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async actionIconsText(action: string) {
|
async actionIconsText(action: string) {
|
||||||
const entry = await this.page.waitForSelector(`.list-view-entry:has-text("${action}")`);
|
const entry = await this.page.waitForSelector(`.tree-view-entry:has-text("${action}")`);
|
||||||
await entry.waitForSelector('.action-icon-value:visible');
|
await entry.waitForSelector('.action-icon-value:visible');
|
||||||
return await entry.$$eval('.action-icon-value:visible', ee => ee.map(e => e.textContent));
|
return await entry.$$eval('.action-icon-value:visible', ee => ee.map(e => e.textContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
async actionIcons(action: string) {
|
async actionIcons(action: string) {
|
||||||
return await this.page.waitForSelector(`.list-view-entry:has-text("${action}") .action-icons`);
|
return await this.page.waitForSelector(`.tree-view-entry:has-text("${action}") .action-icons`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@step
|
@step
|
||||||
|
|
|
||||||
|
|
@ -66,16 +66,16 @@ export function dumpTestTree(page: Page, options: { time?: boolean } = {}): () =
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
const listItems = treeElement.querySelectorAll('[role=listitem]');
|
const treeItems = treeElement.querySelectorAll('[role=treeitem]');
|
||||||
for (const listItem of listItems) {
|
for (const treeItem of treeItems) {
|
||||||
const iconElements = listItem.querySelectorAll('.codicon');
|
const iconElements = treeItem.querySelectorAll('.codicon');
|
||||||
const treeIcon = iconName(iconElements[0]);
|
const treeIcon = iconName(iconElements[0]);
|
||||||
const statusIcon = iconName(iconElements[1]);
|
const statusIcon = iconName(iconElements[1]);
|
||||||
const indent = listItem.querySelectorAll('.list-view-indent').length;
|
const indent = treeItem.querySelectorAll('.tree-view-indent').length;
|
||||||
const watch = listItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : '';
|
const watch = treeItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : '';
|
||||||
const selected = listItem.classList.contains('selected') ? ' <=' : '';
|
const selected = treeItem.getAttribute('aria-selected') === 'true' ? ' <=' : '';
|
||||||
const title = listItem.querySelector('.ui-mode-list-item-title').childNodes[0].textContent;
|
const title = treeItem.querySelector('.ui-mode-tree-item-title').childNodes[0].textContent;
|
||||||
const timeElement = options.time ? listItem.querySelector('.ui-mode-list-item-time') : undefined;
|
const timeElement = options.time ? treeItem.querySelector('.ui-mode-tree-item-time') : undefined;
|
||||||
const time = timeElement ? ' ' + timeElement.textContent.replace(/[.\d]+m?s/, 'XXms') : '';
|
const time = timeElement ? ' ' + timeElement.textContent.replace(/[.\d]+m?s/, 'XXms') : '';
|
||||||
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + title + time + watch + selected);
|
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + title + time + watch + selected);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ test('should display annotations', async ({ runUITest }) => {
|
||||||
});
|
});
|
||||||
await page.getByTitle('Run all').click();
|
await page.getByTitle('Run all').click();
|
||||||
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
|
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
|
||||||
await page.getByRole('listitem').filter({ hasText: 'suite' }).locator('.codicon-chevron-right').click();
|
await page.getByRole('treeitem').filter({ hasText: 'suite' }).locator('.codicon-chevron-right').click();
|
||||||
await page.getByText('annotation test').click();
|
await page.getByText('annotation test').click();
|
||||||
await page.getByText('Annotations', { exact: true }).click();
|
await page.getByText('Annotations', { exact: true }).click();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ test('should display native tags and filter by them on click', async ({ runUITes
|
||||||
test('pwt', { tag: '@smoke' }, () => {});
|
test('pwt', { tag: '@smoke' }, () => {});
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
await page.locator('.ui-mode-list-item-title').getByText('smoke').click();
|
await page.locator('.ui-mode-tree-item-title').getByText('smoke').click();
|
||||||
await expect(page.getByPlaceholder('Filter')).toHaveValue('@smoke');
|
await expect(page.getByPlaceholder('Filter')).toHaveValue('@smoke');
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ test('should update trace live', async ({ runUITest, server }) => {
|
||||||
await page.getByText('live test').dblclick();
|
await page.getByText('live test').dblclick();
|
||||||
|
|
||||||
// It should halt on loading one.html.
|
// It should halt on loading one.html.
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -57,11 +57,11 @@ test('should update trace live', async ({ runUITest, server }) => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
listItem.locator(':scope.selected'),
|
listItem.locator(':scope[aria-selected="true"]'),
|
||||||
'last action to be selected'
|
'last action to be selected'
|
||||||
).toHaveText(/page.goto/);
|
).toHaveText(/page.goto/);
|
||||||
await expect(
|
await expect(
|
||||||
listItem.locator(':scope.selected .codicon.codicon-loading'),
|
listItem.locator(':scope[aria-selected="true"] .codicon.codicon-loading'),
|
||||||
'spinner'
|
'spinner'
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
|
|
@ -83,11 +83,11 @@ test('should update trace live', async ({ runUITest, server }) => {
|
||||||
/page.gotohttp:\/\/localhost:\d+\/two.html/
|
/page.gotohttp:\/\/localhost:\d+\/two.html/
|
||||||
]);
|
]);
|
||||||
await expect(
|
await expect(
|
||||||
listItem.locator(':scope.selected'),
|
listItem.locator(':scope[aria-selected="true"]'),
|
||||||
'last action to be selected'
|
'last action to be selected'
|
||||||
).toHaveText(/page.goto/);
|
).toHaveText(/page.goto/);
|
||||||
await expect(
|
await expect(
|
||||||
listItem.locator(':scope.selected .codicon.codicon-loading'),
|
listItem.locator(':scope[aria-selected="true"] .codicon.codicon-loading'),
|
||||||
'spinner'
|
'spinner'
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ test('should preserve action list selection upon live trace update', async ({ ru
|
||||||
await page.getByText('live test').dblclick();
|
await page.getByText('live test').dblclick();
|
||||||
|
|
||||||
// It should wait on the latch.
|
// It should wait on the latch.
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -157,7 +157,7 @@ test('should preserve action list selection upon live trace update', async ({ ru
|
||||||
/page.setContent[\d.]+m?s/,
|
/page.setContent[\d.]+m?s/,
|
||||||
]);
|
]);
|
||||||
await expect(
|
await expect(
|
||||||
listItem.locator(':scope.selected'),
|
listItem.locator(':scope[aria-selected="true"]'),
|
||||||
'selected action stays the same'
|
'selected action stays the same'
|
||||||
).toHaveText(/page.goto/);
|
).toHaveText(/page.goto/);
|
||||||
});
|
});
|
||||||
|
|
@ -193,7 +193,7 @@ test('should update tracing network live', async ({ runUITest, server }) => {
|
||||||
await page.getByText('live test').dblclick();
|
await page.getByText('live test').dblclick();
|
||||||
|
|
||||||
// It should wait on the latch.
|
// It should wait on the latch.
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -233,7 +233,7 @@ test('should show trace w/ multiple contexts', async ({ runUITest, server, creat
|
||||||
await page.getByText('live test').dblclick();
|
await page.getByText('live test').dblclick();
|
||||||
|
|
||||||
// It should wait on the latch.
|
// It should wait on the latch.
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -278,7 +278,7 @@ test('should show live trace for serial', async ({ runUITest, server, createLatc
|
||||||
await page.getByText('two', { exact: true }).click();
|
await page.getByText('two', { exact: true }).click();
|
||||||
await page.getByTitle('Run all').click();
|
await page.getByTitle('Run all').click();
|
||||||
|
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -318,7 +318,7 @@ test('should show live trace from hooks', async ({ runUITest, createLatch }) =>
|
||||||
`);
|
`);
|
||||||
await page.getByText('test one').dblclick();
|
await page.getByText('test one').dblclick();
|
||||||
|
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ test('should run on hover', async ({ runUITest }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('passes').hover();
|
await page.getByText('passes').hover();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'passes' }).getByTitle('Run').click();
|
await page.getByRole('treeitem').filter({ hasText: 'passes' }).getByTitle('Run').click();
|
||||||
|
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
|
|
@ -275,7 +275,7 @@ test('should run folder', async ({ runUITest }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('folder-b').hover();
|
await page.getByText('folder-b').hover();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'folder-b' }).getByTitle('Run').click();
|
await page.getByRole('treeitem').filter({ hasText: 'folder-b' }).getByTitle('Run').click();
|
||||||
|
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
▼ ✅ folder-b <=
|
▼ ✅ folder-b <=
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ test('should run part of the setup only', async ({ runUITest }) => {
|
||||||
await page.getByLabel('test').setChecked(true);
|
await page.getByLabel('test').setChecked(true);
|
||||||
|
|
||||||
await page.getByText('setup.ts').hover();
|
await page.getByText('setup.ts').hover();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'setup.ts' }).getByTitle('Run').click();
|
await page.getByRole('treeitem').filter({ hasText: 'setup.ts' }).getByTitle('Run').click();
|
||||||
|
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ✅ setup.ts <=
|
▼ ✅ setup.ts <=
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ test('should not loose run information after execution if test wrote into testDi
|
||||||
await page.getByTitle('Run all').click();
|
await page.getByTitle('Run all').click();
|
||||||
await page.waitForTimeout(5_000);
|
await page.waitForTimeout(5_000);
|
||||||
await expect(page.getByText('Did not run')).toBeHidden();
|
await expect(page.getByText('Did not run')).toBeHidden();
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -215,7 +215,7 @@ test('should update test locations', async ({ runUITest, writeFiles }) => {
|
||||||
const messages: any[] = [];
|
const messages: any[] = [];
|
||||||
await page.exposeBinding('__logForTest', (source, arg) => messages.push(arg));
|
await page.exposeBinding('__logForTest', (source, arg) => messages.push(arg));
|
||||||
|
|
||||||
const passesItemLocator = page.getByRole('listitem').filter({ hasText: 'passes' });
|
const passesItemLocator = page.getByRole('treeitem').filter({ hasText: 'passes' });
|
||||||
await passesItemLocator.hover();
|
await passesItemLocator.hover();
|
||||||
await passesItemLocator.getByTitle('Show source').click();
|
await passesItemLocator.getByTitle('Show source').click();
|
||||||
await page.getByTitle('Open in VS Code').click();
|
await page.getByTitle('Open in VS Code').click();
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,14 @@ test('should watch files', async ({ runUITest, writeFiles }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('fails').click();
|
await page.getByText('fails').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'fails' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'fails' }).getByTitle('Watch').click();
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails 👁 <=
|
◯ fails 👁 <=
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await page.getByRole('listitem').filter({ hasText: 'fails' }).getByTitle('Run').click();
|
await page.getByRole('treeitem').filter({ hasText: 'fails' }).getByTitle('Run').click();
|
||||||
|
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ❌ a.test.ts
|
▼ ❌ a.test.ts
|
||||||
|
|
@ -75,7 +75,7 @@ test('should watch e2e deps', async ({ runUITest, writeFiles }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('answer').click();
|
await page.getByText('answer').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'answer' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'answer' }).getByTitle('Watch').click();
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ answer 👁 <=
|
◯ answer 👁 <=
|
||||||
|
|
@ -102,13 +102,13 @@ test('should batch watch updates', async ({ runUITest, writeFiles }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('a.test.ts').click();
|
await page.getByText('a.test.ts').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
|
||||||
await page.getByText('b.test.ts').click();
|
await page.getByText('b.test.ts').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'b.test.ts' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'b.test.ts' }).getByTitle('Watch').click();
|
||||||
await page.getByText('c.test.ts').click();
|
await page.getByText('c.test.ts').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'c.test.ts' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'c.test.ts' }).getByTitle('Watch').click();
|
||||||
await page.getByText('d.test.ts').click();
|
await page.getByText('d.test.ts').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'd.test.ts' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'd.test.ts' }).getByTitle('Watch').click();
|
||||||
|
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ◯ a.test.ts 👁
|
▼ ◯ a.test.ts 👁
|
||||||
|
|
@ -229,7 +229,7 @@ test('should run added test in watched file', async ({ runUITest, writeFiles })
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('a.test.ts').click();
|
await page.getByText('a.test.ts').click();
|
||||||
await page.getByRole('listitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
|
await page.getByRole('treeitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
|
||||||
|
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
▼ ◯ a.test.ts 👁 <=
|
▼ ◯ a.test.ts 👁 <=
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ test('should merge trace events', async ({ runUITest }) => {
|
||||||
|
|
||||||
await page.getByText('trace test').dblclick();
|
await page.getByText('trace test').dblclick();
|
||||||
|
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -61,7 +61,7 @@ test('should merge web assertion events', async ({ runUITest }, testInfo) => {
|
||||||
|
|
||||||
await page.getByText('trace test').dblclick();
|
await page.getByText('trace test').dblclick();
|
||||||
|
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -86,7 +86,7 @@ test('should merge screenshot assertions', async ({ runUITest }, testInfo) => {
|
||||||
|
|
||||||
await page.getByText('trace test').dblclick();
|
await page.getByText('trace test').dblclick();
|
||||||
|
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -134,7 +134,7 @@ test('should show snapshots for sync assertions', async ({ runUITest }) => {
|
||||||
|
|
||||||
await page.getByText('trace test').dblclick();
|
await page.getByText('trace test').dblclick();
|
||||||
|
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
'action list'
|
'action list'
|
||||||
|
|
@ -214,7 +214,7 @@ test('should not fail on internal page logs', async ({ runUITest, server }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('pass').dblclick();
|
await page.getByText('pass').dblclick();
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
|
|
@ -241,7 +241,7 @@ test('should not show caught errors in the errors tab', async ({ runUITest }, te
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('pass').dblclick();
|
await page.getByText('pass').dblclick();
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
|
|
@ -272,7 +272,7 @@ test('should reveal errors in the sourcetab', async ({ runUITest }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByText('pass').dblclick();
|
await page.getByText('pass').dblclick();
|
||||||
const listItem = page.getByTestId('actions-tree').getByRole('listitem');
|
const listItem = page.getByTestId('actions-tree').getByRole('treeitem');
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
listItem,
|
listItem,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue