mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-09 00:19:13 +08:00
Merge pull request #29725 from storybookjs/version-non-patch-from-8.5.0-alpha.11
Release: Prerelease 8.5.0-alpha.12
This commit is contained in:
commit
021d6bf660
@ -1,3 +1,10 @@
|
||||
## 8.5.0-alpha.12
|
||||
|
||||
- Core / Addon Test: Add config UI to Testing Module - [#29708](https://github.com/storybookjs/storybook/pull/29708), thanks @ghengeveld!
|
||||
- Manager: Add tags property to GroupEntry objects - [#29672](https://github.com/storybookjs/storybook/pull/29672), thanks @Sidnioulz!
|
||||
- Svelte: Support `@sveltejs/vite-plugin-svelte` v5 - [#29731](https://github.com/storybookjs/storybook/pull/29731), thanks @JReinhold!
|
||||
- Toolbars: Suppress deprecation warning when using dynamic icons - [#29545](https://github.com/storybookjs/storybook/pull/29545), thanks @ValeraS!
|
||||
|
||||
## 8.5.0-alpha.11
|
||||
|
||||
- Core + Addon Test: Refactor test API and fix total test count - [#29656](https://github.com/storybookjs/storybook/pull/29656), thanks @ghengeveld!
|
||||
|
@ -23,7 +23,7 @@ export function GuidedTour({
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
setStepIndex((current) => {
|
||||
const index = steps.findIndex(({ key }) => key === step);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { Link as LinkComponent } from 'storybook/internal/components';
|
||||
import { type TestProviderConfig, type TestProviderState } from 'storybook/internal/core-events';
|
||||
@ -11,6 +11,10 @@ export const DescriptionStyle = styled.div(({ theme }) => ({
|
||||
color: theme.barTextColor,
|
||||
}));
|
||||
|
||||
const PositiveText = styled.span(({ theme }) => ({
|
||||
color: theme.color.positiveText,
|
||||
}));
|
||||
|
||||
export function Description({
|
||||
errorMessage,
|
||||
setIsModalOpen,
|
||||
@ -20,9 +24,24 @@ export function Description({
|
||||
errorMessage: string;
|
||||
setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
let description: string | React.ReactNode = 'Not run';
|
||||
const isMounted = React.useRef(false);
|
||||
const [isUpdated, setUpdated] = React.useState(false);
|
||||
|
||||
if (state.running) {
|
||||
useEffect(() => {
|
||||
if (isMounted.current) {
|
||||
setUpdated(true);
|
||||
const timeout = setTimeout(setUpdated, 2000, false);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
isMounted.current = true;
|
||||
}, [state.config]);
|
||||
|
||||
let description: string | React.ReactNode = 'Not run';
|
||||
if (isUpdated) {
|
||||
description = <PositiveText>Settings updated</PositiveText>;
|
||||
} else if (state.running) {
|
||||
description = state.progress
|
||||
? `Testing... ${state.progress.numPassedTests}/${state.progress.numTotalTests}`
|
||||
: 'Starting...';
|
||||
|
@ -23,7 +23,9 @@ const managerContext: any = {
|
||||
},
|
||||
},
|
||||
api: {
|
||||
getDocsUrl: fn().mockName('api::getDocsUrl'),
|
||||
getDocsUrl: fn(({ subpath }) => `https://storybook.js.org/docs/${subpath}`).mockName(
|
||||
'api::getDocsUrl'
|
||||
),
|
||||
emit: fn().mockName('api::emit'),
|
||||
updateTestProviderState: fn().mockName('api::updateTestProviderState'),
|
||||
},
|
||||
@ -98,6 +100,9 @@ export default {
|
||||
</ManagerContext.Provider>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
} as Meta<typeof TestProviderRender>;
|
||||
|
||||
export const Default: Story = {
|
||||
@ -153,6 +158,6 @@ export const EnableEditing: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const screen = within(canvasElement);
|
||||
|
||||
screen.getByLabelText('Edit').click();
|
||||
screen.getByLabelText(/Open settings/).click();
|
||||
},
|
||||
};
|
||||
|
@ -1,20 +1,43 @@
|
||||
import React, { type FC, Fragment, useCallback, useRef, useState } from 'react';
|
||||
import React, { type FC, useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { Button } from 'storybook/internal/components';
|
||||
import { Button, ListItem } from 'storybook/internal/components';
|
||||
import {
|
||||
TESTING_MODULE_CONFIG_CHANGE,
|
||||
type TestProviderConfig,
|
||||
type TestProviderState,
|
||||
type TestingModuleConfigChangePayload,
|
||||
} from 'storybook/internal/core-events';
|
||||
import type { API } from 'storybook/internal/manager-api';
|
||||
import { styled } from 'storybook/internal/theming';
|
||||
import { styled, useTheme } from 'storybook/internal/theming';
|
||||
|
||||
import { EditIcon, EyeIcon, PlayHollowIcon, StopAltHollowIcon } from '@storybook/icons';
|
||||
import {
|
||||
AccessibilityIcon,
|
||||
EditIcon,
|
||||
EyeIcon,
|
||||
PlayHollowIcon,
|
||||
PointerHandIcon,
|
||||
ShieldIcon,
|
||||
StopAltHollowIcon,
|
||||
} from '@storybook/icons';
|
||||
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import { debounce } from 'es-toolkit/compat';
|
||||
|
||||
import { type Config, type Details, TEST_PROVIDER_ID } from '../constants';
|
||||
import { Description } from './Description';
|
||||
import { GlobalErrorModal } from './GlobalErrorModal';
|
||||
import { TestStatusIcon } from './TestStatusIcon';
|
||||
|
||||
const Container = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
const Heading = styled.div({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: '8px 2px',
|
||||
gap: 6,
|
||||
});
|
||||
|
||||
const Info = styled.div({
|
||||
display: 'flex',
|
||||
@ -33,32 +56,37 @@ const Actions = styled.div({
|
||||
gap: 6,
|
||||
});
|
||||
|
||||
const Head = styled.div({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 6,
|
||||
const Extras = styled.div({
|
||||
marginBottom: 2,
|
||||
});
|
||||
|
||||
const Checkbox = styled.input({
|
||||
margin: 0,
|
||||
'&:enabled': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
});
|
||||
|
||||
export const TestProviderRender: FC<{
|
||||
api: API;
|
||||
state: TestProviderConfig & TestProviderState<Details, Config>;
|
||||
}> = ({ state, api }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const theme = useTheme();
|
||||
|
||||
const title = state.crashed || state.failed ? 'Component tests failed' : 'Component tests';
|
||||
const title = state.crashed || state.failed ? 'Local tests failed' : 'Run local tests';
|
||||
const errorMessage = state.error?.message;
|
||||
|
||||
const [config, changeConfig] = useConfig(
|
||||
const [config, updateConfig] = useConfig(
|
||||
api,
|
||||
state.id,
|
||||
state.config || { a11y: false, coverage: false },
|
||||
api
|
||||
state.config || { a11y: false, coverage: false }
|
||||
);
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Head>
|
||||
<Container>
|
||||
<Heading>
|
||||
<Info>
|
||||
<Title crashed={state.crashed} id="testing-module-title">
|
||||
{title}
|
||||
@ -68,11 +96,11 @@ export const TestProviderRender: FC<{
|
||||
|
||||
<Actions>
|
||||
<Button
|
||||
aria-label={`Edit`}
|
||||
aria-label={`${isEditing ? 'Close' : 'Open'} settings for ${state.name}`}
|
||||
variant="ghost"
|
||||
padding="small"
|
||||
active={isEditing}
|
||||
onClick={() => setIsEditing((v) => !v)}
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
>
|
||||
<EditIcon />
|
||||
</Button>
|
||||
@ -105,7 +133,7 @@ export const TestProviderRender: FC<{
|
||||
aria-label={`Start ${state.name}`}
|
||||
variant="ghost"
|
||||
padding="small"
|
||||
onClick={() => api.runTestProvider(state.id, {})}
|
||||
onClick={() => api.runTestProvider(state.id)}
|
||||
disabled={state.crashed || state.running}
|
||||
>
|
||||
<PlayHollowIcon />
|
||||
@ -114,29 +142,60 @@ export const TestProviderRender: FC<{
|
||||
</>
|
||||
)}
|
||||
</Actions>
|
||||
</Head>
|
||||
</Heading>
|
||||
|
||||
{!isEditing ? (
|
||||
<Fragment>
|
||||
{Object.entries(config).map(([key, value]) => (
|
||||
<div key={key}>
|
||||
{key}: {value ? 'ON' : 'OFF'}
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
{isEditing ? (
|
||||
<Extras>
|
||||
<ListItem
|
||||
as="label"
|
||||
title="Component tests"
|
||||
icon={<PointerHandIcon color={theme.textMutedColor} />}
|
||||
right={<Checkbox type="checkbox" checked disabled />}
|
||||
/>
|
||||
<ListItem
|
||||
as="label"
|
||||
title="Coverage"
|
||||
icon={<ShieldIcon color={theme.textMutedColor} />}
|
||||
right={
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
disabled // TODO: Implement coverage
|
||||
checked={config.coverage}
|
||||
onChange={() => updateConfig({ coverage: !config.coverage })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ListItem
|
||||
as="label"
|
||||
title="Accessibility"
|
||||
icon={<AccessibilityIcon color={theme.textMutedColor} />}
|
||||
right={
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
disabled // TODO: Implement a11y
|
||||
checked={config.a11y}
|
||||
onChange={() => updateConfig({ a11y: !config.a11y })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Extras>
|
||||
) : (
|
||||
<Fragment>
|
||||
{Object.entries(config).map(([key, value]) => (
|
||||
<div
|
||||
key={key}
|
||||
onClick={() => {
|
||||
changeConfig({ [key]: !value });
|
||||
}}
|
||||
>
|
||||
{key}: {value ? 'ON' : 'OFF'}
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
<Extras>
|
||||
<ListItem
|
||||
title="Component tests"
|
||||
icon={<TestStatusIcon status="positive" aria-label="status: passed" />}
|
||||
/>
|
||||
<ListItem
|
||||
title="Coverage"
|
||||
icon={<TestStatusIcon percentage={60} status="warning" aria-label="status: warning" />}
|
||||
right={`60%`}
|
||||
/>
|
||||
<ListItem
|
||||
title="Accessibility"
|
||||
icon={<TestStatusIcon status="negative" aria-label="status: failed" />}
|
||||
right={73}
|
||||
/>
|
||||
</Extras>
|
||||
)}
|
||||
|
||||
<GlobalErrorModal
|
||||
@ -150,33 +209,35 @@ export const TestProviderRender: FC<{
|
||||
api.runTestProvider(TEST_PROVIDER_ID);
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
function useConfig(id: string, config: Config, api: API) {
|
||||
const data = useRef<Config>(config);
|
||||
data.current = config || {
|
||||
a11y: false,
|
||||
coverage: false,
|
||||
};
|
||||
function useConfig(api: API, providerId: string, initialConfig: Config) {
|
||||
const [currentConfig, setConfig] = useState<Config>(initialConfig);
|
||||
const lastConfig = useRef(initialConfig);
|
||||
|
||||
const changeConfig = useCallback(
|
||||
(update: Partial<Config>) => {
|
||||
const newConfig = {
|
||||
...data.current,
|
||||
...update,
|
||||
};
|
||||
api.updateTestProviderState(id, {
|
||||
config: newConfig,
|
||||
});
|
||||
api.emit(TESTING_MODULE_CONFIG_CHANGE, {
|
||||
providerId: id,
|
||||
config: newConfig,
|
||||
} as TestingModuleConfigChangePayload);
|
||||
},
|
||||
[api, id]
|
||||
const saveConfig = useCallback(
|
||||
debounce((config: Config) => {
|
||||
if (!isEqual(config, lastConfig.current)) {
|
||||
api.updateTestProviderState(providerId, { config });
|
||||
api.emit(TESTING_MODULE_CONFIG_CHANGE, { providerId, config });
|
||||
lastConfig.current = config;
|
||||
}
|
||||
}, 500),
|
||||
[api, providerId]
|
||||
);
|
||||
|
||||
return [data.current, changeConfig] as const;
|
||||
const updateConfig = useCallback(
|
||||
(update: Partial<Config>) => {
|
||||
setConfig((value) => {
|
||||
const updated = { ...value, ...update };
|
||||
saveConfig(updated);
|
||||
return updated;
|
||||
});
|
||||
},
|
||||
[saveConfig]
|
||||
);
|
||||
|
||||
return [currentConfig, updateConfig] as const;
|
||||
}
|
||||
|
63
code/addons/test/src/components/TestStatusIcon.stories.tsx
Normal file
63
code/addons/test/src/components/TestStatusIcon.stories.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { TestStatusIcon } from './TestStatusIcon';
|
||||
|
||||
const meta = {
|
||||
component: TestStatusIcon,
|
||||
} satisfies Meta<typeof TestStatusIcon>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Unknown: Story = {
|
||||
args: {
|
||||
status: 'unknown',
|
||||
},
|
||||
};
|
||||
|
||||
export const Positive: Story = {
|
||||
args: {
|
||||
status: 'positive',
|
||||
},
|
||||
};
|
||||
|
||||
export const Warning: Story = {
|
||||
args: {
|
||||
status: 'warning',
|
||||
},
|
||||
};
|
||||
|
||||
export const Negative: Story = {
|
||||
args: {
|
||||
status: 'negative',
|
||||
},
|
||||
};
|
||||
|
||||
export const UnknownPercentage: Story = {
|
||||
args: {
|
||||
status: 'unknown',
|
||||
percentage: 50,
|
||||
},
|
||||
};
|
||||
|
||||
export const PositivePercentage: Story = {
|
||||
args: {
|
||||
status: 'positive',
|
||||
percentage: 60,
|
||||
},
|
||||
};
|
||||
|
||||
export const WarningPercentage: Story = {
|
||||
args: {
|
||||
status: 'warning',
|
||||
percentage: 40,
|
||||
},
|
||||
};
|
||||
|
||||
export const NegativePercentage: Story = {
|
||||
args: {
|
||||
status: 'negative',
|
||||
percentage: 30,
|
||||
},
|
||||
};
|
36
code/addons/test/src/components/TestStatusIcon.tsx
Normal file
36
code/addons/test/src/components/TestStatusIcon.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { styled } from 'storybook/internal/theming';
|
||||
|
||||
export const TestStatusIcon = styled.div<{
|
||||
status: 'positive' | 'warning' | 'negative' | 'unknown';
|
||||
percentage?: number;
|
||||
}>(
|
||||
({ percentage }) => ({
|
||||
width: percentage ? 12 : 6,
|
||||
height: percentage ? 12 : 6,
|
||||
margin: percentage ? 1 : 4,
|
||||
background: percentage
|
||||
? `conic-gradient(var(--status-color) ${percentage}%, var(--status-background) ${percentage + 1}%)`
|
||||
: 'var(--status-color)',
|
||||
borderRadius: '50%',
|
||||
}),
|
||||
({ status, theme }) =>
|
||||
status === 'positive' && {
|
||||
'--status-color': theme.color.positive,
|
||||
'--status-background': `${theme.color.positive}66`,
|
||||
},
|
||||
({ status, theme }) =>
|
||||
status === 'warning' && {
|
||||
'--status-color': theme.color.gold,
|
||||
'--status-background': `${theme.color.gold}66`,
|
||||
},
|
||||
({ status, theme }) =>
|
||||
status === 'negative' && {
|
||||
'--status-color': theme.color.negative,
|
||||
'--status-background': `${theme.color.negative}66`,
|
||||
},
|
||||
({ status, theme }) =>
|
||||
status === 'unknown' && {
|
||||
'--status-color': theme.color.mediumdark,
|
||||
'--status-background': `${theme.color.mediumdark}66`,
|
||||
}
|
||||
);
|
@ -35,6 +35,7 @@ addons.register(ADDON_ID, (api) => {
|
||||
runnable: true,
|
||||
watchable: true,
|
||||
name: 'Component tests',
|
||||
render: (state) => <TestProviderRender api={api} state={state} />,
|
||||
|
||||
sidebarContextMenu: ({ context, state }) => {
|
||||
if (context.type === 'docs') {
|
||||
@ -47,8 +48,6 @@ addons.register(ADDON_ID, (api) => {
|
||||
return <ContextMenuItem context={context} state={state} />;
|
||||
},
|
||||
|
||||
render: (state) => <TestProviderRender api={api} state={state} />,
|
||||
|
||||
mapStatusUpdate: (state) =>
|
||||
Object.fromEntries(
|
||||
(state.details.testResults || []).flatMap((testResult) =>
|
||||
|
@ -21,7 +21,9 @@ export const ToolbarMenuListItem = ({
|
||||
disabled,
|
||||
currentValue,
|
||||
}: ToolbarMenuListItemProps) => {
|
||||
const Icon = icon && <Icons style={{ opacity: 1 }} icon={icon} />;
|
||||
const Icon = icon && (
|
||||
<Icons style={{ opacity: 1 }} icon={icon} __suppressDeprecationWarning={true} />
|
||||
);
|
||||
|
||||
const Item: TooltipLinkListLink = {
|
||||
id: value ?? '_reset',
|
||||
|
@ -150,6 +150,15 @@ const Item = styled.div<ItemProps>(
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
({ theme, as }) =>
|
||||
as === 'label' && {
|
||||
'&:has(input:not(:disabled))': {
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
background: theme.background.hoverable,
|
||||
},
|
||||
},
|
||||
},
|
||||
({ disabled }) => disabled && { cursor: 'not-allowed' }
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default <T>(a: T[], b: T[]): T[] => {
|
||||
// no point in intersecting if one of the input is ill-defined
|
||||
if (!a || !b) {
|
||||
if (!Array.isArray(a) || !Array.isArray(b) || !a.length || !b.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type {
|
||||
API_BaseEntry,
|
||||
API_ComponentEntry,
|
||||
API_DocsEntry,
|
||||
API_GroupEntry,
|
||||
@ -16,6 +17,7 @@ import type {
|
||||
StoryId,
|
||||
StoryIndexV2,
|
||||
StoryIndexV3,
|
||||
Tag,
|
||||
} from '@storybook/core/types';
|
||||
import { sanitize } from '@storybook/csf';
|
||||
|
||||
@ -248,6 +250,7 @@ export const transformStoryIndexToStoriesHash = (
|
||||
type: 'root',
|
||||
id,
|
||||
name: names[idx],
|
||||
tags: [],
|
||||
depth: idx,
|
||||
renderLabel,
|
||||
startCollapsed: collapsedRoots.includes(id),
|
||||
@ -267,6 +270,7 @@ export const transformStoryIndexToStoriesHash = (
|
||||
type: 'component',
|
||||
id,
|
||||
name: names[idx],
|
||||
tags: [],
|
||||
parent: paths[idx - 1],
|
||||
depth: idx,
|
||||
renderLabel,
|
||||
@ -274,14 +278,12 @@ export const transformStoryIndexToStoriesHash = (
|
||||
children: [childId],
|
||||
}),
|
||||
});
|
||||
// merge computes a union of arrays but we want an intersection on this
|
||||
// specific array property, so it's easier to add it after the merge.
|
||||
acc[id].tags = intersect(acc[id]?.tags ?? item.tags, item.tags);
|
||||
} else {
|
||||
acc[id] = merge<API_GroupEntry>((acc[id] || {}) as API_GroupEntry, {
|
||||
type: 'group',
|
||||
id,
|
||||
name: names[idx],
|
||||
tags: [],
|
||||
parent: paths[idx - 1],
|
||||
depth: idx,
|
||||
renderLabel,
|
||||
@ -295,6 +297,7 @@ export const transformStoryIndexToStoriesHash = (
|
||||
// Finally add an entry for the docs/story itself
|
||||
acc[item.id] = {
|
||||
type: 'story',
|
||||
tags: [],
|
||||
...item,
|
||||
depth: paths.length,
|
||||
parent: paths[paths.length - 1],
|
||||
@ -313,9 +316,18 @@ export const transformStoryIndexToStoriesHash = (
|
||||
}
|
||||
|
||||
acc[item.id] = item;
|
||||
// Ensure we add the children depth-first *before* inserting any other entries
|
||||
// Ensure we add the children depth-first *before* inserting any other entries,
|
||||
// and compute tags from the children put in the accumulator afterwards, once
|
||||
// they're all known and we can compute a sound intersection.
|
||||
if (item.type === 'root' || item.type === 'group' || item.type === 'component') {
|
||||
item.children.forEach((childId: any) => addItem(acc, storiesHashOutOfOrder[childId]));
|
||||
|
||||
item.tags = item.children.reduce((currentTags: Tag[] | null, childId: any): Tag[] => {
|
||||
const child = acc[childId];
|
||||
|
||||
// On the first child, we have nothing to intersect against so we use it as a source of data.
|
||||
return currentTags === null ? child.tags : intersect(currentTags, child.tags);
|
||||
}, null);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ export type SubState = {
|
||||
};
|
||||
|
||||
const initialTestProviderState: TestProviderState = {
|
||||
config: {} as { [key: string]: any },
|
||||
details: {} as { [key: string]: any },
|
||||
cancellable: false,
|
||||
cancelling: false,
|
||||
|
@ -162,6 +162,7 @@ describe('stories API', () => {
|
||||
expect(index!['design-system']).toMatchObject({
|
||||
type: 'root',
|
||||
name: 'Design System', // root name originates from `kind`, so it gets trimmed
|
||||
tags: [],
|
||||
});
|
||||
expect(index!['design-system-some-component']).toMatchObject({
|
||||
type: 'component',
|
||||
@ -186,6 +187,7 @@ describe('stories API', () => {
|
||||
title: 'Root/First',
|
||||
name: 'Story 1',
|
||||
importPath: './path/to/root/first.ts',
|
||||
tags: [],
|
||||
},
|
||||
...mockEntries,
|
||||
},
|
||||
@ -207,6 +209,7 @@ describe('stories API', () => {
|
||||
type: 'root',
|
||||
id: 'root',
|
||||
children: ['root-first'],
|
||||
tags: [],
|
||||
});
|
||||
});
|
||||
it('sets roots when showRoots = true', () => {
|
||||
@ -222,6 +225,7 @@ describe('stories API', () => {
|
||||
id: 'a-b--1',
|
||||
title: 'a/b',
|
||||
name: '1',
|
||||
tags: [],
|
||||
importPath: './a/b.ts',
|
||||
},
|
||||
},
|
||||
@ -233,6 +237,7 @@ describe('stories API', () => {
|
||||
type: 'root',
|
||||
id: 'a',
|
||||
children: ['a-b'],
|
||||
tags: [],
|
||||
});
|
||||
expect(index!['a-b']).toMatchObject({
|
||||
type: 'component',
|
||||
@ -332,6 +337,76 @@ describe('stories API', () => {
|
||||
tags: ['shared', 'two-specific'],
|
||||
});
|
||||
});
|
||||
|
||||
it('intersects story/docs tags to compute tags for root and group entries', () => {
|
||||
const moduleArgs = createMockModuleArgs({});
|
||||
const { api } = initStories(moduleArgs as unknown as ModuleArgs);
|
||||
const { store } = moduleArgs;
|
||||
api.setIndex({
|
||||
v: 5,
|
||||
entries: {
|
||||
'a-sampleone': {
|
||||
type: 'story',
|
||||
id: 'a-sampleone',
|
||||
title: 'A/SampleOne',
|
||||
name: '1',
|
||||
tags: ['shared', 'one-specific'],
|
||||
importPath: './a.ts',
|
||||
},
|
||||
'a-sampletwo': {
|
||||
type: 'story',
|
||||
id: 'a-sampletwo',
|
||||
title: 'A/SampleTwo',
|
||||
name: '2',
|
||||
tags: ['shared', 'two-specific'],
|
||||
importPath: './a.ts',
|
||||
},
|
||||
'a-embedded-othertopic': {
|
||||
type: 'docs',
|
||||
id: 'a-embedded-othertopic',
|
||||
title: 'A/Embedded/OtherTopic',
|
||||
name: '3',
|
||||
tags: ['shared', 'embedded-docs-specific', 'other'],
|
||||
storiesImports: [],
|
||||
importPath: './embedded/other.mdx',
|
||||
},
|
||||
'a-embedded-extras': {
|
||||
type: 'docs',
|
||||
id: 'a-embedded-extras',
|
||||
title: 'A/Embedded/Extras',
|
||||
name: '3',
|
||||
tags: ['shared', 'embedded-docs-specific', 'extras'],
|
||||
storiesImports: [],
|
||||
importPath: './embedded/extras.mdx',
|
||||
},
|
||||
},
|
||||
});
|
||||
const { index } = store.getState();
|
||||
// We need exact key ordering, even if in theory JS doesn't guarantee it
|
||||
expect(Object.keys(index!)).toEqual([
|
||||
'a',
|
||||
'a-sampleone',
|
||||
'a-sampletwo',
|
||||
'a-embedded',
|
||||
'a-embedded-othertopic',
|
||||
'a-embedded-extras',
|
||||
]);
|
||||
// Acts as the root, so that the next level is a group we're testing.
|
||||
expect(index!.a).toMatchObject({
|
||||
type: 'root',
|
||||
id: 'a',
|
||||
children: ['a-sampleone', 'a-sampletwo', 'a-embedded'],
|
||||
tags: ['shared'],
|
||||
});
|
||||
// The object of this test.
|
||||
expect(index!['a-embedded']).toMatchObject({
|
||||
type: 'group',
|
||||
id: 'a-embedded',
|
||||
parent: 'a',
|
||||
name: 'Embedded',
|
||||
tags: ['shared', 'embedded-docs-specific'],
|
||||
});
|
||||
});
|
||||
// Stories can get out of order for a few reasons -- see reproductions on
|
||||
// https://github.com/storybookjs/storybook/issues/5518
|
||||
it('does the right thing for out of order stories', async () => {
|
||||
@ -1515,6 +1590,7 @@ describe('stories API', () => {
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
"renderLabel": undefined,
|
||||
"tags": [],
|
||||
"title": "a",
|
||||
"type": "story",
|
||||
},
|
||||
@ -1526,6 +1602,7 @@ describe('stories API', () => {
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
"renderLabel": undefined,
|
||||
"tags": [],
|
||||
"title": "a",
|
||||
"type": "story",
|
||||
},
|
||||
@ -1581,6 +1658,7 @@ describe('stories API', () => {
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
"renderLabel": undefined,
|
||||
"tags": [],
|
||||
"title": "a",
|
||||
"type": "story",
|
||||
},
|
||||
@ -1623,6 +1701,7 @@ describe('stories API', () => {
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
"renderLabel": undefined,
|
||||
"tags": [],
|
||||
"title": "a",
|
||||
"type": "story",
|
||||
},
|
||||
@ -1634,6 +1713,7 @@ describe('stories API', () => {
|
||||
"parent": "a",
|
||||
"prepared": false,
|
||||
"renderLabel": undefined,
|
||||
"tags": [],
|
||||
"title": "a",
|
||||
"type": "story",
|
||||
},
|
||||
|
@ -7,6 +7,12 @@ import { EyeIcon, PlayHollowIcon, StopAltHollowIcon } from '@storybook/icons';
|
||||
import type { TestProviders } from '@storybook/core/core-events';
|
||||
import { useStorybookApi } from '@storybook/core/manager-api';
|
||||
|
||||
const Container = styled.div({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: '8px 2px',
|
||||
});
|
||||
|
||||
const Info = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@ -35,7 +41,7 @@ export const LegacyRender = ({ ...state }: TestProviders[keyof TestProviders]) =
|
||||
const api = useStorybookApi();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<Info>
|
||||
<TitleWrapper crashed={state.crashed} id="testing-module-title">
|
||||
<Title {...state} />
|
||||
@ -84,6 +90,6 @@ export const LegacyRender = ({ ...state }: TestProviders[keyof TestProviders]) =
|
||||
</>
|
||||
)}
|
||||
</Actions>
|
||||
</>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -155,7 +155,9 @@ export const DynamicHeight: StoryObj = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const screen = await within(canvasElement);
|
||||
|
||||
const toggleButton = await screen.getByLabelText('Collapse testing module');
|
||||
const toggleButton = await screen.getByLabelText(/Expand/);
|
||||
await userEvent.click(toggleButton);
|
||||
|
||||
const content = await screen.findByText('CUSTOM CONTENT WITH DYNAMIC HEIGHT');
|
||||
const collapse = await screen.getByTestId('collapse');
|
||||
|
||||
|
@ -1,14 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { Listener } from '@storybook/core/channels';
|
||||
import { styled } from '@storybook/core/theming';
|
||||
import { Addon_TypesEnum } from '@storybook/core/types';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn, userEvent } from '@storybook/test';
|
||||
import { fireEvent, fn } from '@storybook/test';
|
||||
|
||||
import type { TestProviders } from '@storybook/core/core-events';
|
||||
import { ManagerContext } from '@storybook/core/manager-api';
|
||||
import { TESTING_MODULE_CONFIG_CHANGE, type TestProviders } from '@storybook/core/core-events';
|
||||
import { ManagerContext, mockChannel } from '@storybook/core/manager-api';
|
||||
|
||||
import { TestingModule } from './TestingModule';
|
||||
|
||||
const TestProvider = styled.div({
|
||||
padding: 8,
|
||||
fontSize: 12,
|
||||
});
|
||||
|
||||
const baseState = {
|
||||
details: {},
|
||||
cancellable: false,
|
||||
@ -24,13 +31,8 @@ const testProviders: TestProviders[keyof TestProviders][] = [
|
||||
type: Addon_TypesEnum.experimental_TEST_PROVIDER,
|
||||
id: 'component-tests',
|
||||
name: 'Component tests',
|
||||
render: () => (
|
||||
<>
|
||||
Component tests
|
||||
<br />
|
||||
Ran 2 seconds ago
|
||||
</>
|
||||
),
|
||||
title: () => 'Component tests',
|
||||
description: () => 'Ran 2 seconds ago',
|
||||
runnable: true,
|
||||
watchable: true,
|
||||
...baseState,
|
||||
@ -39,13 +41,8 @@ const testProviders: TestProviders[keyof TestProviders][] = [
|
||||
type: Addon_TypesEnum.experimental_TEST_PROVIDER,
|
||||
id: 'visual-tests',
|
||||
name: 'Visual tests',
|
||||
render: () => (
|
||||
<>
|
||||
Visual tests
|
||||
<br />
|
||||
Not run
|
||||
</>
|
||||
),
|
||||
title: () => 'Visual tests',
|
||||
description: () => 'Not run',
|
||||
runnable: true,
|
||||
...baseState,
|
||||
},
|
||||
@ -53,20 +50,23 @@ const testProviders: TestProviders[keyof TestProviders][] = [
|
||||
type: Addon_TypesEnum.experimental_TEST_PROVIDER,
|
||||
id: 'linting',
|
||||
name: 'Linting',
|
||||
render: () => (
|
||||
<>
|
||||
Linting
|
||||
<br />
|
||||
Watching for changes
|
||||
</>
|
||||
),
|
||||
render: () => <TestProvider>Custom render function</TestProvider>,
|
||||
...baseState,
|
||||
watching: true,
|
||||
},
|
||||
];
|
||||
|
||||
let triggerUpdate: () => void;
|
||||
const channel = mockChannel();
|
||||
const managerContext: any = {
|
||||
api: {
|
||||
on: (eventName: string, listener: Listener) => {
|
||||
if (eventName === TESTING_MODULE_CONFIG_CHANGE) {
|
||||
triggerUpdate = listener;
|
||||
}
|
||||
return channel.on(eventName, listener);
|
||||
},
|
||||
off: (eventName: string, listener: Listener) => channel.off(eventName, listener),
|
||||
runTestProvider: fn().mockName('api::runTestProvider'),
|
||||
cancelTestProvider: fn().mockName('api::cancelTestProvider'),
|
||||
updateTestProviderState: fn().mockName('api::updateTestProviderState'),
|
||||
@ -104,10 +104,11 @@ type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Collapsed: Story = {
|
||||
export const Expanded: Story = {
|
||||
play: async ({ canvas }) => {
|
||||
const button = await canvas.findByRole('button', { name: /Collapse/ });
|
||||
await userEvent.click(button);
|
||||
const button = await canvas.findByRole('button', { name: /Expand/ });
|
||||
await fireEvent.click(button);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
},
|
||||
};
|
||||
|
||||
@ -116,6 +117,7 @@ export const Statuses: Story = {
|
||||
errorCount: 14,
|
||||
warningCount: 42,
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const ErrorsActive: Story = {
|
||||
@ -123,6 +125,7 @@ export const ErrorsActive: Story = {
|
||||
...Statuses.args,
|
||||
errorsActive: true,
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const WarningsActive: Story = {
|
||||
@ -130,6 +133,7 @@ export const WarningsActive: Story = {
|
||||
...Statuses.args,
|
||||
warningsActive: true,
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const BothActive: Story = {
|
||||
@ -138,28 +142,29 @@ export const BothActive: Story = {
|
||||
errorsActive: true,
|
||||
warningsActive: true,
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const CollapsedStatuses: Story = {
|
||||
args: Statuses.args,
|
||||
play: Collapsed.play,
|
||||
};
|
||||
|
||||
export const Running: Story = {
|
||||
args: {
|
||||
testProviders: [{ ...testProviders[0], running: true }, ...testProviders.slice(1)],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const RunningAll: Story = {
|
||||
args: {
|
||||
testProviders: testProviders.map((tp) => ({ ...tp, running: !!tp.runnable })),
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const CollapsedRunning: Story = {
|
||||
args: RunningAll.args,
|
||||
play: Collapsed.play,
|
||||
};
|
||||
|
||||
export const Cancellable: Story = {
|
||||
@ -169,6 +174,7 @@ export const Cancellable: Story = {
|
||||
...testProviders.slice(1),
|
||||
],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const Cancelling: Story = {
|
||||
@ -178,12 +184,14 @@ export const Cancelling: Story = {
|
||||
...testProviders.slice(1),
|
||||
],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const Watching: Story = {
|
||||
args: {
|
||||
testProviders: [{ ...testProviders[0], watching: true }, ...testProviders.slice(1)],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const Failing: Story = {
|
||||
@ -193,12 +201,14 @@ export const Failing: Story = {
|
||||
...testProviders.slice(1),
|
||||
],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const Failed: Story = {
|
||||
args: {
|
||||
testProviders: [{ ...testProviders[0], failed: true }, ...testProviders.slice(1)],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const Crashed: Story = {
|
||||
@ -207,17 +217,26 @@ export const Crashed: Story = {
|
||||
{
|
||||
...testProviders[0],
|
||||
render: () => (
|
||||
<>
|
||||
<TestProvider>
|
||||
Component tests didn't complete
|
||||
<br />
|
||||
Problems!
|
||||
</>
|
||||
</TestProvider>
|
||||
),
|
||||
crashed: true,
|
||||
},
|
||||
...testProviders.slice(1),
|
||||
],
|
||||
},
|
||||
play: Expanded.play,
|
||||
};
|
||||
|
||||
export const Updated: Story = {
|
||||
args: {},
|
||||
play: async (context) => {
|
||||
await Expanded.play!(context);
|
||||
triggerUpdate?.();
|
||||
},
|
||||
};
|
||||
|
||||
export const NoTestProvider: Story = {
|
||||
|
@ -1,18 +1,11 @@
|
||||
import React, {
|
||||
Fragment,
|
||||
type SyntheticEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { type SyntheticEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Button, TooltipNote } from '@storybook/core/components';
|
||||
import { WithTooltip } from '@storybook/core/components';
|
||||
import { keyframes, styled } from '@storybook/core/theming';
|
||||
import { ChevronSmallUpIcon, PlayAllHollowIcon } from '@storybook/icons';
|
||||
|
||||
import type { TestProviders } from '@storybook/core/core-events';
|
||||
import { TESTING_MODULE_CONFIG_CHANGE, type TestProviders } from '@storybook/core/core-events';
|
||||
import { useStorybookApi } from '@storybook/core/manager-api';
|
||||
|
||||
import { LegacyRender } from './LegacyRender';
|
||||
@ -29,42 +22,42 @@ const spin = keyframes({
|
||||
'100%': { transform: 'rotate(360deg)' },
|
||||
});
|
||||
|
||||
const Outline = styled.div<{ crashed: boolean; failed: boolean; running: boolean }>(
|
||||
({ crashed, running, theme, failed }) => ({
|
||||
position: 'relative',
|
||||
lineHeight: '20px',
|
||||
width: '100%',
|
||||
padding: 1,
|
||||
overflow: 'hidden',
|
||||
background: `var(--sb-sidebar-bottom-card-background, ${theme.background.content})`,
|
||||
borderRadius:
|
||||
`var(--sb-sidebar-bottom-card-border-radius, ${theme.appBorderRadius + 1}px)` as any,
|
||||
boxShadow: `inset 0 0 0 1px ${crashed && !running ? theme.color.negative : theme.appBorderColor}, var(--sb-sidebar-bottom-card-box-shadow, 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0px -5px 20px 10px ${theme.background.app})`,
|
||||
transitionProperty:
|
||||
'color, background-color, border-color, text-decoration-color, fill, stroke',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transitionDuration: '0.15s',
|
||||
const Outline = styled.div<{
|
||||
crashed: boolean;
|
||||
failed: boolean;
|
||||
running: boolean;
|
||||
updated: boolean;
|
||||
}>(({ crashed, failed, running, theme, updated }) => ({
|
||||
position: 'relative',
|
||||
lineHeight: '20px',
|
||||
width: '100%',
|
||||
padding: 1,
|
||||
overflow: 'hidden',
|
||||
backgroundColor: `var(--sb-sidebar-bottom-card-background, ${theme.background.content})`,
|
||||
borderRadius:
|
||||
`var(--sb-sidebar-bottom-card-border-radius, ${theme.appBorderRadius + 1}px)` as any,
|
||||
boxShadow: `inset 0 0 0 1px ${crashed && !running ? theme.color.negative : updated ? theme.color.positive : theme.appBorderColor}, var(--sb-sidebar-bottom-card-box-shadow, 0 1px 2px 0 rgba(0, 0, 0, 0.05), 0px -5px 20px 10px ${theme.background.app})`,
|
||||
transition: 'box-shadow 1s',
|
||||
|
||||
'&:after': {
|
||||
content: '""',
|
||||
display: running ? 'block' : 'none',
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: 'calc(max(100vw, 100vh) * -0.5)',
|
||||
marginTop: 'calc(max(100vw, 100vh) * -0.5)',
|
||||
height: 'max(100vw, 100vh)',
|
||||
width: 'max(100vw, 100vh)',
|
||||
animation: `${spin} 3s linear infinite`,
|
||||
background: failed
|
||||
? // Hardcoded colors to prevent themes from messing with them (orange+gold, secondary+seafoam)
|
||||
`conic-gradient(transparent 90deg, #FC521F 150deg, #FFAE00 210deg, transparent 270deg)`
|
||||
: `conic-gradient(transparent 90deg, #029CFD 150deg, #37D5D3 210deg, transparent 270deg)`,
|
||||
opacity: 1,
|
||||
willChange: 'auto',
|
||||
},
|
||||
})
|
||||
);
|
||||
'&:after': {
|
||||
content: '""',
|
||||
display: running ? 'block' : 'none',
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
marginLeft: 'calc(max(100vw, 100vh) * -0.5)',
|
||||
marginTop: 'calc(max(100vw, 100vh) * -0.5)',
|
||||
height: 'max(100vw, 100vh)',
|
||||
width: 'max(100vw, 100vh)',
|
||||
animation: `${spin} 3s linear infinite`,
|
||||
background: failed
|
||||
? // Hardcoded colors to prevent themes from messing with them (orange+gold, secondary+seafoam)
|
||||
`conic-gradient(transparent 90deg, #FC521F 150deg, #FFAE00 210deg, transparent 270deg)`
|
||||
: `conic-gradient(transparent 90deg, #029CFD 150deg, #37D5D3 210deg, transparent 270deg)`,
|
||||
opacity: 1,
|
||||
willChange: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const Card = styled.div(({ theme }) => ({
|
||||
position: 'relative',
|
||||
@ -85,10 +78,8 @@ const Collapsible = styled.div(({ theme }) => ({
|
||||
}));
|
||||
|
||||
const Content = styled.div({
|
||||
padding: '12px 6px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '12px',
|
||||
});
|
||||
|
||||
const Bar = styled.div<{ onClick?: (e: SyntheticEvent) => void }>(({ onClick }) => ({
|
||||
@ -145,11 +136,13 @@ const StatusButton = styled(Button)<{ status: 'negative' | 'warning' }>(
|
||||
})
|
||||
);
|
||||
|
||||
const TestProvider = styled.div({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 6,
|
||||
});
|
||||
const TestProvider = styled.div(({ theme }) => ({
|
||||
padding: 4,
|
||||
|
||||
'&:not(:last-child)': {
|
||||
boxShadow: `inset 0 -1px 0 ${theme.appBorderColor}`,
|
||||
},
|
||||
}));
|
||||
|
||||
interface TestingModuleProps {
|
||||
testProviders: TestProviders[keyof TestProviders][];
|
||||
@ -172,13 +165,13 @@ export const TestingModule = ({
|
||||
}: TestingModuleProps) => {
|
||||
const api = useStorybookApi();
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const timeoutRef = useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [maxHeight, setMaxHeight] = useState(DEFAULT_HEIGHT);
|
||||
|
||||
const [isCollapsed, setCollapsed] = useState(false);
|
||||
const [isUpdated, setUpdated] = useState(false);
|
||||
const [isCollapsed, setCollapsed] = useState(true);
|
||||
const [isChangingCollapse, setChangingCollapse] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setMaxHeight(contentRef.current?.getBoundingClientRect().height || DEFAULT_HEIGHT);
|
||||
@ -197,6 +190,19 @@ export const TestingModule = ({
|
||||
}
|
||||
}, [isCollapsed]);
|
||||
|
||||
useEffect(() => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const handler = () => {
|
||||
setUpdated(true);
|
||||
timeout = setTimeout(setUpdated, 1000, false);
|
||||
};
|
||||
api.on(TESTING_MODULE_CONFIG_CHANGE, handler);
|
||||
return () => {
|
||||
api.off(TESTING_MODULE_CONFIG_CHANGE, handler);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
const toggleCollapsed = useCallback((event: SyntheticEvent) => {
|
||||
event.stopPropagation();
|
||||
setChangingCollapse(true);
|
||||
@ -224,6 +230,7 @@ export const TestingModule = ({
|
||||
running={isRunning}
|
||||
crashed={isCrashed}
|
||||
failed={isFailed || errorCount > 0}
|
||||
updated={isUpdated}
|
||||
>
|
||||
<Card>
|
||||
{hasTestProviders && (
|
||||
@ -238,13 +245,9 @@ export const TestingModule = ({
|
||||
<Content ref={contentRef}>
|
||||
{testProviders.map((state) => {
|
||||
const { render: Render } = state;
|
||||
return Render ? (
|
||||
<Fragment key={state.id}>
|
||||
<Render {...state} />
|
||||
</Fragment>
|
||||
) : (
|
||||
<TestProvider key={state.id}>
|
||||
<LegacyRender {...state} />
|
||||
return (
|
||||
<TestProvider key={state.id} data-module-id={state.id}>
|
||||
{Render ? <Render {...state} /> : <LegacyRender {...state} />}
|
||||
</TestProvider>
|
||||
);
|
||||
})}
|
||||
|
@ -50,6 +50,7 @@ const generateStories = ({ title, refId }: { title: string; refId?: string }): A
|
||||
name: root,
|
||||
children: [componentId],
|
||||
startCollapsed: false,
|
||||
tags: [],
|
||||
},
|
||||
{
|
||||
type: 'component',
|
||||
|
@ -7,6 +7,7 @@ export interface API_BaseEntry {
|
||||
id: StoryId;
|
||||
depth: number;
|
||||
name: string;
|
||||
tags: Tag[];
|
||||
refId?: string;
|
||||
renderLabel?: (item: API_BaseEntry, api: any) => any;
|
||||
}
|
||||
@ -27,7 +28,6 @@ export interface API_ComponentEntry extends API_BaseEntry {
|
||||
type: 'component';
|
||||
parent?: StoryId;
|
||||
children: StoryId[];
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export interface API_DocsEntry extends API_BaseEntry {
|
||||
@ -35,7 +35,6 @@ export interface API_DocsEntry extends API_BaseEntry {
|
||||
parent: StoryId;
|
||||
title: ComponentTitle;
|
||||
importPath: Path;
|
||||
tags: Tag[];
|
||||
prepared: boolean;
|
||||
parameters?: {
|
||||
[parameterName: string]: any;
|
||||
@ -47,7 +46,6 @@ export interface API_StoryEntry extends API_BaseEntry {
|
||||
parent: StoryId;
|
||||
title: ComponentTitle;
|
||||
importPath: Path;
|
||||
tags: Tag[];
|
||||
prepared: boolean;
|
||||
parameters?: {
|
||||
[parameterName: string]: any;
|
||||
|
@ -64,7 +64,7 @@
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
|
||||
"storybook": "workspace:^",
|
||||
"svelte": "^4.0.0 || ^5.0.0",
|
||||
"vite": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
|
@ -293,5 +293,6 @@
|
||||
"Dependency Upgrades"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"deferredNextVersion": "8.5.0-alpha.12"
|
||||
}
|
||||
|
@ -7430,7 +7430,7 @@ __metadata:
|
||||
typescript: "npm:^5.3.2"
|
||||
vite: "npm:^4.0.0"
|
||||
peerDependencies:
|
||||
"@sveltejs/vite-plugin-svelte": ^2.0.0 || ^3.0.0 || ^4.0.0
|
||||
"@sveltejs/vite-plugin-svelte": ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
|
||||
storybook: "workspace:^"
|
||||
svelte: ^4.0.0 || ^5.0.0
|
||||
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
|
||||
|
@ -1 +1 @@
|
||||
{"version":"8.5.0-alpha.11","info":{"plain":"- Core + Addon Test: Refactor test API and fix total test count - [#29656](https://github.com/storybookjs/storybook/pull/29656), thanks @ghengeveld!\n- Core: Emit deprecated `TESTING_MODULE_RUN_ALL_REQUEST` for backward compatibility - [#29711](https://github.com/storybookjs/storybook/pull/29711), thanks @ghengeveld!\n- Frameworks: Add Vite 6 support - [#29710](https://github.com/storybookjs/storybook/pull/29710), thanks @yannbf!\n- TestAddon: Refactor UI & add config options - [#29662](https://github.com/storybookjs/storybook/pull/29662), thanks @ndelangen!\n- Vue: Fix `vue-component-meta` docgen HMR not working - [#29518](https://github.com/storybookjs/storybook/pull/29518), thanks @IonianPlayboy!"}}
|
||||
{"version":"8.5.0-alpha.12","info":{"plain":"- Core / Addon Test: Add config UI to Testing Module - [#29708](https://github.com/storybookjs/storybook/pull/29708), thanks @ghengeveld!\n- Manager: Add tags property to GroupEntry objects - [#29672](https://github.com/storybookjs/storybook/pull/29672), thanks @Sidnioulz!\n- Svelte: Support `@sveltejs/vite-plugin-svelte` v5 - [#29731](https://github.com/storybookjs/storybook/pull/29731), thanks @JReinhold!\n- Toolbars: Suppress deprecation warning when using dynamic icons - [#29545](https://github.com/storybookjs/storybook/pull/29545), thanks @ValeraS!"}}
|
||||
|
@ -89,7 +89,7 @@ export const install: Task['run'] = async ({ sandboxDir, key }, { link, dryRun,
|
||||
dryRun,
|
||||
debug,
|
||||
});
|
||||
await addWorkaroundResolutions({ cwd, dryRun, debug });
|
||||
await addWorkaroundResolutions({ cwd, dryRun, debug, key });
|
||||
} else {
|
||||
// We need to add package resolutions to ensure that we only ever install the latest version
|
||||
// of any storybook packages as verdaccio is not able to both proxy to npm and publish over
|
||||
|
@ -94,7 +94,7 @@ export const addWorkaroundResolutions = async ({
|
||||
'@testing-library/user-event': '^14.5.2',
|
||||
};
|
||||
|
||||
if (key.includes('svelte-kit')) {
|
||||
if (key?.includes('svelte-kit')) {
|
||||
packageJson.resolutions['@sveltejs/vite-plugin-svelte'] = '^3.0.0';
|
||||
}
|
||||
|
||||
|
@ -49,11 +49,14 @@ test.describe("component testing", () => {
|
||||
|
||||
await sbPage.navigateToStory("addons/test", "Mismatch Failure");
|
||||
|
||||
const expandButton = await page.getByLabel('Expand testing module')
|
||||
await expandButton.click();
|
||||
|
||||
// For whatever reason, sometimes it takes longer for the story to load
|
||||
const storyElement = sbPage
|
||||
.getCanvasBodyElement()
|
||||
.getByRole("button", { name: "test" });
|
||||
await expect(storyElement).toBeVisible({ timeout: 10000 });
|
||||
await expect(storyElement).toBeVisible({ timeout: 30000 });
|
||||
|
||||
await sbPage.viewAddonPanel("Component tests");
|
||||
|
||||
@ -65,13 +68,12 @@ test.describe("component testing", () => {
|
||||
if ((await testStoryElement.getAttribute("aria-expanded")) !== "true") {
|
||||
testStoryElement.click();
|
||||
}
|
||||
|
||||
|
||||
const testingModuleDescription = await page.locator('#testing-module-description');
|
||||
|
||||
await expect(testingModuleDescription).toContainText('Not run');
|
||||
|
||||
const runTestsButton = await page.getByLabel('Start component tests')
|
||||
|
||||
await runTestsButton.click();
|
||||
|
||||
await expect(testingModuleDescription).toContainText('Testing', { timeout: 60000 });
|
||||
@ -117,14 +119,17 @@ test.describe("component testing", () => {
|
||||
|
||||
const sbPage = new SbPage(page, expect);
|
||||
await sbPage.navigateToStory("addons/test", "Expected Failure");
|
||||
|
||||
|
||||
const expandButton = await page.getByLabel('Expand testing module')
|
||||
await expandButton.click();
|
||||
|
||||
// For whatever reason, sometimes it takes longer for the story to load
|
||||
const storyElement = sbPage
|
||||
.getCanvasBodyElement()
|
||||
.getByRole("button", { name: "test" });
|
||||
await expect(storyElement).toBeVisible({ timeout: 10000 });
|
||||
await expect(storyElement).toBeVisible({ timeout: 30000 });
|
||||
|
||||
await expect(page.locator('#testing-module-title')).toHaveText('Component tests');
|
||||
await expect(page.locator('#testing-module-title')).toHaveText('Run local tests');
|
||||
|
||||
const testingModuleDescription = await page.locator('#testing-module-description');
|
||||
|
||||
@ -142,7 +147,7 @@ test.describe("component testing", () => {
|
||||
|
||||
// Wait for test results to appear
|
||||
await expect(testingModuleDescription).toHaveText(/Ran \d+ tests/, { timeout: 30000 });
|
||||
|
||||
|
||||
await expect(runTestsButton).toBeEnabled();
|
||||
await expect(watchModeButton).toBeEnabled();
|
||||
|
||||
@ -186,11 +191,14 @@ test.describe("component testing", () => {
|
||||
const sbPage = new SbPage(page, expect);
|
||||
await sbPage.navigateToStory("addons/test", "Expected Failure");
|
||||
|
||||
const expandButton = await page.getByLabel('Expand testing module')
|
||||
await expandButton.click();
|
||||
|
||||
// For whatever reason, sometimes it takes longer for the story to load
|
||||
const storyElement = sbPage
|
||||
.getCanvasBodyElement()
|
||||
.getByRole("button", { name: "test" });
|
||||
await expect(storyElement).toBeVisible({ timeout: 10000 });
|
||||
await expect(storyElement).toBeVisible({ timeout: 30000 });
|
||||
|
||||
await page.getByLabel("Enable watch mode for Component tests").click();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user