Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor VisualTests to allow selecting viewport + browser. #18

Merged
Merged
110 changes: 110 additions & 0 deletions src/components/BrowserSelector.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { action } from "@storybook/addon-actions";
import type { Meta, StoryObj } from "@storybook/react";

import { Browser, ComparisonResult } from "../gql/graphql";
import { BrowserSelector } from "./BrowserSelector";

const meta = {
component: BrowserSelector,
args: {
onSelectBrowser: action("onSelectBrowser"),
},
} satisfies Meta<typeof BrowserSelector>;

export default meta;
type Story = StoryObj<typeof meta>;

const browserChrome = {
id: "_chrome",
key: Browser.Chrome,
name: "Chrome",
};
const browserSafari = {
id: "_safari",
key: Browser.Safari,
name: "Safari",
};

export const WithSingleBrowserChanged: Story = {
args: {
selectedBrowser: browserChrome,
browserResults: [
{
browser: browserChrome,
result: ComparisonResult.Changed,
},
],
},
};

export const WithSingleBrowserEqual: Story = {
args: {
selectedBrowser: browserChrome,
browserResults: [
{
browser: browserChrome,
result: ComparisonResult.Equal,
},
],
},
};

export const WithSingleBrowserError: Story = {
args: {
selectedBrowser: browserChrome,
browserResults: [
{
browser: browserChrome,
result: ComparisonResult.CaptureError,
},
],
},
};

export const WithManyBrowsersEqual: Story = {
args: {
selectedBrowser: browserChrome,
browserResults: [
{
browser: browserChrome,
result: ComparisonResult.Equal,
},
{
browser: browserSafari,
result: ComparisonResult.Equal,
},
],
},
};

export const WithManyBrowsersSecondSelected: Story = {
args: {
selectedBrowser: browserSafari,
browserResults: [
{
browser: browserChrome,
result: ComparisonResult.Equal,
},
{
browser: browserSafari,
result: ComparisonResult.Equal,
},
],
},
};

export const WithManyBrowsersVaried: Story = {
args: {
selectedBrowser: browserChrome,
browserResults: [
{
browser: browserChrome,
result: ComparisonResult.Changed,
},
{
browser: browserSafari,
result: ComparisonResult.Equal,
},
],
},
};
23 changes: 9 additions & 14 deletions src/components/BrowserSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,30 @@ const browserIcons = {
type BrowserData = Pick<BrowserInfo, "id" | "key" | "name">;

interface BrowserSelectorProps {
selectedBrowser: BrowserData;
browserResults: { browser: BrowserData; result: ComparisonResult }[];
onSelectBrowser: (browser: BrowserData) => void;
}

export const BrowserSelector = ({ browserResults, onSelectBrowser }: BrowserSelectorProps) => {
const [selected, setSelected] = React.useState(browserResults[0].browser);

const handleSelect = React.useCallback(
(browser: BrowserData) => {
setSelected(browser);
onSelectBrowser(browser);
},
[onSelectBrowser]
);

export const BrowserSelector = ({
selectedBrowser,
browserResults,
onSelectBrowser,
}: BrowserSelectorProps) => {
const links = browserResults
.filter(({ browser }) => browser.key in browserIcons)
.map(({ browser, result }) => ({
active: selected === browser,
active: selectedBrowser === browser,
id: browser.id,
onClick: () => handleSelect(browser),
onClick: () => onSelectBrowser(browser),
right: result !== ComparisonResult.Equal && <StatusDot status={result} />,
title: browser.name,
}));
tmeasday marked this conversation as resolved.
Show resolved Hide resolved

const aggregate = aggregateResult(browserResults.map(({ result }) => result));
if (!aggregate) return null;

const icon = browserIcons[selected.key];
const icon = browserIcons[selectedBrowser.key];
return (
<TooltipMenu placement="bottom" links={links}>
{aggregate === ComparisonResult.Equal ? (
Expand Down
108 changes: 108 additions & 0 deletions src/components/ViewportSelector.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { action } from "@storybook/addon-actions";
import type { Meta, StoryObj } from "@storybook/react";

import { ComparisonResult } from "../gql/graphql";
import { ViewportSelector } from "./ViewportSelector";

const viewport800Px = {
id: "_800",
name: "800px",
};
const viewport1200Px = {
id: "_1200",
name: "1200px",
};

const meta = {
component: ViewportSelector,
args: {
onSelectViewport: action("onSelectViewport"),
},
} satisfies Meta<typeof ViewportSelector>;

export default meta;
type Story = StoryObj<typeof meta>;

export const WithSingleViewportChanged: Story = {
args: {
selectedViewport: viewport1200Px,
viewportResults: [
{
viewport: viewport1200Px,
result: ComparisonResult.Changed,
},
],
},
};

export const WithSingleViewportEqual: Story = {
args: {
selectedViewport: viewport1200Px,
viewportResults: [
{
viewport: viewport1200Px,
result: ComparisonResult.Equal,
},
],
},
};

export const WithSingleViewportError: Story = {
args: {
selectedViewport: viewport1200Px,
viewportResults: [
{
viewport: viewport1200Px,
result: ComparisonResult.CaptureError,
},
],
},
};

export const WithManyViewportsEqual: Story = {
args: {
selectedViewport: viewport800Px,
viewportResults: [
{
viewport: viewport800Px,
result: ComparisonResult.Equal,
},
{
viewport: viewport1200Px,
result: ComparisonResult.Equal,
},
],
},
};

export const WithManyViewportsSecondSelected: Story = {
args: {
selectedViewport: viewport1200Px,
viewportResults: [
{
viewport: viewport800Px,
result: ComparisonResult.Equal,
},
{
viewport: viewport1200Px,
result: ComparisonResult.Changed,
},
],
},
};

export const WithManyViewportsVaried: Story = {
args: {
selectedViewport: viewport800Px,
viewportResults: [
{
viewport: viewport800Px,
result: ComparisonResult.Equal,
},
{
viewport: viewport1200Px,
result: ComparisonResult.Changed,
},
],
},
};
25 changes: 10 additions & 15 deletions src/components/ViewportSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,16 @@ import { TooltipMenu } from "./TooltipMenu";
type ViewportData = Pick<ViewportInfo, "id" | "name">;

interface ViewportSelectorProps {
viewportResults: { viewport: ViewportData; result: ComparisonResult }[];
selectedViewport: ViewportData;
onSelectViewport: (viewport: ViewportData) => void;
viewportResults: { viewport: ViewportData; result: ComparisonResult }[];
}

export const ViewportSelector = ({ viewportResults, onSelectViewport }: ViewportSelectorProps) => {
const [selected, setSelected] = React.useState(viewportResults[0].viewport);

const handleSelect = React.useCallback(
(viewport: ViewportData) => {
setSelected(viewport);
onSelectViewport(viewport);
},
[onSelectViewport]
);

export const ViewportSelector = ({
selectedViewport,
viewportResults,
onSelectViewport,
}: ViewportSelectorProps) => {
const aggregate = aggregateResult(viewportResults.map(({ result }) => result));
if (!aggregate) return null;

Expand All @@ -35,8 +30,8 @@ export const ViewportSelector = ({ viewportResults, onSelectViewport }: Viewport
id: viewport.id,
title: viewport.name,
right: result !== ComparisonResult.Equal && <StatusDot status={result} />,
onClick: () => handleSelect(viewport),
active: selected === viewport,
onClick: () => onSelectViewport(viewport),
active: selectedViewport === viewport,
}))}
>
{aggregate === ComparisonResult.Equal ? (
Expand All @@ -46,7 +41,7 @@ export const ViewportSelector = ({ viewportResults, onSelectViewport }: Viewport
<Icon icon="grow" />
</StatusDotWrapper>
)}
{selected.name}
{selectedViewport.name}
<ArrowIcon icon="arrowdown" />
</TooltipMenu>
);
Expand Down
4 changes: 2 additions & 2 deletions src/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const documents = {
"\n query SelectProjectsQuery {\n viewer {\n accounts {\n id\n name\n avatarUrl\n projects {\n id\n name\n webUrl\n projectToken\n lastBuild {\n branch\n number\n }\n }\n }\n }\n }\n": types.SelectProjectsQueryDocument,
"\n query ProjectQuery($projectId: ID!) {\n project(id: $projectId) {\n id\n name\n webUrl\n lastBuild {\n branch\n number\n }\n }\n }\n": types.ProjectQueryDocument,
"\n query Build($hasBuildId: Boolean!, $buildId: ID!, $projectId: ID!, $branch: String!) {\n build(id: $buildId) @include(if: $hasBuildId) {\n ...BuildFields\n }\n project(id: $projectId) @skip(if: $hasBuildId) {\n name\n lastBuild(branches: [$branch]) {\n ...BuildFields\n }\n }\n }\n": types.BuildDocument,
"\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n": types.BuildFieldsFragmentDoc,
"\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n brokenCount: testCount(results: [CAPTURE_ERROR])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n brokenCount: testCount(results: [CAPTURE_ERROR])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n": types.BuildFieldsFragmentDoc,
"\n fragment TestFields on Test {\n id\n status\n result\n webUrl\n comparisons {\n id\n result\n browser {\n id\n key\n name\n version\n }\n captureDiff {\n diffImage {\n imageUrl\n }\n }\n headCapture {\n captureImage {\n imageUrl\n }\n }\n viewport {\n id\n name\n width\n isDefault\n }\n }\n parameters {\n viewport {\n id\n name\n width\n isDefault\n }\n }\n story {\n storyId\n }\n }\n": types.TestFieldsFragmentDoc,
"\n mutation ReviewTest($input: ReviewTestInput!) {\n reviewTest(input: $input) {\n updatedTests {\n id\n status\n }\n userErrors {\n ... on UserError {\n __typename\n message\n }\n ... on BuildSupersededError {\n build {\n id\n }\n }\n ... on TestUnreviewableError {\n test {\n id\n }\n }\n }\n }\n }\n": types.ReviewTestDocument,
};
Expand Down Expand Up @@ -50,7 +50,7 @@ export function graphql(source: "\n query Build($hasBuildId: Boolean!, $buildId
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n"): (typeof documents)["\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n"];
export function graphql(source: "\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n brokenCount: testCount(results: [CAPTURE_ERROR])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n brokenCount: testCount(results: [CAPTURE_ERROR])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n"): (typeof documents)["\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n brokenCount: testCount(results: [CAPTURE_ERROR])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n brokenCount: testCount(results: [CAPTURE_ERROR])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down