Merge pull request #17726 from passbyval/passbyval-patch-1

Fix nesting issue for refs in sidebar component
This commit is contained in:
Norbert de Langen 2022-03-21 16:11:51 +01:00 committed by GitHub
commit 7053de3384
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 138 additions and 29 deletions

View File

@ -25,6 +25,7 @@ import { createContext } from './context';
import Store, { Options } from './store';
import getInitialState from './initial-state';
import type { StoriesHash, Story, Root, Group } from './lib/stories';
import type { ComposedRef, Refs } from './modules/refs';
import { isGroup, isRoot, isStory } from './lib/stories';
import * as provider from './modules/provider';
@ -328,7 +329,7 @@ export function useStorybookApi(): API {
return api;
}
export type { StoriesHash, Story, Root, Group };
export type { StoriesHash, Story, Root, Group, ComposedRef, Refs };
export { ManagerConsumer as Consumer, ManagerProvider as Provider, isGroup, isRoot, isStory };
export interface EventMap {

View File

@ -3,7 +3,7 @@ import React, { FunctionComponent, useMemo } from 'react';
import { styled } from '@storybook/theming';
import { ScrollArea, Spaced } from '@storybook/components';
import type { StoriesHash, State } from '@storybook/api';
import type { StoriesHash, State, ComposedRef } from '@storybook/api';
import { Heading } from './Heading';
@ -100,28 +100,24 @@ export const Sidebar: FunctionComponent<SidebarProps> = React.memo(
enableShortcuts = true,
refs = {},
}) => {
const collapseFn = DOCS_MODE ? collapseAllStories : collapseDocsOnlyStories;
const selected: Selection = useMemo(() => storyId && { storyId, refId }, [storyId, refId]);
const stories = useMemo(
() => (DOCS_MODE ? collapseAllStories : collapseDocsOnlyStories)(storiesHash),
[DOCS_MODE, storiesHash]
);
const stories = useMemo(() => collapseFn(storiesHash), [DOCS_MODE, storiesHash]);
const adaptedRefs = useMemo(() => {
if (DOCS_MODE) {
return Object.keys(refs).reduce((acc: Refs, cur) => {
const ref = refs[cur];
if (ref.stories) {
acc[cur] = {
...ref,
stories: collapseDocsOnlyStories(ref.stories),
};
} else {
acc[cur] = ref;
}
return acc;
}, {});
}
return refs;
return Object.entries(refs).reduce((acc: Refs, [id, ref]: [string, ComposedRef]) => {
if (ref.stories) {
acc[id] = {
...ref,
stories: collapseFn(ref.stories),
};
} else {
acc[id] = ref;
}
return acc;
}, {});
}, [DOCS_MODE, refs]);
const dataset = useCombination(stories, storiesConfigured, storiesFailed, adaptedRefs);
const isLoading = !dataset.hash[DEFAULT_REF_ID].ready;
const lastViewedProps = useLastViewed(selected);

View File

@ -0,0 +1,112 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { ThemeProvider, ensure, themes } from '@storybook/theming';
import type { Story, StoriesHash, Refs } from '@storybook/api';
import type { Theme } from '@storybook/theming';
import type { RenderResult } from '@testing-library/react';
import { Sidebar } from '../Sidebar';
import type { SidebarProps } from '../Sidebar';
global.DOCS_MODE = false;
const PAGE_NAME = 'Page';
const factory = (props: Partial<SidebarProps>): RenderResult => {
const theme: Theme = ensure(themes.light);
return render(
<ThemeProvider theme={theme}>
<Sidebar storiesConfigured menu={[]} stories={{}} refs={{}} {...props} />
</ThemeProvider>
);
};
const generateStories = ({ kind, refId }: { kind: string; refId?: string }): StoriesHash => {
const [root, storyName]: [string, string] = kind.split('/') as any;
const rootId: string = root.toLowerCase().replace(/\s+/g, '-');
const hypenatedstoryName: string = storyName.toLowerCase().replace(/\s+/g, '-');
const storyId = `${rootId}-${hypenatedstoryName}`;
const pageId = `${rootId}-${hypenatedstoryName}--page`;
const storyBase: Partial<Story>[] = [
{
id: rootId,
name: root,
children: [storyId],
startCollapsed: false,
},
{
id: storyId,
name: storyName,
children: [pageId],
isComponent: true,
parent: rootId,
},
{
id: pageId,
name: PAGE_NAME,
story: PAGE_NAME,
kind,
componentId: storyId,
parent: storyId,
title: kind,
},
];
return storyBase.reduce(
(accumulator: StoriesHash, current: Partial<Story>, index: number): StoriesHash => {
const { id, name } = current;
const isRoot: boolean = index === 0;
const story: Story = {
...current,
depth: index,
isRoot,
isLeaf: name === PAGE_NAME,
refId,
};
if (!isRoot) {
story.parameters = {};
story.parameters.docsOnly = true;
}
accumulator[id] = story;
return accumulator;
},
{}
);
};
describe('Sidebar', () => {
test("should not render an extra nested 'Page'", async () => {
const refId = 'next';
const kind = 'Getting Started/Install';
const refStories: StoriesHash = generateStories({ refId, kind });
const internalStories: StoriesHash = generateStories({ kind: 'Welcome/Example' });
const refs: Refs = {
[refId]: {
stories: refStories,
id: refId,
ready: true,
title: refId,
},
};
factory({
refs,
refId,
stories: internalStories,
});
fireEvent.click(screen.getByText('Install'));
fireEvent.click(screen.getByText('Example'));
const pageItems: HTMLElement[] = await screen.queryAllByText('Page');
expect(pageItems).toHaveLength(0);
});
});

View File

@ -1,5 +1,5 @@
import type { StoriesHash } from '@storybook/api';
import { collapseDocsOnlyStories, collapseAllStories } from './data';
import { collapseDocsOnlyStories, collapseAllStories } from '../data';
type Item = StoriesHash[keyof StoriesHash];

View File

@ -9,11 +9,11 @@ export const collapseAllStories = (stories: StoriesHash) => {
// 1) remove all leaves
const leavesRemoved = Object.values(stories).filter(
(item) => !(item.isLeaf && stories[item.parent].isComponent)
(item: Story) => !(item.isLeaf && stories[item.parent].isComponent)
);
// 2) make all components leaves and rewrite their ID's to the first leaf child
const componentsFlattened = leavesRemoved.map((item) => {
const componentsFlattened = leavesRemoved.map((item: Story) => {
const { id, isComponent, children, ...rest } = item;
// this is a folder, so just leave it alone
@ -23,7 +23,7 @@ export const collapseAllStories = (stories: StoriesHash) => {
const nonLeafChildren: string[] = [];
const leafChildren: string[] = [];
children.forEach((child) =>
children.forEach((child: string) =>
(stories[child].isLeaf ? leafChildren : nonLeafChildren).push(child)
);
@ -61,7 +61,7 @@ export const collapseAllStories = (stories: StoriesHash) => {
}
const { children, ...rest } = item;
const rewritten = children.map((child) => componentIdToLeafId[child] || child);
const rewritten = children.map((child: string) => componentIdToLeafId[child] || child);
return { children: rewritten, ...rest };
});
@ -76,7 +76,7 @@ export const collapseAllStories = (stories: StoriesHash) => {
export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
// keep track of component IDs that have been rewritten to the ID of their first leaf child
const componentIdToLeafId: Record<string, string> = {};
const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item) => {
const docsOnlyStoriesRemoved = Object.values(storiesHash).filter((item: Story) => {
if (item.isLeaf && item.parameters && item.parameters.docsOnly) {
componentIdToLeafId[item.parent] = item.id;
return false; // filter it out
@ -84,7 +84,7 @@ export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
return true;
});
const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item) => {
const docsOnlyComponentsCollapsed = docsOnlyStoriesRemoved.map((item: Story) => {
// collapse docs-only components
const { isComponent, children, id } = item;
if (isComponent && children.length === 1) {
@ -103,7 +103,7 @@ export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
// update groups
if (children) {
const rewritten = children.map((child) => componentIdToLeafId[child] || child);
const rewritten = children.map((child: string) => componentIdToLeafId[child] || child);
return { ...item, children: rewritten };
}