Skip to content

Commit

Permalink
feat(react-native-storybook): isRunningVisualTests & handled errors (#40
Browse files Browse the repository at this point in the history
)
  • Loading branch information
dawidk92 authored Aug 25, 2024
1 parent 0a713f2 commit 8c8bd3c
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 36 deletions.
1 change: 1 addition & 0 deletions examples/expo-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"sherlo:demo": "sherlo --config ../../configs/demo.preview.json",
"sherlo:eas": "sherlo --config ../../configs/eas.preview.json",
"sherlo:sync": "sherlo --config ../../configs/sync.preview.json",
"start": "expo start",
"start:android:dev": "adb install builds/development/android.apk && expo start --android --dev-client",
"start:android:go": "expo start --android --go",
"start:ios:dev": "tar -xzvf builds/development/ios.tar.gz -C builds/ && xcrun simctl install booted builds/sherloexpoexample.app && expo start --ios --dev-client",
Expand Down
18 changes: 15 additions & 3 deletions examples/expo-example/src/components/screens/Test/Test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { isRunningVisualTests } from '@sherlo/react-native-storybook';
import * as Localization from 'expo-localization';
import { useColorScheme } from 'react-native';
import { Text, View, StyleSheet, useColorScheme } from 'react-native';

const Test = () => {
const theme = useColorScheme(); // 'light' or 'dark'
Expand All @@ -17,16 +16,29 @@ const Test = () => {
<View style={[styles.container, { backgroundColor }]}>
<View style={styles.infoContainer}>
<MaterialIcons name="record-voice-over" size={24} color={textColor} />

<Text style={[styles.text, { color: textColor }]}>Language: {language}</Text>
</View>

<View style={styles.infoContainer}>
<MaterialIcons name="place" size={24} color={textColor} />

<Text style={[styles.text, { color: textColor }]}>Country: {country}</Text>
</View>

<View style={styles.infoContainer}>
<MaterialIcons name="brightness-4" size={24} color={textColor} />

<Text style={[styles.text, { color: textColor }]}>Theme: {theme || 'undefined'}</Text>
</View>

<View style={styles.infoContainer}>
<MaterialIcons name="directions-run" size={24} color={textColor} />

<Text style={[styles.text, { color: textColor }]}>
isRunningVisualTests: {isRunningVisualTests.toString()}
</Text>
</View>
</View>
);
};
Expand Down
34 changes: 25 additions & 9 deletions packages/react-native-storybook/src/helpers/SherloModule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import base64 from 'base-64';
import { NativeModules } from 'react-native';
import utf8 from 'utf8';
import isExpoGo from './isExpoGo';

let isExpoGo = false;
try {
const Constants = require('expo-constants').default;
isExpoGo = Constants.appOwnership === 'expo';
} catch {}

type SherloModule = {
getInitialMode: () => 'testing' | 'default';
Expand All @@ -20,12 +25,6 @@ if (SherloNativeModule !== null) {
SherloModule = createSherloModule();
} else {
SherloModule = createDummySherloModule();

if (!isExpoGo) {
console.warn(
'@sherlo/react-native-storybook: Sherlo native module is not accessible. Rebuild the app to link it on the native side.'
);
}
}

export default SherloModule;
Expand Down Expand Up @@ -65,13 +64,30 @@ function normalizePath(path: string): string {
}

function createDummySherloModule(): SherloModule {
const noNativeModuleErrorMessage = getNoNativeModuleErrorMessage();

return {
storybookRegistered: async () => {},
getInitialMode: () => 'default',
appendFile: async () => {},
mkdir: async () => {},
readFile: async () => '',
openStorybook: async () => {},
toggleStorybook: async () => {},
openStorybook: async () => {
throw new Error(noNativeModuleErrorMessage);
},
toggleStorybook: async () => {
throw new Error(noNativeModuleErrorMessage);
},
};
}

function getNoNativeModuleErrorMessage() {
if (isExpoGo) {
return [
'@sherlo/react-native-storybook: Accessing Storybook via Expo Go is not supported. Use a developer build or set up Storybook as a standalone app.',
'Learn more: https://docs.sherlo.io/getting-started/setup#storybook-entry-point',
].join('\n\n');
} else {
return '@sherlo/react-native-storybook: Sherlo native module is not accessible. Rebuild the app to link it on the native side.';
}
}
1 change: 0 additions & 1 deletion packages/react-native-storybook/src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { default as isExpoGo } from './isExpoGo';
export { default as RunnerBridge } from './RunnerBridge';
export { default as SherloModule } from './SherloModule';
11 changes: 0 additions & 11 deletions packages/react-native-storybook/src/helpers/isExpoGo.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/react-native-storybook/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as getStorybook } from './getStorybook';
export { default as isRunningVisualTests } from './isRunningVisualTests';
export { default as openStorybook } from './openStorybook';
export { default as registerStorybook } from './registerStorybook';
7 changes: 7 additions & 0 deletions packages/react-native-storybook/src/isRunningVisualTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NativeModules } from 'react-native';

const { SherloModule } = NativeModules;

const isRunningVisualTests = SherloModule?.getConstants().initialMode === 'testing';

export default isRunningVisualTests;
13 changes: 8 additions & 5 deletions packages/react-native-storybook/src/openStorybook.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { SherloModule } from './helpers';
import { handleAsyncError } from './utils';

function openStorybook(): Promise<void> {
return SherloModule.openStorybook().catch((error) => {
function openStorybook(): void {
SherloModule.openStorybook().catch((error) => {
if (error.code === 'NOT_REGISTERED') {
console.log(
'To use `openStorybook()`, you need to first call `registerStorybook()`.\n\nLearn more: https://docs.sherlo.io/getting-started/setup?storybook-entry-point=integrated#storybook-entry-point'
handleAsyncError(
new Error(
'To use `openStorybook()`, you need to first call `registerStorybook()`.\n\nLearn more: https://docs.sherlo.io/getting-started/setup?storybook-entry-point=integrated#storybook-entry-point'
)
);
} else {
throw error;
handleAsyncError(error);
}
});
}
Expand Down
21 changes: 14 additions & 7 deletions packages/react-native-storybook/src/registerStorybook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ReactElement } from 'react';
import { AppRegistry, DevSettings } from 'react-native';
import { SherloModule } from './helpers';
import { handleAsyncError } from './utils';

let ExpoDevMenu: any;
try {
ExpoDevMenu = require('expo-dev-menu');
} catch {}

function registerStorybook(StorybookComponent: () => ReactElement) {
AppRegistry.registerComponent('SherloStorybook', () => StorybookComponent);
Expand All @@ -13,21 +19,22 @@ export default registerStorybook;

/* ========================================================================== */

let ExpoDevMenu: any;
try {
ExpoDevMenu = require('expo-dev-menu');
} catch {}
let hasAddedDevMenuItem = false;

function addToggleStorybookToDevMenu() {
// Only add the menu item in development builds
if (!__DEV__) return;
// Add menu item once in development build
if (!__DEV__ || hasAddedDevMenuItem) return;

const MENU_LABEL = 'Toggle Storybook';
const toggleStorybook = () => SherloModule.toggleStorybook();
const toggleStorybook = () => {
SherloModule.toggleStorybook().catch(handleAsyncError);
};

DevSettings.addMenuItem(MENU_LABEL, toggleStorybook);

if (ExpoDevMenu) {
ExpoDevMenu.registerDevMenuItems([{ name: MENU_LABEL, callback: toggleStorybook }]);
}

hasAddedDevMenuItem = true;
}
11 changes: 11 additions & 0 deletions packages/react-native-storybook/src/utils/handleAsyncError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function handleAsyncError(error: Error) {
/**
* setTimeout with 0 delay is used because it forces the error to be thrown on
* the main thread, ensuring React Native properly handles and displays the error
*/
setTimeout(() => {
throw error;
}, 0);
}

export default handleAsyncError;
1 change: 1 addition & 0 deletions packages/react-native-storybook/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as getGlobalStates } from './getGlobalStates';
export { default as handleAsyncError } from './handleAsyncError';
export { default as isObject } from './isObject';

0 comments on commit 8c8bd3c

Please sign in to comment.