Skip to content

Commit

Permalink
Merge pull request #18460 from storybookjs/ghengeveld/ap-1950-fix-wai…
Browse files Browse the repository at this point in the history
…tfor-behavior-while-debugging

Interactions: Fix `waitFor` behavior while debugging
  • Loading branch information
shilman committed Jun 16, 2022
2 parents 15ce29a + 73416f7 commit d4efb62
Show file tree
Hide file tree
Showing 13 changed files with 507 additions and 248 deletions.
17 changes: 10 additions & 7 deletions addons/interactions/src/Panel.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ComponentStoryObj, ComponentMeta } from '@storybook/react';
import { CallStates } from '@storybook/instrumenter';
import { styled } from '@storybook/theming';

import { getCall } from './mocks';
import { getCalls, getInteractions } from './mocks';
import { AddonPanelPure } from './Panel';
import SubnavStories from './components/Subnav/Subnav.stories';

Expand All @@ -20,6 +20,8 @@ const StyledWrapper = styled.div(({ theme }) => ({
overflow: 'auto',
}));

const interactions = getInteractions(CallStates.DONE);

export default {
title: 'Addons/Interactions/Panel',
component: AddonPanelPure,
Expand All @@ -34,10 +36,10 @@ export default {
layout: 'fullscreen',
},
args: {
calls: new Map(),
calls: new Map(getCalls(CallStates.DONE).map((call) => [call.id, call])),
controls: SubnavStories.args.controls,
controlStates: SubnavStories.args.controlStates,
interactions: [getCall(CallStates.DONE)],
interactions,
fileName: 'addon-interactions.stories.tsx',
hasException: false,
isPlaying: false,
Expand All @@ -52,14 +54,14 @@ type Story = ComponentStoryObj<typeof AddonPanelPure>;

export const Passing: Story = {
args: {
interactions: [getCall(CallStates.DONE)],
interactions: getInteractions(CallStates.DONE),
},
};

export const Paused: Story = {
args: {
isPlaying: true,
interactions: [getCall(CallStates.WAITING)],
interactions: getInteractions(CallStates.WAITING),
controlStates: {
debugger: true,
start: false,
Expand All @@ -68,20 +70,21 @@ export const Paused: Story = {
next: true,
end: true,
},
pausedAt: interactions[interactions.length - 1].id,
},
};

export const Playing: Story = {
args: {
isPlaying: true,
interactions: [getCall(CallStates.ACTIVE)],
interactions: getInteractions(CallStates.ACTIVE),
},
};

export const Failed: Story = {
args: {
hasException: true,
interactions: [getCall(CallStates.ERROR)],
interactions: getInteractions(CallStates.ERROR),
},
};

Expand Down
27 changes: 18 additions & 9 deletions addons/interactions/src/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface InteractionsPanelProps {
fileName?: string;
hasException?: boolean;
isPlaying?: boolean;
pausedAt?: Call['id'];
calls: Map<string, any>;
endRef?: React.Ref<HTMLDivElement>;
onScrollToEnd?: () => void;
Expand Down Expand Up @@ -66,6 +67,7 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
fileName,
hasException,
isPlaying,
pausedAt,
onScrollToEnd,
endRef,
isRerunAnimating,
Expand All @@ -87,15 +89,18 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
setIsRerunAnimating={setIsRerunAnimating}
/>
)}
{interactions.map((call) => (
<Interaction
key={call.id}
call={call}
callsById={calls}
controls={controls}
controlStates={controlStates}
/>
))}
<div>
{interactions.map((call) => (
<Interaction
key={call.id}
call={call}
callsById={calls}
controls={controls}
controlStates={controlStates}
pausedAt={pausedAt}
/>
))}
</div>
<div ref={endRef} />
{!isPlaying && interactions.length === 0 && (
<Placeholder>
Expand All @@ -116,6 +121,7 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
export const Panel: React.FC<AddonPanelProps> = (props) => {
const [storyId, setStoryId] = React.useState<StoryId>();
const [controlStates, setControlStates] = React.useState<ControlStates>(INITIAL_CONTROL_STATES);
const [pausedAt, setPausedAt] = React.useState<Call['id']>();
const [isPlaying, setPlaying] = React.useState(false);
const [isRerunAnimating, setIsRerunAnimating] = React.useState(false);
const [scrollTarget, setScrollTarget] = React.useState<HTMLElement>();
Expand Down Expand Up @@ -146,10 +152,12 @@ export const Panel: React.FC<AddonPanelProps> = (props) => {
[EVENTS.SYNC]: (payload) => {
setControlStates(payload.controlStates);
setLog(payload.logItems);
setPausedAt(payload.pausedAt);
},
[STORY_RENDER_PHASE_CHANGED]: (event) => {
setStoryId(event.storyId);
setPlaying(event.newPhase === 'playing');
setPausedAt(undefined);
},
},
[]
Expand Down Expand Up @@ -191,6 +199,7 @@ export const Panel: React.FC<AddonPanelProps> = (props) => {
fileName={fileName}
hasException={hasException}
isPlaying={isPlaying}
pausedAt={pausedAt}
endRef={endRef}
onScrollToEnd={scrollTarget && scrollToTarget}
isRerunAnimating={isRerunAnimating}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export const StandardEmailFailed: CSF3Story = {
await userEvent.click(canvas.getByRole('button', { name: /create account/i }));

await canvas.findByText('Please enter a correctly formatted email address');
expect(args.onSubmit).not.toHaveBeenCalled();
await expect(args.onSubmit).not.toHaveBeenCalled();
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ComponentStoryObj, ComponentMeta } from '@storybook/react';
import { expect } from '@storybook/jest';
import { CallStates } from '@storybook/instrumenter';
import { userEvent, within } from '@storybook/testing-library';
import { getCall } from '../../mocks';
import { getCalls } from '../../mocks';

import { Interaction } from './Interaction';
import SubnavStories from '../Subnav/Subnav.stories';
Expand All @@ -13,33 +13,39 @@ export default {
title: 'Addons/Interactions/Interaction',
component: Interaction,
args: {
callsById: new Map(),
callsById: new Map(getCalls(CallStates.DONE).map((call) => [call.id, call])),
controls: SubnavStories.args.controls,
controlStates: SubnavStories.args.controlStates,
},
} as ComponentMeta<typeof Interaction>;

export const Active: Story = {
args: {
call: getCall(CallStates.ACTIVE),
call: getCalls(CallStates.ACTIVE).slice(-1)[0],
},
};

export const Waiting: Story = {
args: {
call: getCall(CallStates.WAITING),
call: getCalls(CallStates.WAITING).slice(-1)[0],
},
};

export const Failed: Story = {
args: {
call: getCall(CallStates.ERROR),
call: getCalls(CallStates.ERROR).slice(-1)[0],
},
};

export const Done: Story = {
args: {
call: getCall(CallStates.DONE),
call: getCalls(CallStates.DONE).slice(-1)[0],
},
};

export const WithParent: Story = {
args: {
call: { ...getCalls(CallStates.DONE).slice(-1)[0], parentId: 'parent-id' },
},
};

Expand Down
94 changes: 69 additions & 25 deletions addons/interactions/src/components/Interaction/Interaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,45 @@ const MethodCallWrapper = styled.div(() => ({
inlineSize: 'calc( 100% - 40px )',
}));

const RowContainer = styled('div', { shouldForwardProp: (prop) => !['call'].includes(prop) })<{
call: Call;
}>(({ theme, call }) => ({
display: 'flex',
flexDirection: 'column',
borderBottom: `1px solid ${theme.appBorderColor}`,
fontFamily: typography.fonts.base,
fontSize: 13,
...(call.status === CallStates.ERROR && {
backgroundColor:
theme.base === 'dark' ? transparentize(0.93, theme.color.negative) : theme.background.warning,
const RowContainer = styled('div', {
shouldForwardProp: (prop) => !['call', 'pausedAt'].includes(prop),
})<{ call: Call; pausedAt: Call['id'] }>(
({ theme, call }) => ({
position: 'relative',
display: 'flex',
flexDirection: 'column',
borderBottom: `1px solid ${theme.appBorderColor}`,
fontFamily: typography.fonts.base,
fontSize: 13,
...(call.status === CallStates.ERROR && {
backgroundColor:
theme.base === 'dark'
? transparentize(0.93, theme.color.negative)
: theme.background.warning,
}),
paddingLeft: call.parentId ? 20 : 0,
}),
}));
({ theme, call, pausedAt }) =>
pausedAt === call.id && {
'&::before': {
content: '""',
position: 'absolute',
top: -5,
zIndex: 1,
borderTop: '4.5px solid transparent',
borderLeft: `7px solid ${theme.color.warning}`,
borderBottom: '4.5px solid transparent',
},
'&::after': {
content: '""',
position: 'absolute',
top: -1,
zIndex: 1,
width: '100%',
borderTop: `1.5px solid ${theme.color.warning}`,
},
}
);

const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].includes(prop) })<
React.ButtonHTMLAttributes<HTMLButtonElement> & { call: Call }
Expand Down Expand Up @@ -55,30 +81,52 @@ const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].inclu
},
}));

const RowMessage = styled('pre')({
margin: 0,
padding: '8px 10px 8px 30px',
const RowMessage = styled('div')(({ theme }) => ({
padding: '8px 10px 8px 36px',
fontSize: typography.size.s1,
});
pre: {
margin: 0,
padding: 0,
},
p: {
color: theme.color.dark,
},
}));

const Exception = ({ exception }: { exception: Call['exception'] }) => {
if (exception.message.startsWith('expect(')) {
return <MatcherResult {...exception} />;
}
const paragraphs = exception.message.split('\n\n');
const more = paragraphs.length > 1;
return (
<RowMessage>
<pre>{paragraphs[0]}</pre>
{more && <p>See the full stack trace in the browser console.</p>}
</RowMessage>
);
};

export const Interaction = ({
call,
callsById,
controls,
controlStates,
pausedAt,
}: {
call: Call;
callsById: Map<Call['id'], Call>;
controls: Controls;
controlStates: ControlStates;
pausedAt?: Call['id'];
}) => {
const [isHovered, setIsHovered] = React.useState(false);
return (
<RowContainer call={call}>
<RowContainer call={call} pausedAt={pausedAt}>
<RowLabel
call={call}
onClick={() => controls.goto(call.id)}
disabled={!controlStates.goto}
disabled={!controlStates.goto || !call.interceptable || !!call.parentId}
onMouseEnter={() => controlStates.goto && setIsHovered(true)}
onMouseLeave={() => controlStates.goto && setIsHovered(false)}
>
Expand All @@ -87,13 +135,9 @@ export const Interaction = ({
<MethodCall call={call} callsById={callsById} />
</MethodCallWrapper>
</RowLabel>
{call.status === CallStates.ERROR &&
call.exception &&
(call.exception.message.startsWith('expect(') ? (
<MatcherResult {...call.exception} />
) : (
<RowMessage>{call.exception.message}</RowMessage>
))}
{call.status === CallStates.ERROR && call.exception?.callId === call.id && (
<Exception exception={call.exception} />
)}
</RowContainer>
);
};
2 changes: 1 addition & 1 deletion addons/interactions/src/components/MatcherResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const MatcherResult = ({ message }: { message: string }) => {
<pre
style={{
margin: 0,
padding: '8px 10px 8px 30px',
padding: '8px 10px 8px 36px',
fontSize: typography.size.s1,
}}
>
Expand Down
Loading

0 comments on commit d4efb62

Please sign in to comment.