Skip to content

Commit

Permalink
Merge pull request #29727 from storybookjs/context-menu-ui
Browse files Browse the repository at this point in the history
Addon Test: Context menu UI
  • Loading branch information
ghengeveld authored Dec 2, 2024
2 parents a6155b6 + 9622f5c commit dd16ceb
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 157 deletions.
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>
</>
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;
}>({
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

0 comments on commit dd16ceb

Please sign in to comment.