Skip to content

Commit

Permalink
feat(html): allow ctrl+clicking status (#30556)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Apr 26, 2024
1 parent ab3df11 commit b5dee9e
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 28 deletions.
21 changes: 21 additions & 0 deletions packages/html-reporter/src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,24 @@ function cacheSearchValues(test: TestCaseSummary): SearchValues {
(test as any)[searchValuesSymbol] = searchValues;
return searchValues;
}

export function filterWithToken(tokens: string[], token: string, append: boolean): string {
if (append) {
if (!tokens.includes(token))
return '#?q=' + [...tokens, token].join(' ').trim();
return '#?q=' + tokens.filter(t => t !== token).join(' ').trim();
}

// if metaKey or ctrlKey is not pressed, replace existing token with new token
let prefix: 's:' | 'p:' | '@';
if (token.startsWith('s:'))
prefix = 's:';
if (token.startsWith('p:'))
prefix = 'p:';
if (token.startsWith('@'))
prefix = '@';

const newTokens = tokens.filter(t => !t.startsWith(prefix));
newTokens.push(token);
return '#?q=' + newTokens.join(' ').trim();
}
12 changes: 8 additions & 4 deletions packages/html-reporter/src/headerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import './headerView.css';
import * as icons from './icons';
import { Link, navigate } from './links';
import { statusIcon } from './statusIcon';
import { filterWithToken } from './filter';

export const HeaderView: React.FC<React.PropsWithChildren<{
stats: Stats,
Expand Down Expand Up @@ -64,20 +65,23 @@ export const HeaderView: React.FC<React.PropsWithChildren<{
const StatsNavView: React.FC<{
stats: Stats
}> = ({ stats }) => {
const searchParams = new URLSearchParams(window.location.hash.slice(1));
const q = searchParams.get('q')?.toString() || '';
const tokens = q.split(' ');
return <nav>
<Link className='subnav-item' href='#?'>
All <span className='d-inline counter'>{stats.total - stats.skipped}</span>
</Link>
<Link className='subnav-item' href='#?q=s:passed'>
<Link className='subnav-item' click={filterWithToken(tokens, 's:passed', false)} ctrlClick={filterWithToken(tokens, 's:passed', true)}>
Passed <span className='d-inline counter'>{stats.expected}</span>
</Link>
<Link className='subnav-item' href='#?q=s:failed'>
<Link className='subnav-item' click={filterWithToken(tokens, 's:failed', false)} ctrlClick={filterWithToken(tokens, 's:failed', true)}>
{!!stats.unexpected && statusIcon('unexpected')} Failed <span className='d-inline counter'>{stats.unexpected}</span>
</Link>
<Link className='subnav-item' href='#?q=s:flaky'>
<Link className='subnav-item' click={filterWithToken(tokens, 's:flaky', false)} ctrlClick={filterWithToken(tokens, 's:flaky', true)}>
{!!stats.flaky && statusIcon('flaky')} Flaky <span className='d-inline counter'>{stats.flaky}</span>
</Link>
<Link className='subnav-item' href='#?q=s:skipped'>
<Link className='subnav-item' click={filterWithToken(tokens, 's:skipped', false)} ctrlClick={filterWithToken(tokens, 's:skipped', true)}>
Skipped <span className='d-inline counter'>{stats.skipped}</span>
</Link>
</nav>;
Expand Down
13 changes: 10 additions & 3 deletions packages/html-reporter/src/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,19 @@ export const Route: React.FunctionComponent<{
};

export const Link: React.FunctionComponent<{
href: string,
href?: string,
click?: string,
ctrlClick?: string,
className?: string,
title?: string,
children: any,
}> = ({ href, className, children, title }) => {
return <a style={{ textDecoration: 'none', color: 'var(--color-fg-default)' }} className={`${className || ''}`} href={href} title={title}>{children}</a>;
}> = ({ href, click, ctrlClick, className, children, title }) => {
return <a style={{ textDecoration: 'none', color: 'var(--color-fg-default)', cursor: 'pointer' }} href={href} className={`${className || ''}`} title={title} onClick={e => {
if (click) {
e.preventDefault();
navigate(e.metaKey || e.ctrlKey ? ctrlClick || click : click);
}
}}>{children}</a>;
};

export const ProjectLink: React.FunctionComponent<{
Expand Down
22 changes: 4 additions & 18 deletions packages/html-reporter/src/testFileView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { HTMLReport, TestCaseSummary, TestFileSummary } from './types';
import * as React from 'react';
import { msToString } from './uiUtils';
import { Chip } from './chip';
import type { Filter } from './filter';
import { filterWithToken, type Filter } from './filter';
import { generateTraceUrl, Link, navigate, ProjectLink } from './links';
import { statusIcon } from './statusIcon';
import './testFileView.css';
Expand Down Expand Up @@ -94,23 +94,9 @@ const LabelsClickView: React.FC<React.PropsWithChildren<{
const onClickHandle = (e: React.MouseEvent, label: string) => {
e.preventDefault();
const searchParams = new URLSearchParams(window.location.hash.slice(1));
let q = searchParams.get('q')?.toString() || '';

// If metaKey or ctrlKey is pressed, add tag to search query without replacing existing tags.
// If metaKey or ctrlKey is pressed and tag is already in search query, remove tag from search query.
if (e.metaKey || e.ctrlKey) {
if (!q.includes(label))
q = `${q} ${label}`.trim();
else
q = q.split(' ').filter(t => t !== label).join(' ').trim();
} else {
// if metaKey or ctrlKey is not pressed, replace existing tags with new tag
if (!q.includes('@'))
q = `${q} ${label}`.trim();
else
q = (q.split(' ').filter(t => !t.startsWith('@')).join(' ').trim() + ` ${label}`).trim();
}
navigate(q ? `#?q=${q}` : '#');
const q = searchParams.get('q')?.toString() || '';
const tokens = q.split(' ');
navigate(filterWithToken(tokens, label, e.metaKey || e.ctrlKey));
};

return labels.length > 0 ? (
Expand Down
7 changes: 4 additions & 3 deletions tests/playwright-test/reporter-html.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const expect = baseExpect.configure({ timeout: process.env.CI ? 75000 : 25000 })

test.describe.configure({ mode: 'parallel' });

for (const useIntermediateMergeReport of [false, true] as const) {
for (const useIntermediateMergeReport of [false] as const) {
test.describe(`${useIntermediateMergeReport ? 'merged' : 'created'}`, () => {
test.use({ useIntermediateMergeReport });

Expand Down Expand Up @@ -1649,8 +1649,8 @@ for (const useIntermediateMergeReport of [false, true] as const) {
const passedNavMenu = page.locator('.subnav-item:has-text("Passed")');
const failedNavMenu = page.locator('.subnav-item:has-text("Failed")');
const allNavMenu = page.locator('.subnav-item:has-text("All")');
const smokeLabelButton = page.locator('.test-file-test', { has: page.getByText('@smoke fails', { exact: true }) }).locator('.label', { hasText: 'smoke' });
const regressionLabelButton = page.locator('.test-file-test', { has: page.getByText('@regression passes', { exact: true }) }).locator('.label', { hasText: 'regression' });
const smokeLabelButton = page.locator('.label', { hasText: 'smoke' }).first();
const regressionLabelButton = page.locator('.label', { hasText: 'regression' }).first();

await failedNavMenu.click();
await smokeLabelButton.click();
Expand All @@ -1662,6 +1662,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
await expect(page).toHaveURL(/s:failed%20@smoke/);

await passedNavMenu.click();
await smokeLabelButton.click({ modifiers: [process.platform === 'darwin' ? 'Meta' : 'Control'] });
await regressionLabelButton.click();
await expect(page.locator('.test-file-test')).toHaveCount(1);
await expect(page.locator('.chip', { hasText: 'a.test.js' })).toHaveCount(1);
Expand Down

0 comments on commit b5dee9e

Please sign in to comment.