Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript: Improve @storybook/ui types #9820

Merged
merged 26 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2f38e8e
CLEANUP splitting into multiple files
ndelangen Feb 10, 2020
5bfcbf2
REFACTOR preview
ndelangen Feb 10, 2020
867a662
REFACTOR preview
ndelangen Feb 11, 2020
07ca09f
REFACTOR fakeProvider
ndelangen Feb 11, 2020
47fbee9
REFACTOR mockdate && MIGRATE stories to CSF
ndelangen Feb 11, 2020
5e04834
REFACTOR preview to use hooks and FunctionComponent
ndelangen Feb 11, 2020
59a01cd
MIGRATE stories to TS
ndelangen Feb 11, 2020
9dcf5f2
MIGRATE more stories to TS
ndelangen Feb 11, 2020
3325f48
RENAME Nav to Sidebar
ndelangen Feb 11, 2020
b7a57a2
MIGRATE more stories to TS && FIX linting
ndelangen Feb 11, 2020
1a3b481
IMPROVE types
ndelangen Feb 11, 2020
6b5ff27
IMPROVE types
ndelangen Feb 11, 2020
bd560a1
Merge branch 'next' into core/ui-cleanup
ndelangen Feb 11, 2020
6e7c182
Merge branch 'core/ui-cleanup' into core/ui-types-improvements
ndelangen Feb 11, 2020
db15015
FIX missing testresults
ndelangen Feb 11, 2020
bca6253
ADD back to gitignore
ndelangen Feb 11, 2020
661536a
Merge branch 'core/ui-types-improvements' into core/ui-cleanup
ndelangen Feb 12, 2020
e2565d7
FIX tests
ndelangen Feb 12, 2020
26b5640
FIX tests && CLEANUP
ndelangen Feb 12, 2020
57b7ace
Merge branch 'next' into core/ui-cleanup
ndelangen Feb 13, 2020
5ddd46e
CLEANUP
ndelangen Feb 13, 2020
f1da8aa
CLEANUP
ndelangen Feb 13, 2020
de5e8a9
ADD exports to lib/api for StoriesHash & Item types & typecheck-funct…
ndelangen Feb 13, 2020
cab7552
CHANGE usage of State.storiesHash to StoriesHash & import other types…
ndelangen Feb 13, 2020
bd37f1c
CLEANUP
ndelangen Feb 13, 2020
f079a57
CLEANUP
ndelangen Feb 13, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions addons/storysource/src/StoryPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@storybook/components';

import { SourceBlock, LocationsMap } from '@storybook/source-loader';
import { Story } from '@storybook/api/dist/lib/stories';
ndelangen marked this conversation as resolved.
Show resolved Hide resolved

const StyledStoryLink = styled(Link)<{ to: string; key: string }>(({ theme }) => ({
display: 'block',
Expand Down Expand Up @@ -45,21 +46,13 @@ interface SourceParams {
source: string;
locationsMap: LocationsMap;
}
export interface StoryData {
id: string;
kind?: string;
parameters?: {
storySource?: SourceParams;
mdxSource?: string;
};
}
export const StoryPanel: React.FC<StoryPanelProps> = ({ api }) => {
const [state, setState] = React.useState<SourceParams & { currentLocation?: SourceBlock }>({
source: 'loading source...',
locationsMap: {},
});

const story: StoryData | undefined = api.getCurrentStoryData();
const story: Story | undefined = api.getCurrentStoryData() as Story;
const selectedStoryRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (story) {
Expand Down
2 changes: 1 addition & 1 deletion app/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"global": "^4.3.2",
"regenerator-runtime": "^0.13.3",
"safe-identifier": "^0.3.1",
"ts-dedent": "^1.1.0"
"ts-dedent": "^1.1.1"
},
"devDependencies": {
"fs-extra": "^8.0.1"
Expand Down
1 change: 1 addition & 0 deletions examples/angular-cli/addon-jest.testresults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":3,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":3,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1581465367102,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should create the app","location":null,"status":"passed","title":"should create the app"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should have as title 'app'","location":null,"status":"passed","title":"should have as title 'app'"},{"ancestorTitles":["AppComponent"],"failureMessages":[],"fullName":"AppComponent should render title in a h1 tag","location":null,"status":"passed","title":"should render title in a h1 tag"}],"endTime":1581465368794,"message":"","name":"/Users/dev/Projects/GitHub/storybook/core/examples/angular-cli/src/app/app.component.spec.ts","startTime":1581465367711,"status":"passed","summary":""}],"wasInterrupted":false}
1 change: 1 addition & 0 deletions lib/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"shallow-equal": "^1.1.0",
"store2": "^2.7.1",
"telejson": "^3.2.0",
"ts-dedent": "^1.1.1",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
Expand Down
36 changes: 22 additions & 14 deletions lib/api/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import React, {
ReactElement,
Component,
useContext,
useEffect,
useMemo,
useRef,
ReactNode,
} from 'react';
import React, { ReactElement, Component, useContext, useEffect, useMemo, ReactNode } from 'react';
import memoize from 'memoizerific';
// @ts-ignore shallow-equal is not in DefinitelyTyped
import shallowEqualObjects from 'shallow-equal/objects';
Expand Down Expand Up @@ -34,11 +26,17 @@ import initNotifications, {
SubState as NotificationState,
SubAPI as NotificationAPI,
} from './modules/notifications';
import initStories, {
SubState as StoriesSubState,
SubAPI as StoriesAPI,
import initStories, { SubState as StoriesSubState, SubAPI as StoriesAPI } from './modules/stories';
import {
StoriesRaw,
} from './modules/stories';
StoriesHash,
Story,
Root,
Group,
isGroup,
isRoot,
isStory,
} from './lib/stories';
import initLayout, {
ActiveTabs,
SubState as LayoutSubState,
Expand Down Expand Up @@ -318,7 +316,17 @@ export function useStorybookApi(): API {
return api;
}

export { ManagerConsumer as Consumer, ManagerProvider as Provider };
export {
ManagerConsumer as Consumer,
ManagerProvider as Provider,
StoriesHash,
Story,
Root,
Group,
isGroup,
isRoot,
isStory,
};

export interface EventMap {
[eventId: string]: Listener;
Expand Down
1 change: 1 addition & 0 deletions lib/api/src/init-provider-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Provider {
channel?: Channel;
renderPreview?: IframeRenderer;
handleAPI(api: API): void;
getConfig(): Record<string, any>;
[key: string]: any;
}

Expand Down
262 changes: 262 additions & 0 deletions lib/api/src/lib/stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import deprecate from 'util-deprecate';
import dedent from 'ts-dedent';
import { sanitize, parseKind } from '@storybook/csf';
import merge from './merge';

export type StoryId = string;

export interface Root {
id: StoryId;
depth: 0;
name: string;
children: StoryId[];
isComponent: false;
isRoot: true;
isLeaf: false;
// MDX stories are "Group" type
parameters?: any;
}

export interface Group {
id: StoryId;
depth: number;
name: string;
children: StoryId[];
parent?: StoryId;
isComponent: boolean;
isRoot: false;
isLeaf: false;
// MDX stories are "Group" type
parameters?: any;
}

export interface Story {
id: StoryId;
depth: number;
parent: StoryId;
name: string;
kind: string;
children?: StoryId[];
isComponent: boolean;
isRoot: false;
isLeaf: true;
parameters?: {
filename: string;
options: {
hierarchyRootSeparator?: RegExp;
hierarchySeparator?: RegExp;
showRoots?: boolean;
[k: string]: any;
};
[k: string]: any;
};
}

export interface StoryInput {
id: StoryId;
name: string;
kind: string;
children: string[];
parameters: {
filename: string;
options: {
hierarchyRootSeparator: RegExp;
hierarchySeparator: RegExp;
showRoots?: boolean;
[key: string]: any;
};
[parameterName: string]: any;
};
isLeaf: boolean;
}

export interface StoriesHash {
[id: string]: Root | Group | Story;
}

export type StoriesList = (Group | Story)[];

export type GroupsList = (Root | Group)[];

export interface StoriesRaw {
[id: string]: StoryInput;
}

const warnUsingHierarchySeparatorsAndShowRoots = deprecate(
() => {},
dedent`
You cannot use both the hierarchySeparator/hierarchyRootSeparator and showRoots options.
`
);

const warnRemovingHierarchySeparators = deprecate(
() => {},
dedent`
hierarchySeparator and hierarchyRootSeparator are deprecated and will be removed in Storybook 6.0.
Read more about it in the migration guide: https://github.com/storybookjs/storybook/blob/master/MIGRATION.md
`
);

const warnChangingDefaultHierarchySeparators = deprecate(
() => {},
dedent`
The default hierarchy separators are changing in Storybook 6.0.
'|' and '.' will no longer create a hierarchy, but codemods are available.
Read more about it in the migration guide: https://github.com/storybookjs/storybook/blob/master/MIGRATION.md
`
);

const toKey = (input: string) =>
input.replace(/[^a-z0-9]+([a-z0-9])/gi, (...params) => params[1].toUpperCase());

const toGroup = (name: string) => ({
name,
id: toKey(name),
});

export const transformStoriesRawToStoriesHash = (
input: StoriesRaw,
base: StoriesHash
): StoriesHash => {
const anyKindMatchesOldHierarchySeparators = Object.values(input).some(({ kind }) =>
kind.match(/\.|\|/)
);

const storiesHashOutOfOrder = Object.values(input).reduce((acc, item) => {
const { kind, parameters } = item;
const {
hierarchyRootSeparator: rootSeparator = undefined,
hierarchySeparator: groupSeparator = undefined,
showRoots = undefined,
} = (parameters && parameters.options) || {};

const usingShowRoots = typeof showRoots !== 'undefined';

// Kind splitting behavior as per https://github.com/storybookjs/storybook/issues/8793
let root = '';
let groups: string[];
// 1. If the user has passed separators, use the old behavior but warn them
if (typeof rootSeparator !== 'undefined' || typeof groupSeparator !== 'undefined') {
warnRemovingHierarchySeparators();
if (usingShowRoots) warnUsingHierarchySeparatorsAndShowRoots();
({ root, groups } = parseKind(kind, {
rootSeparator: rootSeparator || '|',
groupSeparator: groupSeparator || /\/|\./,
}));

// 2. If the user hasn't passed separators, but is using | or . in kinds, use the old behaviour but warn
} else if (anyKindMatchesOldHierarchySeparators && !usingShowRoots) {
warnChangingDefaultHierarchySeparators();
({ root, groups } = parseKind(kind, { rootSeparator: '|', groupSeparator: /\/|\./ }));

// 3. If the user passes showRoots, or doesn't match above, do a simpler splitting.
} else {
const parts: string[] = kind.split('/');
if (showRoots && parts.length > 1) {
[root, ...groups] = parts;
} else {
groups = parts;
}
}

const rootAndGroups = []
.concat(root || [])
.concat(groups)
.map(toGroup)
// Map a bunch of extra fields onto the groups, collecting the path as we go (thus the reduce)
.reduce((soFar, group, index, original) => {
const { name } = group;
const parent = index > 0 && soFar[index - 1].id;
const id = sanitize(parent ? `${parent}-${name}` : name);
if (parent === id) {
throw new Error(
dedent`
Invalid part '${name}', leading to id === parentId ('${id}'), inside kind '${kind}'

Did you create a path that uses the separator char accidentally, such as 'Vue <docs/>' where '/' is a separator char? See https://github.com/storybookjs/storybook/issues/6128
`
);
}

if (!!root && index === 0) {
const result: Root = {
...group,
id,
depth: index,
children: [],
isComponent: false,
isLeaf: false,
isRoot: true,
parameters,
};
return soFar.concat([result]);
}
const result: Group = {
...group,
id,
parent,
depth: index,
children: [],
isComponent: false,
isLeaf: false,
isRoot: false,
parameters,
};
return soFar.concat([result]);
}, [] as GroupsList);

const paths = [...rootAndGroups.map(g => g.id), item.id];

// Ok, now let's add everything to the store
rootAndGroups.forEach((group, index) => {
const child = paths[index + 1];
const { id } = group;
acc[id] = merge(acc[id] || {}, {
...group,
...(child && { children: [child] }),
});
});

const story = { ...item, parent: rootAndGroups[rootAndGroups.length - 1].id, isLeaf: true };
acc[item.id] = story as Story;

return acc;
}, {} as StoriesHash);

function addItem(acc: StoriesHash, item: Story | Group) {
if (!acc[item.id]) {
// If we were already inserted as part of a group, that's great.
acc[item.id] = item;
const { children } = item;
if (children) {
const childNodes = children.map(id => storiesHashOutOfOrder[id]) as (Story | Group)[];
acc[item.id].isComponent = childNodes.every(childNode => childNode.isLeaf);
childNodes.forEach(childNode => addItem(acc, childNode));
}
}
return acc;
}

return Object.values(storiesHashOutOfOrder).reduce(addItem, base);
};

export type Item = StoriesHash[keyof StoriesHash];

export function isRoot(item: Item): item is Root {
if (item as Root) {
return item.isRoot;
}
return false;
}
export function isGroup(item: Item): item is Group {
if (item as Group) {
return !item.isRoot && !item.isLeaf;
}
return false;
}
export function isStory(item: Item): item is Story {
if (item as Story) {
return item.isLeaf;
}
return false;
}
Loading