diff --git a/app/react-native/scripts/__snapshots__/loader.test.js.snap b/app/react-native/scripts/__snapshots__/loader.test.js.snap index 0e4a91d55a..c56477a62b 100644 --- a/app/react-native/scripts/__snapshots__/loader.test.js.snap +++ b/app/react-native/scripts/__snapshots__/loader.test.js.snap @@ -49,6 +49,55 @@ import \\"@storybook/addon-ondevice-actions/register\\"; " `; +exports[`loader writeRequires when there is a configuration object writes the story imports 1`] = ` +" + /* do not change this file, it is auto generated by storybook. */ + + import { configure, addDecorator, addParameters, addArgsEnhancer, clearDecorators } from '@storybook/react-native'; + + global.STORIES = [{\\"titlePrefix\\":\\"ComponentsPrefix\\",\\"files\\":\\"**/*.stories.tsx\\",\\"directory\\":\\"./scripts/mocks/configuration-objects/components\\",\\"importPathMatcher\\":\\"^\\\\\\\\.[\\\\\\\\\\\\\\\\/](?:scripts\\\\\\\\/mocks\\\\\\\\/configuration-objects\\\\\\\\/components(?:\\\\\\\\/(?!\\\\\\\\.)(?:(?:(?!(?:^|\\\\\\\\/)\\\\\\\\.).)*?)\\\\\\\\/|\\\\\\\\/|$)(?!\\\\\\\\.)(?=.)[^/]*?\\\\\\\\.stories\\\\\\\\.tsx)$\\"}] + + 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) { + if(__DEV__){ + // stops the warning from showing on every HMR + require('react-native').LogBox.ignoreLogs([ + '\`clearDecorators\` is deprecated and will be removed in Storybook 7.0', + ]); + } + // workaround for global decorators getting infinitely applied on HMR, see https://github.com/storybookjs/react-native/issues/185 + clearDecorators(); + decorators.forEach((decorator) => addDecorator(decorator)); + } + + if (parameters) { + addParameters(parameters); + } + + + + try { + argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer)); + } catch{} + + + const getStories=() => { + return {\\"./scripts/mocks/configuration-objects/components/FakeStory.stories.tsx\\": require(\\"./components/FakeStory.stories.tsx\\")}; + } + + configure(getStories, module, false) + " +`; + 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. */ @@ -91,7 +140,7 @@ import \\"@storybook/addon-ondevice-actions/register\\"; const getStories=() => { - return {\\"./scripts/mocks/exclude-config-files/include-components/FakeStory.stories.tsx\\": require(\\"include-components/FakeStory.stories.tsx\\")}; + return {\\"./scripts/mocks/exclude-config-files/include-components/FakeStory.stories.tsx\\": require(\\"./include-components/FakeStory.stories.tsx\\")}; } configure(getStories, module, false) diff --git a/app/react-native/scripts/loader.js b/app/react-native/scripts/loader.js index 282dcbf508..bc9aa975d5 100644 --- a/app/react-native/scripts/loader.js +++ b/app/react-native/scripts/loader.js @@ -72,6 +72,10 @@ function getPreviewExists({ configPath }) { return !!getFilePathExtension({ configPath }, 'preview'); } +function ensureRelativePathHasDot(relativePath) { + return relativePath.startsWith('.') ? relativePath : `./${relativePath}`; +} + function writeRequires({ configPath, absolute = false }) { const storybookRequiresLocation = path.resolve(cwd, configPath, 'storybook.requires.js'); @@ -81,13 +85,30 @@ function writeRequires({ configPath, absolute = false }) { 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, - // default to always ignore (exclude) anything in node_modules - ignore: normalizedExcludePaths !== undefined ? normalizedExcludePaths : ['**/node_modules'], - }); + const storiesSpecifiers = normalizeStories(main.stories, { + configDir: configPath, + workingDir: cwd, + }); + + const storyRequires = storiesSpecifiers.reduce((acc, specifier) => { + const paths = glob + .sync(specifier.files, { + cwd: path.resolve(cwd, specifier.directory), + absolute, + // default to always ignore (exclude) anything in node_modules + ignore: normalizedExcludePaths !== undefined ? normalizedExcludePaths : ['**/node_modules'], + }) + .map((storyPath) => { + const pathWithDirectory = path.join(specifier.directory, storyPath); + const requirePath = absolute + ? storyPath + : ensureRelativePathHasDot(path.relative(configPath, pathWithDirectory)); + + const absolutePath = absolute ? requirePath : path.resolve(configPath, requirePath); + const pathRelativeToCwd = path.relative(cwd, absolutePath); + + return `"./${pathRelativeToCwd}": require("${requirePath}")`; + }); return [...acc, ...paths]; }, []); @@ -97,15 +118,7 @@ function writeRequires({ configPath, absolute = false }) { let previewJs = previewExists ? previewImports : ''; - const storyRequires = storyPaths - .map((storyPath) => { - const storyPathToAbsolute = path.resolve(configPath, storyPath); - const relative = path.relative(cwd, storyPathToAbsolute); - return `"./${relative}": require("${storyPath}")`; - }) - .join(','); - - const path_obj_str = `{${storyRequires}}`; + const path_obj_str = `{${storyRequires.join(',')}}`; const registerAddons = main.addons?.map((addon) => `import "${addon}/register";`).join('\n'); let enhancersImport = ''; diff --git a/app/react-native/scripts/loader.test.js b/app/react-native/scripts/loader.test.js index 94e1cec487..905682b057 100644 --- a/app/react-native/scripts/loader.test.js +++ b/app/react-native/scripts/loader.test.js @@ -171,5 +171,15 @@ describe('loader', () => { ); }); }); + + describe('when there is a configuration object', () => { + it('writes the story imports', () => { + writeRequires({ configPath: 'scripts/mocks/configuration-objects' }); + expect(pathMock).toEqual( + path.resolve(__dirname, 'mocks/configuration-objects/storybook.requires.js') + ); + expect(fileContentMock).toMatchSnapshot(); + }); + }); }); }); diff --git a/app/react-native/scripts/mocks/configuration-objects/components/FakeComponent.tsx b/app/react-native/scripts/mocks/configuration-objects/components/FakeComponent.tsx new file mode 100644 index 0000000000..23915b0493 --- /dev/null +++ b/app/react-native/scripts/mocks/configuration-objects/components/FakeComponent.tsx @@ -0,0 +1 @@ +export const FakeComponent = () => null; diff --git a/app/react-native/scripts/mocks/configuration-objects/components/FakeStory.stories.tsx b/app/react-native/scripts/mocks/configuration-objects/components/FakeStory.stories.tsx new file mode 100644 index 0000000000..a40a317021 --- /dev/null +++ b/app/react-native/scripts/mocks/configuration-objects/components/FakeStory.stories.tsx @@ -0,0 +1,9 @@ +import { FakeComponent } from './FakeComponent'; + +export default { + component: FakeComponent, +}; + +export const Basic = { + args: {}, +}; diff --git a/app/react-native/scripts/mocks/configuration-objects/main.js b/app/react-native/scripts/mocks/configuration-objects/main.js new file mode 100644 index 0000000000..6c0240e977 --- /dev/null +++ b/app/react-native/scripts/mocks/configuration-objects/main.js @@ -0,0 +1,15 @@ +module.exports = { + stories: [ + { + files: '**/*.stories.tsx', + directory: './components', + titlePrefix: 'ComponentsPrefix', + }, + ], + 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/configuration-objects/preview.js b/app/react-native/scripts/mocks/configuration-objects/preview.js new file mode 100644 index 0000000000..13bad13073 --- /dev/null +++ b/app/react-native/scripts/mocks/configuration-objects/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 }, +});