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

Addon Test: Context menu UI #29727

Merged
merged 16 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 7 additions & 31 deletions code/addons/test/src/components/ContextMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@ import React, {
} from 'react';

import { Button, ListItem } from 'storybook/internal/components';
import type { TestProviderConfig } from 'storybook/internal/core-events';
import { useStorybookApi } from 'storybook/internal/manager-api';
import { useTheme } from 'storybook/internal/theming';
import { type API_HashEntry, type Addon_TestProviderState } from 'storybook/internal/types';

import { PlayHollowIcon, StopAltHollowIcon } from '@storybook/icons';

import { TEST_PROVIDER_ID } from '../constants';
import type { TestResult } from '../node/reporter';
import { RelativeTime } from './RelativeTime';
import { type Config, type Details, TEST_PROVIDER_ID } from '../constants';
import { Description } from './Description';
import { Title } from './Title';

export const ContextMenuItem: FC<{
context: API_HashEntry;
state: Addon_TestProviderState<{
testResults: TestResult[];
}>;
state: TestProviderConfig & Addon_TestProviderState<Details, Config>;
}> = ({ context, state }) => {
const api = useStorybookApi();
const [isDisabled, setDisabled] = useState(false);
Expand Down Expand Up @@ -51,29 +50,6 @@ export const ContextMenuItem: FC<{

const theme = useTheme();

const title = state.crashed || state.failed ? 'Component tests failed' : 'Component tests';
const errorMessage = state.error?.message;
let description: string | React.ReactNode = 'Not run';

if (state.running) {
description = state.progress
? `Testing... ${state.progress.numPassedTests}/${state.progress.numTotalTests}`
: 'Starting...';
} else if (state.failed && !errorMessage) {
description = '';
} else if (state.crashed || (state.failed && errorMessage)) {
description = 'An error occured';
} else if (state.progress?.finishedAt) {
description = (
<RelativeTime
timestamp={new Date(state.progress.finishedAt)}
testCount={state.progress.numTotalTests}
/>
);
} else if (state.watching) {
description = 'Watching for file changes';
}

return (
<div
onClick={(event) => {
Expand All @@ -82,8 +58,8 @@ export const ContextMenuItem: FC<{
}}
>
<ListItem
title={title}
center={description}
title={<Title state={state} />}
center={<Description state={state} />}
right={
<Button
onClick={onClick}
Expand Down
35 changes: 17 additions & 18 deletions code/addons/test/src/components/Description.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React, { useEffect } from 'react';
import React, { type ComponentProps, useEffect } from 'react';

import { Link as LinkComponent } from 'storybook/internal/components';
import { type TestProviderConfig, type TestProviderState } from 'storybook/internal/core-events';
import { styled } from 'storybook/internal/theming';

import { GlobalErrorContext } from './GlobalErrorModal';
import { RelativeTime } from './RelativeTime';

export const DescriptionStyle = styled.div(({ theme }) => ({
export const Wrapper = styled.div(({ theme }) => ({
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
fontSize: theme.typography.size.s1,
color: theme.barTextColor,
}));
Expand All @@ -15,17 +19,14 @@ const PositiveText = styled.span(({ theme }) => ({
color: theme.color.positiveText,
}));

export function Description({
errorMessage,
setIsModalOpen,
state,
}: {
interface DescriptionProps extends ComponentProps<typeof Wrapper> {
state: TestProviderConfig & TestProviderState;
errorMessage: string;
setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
}) {
}

export function Description({ state, ...props }: DescriptionProps) {
const isMounted = React.useRef(false);
const [isUpdated, setUpdated] = React.useState(false);
const { setModalOpen } = React.useContext(GlobalErrorContext);

useEffect(() => {
if (isMounted.current) {
Expand All @@ -38,6 +39,8 @@ export function Description({
isMounted.current = true;
}, [state.config]);

const errorMessage = state.error?.message;

let description: string | React.ReactNode = 'Not run';
if (isUpdated) {
description = <PositiveText>Settings updated</PositiveText>;
Expand All @@ -46,16 +49,11 @@ export function Description({
? `Testing... ${state.progress.numPassedTests}/${state.progress.numTotalTests}`
: 'Starting...';
} else if (state.failed && !errorMessage) {
description = '';
description = 'Failed';
} else if (state.crashed || (state.failed && errorMessage)) {
description = (
<>
<LinkComponent
isButton
onClick={() => {
setIsModalOpen(true);
}}
>
<LinkComponent isButton onClick={() => setModalOpen(true)}>
{state.error?.name || 'View full error'}
</LinkComponent>
</>
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -70,5 +68,6 @@ export function Description({
} else if (state.watching) {
description = 'Watching for file changes';
}
return <DescriptionStyle id="testing-module-description">{description}</DescriptionStyle>;

return <Wrapper {...props}>{description}</Wrapper>;
}
40 changes: 18 additions & 22 deletions code/addons/test/src/components/GlobalErrorModal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { expect, fn, userEvent, within } from '@storybook/test';

import dedent from 'ts-dedent';

import { GlobalErrorModal } from './GlobalErrorModal';
import { GlobalErrorContext, GlobalErrorModal } from './GlobalErrorModal';

type Story = StoryObj<typeof meta>;

Expand Down Expand Up @@ -41,36 +41,32 @@ const meta = {
],
args: {
onRerun: fn(),
onClose: fn(),
open: false,
},
} satisfies Meta<typeof GlobalErrorModal>;

export default meta;

export const Default: Story = {
args: {
error: dedent`
ReferenceError: FAIL is not defined
at Constraint.execute (the-best-file.js:525:2)
at Constraint.recalculate (the-best-file.js:424:21)
at Planner.addPropagate (the-best-file.js:701:6)
at Constraint.satisfy (the-best-file.js:184:15)
at Planner.incrementalAdd (the-best-file.js:591:21)
at Constraint.addConstraint (the-best-file.js:162:10)
at Constraint.BinaryConstraint (the-best-file.js:346:7)
at Constraint.EqualityConstraint (the-best-file.js:515:38)
at chainTest (the-best-file.js:807:6)
at deltaBlue (the-best-file.js:879:2)`,
},
render: (props) => {
const [isOpen, setOpen] = useState(false);
const [isModalOpen, setModalOpen] = useState(false);
const error = dedent`
ReferenceError: FAIL is not defined
at Constraint.execute (the-best-file.js:525:2)
at Constraint.recalculate (the-best-file.js:424:21)
at Planner.addPropagate (the-best-file.js:701:6)
at Constraint.satisfy (the-best-file.js:184:15)
at Planner.incrementalAdd (the-best-file.js:591:21)
at Constraint.addConstraint (the-best-file.js:162:10)
at Constraint.BinaryConstraint (the-best-file.js:346:7)
at Constraint.EqualityConstraint (the-best-file.js:515:38)
at chainTest (the-best-file.js:807:6)
at deltaBlue (the-best-file.js:879:2)`;

return (
<>
<GlobalErrorModal {...props} open={isOpen} />
<button onClick={() => setOpen(true)}>Open modal</button>
</>
<GlobalErrorContext.Provider value={{ isModalOpen, setModalOpen, error }}>
<GlobalErrorModal {...props} />
<button onClick={() => setModalOpen(true)}>Open modal</button>
</GlobalErrorContext.Provider>
);
},
play: async ({ canvasElement }) => {
Expand Down
33 changes: 21 additions & 12 deletions code/addons/test/src/components/GlobalErrorModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import React from 'react';
import React, { useContext } from 'react';

import { Button, IconButton, Modal } from 'storybook/internal/components';
import { useStorybookApi } from 'storybook/internal/manager-api';
import { styled } from 'storybook/internal/theming';

import { CloseIcon, SyncIcon } from '@storybook/icons';
import { styled } from '@storybook/theming';

import { DOCUMENTATION_FATAL_ERROR_LINK } from '../constants';

interface GlobalErrorModalProps {
error: string;
open: boolean;
onClose: () => void;
onRerun: () => void;
}

const ModalBar = styled.div({
display: 'flex',
justifyContent: 'space-between',
Expand Down Expand Up @@ -49,8 +42,24 @@ const TroubleshootLink = styled.a(({ theme }) => ({
color: theme.color.defaultText,
}));

export function GlobalErrorModal({ onRerun, onClose, error, open }: GlobalErrorModalProps) {
export const GlobalErrorContext = React.createContext<{
isModalOpen: boolean;
setModalOpen: (isOpen: boolean) => void;
error?: string;
}>({
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
isModalOpen: false,
setModalOpen: () => {},
error: undefined,
});

interface GlobalErrorModalProps {
onRerun: () => void;
}

export function GlobalErrorModal({ onRerun }: GlobalErrorModalProps) {
const api = useStorybookApi();
const { error, isModalOpen, setModalOpen } = useContext(GlobalErrorContext);
const handleClose = () => setModalOpen(false);

const troubleshootURL = api.getDocsUrl({
subpath: DOCUMENTATION_FATAL_ERROR_LINK,
Expand All @@ -59,7 +68,7 @@ export function GlobalErrorModal({ onRerun, onClose, error, open }: GlobalErrorM
});

return (
<Modal onEscapeKeyDown={onClose} onInteractOutside={onClose} open={open}>
<Modal onEscapeKeyDown={handleClose} onInteractOutside={handleClose} open={isModalOpen}>
<ModalBar>
<ModalTitle>Storybook Tests error details</ModalTitle>
<ModalActionBar>
Expand All @@ -72,7 +81,7 @@ export function GlobalErrorModal({ onRerun, onClose, error, open }: GlobalErrorM
Troubleshoot
</a>
</Button>
<IconButton onClick={onClose}>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
</ModalActionBar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const baseState: TestProviderState<Details, Config> = {
results: [
{
storyId: 'story-id',
status: 'success',
status: 'passed',
duration: 100,
testRunId: 'test-run-id',
},
Expand Down
Loading
Loading