diff --git a/packages/rn-storybook-auto-screenshots/src/StoryRenderer.tsx b/packages/rn-storybook-auto-screenshots/src/StoryRenderer.tsx index 2fb93a8..0f163d4 100644 --- a/packages/rn-storybook-auto-screenshots/src/StoryRenderer.tsx +++ b/packages/rn-storybook-auto-screenshots/src/StoryRenderer.tsx @@ -29,7 +29,8 @@ type StoryRendererProps = { * This allows the Android test to discover stories automatically. */ let storiesRegistered = false; -function registerStoriesWithNative() { +/** @internal Exported for testing only */ +export function registerStoriesWithNative() { if (storiesRegistered || !StorybookRegistry) { return; } diff --git a/packages/rn-storybook-auto-screenshots/src/__tests__/registerStoriesWithNative.test.ts b/packages/rn-storybook-auto-screenshots/src/__tests__/registerStoriesWithNative.test.ts new file mode 100644 index 0000000..2824df9 --- /dev/null +++ b/packages/rn-storybook-auto-screenshots/src/__tests__/registerStoriesWithNative.test.ts @@ -0,0 +1,100 @@ +const mockRegisterStories = jest.fn(); + +jest.mock('react-native', () => ({ + View: 'View', + Text: 'Text', + StyleSheet: { create: (s: any) => s }, + NativeModules: { StorybookRegistry: { registerStories: mockRegisterStories } }, +})); + +jest.mock('react', () => ({ + useEffect: jest.fn(), + useState: jest.fn(() => [null, jest.fn()]), + default: { createElement: jest.fn() }, +})); + +function loadFreshModule() { + let mod: any; + jest.isolateModules(() => { + mod = require('../StoryRenderer'); + }); + return mod as typeof import('../StoryRenderer'); +} + +describe('registerStoriesWithNative', () => { + beforeEach(() => { + mockRegisterStories.mockClear(); + }); + + it('calls StorybookRegistry.registerStories with story data', () => { + const { configure, registerStoriesWithNative } = loadFreshModule(); + configure({ + _storyIndex: { + entries: { + 'button--primary': { title: 'Button', name: 'Primary' }, + }, + }, + }); + + registerStoriesWithNative(); + + expect(mockRegisterStories).toHaveBeenCalledWith([ + { id: 'button--primary', title: 'Button', name: 'Primary' }, + ]); + }); + + it('does not call registerStories when no stories are available', () => { + const { configure, registerStoriesWithNative } = loadFreshModule(); + configure({ _storyIndex: { entries: {} } }); + + registerStoriesWithNative(); + + expect(mockRegisterStories).not.toHaveBeenCalled(); + }); + + it('does not call registerStories when storybookView is not configured', () => { + const { registerStoriesWithNative } = loadFreshModule(); + + registerStoriesWithNative(); + + expect(mockRegisterStories).not.toHaveBeenCalled(); + }); + + it('only registers once even if called multiple times', () => { + const { configure, registerStoriesWithNative } = loadFreshModule(); + configure({ + _storyIndex: { + entries: { + 'button--primary': { title: 'Button', name: 'Primary' }, + }, + }, + }); + + registerStoriesWithNative(); + registerStoriesWithNative(); + registerStoriesWithNative(); + + expect(mockRegisterStories).toHaveBeenCalledTimes(1); + }); + + it('logs a warning and does not throw when registerStories throws', () => { + const { configure, registerStoriesWithNative } = loadFreshModule(); + configure({ + _storyIndex: { + entries: { + 'button--primary': { title: 'Button', name: 'Primary' }, + }, + }, + }); + mockRegisterStories.mockImplementation(() => { + throw new Error('native module error'); + }); + + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + expect(() => registerStoriesWithNative()).not.toThrow(); + expect(warnSpy).toHaveBeenCalled(); + + warnSpy.mockRestore(); + }); +});