diff --git a/app/react-native/scripts/__snapshots__/loader.test.js.snap b/app/react-native/scripts/__snapshots__/loader.test.js.snap index 5906ee65d4..c0680bd44f 100644 --- a/app/react-native/scripts/__snapshots__/loader.test.js.snap +++ b/app/react-native/scripts/__snapshots__/loader.test.js.snap @@ -1,5 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`loader writeRequires when there is a story glob and exclude paths globs writes the story imports 1`] = ` +" + /* do not change this file, it is auto generated by storybook. */ + + import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native'; + + import \\"@storybook/addon-ondevice-notes/register\\"; +import \\"@storybook/addon-ondevice-controls/register\\"; +import \\"@storybook/addon-ondevice-backgrounds/register\\"; +import \\"@storybook/addon-ondevice-actions/register\\"; + + import { argsEnhancers } from \\"@storybook/addon-actions/dist/modern/preset/addArgs\\" + + + import { decorators, parameters } from './preview'; + + if (decorators) { + decorators.forEach((decorator) => addDecorator(decorator)); + } + + if (parameters) { + addParameters(parameters); + } + + + argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer)) + + const getStories=() => { + return [require(\\"include-components/FakeStory.stories.tsx\\")]; + } + + configure(getStories, module, false) + " +`; + exports[`loader writeRequires when there is a story glob writes the story imports 1`] = ` " /* do not change this file, it is auto generated by storybook. */ diff --git a/app/react-native/scripts/loader.js b/app/react-native/scripts/loader.js index a0711c162e..85b6f71e2c 100644 --- a/app/react-native/scripts/loader.js +++ b/app/react-native/scripts/loader.js @@ -17,6 +17,21 @@ const previewImports = ` } `; +function normalizeExcludePaths(paths) { + // automatically convert a string to an array of a single string + if (typeof paths === 'string') { + return [paths]; + } + + // ensure the paths is an array and if any items exists, they are strings + if (Array.isArray(paths) && paths.every((p) => typeof p === 'string')) { + return paths; + } + + // when the paths aren't a string or an (empty) array of strings, return + return undefined; +} + function requireUncached(module) { delete require.cache[require.resolve(module)]; return require(module); @@ -36,8 +51,19 @@ function writeRequires({ configPath, absolute = false }) { const storybookRequiresLocation = path.resolve(cwd, configPath, 'storybook.requires.js'); const main = getMain({ configPath }); + const reactNativeOptions = main.reactNativeOptions; + const excludePaths = reactNativeOptions && reactNativeOptions.excludePaths; + const normalizedExcludePaths = normalizeExcludePaths(excludePaths); + const storyPaths = main.stories.reduce((acc, storyGlob) => { - const paths = glob.sync(storyGlob, { cwd: path.resolve(cwd, configPath), absolute }); + const paths = glob.sync(storyGlob, { + cwd: path.resolve(cwd, configPath), + absolute, + // default to always ignore (exclude) anything in node_modules + ignore: normalizedExcludePaths !== undefined + ? normalizedExcludePaths + : ['**/node_modules'], + }); return [...acc, ...paths]; }, []); diff --git a/app/react-native/scripts/loader.test.js b/app/react-native/scripts/loader.test.js index a7f5ada133..ff9e195748 100644 --- a/app/react-native/scripts/loader.test.js +++ b/app/react-native/scripts/loader.test.js @@ -86,6 +86,20 @@ describe('loader', () => { }); }); + describe('when there is a story glob and exclude paths globs', () => { + it('writes the story imports', () => { + writeRequires({ configPath: 'scripts/mocks/exclude-config-files' }); + expect(pathMock).toEqual( + path.resolve(__dirname, 'mocks/exclude-config-files/storybook.requires.js') + ); + + expect(fileContentMock).toContain('include-components/FakeStory.stories.tsx'); + expect(fileContentMock).not.toContain('exclude-components/FakeStory.stories.tsx'); + + expect(fileContentMock).toMatchSnapshot(); + }); + }); + describe('when there is no story glob or addons', () => { it('writes no story imports or addons', () => { writeRequires({ configPath: 'scripts/mocks/blank-config' }); diff --git a/app/react-native/scripts/mocks/exclude-config-files/exclude-components/FakeComponent.tsx b/app/react-native/scripts/mocks/exclude-config-files/exclude-components/FakeComponent.tsx new file mode 100644 index 0000000000..23915b0493 --- /dev/null +++ b/app/react-native/scripts/mocks/exclude-config-files/exclude-components/FakeComponent.tsx @@ -0,0 +1 @@ +export const FakeComponent = () => null; diff --git a/app/react-native/scripts/mocks/exclude-config-files/exclude-components/FakeStory.stories.tsx b/app/react-native/scripts/mocks/exclude-config-files/exclude-components/FakeStory.stories.tsx new file mode 100644 index 0000000000..2176b3bf74 --- /dev/null +++ b/app/react-native/scripts/mocks/exclude-config-files/exclude-components/FakeStory.stories.tsx @@ -0,0 +1,10 @@ +import { FakeComponentExcluded } from './FakeComponent'; + +export default { + title: 'components/FakeComponentExcluded', + component: FakeComponentExcluded, +}; + +export const Basic = { + args: {}, +}; diff --git a/app/react-native/scripts/mocks/exclude-config-files/include-components/FakeComponent.tsx b/app/react-native/scripts/mocks/exclude-config-files/include-components/FakeComponent.tsx new file mode 100644 index 0000000000..23915b0493 --- /dev/null +++ b/app/react-native/scripts/mocks/exclude-config-files/include-components/FakeComponent.tsx @@ -0,0 +1 @@ +export const FakeComponent = () => null; diff --git a/app/react-native/scripts/mocks/exclude-config-files/include-components/FakeStory.stories.tsx b/app/react-native/scripts/mocks/exclude-config-files/include-components/FakeStory.stories.tsx new file mode 100644 index 0000000000..ca6d412600 --- /dev/null +++ b/app/react-native/scripts/mocks/exclude-config-files/include-components/FakeStory.stories.tsx @@ -0,0 +1,10 @@ +import { FakeComponent } from './FakeComponent'; + +export default { + title: 'components/FakeComponent', + component: FakeComponent, +}; + +export const Basic = { + args: {}, +}; diff --git a/app/react-native/scripts/mocks/exclude-config-files/main.js b/app/react-native/scripts/mocks/exclude-config-files/main.js new file mode 100644 index 0000000000..34b0ea2304 --- /dev/null +++ b/app/react-native/scripts/mocks/exclude-config-files/main.js @@ -0,0 +1,12 @@ +module.exports = { + stories: ['**/*.stories.tsx'], + reactNativeOptions: { + excludePaths: '**/exclude-components/**', + }, + addons: [ + '@storybook/addon-ondevice-notes', + '@storybook/addon-ondevice-controls', + '@storybook/addon-ondevice-backgrounds', + '@storybook/addon-ondevice-actions', + ], +}; diff --git a/app/react-native/scripts/mocks/exclude-config-files/preview.js b/app/react-native/scripts/mocks/exclude-config-files/preview.js new file mode 100644 index 0000000000..13bad13073 --- /dev/null +++ b/app/react-native/scripts/mocks/exclude-config-files/preview.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds'; + +export const decorators = [ + (StoryFn) => ( + + + + ), + withBackgrounds, +]; +export const parameters = { + my_param: 'anything', + backgrounds: [ + { name: 'plain', value: 'white', default: true }, + { name: 'warm', value: 'hotpink' }, + { name: 'cool', value: 'deepskyblue' }, + ], +}; + +const styles = StyleSheet.create({ + container: { padding: 8, flex: 1 }, +});