rename to anchor + id, support revealing already-revealed

This commit is contained in:
Simon Knott 2024-11-11 11:20:39 +01:00
parent db26e3a2db
commit cae5a5ddcf
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
4 changed files with 26 additions and 24 deletions

View file

@ -20,7 +20,7 @@ import './colors.css';
import './common.css'; import './common.css';
import * as icons from './icons'; import * as icons from './icons';
import { clsx } from '@web/uiUtils'; import { clsx } from '@web/uiUtils';
import { Reveal } from './links'; import { Anchor } from './links';
export const Chip: React.FC<{ export const Chip: React.FC<{
header: JSX.Element | string, header: JSX.Element | string,
@ -53,15 +53,15 @@ export const AutoChip: React.FC<{
noInsets?: boolean, noInsets?: boolean,
children?: any, children?: any,
dataTestId?: string, dataTestId?: string,
revealId?: string, anchorId?: string,
}> = ({ header, initialExpanded, noInsets, children, dataTestId, revealId }) => { }> = ({ header, initialExpanded, noInsets, children, dataTestId, anchorId }) => {
const [expanded, setExpanded] = React.useState(initialExpanded ?? true); const [expanded, setExpanded] = React.useState(initialExpanded ?? true);
const onChangeReveal = React.useCallback((isRevealed: boolean) => { const onChangeReveal = React.useCallback((isRevealed: boolean) => {
if (isRevealed) if (isRevealed)
setExpanded(true); setExpanded(true);
}, [setExpanded]); }, [setExpanded]);
return <Reveal return <Anchor
revealId={revealId} id={anchorId}
onChange={onChangeReveal}> onChange={onChangeReveal}>
<Chip <Chip
header={header} header={header}
@ -72,5 +72,5 @@ export const AutoChip: React.FC<{
> >
{children} {children}
</Chip> </Chip>
</Reveal>; </Anchor>;
}; };

View file

@ -114,19 +114,20 @@ export function generateTraceUrl(traces: TestAttachment[]) {
const kMissingContentType = 'x-playwright/missing'; const kMissingContentType = 'x-playwright/missing';
export function useAnchor(id: string | undefined, onChange: (isRevealed: boolean) => void) {
export function useRevealed(revealId?: string) { React.useEffect(() => {
const searchParams = React.useContext(SearchParamsContext); const listener = () => {
if (revealId === undefined) const params = new URLSearchParams(window.location.hash.slice(1));
return false; onChange(params.get('anchor') === id);
return searchParams.get('reveal') === revealId; };
window.addEventListener('popstate', listener);
return () => window.removeEventListener('popstate', listener);
}, [id, onChange]);
} }
export function Reveal({ revealId, onChange, children }: React.PropsWithChildren<{ revealId?: string, onChange?(isRevealed: boolean, ref: HTMLDivElement): void }>) { export function Anchor({ id, onChange, children }: React.PropsWithChildren<{ id?: string, onChange?(isRevealed: boolean, ref: HTMLDivElement): void }>) {
const isRevealed = useRevealed(revealId);
const ref = React.useRef<HTMLDivElement>(null); const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => { const onAnchorChange = React.useCallback((isRevealed: boolean) => {
if (!ref.current) if (!ref.current)
return; return;
@ -135,8 +136,9 @@ export function Reveal({ revealId, onChange, children }: React.PropsWithChildren
return; return;
if (isRevealed) if (isRevealed)
ref.current?.scrollIntoView({ block: 'start', inline: 'start' }); requestAnimationFrame(() => ref.current?.scrollIntoView({ block: 'start', inline: 'start' }));
}, [isRevealed, onChange]); }, [onChange]);
useAnchor(id, onAnchorChange);
return <div ref={ref}>{children}</div>; return <div ref={ref}>{children}</div>;
} }

View file

@ -75,12 +75,12 @@ function imageDiffBadge(test: TestCaseSummary): JSX.Element | undefined {
const resultWithImageDiff = test.results.find(result => result.attachments.some(attachment => { const resultWithImageDiff = test.results.find(result => result.attachments.some(attachment => {
return attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/); return attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/);
})); }));
return resultWithImageDiff ? <Link href={`#?testId=${test.testId}&reveal=diff-0&run=${test.results.indexOf(resultWithImageDiff)}`} title='View images' className='test-file-badge'>{image()}</Link> : undefined; return resultWithImageDiff ? <Link href={`#?testId=${test.testId}&anchor=diff-0&run=${test.results.indexOf(resultWithImageDiff)}`} title='View images' className='test-file-badge'>{image()}</Link> : undefined;
} }
function videoBadge(test: TestCaseSummary): JSX.Element | undefined { function videoBadge(test: TestCaseSummary): JSX.Element | undefined {
const resultWithVideo = test.results.find(result => result.attachments.some(attachment => attachment.name === 'video')); const resultWithVideo = test.results.find(result => result.attachments.some(attachment => attachment.name === 'video'));
return resultWithVideo ? <Link href={`#?testId=${test.testId}&reveal=videos&run=${test.results.indexOf(resultWithVideo)}`} title='View video' className='test-file-badge'>{video()}</Link> : undefined; return resultWithVideo ? <Link href={`#?testId=${test.testId}&anchor=videos&run=${test.results.indexOf(resultWithVideo)}`} title='View video' className='test-file-badge'>{video()}</Link> : undefined;
} }
function traceBadge(test: TestCaseSummary): JSX.Element | undefined { function traceBadge(test: TestCaseSummary): JSX.Element | undefined {

View file

@ -20,7 +20,7 @@ import { TreeItem } from './treeItem';
import { msToString } from './utils'; import { msToString } from './utils';
import { AutoChip } from './chip'; import { AutoChip } from './chip';
import { traceImage } from './images'; import { traceImage } from './images';
import { AttachmentLink, generateTraceUrl } from './links'; import { AttachmentLink, generateTraceUrl, Link } from './links';
import { statusIcon } from './statusIcon'; import { statusIcon } from './statusIcon';
import type { ImageDiff } from '@web/shared/imageDiffView'; import type { ImageDiff } from '@web/shared/imageDiffView';
import { ImageDiffView } from '@web/shared/imageDiffView'; import { ImageDiffView } from '@web/shared/imageDiffView';
@ -91,7 +91,7 @@ export const TestResultView: React.FC<{
</AutoChip>} </AutoChip>}
{diffs.map((diff, index) => {diffs.map((diff, index) =>
<AutoChip key={`diff-${index}`} dataTestId='test-results-image-diff' header={`Image mismatch: ${diff.name}`} revealId={`diff-${index}`}> <AutoChip key={`diff-${index}`} dataTestId='test-results-image-diff' header={`Image mismatch: ${diff.name}`} anchorId={`diff-${index}`}>
<ImageDiffView diff={diff}/> <ImageDiffView diff={diff}/>
</AutoChip> </AutoChip>
)} )}
@ -107,7 +107,7 @@ export const TestResultView: React.FC<{
})} })}
</AutoChip>} </AutoChip>}
{!!traces.length && <AutoChip header='Traces'> {!!traces.length && <AutoChip header='Traces' anchorId='traces'>
{<div> {<div>
<a href={generateTraceUrl(traces)}> <a href={generateTraceUrl(traces)}>
<img className='screenshot' src={traceImage} style={{ width: 192, height: 117, marginLeft: 20 }} /> <img className='screenshot' src={traceImage} style={{ width: 192, height: 117, marginLeft: 20 }} />
@ -116,7 +116,7 @@ export const TestResultView: React.FC<{
</div>} </div>}
</AutoChip>} </AutoChip>}
{!!videos.length && <AutoChip header='Videos' revealId='videos'> {!!videos.length && <AutoChip header='Videos' anchorId='videos'>
{videos.map((a, i) => <div key={`video-${i}`}> {videos.map((a, i) => <div key={`video-${i}`}>
<video controls> <video controls>
<source src={a.path} type={a.contentType}/> <source src={a.path} type={a.contentType}/>