-
Notifications
You must be signed in to change notification settings - Fork 16
<StaticNavigator> for tests/storybook #75
Comments
<StaticNavigator>
for tests/storybook
How would you provide navigation state params to both the |
If you don't care about the HOC, wouldn't it be better to mock it? |
I got this component working: const TestNavigator = ({
children,
params,
}: {
children: NavigationComponent;
params?: NavigationParams;
}) => {
const Navigator = createAppContainer(
createSwitchNavigator({
TestScreen: { screen: () => Children.only(children), params },
}),
);
return <Navigator />;
}; Then you can use it in render(
<TestNavigator params={{ fakeParam: true }}>
<SomeComponent />
</TestNavigator>,
} My motivation was to get the Here's a full example of the testing helpers I wrote to wrap the tested component in a navigator and a redux provider: (used this for inspiration for the redux stuff) import React, { ReactElement, Children } from 'react';
import 'react-native';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { render } from 'react-native-testing-library';
import configureStore, { MockStore } from 'redux-mock-store';
import {
createAppContainer,
createSwitchNavigator,
NavigationComponent,
NavigationParams,
} from 'react-navigation';
export const createThunkStore = configureStore([thunk]);
const TestNavigator = ({
children,
params,
}: {
children: NavigationComponent;
params?: NavigationParams;
}) => {
const Navigator = createAppContainer(
createSwitchNavigator({
TestScreen: { screen: () => Children.only(children), params },
}),
);
return <Navigator />;
};
interface RenderWithContextParams {
initialState?: {} | undefined;
store?: MockStore;
navParams?: NavigationParams;
}
export function renderWithContext(
component: ReactElement,
{
initialState,
store = createThunkStore(initialState),
navParams,
}: RenderWithContextParams = {},
) {
return {
...render(
<TestNavigator params={navParams}>
<Provider store={store}>{component}</Provider>
</TestNavigator>,
),
store,
};
}
export function snapshotWithContext(
component: ReactElement,
renderWithContextParams?: RenderWithContextParams,
) {
const { toJSON } = renderWithContext(component, renderWithContextParams);
expect(toJSON()).toMatchSnapshot();
} Not sure how I feel about the |
Great job, that's what I'd like to be implemented, + some other details like providing navigationOptions and other things. @satya164 mocking certainly has some advantages like ability to test the RN integration and see what navigation methods are called etc. But for usecases like adding a whole screen to storybook, where you might have several little compos (already tested independently with a mock) coupled to RN, it can be annoying to mock the navigation again. When you start to use things like |
Doing this throws a bunch of duplicate navigator warnings... Added this as a Jest setup file: const originalWarn = console.warn;
beforeAll(() => {
console.warn = (...args: any[]) => {
if (
/You should only render one navigator explicitly in your app, and other navigators should be rendered by including them in that navigator/.test(
args[0],
)
) {
return;
}
originalWarn.call(console, ...args);
};
});
afterAll(() => {
console.warn = originalWarn;
}); |
I discovered my Here's a new version. It feels hacky. My new function renders a navigator and returns the navigationHelpers.tsx: import React from 'react';
import {
createAppContainer,
createSwitchNavigator,
NavigationParams,
NavigationScreenProp,
} from 'react-navigation';
import { render } from 'react-native-testing-library';
export const createNavigationProp = (params?: NavigationParams) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let navigationProp: NavigationScreenProp<any> | undefined;
const Navigator = createAppContainer(
createSwitchNavigator({
TestScreen: {
screen: ({
navigation,
}: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
navigation: NavigationScreenProp<any>;
}) => {
navigationProp = navigation;
return null;
},
params,
},
}),
);
render(<Navigator />);
if (navigationProp === undefined) {
throw 'Unable to get navigation screen prop';
}
return navigationProp;
}; Your test: import React from 'react';
import { Text } from 'react-native';
import { render } from 'react-native-testing-library';
import { NavigationParams } from 'react-navigation';
import { NavigationProvider } from '@react-navigation/core';
import { useNavigationParam } from 'react-navigation-hooks';
import { createNavigationProp } from './navigationHelpers';
const ComponentUnderTest = () => {
const testParam = useNavigationParam('testParam');
return <Text>{testParam}</Text>;
};
it('should render correctly', () => {
const navParams: NavigationParams = { testParam: 'Test' };
const navigation = createNavigationProp(navParams);
const { toJSON } = render(
<NavigationProvider value={navigation}>
<ComponentUnderTest />
</NavigationProvider>,
);
expect(toJSON()).toMatchInlineSnapshot(`
<Text>
Test
</Text>
`);
}); |
There's another issue I just thought of with something like What might be more useful is to provide a context provider (e.g. I think this way, we can support all different types navigators and help with both storybook and test scenarios. |
That makes sense, didn't think about the navigation helpers. What about deriving NavigationMockProvider.Stack for example, if we want to provide mocks for popToTop automatically etc? (and let user override what he wants with jest.fn() if needed) |
I haven't been able to find any documentation on recommended practices for using Are the workarounds posted here still valid for the new version? |
@andreialecu , it should work the same way. I haven't tested but you can try:
|
Thanks @slorber, I ended up with the following: const Stack = createStackNavigator();
const reactNavigationDecorator = story => {
const Screen = () => story();
return (
<NavigationContainer independent={true}>
<Stack.Navigator>
<Stack.Screen name="MyStorybookScreen" component={Screen} options={{header: () => null}} />
</Stack.Navigator>
</NavigationContainer>
)
}
addDecorator(reactNavigationDecorator); I needed Not sure if react-navigation can do anything about this or it's just a StoryBook issue. I was able to work around it by disabling the screen The story itself would look like this: storiesOf("Forms", module).add("Confirm Email", () => {
const navigation = useNavigation<
StackNavigationProp<AppStackParamList, "RegConfirm">
>();
const route = useRoute<RouteProp<AppStackParamList, "RegConfirm">>();
route.params = {
values: {
email: "[email protected]",
username: "tester",
password: "justtesting"
}
};
return (
<ConfirmForm
navigation={navigation}
route={route}
onSubmit={(values) => {
Alert.alert("", JSON.stringify(values, null, 2));
}}
/>
);
}); |
great to see it works for you ;) |
Basically the way I resolved this is by using webpack to mock useNavigation and useRoute rather than trying to mock all of the providers and scaffolding of react navigation. Really simple stuff: /.storybook/mocks/navigation.js: export const useRoute = () => {
return {
name: 'fun route',
params: {
nothing: 'nice ocean view'
}
};
};
export const useNavigation = () => {
return {
push: () => null,
goBack: () => null,
pop: () => null,
popToTop: () => null,
reset: () => null,
replace: () => null,
navigate: () => null,
setParams: () => null,
jumpTo: () => null
};
}; |
Hey sir,
Regarding this,
How do you this file ya? Can you share your webpack file as well? I have tried in my
But it still didnt use the function in mock folder. And I will get this error:
|
Hi,
I have screens on which some components are "connected" to react-navigation through
withNavigation()
hoc (a screen navbar header actually)I want to be able to render these components in envs like tests or RN storybook, but by default it fails because no navigation is found in React context.
I don't really care if when pressing the backbutton it does not really do anything (ie the navigation action is ignored because unknown), as long as it renders.
The current workaround does not look very idiomatic, and I think having an official support for this feature could be helpful
Here's my solution for Storybook:
I'd find this more idiomatic to do:
This would be quite similar to the of react-router
The text was updated successfully, but these errors were encountered: