This commit is contained in:
Max Schmitt 2024-03-25 22:57:50 +01:00
parent 791c68df42
commit 8a73182908
6 changed files with 61 additions and 26 deletions

View file

@ -0,0 +1,23 @@
/**
* 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.
*/
// hash string to integer in range [0, 6] for color index, to get same color for same tag
export function hashStringToInt(str: string) {
let hash = 0;
for (let i = 0; i < str.length; i++)
hash = str.charCodeAt(i) + ((hash << 8) - hash);
return Math.abs(hash % 6);
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
.label { .tag {
display: inline-block; display: inline-block;
padding: 0 8px; padding: 0 8px;
font-size: 12px; font-size: 12px;
@ -29,63 +29,63 @@
font-weight: 600; font-weight: 600;
} }
.light-mode .label-color-0 { .light-mode .tag-color-0 {
background-color: #ddf4ff; background-color: #ddf4ff;
color: #0550ae; color: #0550ae;
border: 1px solid #218bff; border: 1px solid #218bff;
} }
.light-mode .label-color-1 { .light-mode .tag-color-1 {
background-color: #fff8c5; background-color: #fff8c5;
color: #7d4e00; color: #7d4e00;
border: 1px solid #bf8700; border: 1px solid #bf8700;
} }
.light-mode .label-color-2 { .light-mode .tag-color-2 {
background-color: #fbefff; background-color: #fbefff;
color: #6e40c9; color: #6e40c9;
border: 1px solid #a475f9; border: 1px solid #a475f9;
} }
.light-mode .label-color-3 { .light-mode .tag-color-3 {
background-color: #ffeff7; background-color: #ffeff7;
color: #99286e; color: #99286e;
border: 1px solid #e85aad; border: 1px solid #e85aad;
} }
.light-mode .label-color-4 { .light-mode .tag-color-4 {
background-color: #FFF0EB; background-color: #FFF0EB;
color: #9E2F1C; color: #9E2F1C;
border: 1px solid #EA6045; border: 1px solid #EA6045;
} }
.light-mode .label-color-5 { .light-mode .tag-color-5 {
background-color: #fff1e5; background-color: #fff1e5;
color: #9b4215; color: #9b4215;
border: 1px solid #e16f24; border: 1px solid #e16f24;
} }
.dark-mode .label-color-0 { .dark-mode .tag-color-0 {
background-color: #051d4d; background-color: #051d4d;
color: #80ccff; color: #80ccff;
border: 1px solid #218bff; border: 1px solid #218bff;
} }
.dark-mode .label-color-1 { .dark-mode .tag-color-1 {
background-color: #3b2300; background-color: #3b2300;
color: #eac54f; color: #eac54f;
border: 1px solid #bf8700; border: 1px solid #bf8700;
} }
.dark-mode .label-color-2 { .dark-mode .tag-color-2 {
background-color: #271052; background-color: #271052;
color: #d2a8ff; color: #d2a8ff;
border: 1px solid #a475f9; border: 1px solid #a475f9;
} }
.dark-mode .label-color-3 { .dark-mode .tag-color-3 {
background-color: #42062a; background-color: #42062a;
color: #ff9bce; color: #ff9bce;
border: 1px solid #e85aad; border: 1px solid #e85aad;
} }
.dark-mode .label-color-4 { .dark-mode .tag-color-4 {
background-color: #460701; background-color: #460701;
color: #FFA28B; color: #FFA28B;
border: 1px solid #EC6547; border: 1px solid #EC6547;
} }
.dark-mode .label-color-5 { .dark-mode .tag-color-5 {
background-color: #471700; background-color: #471700;
color: #ffa657; color: #ffa657;
border: 1px solid #e16f24; border: 1px solid #e16f24;

View file

@ -16,22 +16,21 @@
import './tag.css'; import './tag.css';
const Tag: React.FC<{ color: number, children: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }> = ({ color, children, style, onClick }) => { export const TagView: React.FC<{ tag: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }> = ({ tag, style, onClick }) => {
return <span return <span
className={`label label-color-${color}`} className={`tag tag-color-${tagNameToColor(tag)}`}
onClick={onClick} onClick={onClick}
style={{ margin: '6px 0 0 6px', ...style }} style={{ margin: '6px 0 0 6px', ...style }}
title={`Click to filter by tag: ${tag}`}
> >
{children} {tag}
</span>; </span>;
}; };
// hash string to integer in range [0, 6] for color index, to get same color for same tag // hash string to integer in range [0, 6] for color index, to get same color for same tag
export function tagNametoColor(str: string) { function tagNameToColor(str: string) {
let hash = 0; let hash = 0;
for (let i = 0; i < str.length; i++) for (let i = 0; i < str.length; i++)
hash = str.charCodeAt(i) + ((hash << 8) - hash); hash = str.charCodeAt(i) + ((hash << 8) - hash);
return Math.abs(hash % 6); return Math.abs(hash % 6);
} }
export default Tag;

View file

@ -30,7 +30,7 @@ import { testStatusIcon } from './testUtils';
import type { TestModel } from './uiModeModel'; import type { TestModel } from './uiModeModel';
import './uiModeTestListView.css'; import './uiModeTestListView.css';
import type { TestServerConnection } from '@testIsomorphic/testServerConnection'; import type { TestServerConnection } from '@testIsomorphic/testServerConnection';
import Tag, { tagNametoColor } from './tag'; import { TagView } from './tag';
const TestTreeView = TreeView<TreeItem>; const TestTreeView = TreeView<TreeItem>;
@ -134,6 +134,20 @@ export const TestListView: React.FC<{
runTests('bounce-if-busy', testTree.collectTestIds(treeItem)); runTests('bounce-if-busy', testTree.collectTestIds(treeItem));
}; };
const handleTagClick = (e: React.MouseEvent, tag: string) => {
e.preventDefault();
e.stopPropagation();
if (e.metaKey) {
const parts = filterText.split(' ');
if (parts.includes(tag))
setFilterText(parts.filter(t => t !== tag).join(' '));
else
setFilterText((filterText + ' ' + tag).trim());
} else {
setFilterText(tag);
}
};
return <TestTreeView return <TestTreeView
name='tests' name='tests'
treeState={treeState} treeState={treeState}
@ -144,7 +158,7 @@ export const TestListView: React.FC<{
return <div className='hbox ui-mode-list-item'> return <div className='hbox ui-mode-list-item'>
<div className='ui-mode-list-item-title' title={treeItem.title}> <div className='ui-mode-list-item-title' title={treeItem.title}>
{treeItem.title} {treeItem.title}
{treeItem.kind === 'case' ? treeItem.tags.map(tag => <Tag key={tag} color={tagNametoColor(tag)} onClick={e => {e.preventDefault(); setFilterText(tag);}}>{tag.slice(1)}</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-list-item-time'>{msToString(treeItem.duration)}</div>}
<Toolbar noMinHeight={true} noShadow={true}> <Toolbar noMinHeight={true} noShadow={true}>

View file

@ -64,7 +64,6 @@ const queryParams = {
}; };
const isMac = navigator.platform === 'MacIntel'; const isMac = navigator.platform === 'MacIntel';
const isUnderTest = searchParams.get('isUnderTest') === 'true';
export const UIModeView: React.FC<{}> = ({ export const UIModeView: React.FC<{}> = ({
}) => { }) => {
@ -377,7 +376,7 @@ export const UIModeView: React.FC<{}> = ({
<div className='title'>UI Mode disconnected</div> <div className='title'>UI Mode disconnected</div>
<div><a href='#' onClick={() => window.location.href = '/'}>Reload the page</a> to reconnect</div> <div><a href='#' onClick={() => window.location.href = '/'}>Reload the page</a> to reconnect</div>
</div>} </div>}
<SplitView sidebarSize={isUnderTest ? 350 : 250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'> <SplitView sidebarSize={250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'>
<div className='vbox'> <div className='vbox'>
<div className={'vbox' + (isShowingOutput ? '' : ' hidden')}> <div className={'vbox' + (isShowingOutput ? '' : ' hidden')}>
<Toolbar> <Toolbar>

View file

@ -60,15 +60,15 @@ test('should display native tags and filter by them on click', async ({ runUITes
const { page } = await runUITest({ const { page } = await runUITest({
'a.test.ts': ` 'a.test.ts': `
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test('passes', () => {}); test('p', () => {});
test('passes with tags', { tag: '@smoke' }, () => {}); test('pwt', { tag: '@smoke' }, () => {});
`, `,
}); });
await page.locator('.ui-mode-list-item-title').getByText('smoke').click(); await page.locator('.ui-mode-list-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
passes with tags <= pwt
`); `);
}); });