Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7ea8a22

Browse files
authored
fix: add type-safety for Storybook preview.jsx config file (#14671)
* fix: add type-safety to Storybook preview.jsx file * fix: add clarifying comments * fix: add type-safety to preview config
1 parent c6bc741 commit 7ea8a22

File tree

1 file changed

+131
-100
lines changed

1 file changed

+131
-100
lines changed

site/.storybook/preview.jsx

+131-100
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,155 @@
1+
// @ts-check
2+
/**
3+
* @file Defines the main configuration file for all of our Storybook tests.
4+
* This file must be a JSX/JS file, but we can at least add some type safety via
5+
* the ts-check directive.
6+
* @see {@link https://storybook.js.org/docs/configure#configure-story-rendering}
7+
*
8+
* @typedef {import("react").ReactElement} ReactElement
9+
* @typedef {import("react").PropsWithChildren} PropsWithChildren
10+
* @typedef {import("react").FC<PropsWithChildren>} FC
11+
*
12+
* @typedef {import("@storybook/react").StoryContext} StoryContext
13+
* @typedef {import("@storybook/react").Preview} Preview
14+
*
15+
* @typedef {(Story: FC, Context: StoryContext) => React.JSX.Element} Decorator A
16+
* Storybook decorator function used to inject baseline data dependencies into
17+
* our React components during testing.
18+
*/
19+
import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
120
import CssBaseline from "@mui/material/CssBaseline";
221
import {
3-
StyledEngineProvider,
4-
ThemeProvider as MuiThemeProvider,
22+
ThemeProvider as MuiThemeProvider,
23+
StyledEngineProvider,
524
} from "@mui/material/styles";
6-
import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
725
import { DecoratorHelpers } from "@storybook/addon-themes";
8-
import { withRouter } from "storybook-addon-remix-react-router";
9-
import { StrictMode } from "react";
10-
import { parseQueryArgs, QueryClient, QueryClientProvider } from "react-query";
26+
import isChromatic from "chromatic/isChromatic";
27+
import React, { StrictMode } from "react";
1128
import { HelmetProvider } from "react-helmet-async";
12-
import themes from "theme";
29+
import { parseQueryArgs, QueryClient, QueryClientProvider } from "react-query";
30+
import { withRouter } from "storybook-addon-remix-react-router";
1331
import "theme/globalFonts";
14-
import isChromatic from "chromatic/isChromatic";
32+
import themes from "../src/theme";
1533

1634
DecoratorHelpers.initializeThemeState(Object.keys(themes), "dark");
1735

18-
export const decorators = [
19-
withRouter,
20-
withQuery,
21-
(Story) => {
22-
return (
23-
<HelmetProvider>
24-
<Story />
25-
</HelmetProvider>
26-
);
27-
},
28-
(Story, context) => {
29-
const selectedTheme = DecoratorHelpers.pluckThemeFromContext(context);
30-
const { themeOverride } = DecoratorHelpers.useThemeParameters();
31-
const selected = themeOverride || selectedTheme || "dark";
32-
33-
return (
34-
<StrictMode>
35-
<StyledEngineProvider injectFirst>
36-
<MuiThemeProvider theme={themes[selected]}>
37-
<EmotionThemeProvider theme={themes[selected]}>
38-
<CssBaseline />
39-
<Story />
40-
</EmotionThemeProvider>
41-
</MuiThemeProvider>
42-
</StyledEngineProvider>
43-
</StrictMode>
44-
);
45-
},
46-
];
36+
/** @type {readonly Decorator[]} */
37+
export const decorators = [withRouter, withQuery, withHelmet, withTheme];
4738

39+
/** @type {Preview["parameters"]} */
4840
export const parameters = {
49-
options: {
50-
storySort: {
51-
method: "alphabetical",
52-
order: ["design", "pages", "modules", "components"],
53-
locales: "en-US",
54-
},
55-
},
56-
controls: {
57-
expanded: true,
58-
matchers: {
59-
color: /(background|color)$/i,
60-
date: /Date$/,
61-
},
62-
},
63-
viewport: {
64-
viewports: {
65-
ipad: {
66-
name: "iPad Mini",
67-
styles: {
68-
height: "1024px",
69-
width: "768px",
70-
},
71-
type: "tablet",
72-
},
73-
terminal: {
74-
name: "Terminal",
75-
styles: {
76-
height: "400",
77-
width: "400",
78-
},
79-
},
80-
},
81-
},
41+
options: {
42+
storySort: {
43+
method: "alphabetical",
44+
order: ["design", "pages", "modules", "components"],
45+
locales: "en-US",
46+
},
47+
},
48+
controls: {
49+
expanded: true,
50+
matchers: {
51+
color: /(background|color)$/i,
52+
date: /Date$/,
53+
},
54+
},
55+
viewport: {
56+
viewports: {
57+
ipad: {
58+
name: "iPad Mini",
59+
styles: {
60+
height: "1024px",
61+
width: "768px",
62+
},
63+
type: "tablet",
64+
},
65+
terminal: {
66+
name: "Terminal",
67+
styles: {
68+
height: "400",
69+
width: "400",
70+
},
71+
},
72+
},
73+
},
8274
};
8375

76+
/**
77+
* There's a mismatch on the React Helmet return type that causes issues when
78+
* mounting the component in JS files only. Have to do type assertion, which is
79+
* especially ugly in JSDoc
80+
*/
81+
const SafeHelmetProvider = /** @type {FC} */ (
82+
/** @type {unknown} */ (HelmetProvider)
83+
);
84+
85+
/** @type {Decorator} */
86+
function withHelmet(Story) {
87+
return (
88+
<SafeHelmetProvider>
89+
<Story />
90+
</SafeHelmetProvider>
91+
);
92+
}
93+
94+
/** @type {Decorator} */
8495
function withQuery(Story, { parameters }) {
85-
const queryClient = new QueryClient({
86-
defaultOptions: {
87-
queries: {
88-
staleTime: Infinity,
89-
retry: false,
90-
},
91-
},
92-
});
96+
const queryClient = new QueryClient({
97+
defaultOptions: {
98+
queries: {
99+
staleTime: Number.POSITIVE_INFINITY,
100+
retry: false,
101+
},
102+
},
103+
});
104+
105+
if (parameters.queries) {
106+
for (const query of parameters.queries) {
107+
if (query.data instanceof Error) {
108+
// This is copied from setQueryData() but sets the error.
109+
const cache = queryClient.getQueryCache();
110+
const parsedOptions = parseQueryArgs(query.key);
111+
const defaultedOptions = queryClient.defaultQueryOptions(parsedOptions);
112+
const cachedQuery = cache.build(queryClient, defaultedOptions);
113+
// Set manual data so react-query will not try to refetch.
114+
cachedQuery.setData(undefined, { manual: true });
115+
cachedQuery.setState({ error: query.data });
116+
} else {
117+
queryClient.setQueryData(query.key, query.data);
118+
}
119+
}
120+
}
121+
122+
return (
123+
<QueryClientProvider client={queryClient}>
124+
<Story />
125+
</QueryClientProvider>
126+
);
127+
}
93128

94-
if (parameters.queries) {
95-
parameters.queries.forEach((query) => {
96-
if (query.data instanceof Error) {
97-
// This is copied from setQueryData() but sets the error.
98-
const cache = queryClient.getQueryCache();
99-
const parsedOptions = parseQueryArgs(query.key)
100-
const defaultedOptions = queryClient.defaultQueryOptions(parsedOptions)
101-
const cachedQuery = cache.build(queryClient, defaultedOptions);
102-
// Set manual data so react-query will not try to refetch.
103-
cachedQuery.setData(undefined, { manual: true });
104-
cachedQuery.setState({ error: query.data });
105-
} else {
106-
queryClient.setQueryData(query.key, query.data);
107-
}
108-
});
109-
}
129+
/** @type {Decorator} */
130+
function withTheme(Story, context) {
131+
const selectedTheme = DecoratorHelpers.pluckThemeFromContext(context);
132+
const { themeOverride } = DecoratorHelpers.useThemeParameters();
133+
const selected = themeOverride || selectedTheme || "dark";
110134

111-
return (
112-
<QueryClientProvider client={queryClient}>
113-
<Story />
114-
</QueryClientProvider>
115-
);
135+
return (
136+
<StrictMode>
137+
<StyledEngineProvider injectFirst>
138+
<MuiThemeProvider theme={themes[selected]}>
139+
<EmotionThemeProvider theme={themes[selected]}>
140+
<CssBaseline />
141+
<Story />
142+
</EmotionThemeProvider>
143+
</MuiThemeProvider>
144+
</StyledEngineProvider>
145+
</StrictMode>
146+
);
116147
}
117148

118149
// Try to fix storybook rendering fonts inconsistently
119150
// https://www.chromatic.com/docs/font-loading/#solution-c-check-fonts-have-loaded-in-a-loader
120151
const fontLoader = async () => ({
121-
fonts: await document.fonts.ready,
152+
fonts: await document.fonts.ready,
122153
});
123154

124155
export const loaders = isChromatic() && document.fonts ? [fontLoader] : [];

0 commit comments

Comments
 (0)