mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-05 08:01:20 +08:00
Error screens
This commit is contained in:
parent
7b48cd06b5
commit
c7e5d90355
@ -29,21 +29,13 @@ const managerContext: any = {
|
||||
state: {},
|
||||
api: {
|
||||
getDocsUrl: fn().mockName('api::getDocsUrl'),
|
||||
getCurrentParameter: fn().mockName('api::getCurrentParameter'),
|
||||
},
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Panel',
|
||||
component: A11YPanel,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<ManagerContext.Provider value={managerContext}>
|
||||
<StyledWrapper id="panel-tab-content">
|
||||
<Story />
|
||||
</StyledWrapper>
|
||||
</ManagerContext.Provider>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
@ -73,7 +65,11 @@ const Template = (args: Pick<A11yContextStore, 'results' | 'error' | 'status' |
|
||||
...args,
|
||||
}}
|
||||
>
|
||||
<A11YPanel />
|
||||
<ManagerContext.Provider value={managerContext}>
|
||||
<StyledWrapper id="panel-tab-content">
|
||||
<A11YPanel />
|
||||
</StyledWrapper>
|
||||
</ManagerContext.Provider>
|
||||
</A11yContext.Provider>
|
||||
);
|
||||
|
||||
@ -154,7 +150,7 @@ export const Error: Story = {
|
||||
<Template
|
||||
results={{ passes: [], incomplete: [], violations: [] }}
|
||||
status="error"
|
||||
error="Test error message"
|
||||
error={`TypeError: Configured rule { impact: "moderate", disable: true } is invalid. Rules must be an object with at least an id property.`}
|
||||
discrepancy={null}
|
||||
/>
|
||||
);
|
||||
@ -173,3 +169,16 @@ export const ErrorStateWithObject: Story = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Broken: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<Template
|
||||
results={{ passes: [], incomplete: [], violations: [] }}
|
||||
status="broken"
|
||||
error={null}
|
||||
discrepancy={null}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { ActionBar, Badge, ScrollArea } from 'storybook/internal/components';
|
||||
import { Badge, Button, ScrollArea } from 'storybook/internal/components';
|
||||
|
||||
import { SyncIcon } from '@storybook/icons';
|
||||
|
||||
import { useParameter } from 'storybook/manager-api';
|
||||
import { styled } from 'storybook/theming';
|
||||
|
||||
import { PARAM_KEY } from '../constants';
|
||||
import { RuleType } from '../types';
|
||||
import { useA11yContext } from './A11yContext';
|
||||
import { Report } from './Report/Report';
|
||||
@ -26,12 +28,34 @@ const Tab = styled.div({
|
||||
gap: 6,
|
||||
});
|
||||
|
||||
const Centered = styled.span({
|
||||
const Centered = styled.span(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
fontSize: theme.typography.size.s2,
|
||||
height: '100%',
|
||||
});
|
||||
gap: 24,
|
||||
|
||||
div: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
},
|
||||
p: {
|
||||
margin: 0,
|
||||
color: theme.textMutedColor,
|
||||
},
|
||||
code: {
|
||||
display: 'inline-block',
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
backgroundColor: theme.background.app,
|
||||
border: `1px solid ${theme.color.border}`,
|
||||
borderRadius: 4,
|
||||
padding: '2px 3px',
|
||||
},
|
||||
}));
|
||||
|
||||
const Count = styled(Badge)({
|
||||
padding: 4,
|
||||
@ -39,6 +63,7 @@ const Count = styled(Badge)({
|
||||
});
|
||||
|
||||
export const A11YPanel: React.FC = () => {
|
||||
const { manual } = useParameter(PARAM_KEY, {} as any);
|
||||
const {
|
||||
results,
|
||||
status,
|
||||
@ -50,10 +75,6 @@ export const A11YPanel: React.FC = () => {
|
||||
toggleOpen,
|
||||
} = useA11yContext();
|
||||
|
||||
const manualActionItems = useMemo(
|
||||
() => [{ title: 'Run test', onClick: handleManual }],
|
||||
[handleManual]
|
||||
);
|
||||
const tabs = useMemo(() => {
|
||||
const { passes, incomplete, violations } = results;
|
||||
return [
|
||||
@ -134,8 +155,20 @@ export const A11YPanel: React.FC = () => {
|
||||
{status === 'initial' && 'Initializing...'}
|
||||
{status === 'manual' && (
|
||||
<>
|
||||
<>Manually run the accessibility scan.</>
|
||||
<ActionBar key="actionbar" actionItems={manualActionItems} />
|
||||
<div>
|
||||
<strong>Accessibility tests run manually for this story</strong>
|
||||
<p>
|
||||
Results will not show when using the testing module. You can still run
|
||||
accessibility tests manually.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="medium" onClick={handleManual}>
|
||||
Run accessibility scan
|
||||
</Button>
|
||||
<p>
|
||||
Update <code>{manual ? 'parameters' : 'globals'}.a11y.manual</code> to disable
|
||||
manual mode.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{status === 'running' && (
|
||||
@ -145,13 +178,33 @@ export const A11YPanel: React.FC = () => {
|
||||
)}
|
||||
{status === 'error' && (
|
||||
<>
|
||||
The accessibility scan encountered an error.
|
||||
<br />
|
||||
{typeof error === 'string'
|
||||
? error
|
||||
: error instanceof Error
|
||||
? error.toString()
|
||||
: JSON.stringify(error)}
|
||||
<div>
|
||||
<strong>The accessibility scan encountered an error</strong>
|
||||
<p>
|
||||
{typeof error === 'string'
|
||||
? error
|
||||
: error instanceof Error
|
||||
? error.toString()
|
||||
: JSON.stringify(error, null, 2)}
|
||||
</p>
|
||||
</div>
|
||||
<Button size="medium" onClick={handleManual}>
|
||||
Rerun accessibility scan
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{status === 'broken' && (
|
||||
<>
|
||||
<div>
|
||||
<strong>This story's component tests failed</strong>
|
||||
<p>
|
||||
Automated accessibility tests will not run until this is resolved. You can still
|
||||
test manually.
|
||||
</p>
|
||||
</div>
|
||||
<Button size="medium" onClick={handleManual}>
|
||||
Run accessibility scan
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Centered>
|
||||
|
@ -11,6 +11,7 @@ import { HIGHLIGHT, RESET_HIGHLIGHT, SCROLL_INTO_VIEW } from '@storybook/addon-h
|
||||
|
||||
import type { AxeResults, Result } from 'axe-core';
|
||||
import {
|
||||
experimental_getStatusStore,
|
||||
experimental_useStatusStore,
|
||||
useAddonState,
|
||||
useChannel,
|
||||
@ -22,7 +23,14 @@ import {
|
||||
import type { Report } from 'storybook/preview-api';
|
||||
import { convert, themes } from 'storybook/theming';
|
||||
|
||||
import { ADDON_ID, EVENTS, PANEL_ID, TEST_PROVIDER_ID } from '../constants';
|
||||
import {
|
||||
ADDON_ID,
|
||||
EVENTS,
|
||||
PANEL_ID,
|
||||
STATUS_TYPE_ID_A11Y,
|
||||
STATUS_TYPE_ID_COMPONENT_TEST,
|
||||
TEST_PROVIDER_ID,
|
||||
} from '../constants';
|
||||
import type { A11yParameters } from '../params';
|
||||
import type { A11YReport } from '../types';
|
||||
import { RuleType } from '../types';
|
||||
@ -55,6 +63,8 @@ export interface A11yContextStore {
|
||||
handleSelectionChange: (key: string) => void;
|
||||
}
|
||||
|
||||
const componentTestStatusStore = experimental_getStatusStore('storybook/component-test');
|
||||
|
||||
const colorsByType = {
|
||||
[RuleType.VIOLATION]: convert(themes.light).color.negative,
|
||||
[RuleType.PASS]: convert(themes.light).color.positive,
|
||||
@ -92,7 +102,7 @@ const defaultResult = {
|
||||
violations: [],
|
||||
};
|
||||
|
||||
type Status = 'initial' | 'manual' | 'running' | 'error' | 'ran' | 'ready';
|
||||
type Status = 'initial' | 'manual' | 'running' | 'error' | 'broken' | 'ran' | 'ready';
|
||||
|
||||
export const A11yContextProvider: FC<PropsWithChildren> = (props) => {
|
||||
const parameters = useParameter<A11yParameters>('a11y', {
|
||||
@ -124,8 +134,15 @@ export const A11yContextProvider: FC<PropsWithChildren> = (props) => {
|
||||
|
||||
const { storyId } = useStorybookState();
|
||||
const currentStoryA11yStatusValue = experimental_useStatusStore(
|
||||
(allStatuses) => allStatuses[storyId]?.[TEST_PROVIDER_ID]?.value
|
||||
(allStatuses) => allStatuses[storyId]?.[STATUS_TYPE_ID_A11Y]?.value
|
||||
);
|
||||
componentTestStatusStore.onAllStatusChange((statuses, previousStatuses) => {
|
||||
const current = statuses[storyId]?.[STATUS_TYPE_ID_COMPONENT_TEST];
|
||||
const previous = previousStatuses[storyId]?.[STATUS_TYPE_ID_COMPONENT_TEST];
|
||||
if (current?.value === 'status-value:error' && previous?.value !== 'status-value:error') {
|
||||
setStatus('broken');
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (status !== 'ran') {
|
||||
|
@ -13,6 +13,7 @@ const managerContext: any = {
|
||||
state: {},
|
||||
api: {
|
||||
getDocsUrl: fn().mockName('api::getDocsUrl'),
|
||||
getCurrentParameter: fn().mockName('api::getCurrentParameter'),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -13,3 +13,6 @@ export const DOCUMENTATION_DISCREPANCY_LINK = `${DOCUMENTATION_LINK}#why-are-my-
|
||||
export const TEST_PROVIDER_ID = 'storybook/addon-a11y/test-provider';
|
||||
|
||||
export const EVENTS = { RESULT, REQUEST, RUNNING, ERROR, MANUAL };
|
||||
|
||||
export const STATUS_TYPE_ID_COMPONENT_TEST = 'storybook/component-test';
|
||||
export const STATUS_TYPE_ID_A11Y = 'storybook/a11y';
|
||||
|
@ -79,7 +79,7 @@ const getErrorOrigin = (error: VitestError): string => {
|
||||
if (error.VITEST_TEST_NAME) {
|
||||
parts.push(
|
||||
dedent`
|
||||
The latest test that might've caused the error is "${error.VITEST_TEST_NAME}".
|
||||
The latest test that might've caused the error is "${error.VITEST_TEST_NAME}".
|
||||
It might mean one of the following:
|
||||
- The error was thrown, while Vitest was running this test.
|
||||
- If the error occurred after the test had been completed, this was the last documented test before it was thrown.
|
||||
|
Loading…
x
Reference in New Issue
Block a user