rename to anchor + id, support revealing already-revealed
This commit is contained in:
parent
db26e3a2db
commit
cae5a5ddcf
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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}/>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue