Skip to content

Commit

Permalink
Update prepareStories to handle more options & strip play functions (#…
Browse files Browse the repository at this point in the history
…573)

* automatically strip play functions for react-native runtime

* support includeStories/excludeStories CSF options

* feat: add playfn option
  • Loading branch information
awinograd authored May 27, 2024
1 parent 6e5a359 commit 9b4579f
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 17 deletions.
3 changes: 3 additions & 0 deletions examples/expo-example/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const main: StorybookConfig = {
'@storybook/addon-ondevice-backgrounds',
'@storybook/addon-ondevice-actions',
],
reactNative: {
playFn: false,
},
};

export default main;
8 changes: 7 additions & 1 deletion examples/expo-example/.storybook/storybook.requires.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,19 @@ global.STORIES = normalizedStories;
// @ts-ignore
module?.hot?.accept?.();

const options = { playFn: false };

if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories,
options,
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({
storyEntries: normalizedStories,
options,
});

global.view._preview.onStoriesChanged({
importFn: async (importPath: string) => importMap[importPath],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ export const AnotherAction: Story = {
argTypes: {
onPress: { action: 'pressed a different button' },
},
play: () => {
console.log('hello');
},
};
3 changes: 3 additions & 0 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
"test:ci": "jest"
},
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!react-native|@react-native)"
],
"modulePathIgnorePatterns": [
"dist/"
],
Expand Down
35 changes: 25 additions & 10 deletions packages/react-native/scripts/__snapshots__/generate.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ import "@storybook/addon-ondevice-actions/register";
// @ts-ignore
module?.hot?.accept?.();
if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories
storyEntries: normalizedStories,
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({ storyEntries: normalizedStories, });
global.view._preview.onStoriesChanged({
importFn: async (importPath: string) => importMap[importPath],
Expand Down Expand Up @@ -89,13 +92,16 @@ import "@storybook/addon-ondevice-actions/register";
// @ts-ignore
module?.hot?.accept?.();
if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories
storyEntries: normalizedStories,
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({ storyEntries: normalizedStories, });
global.view._preview.onStoriesChanged({
importFn: async (importPath: string) => importMap[importPath],
Expand Down Expand Up @@ -144,13 +150,16 @@ import "@storybook/addon-ondevice-actions/register";
// @ts-ignore
module?.hot?.accept?.();
if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories
storyEntries: normalizedStories,
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({ storyEntries: normalizedStories, });
global.view._preview.onStoriesChanged({
importFn: async (importPath: string) => importMap[importPath],
Expand Down Expand Up @@ -199,13 +208,16 @@ import "@storybook/addon-ondevice-actions/register";
// @ts-ignore
module?.hot?.accept?.();
if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories
storyEntries: normalizedStories,
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({ storyEntries: normalizedStories, });
global.view._preview.onStoriesChanged({
importFn: async (importPath: string) => importMap[importPath],
Expand Down Expand Up @@ -249,13 +261,16 @@ import "@storybook/addon-ondevice-actions/register";
module?.hot?.accept?.();
if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories
storyEntries: normalizedStories,
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({ storyEntries: normalizedStories, });
global.view._preview.onStoriesChanged({
importFn: async (importPath) => importMap[importPath],
Expand Down
16 changes: 14 additions & 2 deletions packages/react-native/scripts/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ function generate({ configPath, absolute = false, useJs = false }) {
? "require('@storybook/addon-actions/preview')"
: '';

let options = '';
let optionsVar = '';
const reactNativeOptions = main.reactNative;

if (reactNativeOptions && typeof reactNativeOptions === 'object') {
optionsVar = `const options = ${JSON.stringify(reactNativeOptions)}`;
options = 'options';
}

const previewExists = getPreviewExists({ configPath });

const annotations = `[${previewExists ? "require('./preview')," : ''}${doctools}, ${enhancer}]`;
Expand Down Expand Up @@ -84,13 +93,16 @@ function generate({ configPath, absolute = false, useJs = false }) {
${useJs ? '' : '// @ts-ignore'}
module?.hot?.accept?.();
${optionsVar}
if (!global.view) {
global.view = start({
annotations,
storyEntries: normalizedStories
storyEntries: normalizedStories,
${options}
});
} else {
const { importMap } = prepareStories({ storyEntries: normalizedStories });
const { importMap } = prepareStories({ storyEntries: normalizedStories, ${options} });
global.view._preview.onStoriesChanged({
importFn: async (importPath${useJs ? '' : ': string'}) => importMap[importPath],
Expand Down
135 changes: 135 additions & 0 deletions packages/react-native/src/Start.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { prepareStories } from './Start';

describe('prepareStories', () => {
test('prepares a standard CSF story file', () => {
const req = () => {
return require('../../../examples/expo-example/components/InputExample/TextInput.stories');
};
req.keys = () => ['./TextInput.stories.tsx'];

const result = prepareStories({
storyEntries: [
{
titlePrefix: '',
directory: './src',
files: '**/*.stories.?(ts|tsx|js|jsx)',
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
req,
},
],
});
expect(result).toEqual<ReturnType<typeof prepareStories>>({
importMap: {
'./src/TextInput.stories.tsx': {
Basic: {
args: {
placeholder: 'Type something',
},
},
default: {
component: expect.any(Function),
parameters: {
notes: 'Use this example to test the software keyboard related issues.',
},
title: 'TextInput',
},
},
},
index: {
v: 4,
entries: {
'textinput--basic': {
id: 'textinput--basic',
importPath: './src/TextInput.stories.tsx',
name: 'Basic',
tags: ['story'],
title: 'TextInput',
type: 'story',
},
},
},
});
});

test('ignores stories matching excludeStories pattern', () => {
const req = () => {
const stories = {
...require('../../../examples/expo-example/components/InputExample/TextInput.stories'),
};
stories.Ignored = stories.Basic;
stories.default.excludeStories = /Ignored/;
return stories;
};
req.keys = () => ['./TextInput.stories.tsx'];

const result = prepareStories({
storyEntries: [
{
titlePrefix: '',
directory: './src',
files: '**/*.stories.?(ts|tsx|js|jsx)',
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
req,
},
],
});
expect(result.importMap['./src/TextInput.stories.tsx'].Ignored).toBeUndefined();
expect(result.index.entries['textinput--basic']).toBeDefined();
expect(result.index.entries['textinput--ignored']).toBeUndefined();
});

test('ignores stories not matching includeStories pattern', () => {
const req = () => {
const stories = {
...require('../../../examples/expo-example/components/InputExample/TextInput.stories'),
};
stories.Ignored = stories.Basic;
stories.default.includeStories = /Basic/;
return stories;
};
req.keys = () => ['./TextInput.stories.tsx'];

const result = prepareStories({
storyEntries: [
{
titlePrefix: '',
directory: './src',
files: '**/*.stories.?(ts|tsx|js|jsx)',
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
req,
},
],
});
expect(result.importMap['./src/TextInput.stories.tsx'].Ignored).toBeUndefined();
expect(result.index.entries['textinput--basic']).toBeDefined();
expect(result.index.entries['textinput--ignored']).toBeUndefined();
});

test('strips play functions from stories', () => {
const req = () => {
const stories = {
...require('../../../examples/expo-example/components/InputExample/TextInput.stories'),
};
stories.Basic.play = () => {};
return stories;
};
req.keys = () => ['./TextInput.stories.tsx'];

const result = prepareStories({
storyEntries: [
{
titlePrefix: '',
directory: './src',
files: '**/*.stories.?(ts|tsx|js|jsx)',
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
req,
},
],
});
expect(result.importMap['./src/TextInput.stories.tsx'].Basic.play).toBeUndefined();
});
});
35 changes: 32 additions & 3 deletions packages/react-native/src/Start.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toId, storyNameFromExport } from '@storybook/csf';
import { toId, storyNameFromExport, isExportStory } from '@storybook/csf';
import {
addons as previewAddons,
composeConfigs,
Expand All @@ -12,10 +12,20 @@ import { View } from './View';
import type { ReactRenderer } from '@storybook/react';
import type { NormalizedStoriesSpecifier, StoryIndex } from '@storybook/types';

/** Configuration options that are needed at startup, only serialisable values are possible */
export interface ReactNativeOptions {
/**
* Note that this is for future and play functions are not yet fully supported on native.
*/
playFn?: boolean;
}

export function prepareStories({
storyEntries,
options,
}: {
storyEntries: Array<NormalizedStoriesSpecifier & { req: any }>;
options?: ReactNativeOptions;
}) {
let index: StoryIndex = {
v: 4,
Expand Down Expand Up @@ -59,6 +69,7 @@ export function prepareStories({
const meta = fileExports.default;
Object.keys(fileExports).forEach((key) => {
if (key === 'default') return;
if (!isExportStory(key, fileExports.default)) return;

const exportValue = fileExports[key];
if (!exportValue) return;
Expand All @@ -79,7 +90,23 @@ export function prepareStories({
tags: ['story'],
};

importMap[`${root}/${filename.substring(2)}`] = req(filename);
const importedStories = req(filename);
const stories = Object.entries(importedStories).reduce(
(carry, [storyKey, story]: [string, Readonly<Record<string, unknown>>]) => {
if (!isExportStory(storyKey, fileExports.default)) return carry;
if (story.play && !options?.playFn) {
// play functions are not yet fully supported on native.
// There is a new option in main.js to turn them on for future use.
carry[storyKey] = { ...story, play: undefined };
} else {
carry[storyKey] = story;
}
return carry;
},
{}
);

importMap[`${root}/${filename.substring(2)}`] = stories;
} else {
console.log(`Unexpected error while loading ${filename}: could not find title`);
}
Expand Down Expand Up @@ -119,11 +146,13 @@ export const getProjectAnnotations = (view: View, annotations: any[]) => async (
export function start({
annotations,
storyEntries,
options,
}: {
storyEntries: Array<NormalizedStoriesSpecifier & { req: any }>;
annotations: any[];
options?: ReactNativeOptions;
}) {
const { index, importMap } = prepareStories({ storyEntries });
const { index, importMap } = prepareStories({ storyEntries, options });

const channel = createBrowserChannel({ page: 'preview' });

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/src/StartV6.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function start() {

const previewView = {
prepareForStory: () => {
return <></>;
return (<></>) as any;
},
prepareForDocs: (): any => {},
showErrorDisplay: () => {},
Expand Down
Loading

0 comments on commit 9b4579f

Please sign in to comment.