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
This commit is contained in:
Michael Smith
2024-09-13 17:26:52 -04:00
committed by GitHub
parent c6bc7414aa
commit 7ea8a2253e
+132 -101
View File
@@ -1,124 +1,155 @@
// @ts-check
/**
* @file Defines the main configuration file for all of our Storybook tests.
* This file must be a JSX/JS file, but we can at least add some type safety via
* the ts-check directive.
* @see {@link https://storybook.js.org/docs/configure#configure-story-rendering}
*
* @typedef {import("react").ReactElement} ReactElement
* @typedef {import("react").PropsWithChildren} PropsWithChildren
* @typedef {import("react").FC<PropsWithChildren>} FC
*
* @typedef {import("@storybook/react").StoryContext} StoryContext
* @typedef {import("@storybook/react").Preview} Preview
*
* @typedef {(Story: FC, Context: StoryContext) => React.JSX.Element} Decorator A
* Storybook decorator function used to inject baseline data dependencies into
* our React components during testing.
*/
import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import {
StyledEngineProvider,
ThemeProvider as MuiThemeProvider,
ThemeProvider as MuiThemeProvider,
StyledEngineProvider,
} from "@mui/material/styles";
import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
import { DecoratorHelpers } from "@storybook/addon-themes";
import { withRouter } from "storybook-addon-remix-react-router";
import { StrictMode } from "react";
import { parseQueryArgs, QueryClient, QueryClientProvider } from "react-query";
import { HelmetProvider } from "react-helmet-async";
import themes from "theme";
import "theme/globalFonts";
import isChromatic from "chromatic/isChromatic";
import React, { StrictMode } from "react";
import { HelmetProvider } from "react-helmet-async";
import { parseQueryArgs, QueryClient, QueryClientProvider } from "react-query";
import { withRouter } from "storybook-addon-remix-react-router";
import "theme/globalFonts";
import themes from "../src/theme";
DecoratorHelpers.initializeThemeState(Object.keys(themes), "dark");
export const decorators = [
withRouter,
withQuery,
(Story) => {
return (
<HelmetProvider>
<Story />
</HelmetProvider>
);
},
(Story, context) => {
const selectedTheme = DecoratorHelpers.pluckThemeFromContext(context);
const { themeOverride } = DecoratorHelpers.useThemeParameters();
const selected = themeOverride || selectedTheme || "dark";
return (
<StrictMode>
<StyledEngineProvider injectFirst>
<MuiThemeProvider theme={themes[selected]}>
<EmotionThemeProvider theme={themes[selected]}>
<CssBaseline />
<Story />
</EmotionThemeProvider>
</MuiThemeProvider>
</StyledEngineProvider>
</StrictMode>
);
},
];
/** @type {readonly Decorator[]} */
export const decorators = [withRouter, withQuery, withHelmet, withTheme];
/** @type {Preview["parameters"]} */
export const parameters = {
options: {
storySort: {
method: "alphabetical",
order: ["design", "pages", "modules", "components"],
locales: "en-US",
},
},
controls: {
expanded: true,
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
viewport: {
viewports: {
ipad: {
name: "iPad Mini",
styles: {
height: "1024px",
width: "768px",
},
type: "tablet",
},
terminal: {
name: "Terminal",
styles: {
height: "400",
width: "400",
},
},
},
},
options: {
storySort: {
method: "alphabetical",
order: ["design", "pages", "modules", "components"],
locales: "en-US",
},
},
controls: {
expanded: true,
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
viewport: {
viewports: {
ipad: {
name: "iPad Mini",
styles: {
height: "1024px",
width: "768px",
},
type: "tablet",
},
terminal: {
name: "Terminal",
styles: {
height: "400",
width: "400",
},
},
},
},
};
/**
* There's a mismatch on the React Helmet return type that causes issues when
* mounting the component in JS files only. Have to do type assertion, which is
* especially ugly in JSDoc
*/
const SafeHelmetProvider = /** @type {FC} */ (
/** @type {unknown} */ (HelmetProvider)
);
/** @type {Decorator} */
function withHelmet(Story) {
return (
<SafeHelmetProvider>
<Story />
</SafeHelmetProvider>
);
}
/** @type {Decorator} */
function withQuery(Story, { parameters }) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
retry: false,
},
},
});
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Number.POSITIVE_INFINITY,
retry: false,
},
},
});
if (parameters.queries) {
parameters.queries.forEach((query) => {
if (query.data instanceof Error) {
// This is copied from setQueryData() but sets the error.
const cache = queryClient.getQueryCache();
const parsedOptions = parseQueryArgs(query.key)
const defaultedOptions = queryClient.defaultQueryOptions(parsedOptions)
const cachedQuery = cache.build(queryClient, defaultedOptions);
// Set manual data so react-query will not try to refetch.
cachedQuery.setData(undefined, { manual: true });
cachedQuery.setState({ error: query.data });
} else {
queryClient.setQueryData(query.key, query.data);
}
});
}
if (parameters.queries) {
for (const query of parameters.queries) {
if (query.data instanceof Error) {
// This is copied from setQueryData() but sets the error.
const cache = queryClient.getQueryCache();
const parsedOptions = parseQueryArgs(query.key);
const defaultedOptions = queryClient.defaultQueryOptions(parsedOptions);
const cachedQuery = cache.build(queryClient, defaultedOptions);
// Set manual data so react-query will not try to refetch.
cachedQuery.setData(undefined, { manual: true });
cachedQuery.setState({ error: query.data });
} else {
queryClient.setQueryData(query.key, query.data);
}
}
}
return (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
);
return (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
);
}
/** @type {Decorator} */
function withTheme(Story, context) {
const selectedTheme = DecoratorHelpers.pluckThemeFromContext(context);
const { themeOverride } = DecoratorHelpers.useThemeParameters();
const selected = themeOverride || selectedTheme || "dark";
return (
<StrictMode>
<StyledEngineProvider injectFirst>
<MuiThemeProvider theme={themes[selected]}>
<EmotionThemeProvider theme={themes[selected]}>
<CssBaseline />
<Story />
</EmotionThemeProvider>
</MuiThemeProvider>
</StyledEngineProvider>
</StrictMode>
);
}
// Try to fix storybook rendering fonts inconsistently
// https://www.chromatic.com/docs/font-loading/#solution-c-check-fonts-have-loaded-in-a-loader
const fontLoader = async () => ({
fonts: await document.fonts.ready,
fonts: await document.fonts.ready,
});
export const loaders = isChromatic() && document.fonts ? [fontLoader] : [];