Trying to use Jest with ESM configuration to test React Native components and getting errors that Jest can't parse the imports from 'react-native'. The errors look like this: SyntaxError: The requested module 'react-native' does not provide an export named 'StyleSheet'. Any idea what I'm doing wrong?
RN version: 0.70.4
Example component:
import React, { ReactElement } from 'react';
import { StyleSheet, Text, View } from 'react-native';
const { container } = StyleSheet.create({
container: {
padding: 50,
},
});
const ThisComponent = (): ReactElement => (
<View style={container}>
<Text>Hello World!</Text>
</View>
);
export default ThisComponent;
Example test:
import React, { ReactElement } from 'react';
import { render } from '#testing-library/react-native';
import ThisComponent from '../ThisComponent';
describe('<OTPInputs>', () => {
test('Renders without exploding', async () => {
const { getByText } = render(
<ThisComponent />,
);
expect(getByText('Hello World!')).not.toBeDisabled();
});
});
Jest config (in package.json):
"jest": {
"haste": {
"defaultPlatform": "ios",
"platforms": [
"android",
"ios",
"native"
]
},
"resetMocks": true,
"testEnvironment": "node",
"testMatch": [
"**/src/**/*.(spec|test).[tj]s?(x)"
],
"preset": "ts-jest/presets/default-esm",
"transform": {
"^.+\\.js$": "babel-jest"
},
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)/)"
],
"extensionsToTreatAsEsm": [
".ts",
".tsx"
],
"globals": {
"ts-jest": {
"useESM": true
}
},
"setupFiles": [
"<rootDir>/node_modules/react-native/jest/setup.js"
],
"setupFilesAfterEnv": [
"#testing-library/jest-native/extend-expect"
],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/src/$1",
"^~components/(.*)$": "<rootDir>/src/components/$1",
"^~util/(.*)$": "<rootDir>/src/util/$1",
"^~types/(.*)$": "<rootDir>/src/types/$1"
}
}
EDIT: Looks like the main RN export is a cjs file with some weird syntax in it. Here is a mock that makes it work (with const ThisComponent = (await import('../ThisComponent').default):
jest.unstable_mockModule('react-native', () => ({
__esModule: true,
StyleSheet: jest.requireActual(
'react-native/Libraries/StyleSheet/StyleSheet',
),
Text: jest.requireActual('react-native/Libraries/Text/Text'),
TextInput: jest.requireActual(
'react-native/Libraries/Components/TextInput/TextInput',
),
TouchableOpacity: jest.requireActual(
'react-native/Libraries/Components/Touchable/TouchableOpacity',
),
View: jest.requireActual('react-native/Libraries/Components/View/View'),
}));
Your Jest config looks quite complex and I suspect that it might be the reason. In general importing from react-native package should work out of the box in when using React Native Testing Library.
In case of config issue its always a good idea to start with a working step and then add additional config entries you need. In RNTL we have a basic example app that is useful for that purpose.
Related
I am trying to do testing for my react bottom tab bar component while testing it I am getting below error.
I followed all the solutions available in this Link no luck for me.
https://github.com/react-navigation/react-navigation/issues/8669
/Users/apple/Documents/MM/myproject/node_modules/#react-navigation/elements/lib/commonjs/assets/back-icon.png:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){�PNG
My testing component
Tabbar.test.js
import React from "react";
import { render, fireEvent } from "#testing-library/react-native";
import Tabbar from "../Tabbar";
it("Tab tests", () => {
const addItemButton = render(<Tabbar />).toJSON;
}
My Tab bar component
Tab.js file
import React from "react";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import { Image } from "react-native";
const Tab = createBottomTabNavigator();
const Tabbar = ({ tabData }) => {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
tabBarActiveTintColor: "00000",
tabBarInactiveTintColor: "FFFF",
})}
>
{tabsInfo.map((element) => {
return (
<Tab.Screen
key={element.idx}
name={element.tabName}
component={element.component}
/>
);
})}
</Tab.Navigator>
);
};
export default Tabbar;
This is my jest config code
jest.config.js
module.exports = {
preset: "react-native",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
setupFilesAfterEnv: ["#testing-library/jest-native/extend-expect"],
transformIgnorePatterns: [
"node_modules/(?!(#react-native|react-native|react-native-vector-icons)/)",
],
};
Update transformIgnorePatterns in the jest config file.
transformIgnorePatterns: [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|#react-native-community|rollbar-react-native|#fortawesome|#react-native|#react-navigation)",
],
Reference link https://github.com/react-navigation/react-navigation/issues/8669
Why I get this error:
C:\PC\node_modules\#react-native\polyfills\error-guard.js:14
type ErrorHandler = (error: mixed, isFatal: boolean) => void;
^^^^^^^^^^^^
SyntaxError: Unexpected identifier
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)
at Object.<anonymous> (node_modules/react-native/jest/setup.js:16:6)
This comes when I add this line:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"],
I want to test my navigation
NavigationTest.js
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { render } from '#testing-library/react-native';
import { NativeStackWrapper } from '../src/navigation/CurvedBottomTab';
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
describe('test the Navigation', () => {
it('should render the curved tab navigator without errors', () => {
const { debug } = render(
<NavigationContainer>
<NativeStackWrapper />
</NavigationContainer>)
debug();
});
});
Package.json
...
"jest": {
"preset": "jest-expo",
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"],
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)|(?!lodash-es)"
]
},
The Unit test with jest / Enzyme or React-Native-Testing-Library never reaches 100% of code coverage for problems with the import, some import show warning IEEIEIE if/else not taken
These modules are in node_modules (and node_modules is include in the coveragePathIgnorePatterns and exclude from collectCoverageFrom) or custom modules that do not even have if / else statements
import React from 'react';
import { cleanup, render } from 'react-native-testing-library';
import ItemListaComprobante from './ItemListaComprobante';
describe('test container ConfiguracionPin', () => {
let wrapper;
const props = {
label: 'Test',
value: 'Test',
};
afterEach(() => cleanup);
beforeEach(() => {
wrapper = render(<ItemListaComprobante {...props} />);
});
test('test container ConfiguracionPin render properly ', () => {
expect(wrapper).toMatchSnapshot();
});
});
import React, { memo } from 'react';
import { Text } from 'react-native'; // (without IEEI)
import PropTypes from 'prop-types';
import { // IEEI
Left,
Right,
CardItem,
} from 'native-base';
const propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
};
const ItemListaComprobante = ({ label, value }) => (
<CardItem
bordered
borderedWhite
>
<Left>
<Text>{label}</Text>
</Left>
<Right>
<Text
bold
numberOfLines={1}
ellipsizeMode="tail"
>
{value}
</Text>
</Right>
</CardItem>
);
ItemListaComprobante.propTypes = propTypes;
export default memo(ItemListaComprobante);
My settings of jest
module.exports = {
verbose: true,
preset: 'react-native',
testEnvironment: 'jsdom',
transform: { '^.+\\.js$': '<rootDir>/node_modules/react-native/jest/preprocessor.js' },
setupFiles: ['<rootDir>/jest.setup.js'],
transformIgnorePatterns: ['/node_modules/*.js'],
coveragePathIgnorePatterns: [
'<rootDir>/index.js',
'<rootDir>/App.js',
'<rootDir>/commitlint.config.js',
'/node_modules/',
'jest.setup.js',
'ReactotronConfig.js',
'LogUtils.js',
'jest.config.js',
'rn-cli.config.js',
'transformer.js',
'super-wallet/coverage/lcov-report',
'Str.js',
],
setupFilesAfterEnv: [
'<rootDir>/__mocks__/react-native-camera.js',
'<rootDir>/__mocks__/react-native-fetch-blob.js',
'<rootDir>/__mocks__/react-native-firebase.js',
'<rootDir>/__mocks__/react-navigation.js',
'<rootDir>/__mocks__/react-native-reactotron.js',
'<rootDir>/__mocks__/react-native-user-agent.js',
'<rootDir>/__mocks__/osiris.js',
'<rootDir>/__mocks__/react-native-check-app-install.js',
'<rootDir>/__mocks__/react-native-image-crop-picker.js',
'<rootDir>/__mocks__/react-native-app-link.js',
],
collectCoverageFrom: ['**/*.{js,jsx}', '!**/node_modules/**', '!**/vendor/**'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: -10,
},
},
};
My settings of babelrc
{
"presets": ["module:metro-react-native-babel-preset"]
}
I would appreciate very much if somebody can help me, I have reviewed documentation of jest / istanbul and enzyme and I have not seen anything, even here I have also searched in case someone has had the same problem as me
This is error in istanbul coverage
Istanbul Report Printscreen error
When I build a stand alone android app the header left back button disappears, yet it's there if you click on it. It has no issue on the emulator. What could cause this?
I'm not sure when it started because I was relying on the emulator, but I do know that it was working at some point
Here is my app.json
{
"name": "appname",
"displayName": "appname",
"expo": {
"name": "appname",
"version": "1.0.0",
"slug": "appslug",
"orientation": "portrait",
"privacy": "unlisted",
"sdkVersion": "32.0.0",
"description": "",
"platforms": [
"ios",
"android"
],
"icon": "./assets/images/icon.png",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"enabled": true,
"fallbackToCacheTimeout": 30000,
"checkAutomatically": "ON_LOAD"
},
"ios": {
"buildNumber": "1.0.0",
"icon": "./assets/images/icon.png",
"bundleIdentifier": "my.unique.id"
// "splash": {
// "backgroundColor": "#FFFFFF",
// "resizeMode": "cover",
// "image": "./assets/iphone/Default-667h#2x.png"
// }
},
"android": {
"versionCode": 1,
"icon": "./assets/images/icon.png",
"package": "my.unique.id",
"adaptiveIcon": {
"foregroundImage": "./assets/images/icon.png",
"backgroundColor": "#FFFFFF"
}
// "splash": {
// "backgroundColor": "#FFFFFF",
// "resizeMode": "cover",
// "mdpi": "./assets/android/res/drawable-mdpi/background.9.png", // natural sized image (baseline),
// "hdpi": "./assets/android/res/drawable-hdpi/background.9.png", // scale 1.5x
// "xhdpi": "./assets/android/res/drawable-xhdpi/background.9.png", // scale 2x
// "xxhdpi": "./assets/android/res/drawable-xxhdpi/background.9.png", // scale 3x
// "xxxhdpi": "./assets/android/res/drawable-xxxhdpi/background.9.png" // scale 4x
// }
},
"hooks": {
"postPublish": [
{
"file": "sentry-expo/upload-sourcemaps",
"config": {
"organization": "my.org",
"project": "proj",
"authToken": "************"
}
}
]
},
"primaryColor": "#fefefe"
}
}
And here is my App.js
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font, Icon } from 'expo';
import AppNavigator from './navigation/AppNavigator';
import { Ionicons } from '#expo/vector-icons';
import Sentry from 'sentry-expo';
// Remove this once Sentry is correctly setup.
Sentry.enableInExpoDevelopment = true;
Sentry.config('https://sentry').install();
export default class App extends React.Component {
state = {
isLoadingComplete: false,
};
async componentDidMount() {
await Font.loadAsync({
'Roboto': require('native-base/Fonts/Roboto.ttf'),
'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'),
...Ionicons.font,
});
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</View>
);
}
}
_loadResourcesAsync = async () => {
return Promise.all([
Asset.loadAsync([
require('./assets/images/robot-dev.png'),
require('./assets/images/robot-prod.png'),
]),
Font.loadAsync({
// This is the font that we are using for our tab bar
...Icon.Ionicons.font,
// We include SpaceMono because we use it in HomeScreen.js. Feel free
// to remove this if you are not using it in your app
'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
}),
]);
};
_handleLoadingError = error => {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
/navigation/MainTabNavigator.js
import React from 'react';
import {Platform} from 'react-native';
import {createBottomTabNavigator, createStackNavigator} from 'react-navigation';
import TabBarIcon from '../components/TabBarIcon';
import HomeScreen from '../screens/HomeScreen';
import NotificationScreen from '../screens/NotificationScreen';
import SettingsScreen from '../screens/SettingsScreen';
import ProfileScreen from "../screens/ProfileScreen";
import DraftScreen from "../screens/DraftScreen";
import StatsScreen from "../screens/StatsScreen";
import Colors from "../constants/Colors";
import ViewStoryScreen from "../screens/ViewStoryScreen";
import LoginScreen from "../screens/LoginScreen";
import RegisterScreen from "../screens/RegisterScreen";
import MyStoriesScreen from "../screens/MyStories";
import EditStoryScreen from "../screens/EditStoryScreen";
import AddStoryScreen from "../screens/AddStoryScreen";
const headerStyle = {
/* The header config from HomeScreen is now here */
/*
For full list of options
https://reactnavigation.org/docs/en/stack-navigator.html#navigationoptions-for-screens-inside-of-the-navigator
*/
defaultNavigationOptions: {
headerStyle: {
backgroundColor: Colors.headerBackgroundColor,
},
headerTintColor: Colors.headerTintColor,
headerTitleStyle: {
fontWeight: 'bold',
},
headerBackTitleStyle: {color: Colors.headerTintColor},
headerBackStyle: {color: Colors.headerTintColor},
headerBackAllowFontScaling: true,
},
};
const HomeStack = createStackNavigator({
Home: HomeScreen,
ViewStoryScreen: ViewStoryScreen,
EditStory: EditStoryScreen,
AddStory: AddStoryScreen,
},
{
/* The header config from HomeScreen is now here */
defaultNavigationOptions: headerStyle.defaultNavigationOptions
}
);
HomeStack.navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={
Platform.OS === 'ios'
? `ios-home`
: 'md-home'
}
/>
),
};
const NotificationStack = createStackNavigator({
Links: NotificationScreen,
ViewStoryScreen: ViewStoryScreen,
},
{
/* The header config from HomeScreen is now here */
defaultNavigationOptions: headerStyle.defaultNavigationOptions
});
NotificationStack.navigationOptions = {
tabBarLabel: 'Notifications',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={Platform.OS === 'ios' ? 'ios-notifications' : 'md-notifications'}
/>
),
};
const SettingsStack = createStackNavigator({
Settings: SettingsScreen,
Profile: ProfileScreen,
Drafts: DraftScreen,
Stats: StatsScreen,
Login: LoginScreen,
Register: RegisterScreen,
MyStories: MyStoriesScreen,
ViewStoryScreen: ViewStoryScreen,
EditStory: EditStoryScreen,
AddStory: AddStoryScreen,
},
{
/* The header config from HomeScreen is now here */
defaultNavigationOptions: headerStyle.defaultNavigationOptions
}
);
SettingsStack.navigationOptions = {
tabBarLabel: 'Settings',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={Platform.OS === 'ios' ? 'ios-options' : 'md-options'}
/>
),
};
export default createBottomTabNavigator({
HomeStack,
NotificationStack,
SettingsStack,
});
Solution
As #Masuk Helal Anik mentioned this is a bug
Here is what worked for me, but had to sacrifice header back title.
In every screen add this
static navigationOptions = ({navigation}) => {
return {
headerLeft: (
<Ionicons
name={Platform.OS === "ios" ? "ios-arrow-back" : "md-arrow-back"}
size={Platform.OS === "ios" ? 35 : 24}
color={Colors.headerTintColor}
style={
Platform.OS === "ios"
? { marginBottom: -4, width: 25, marginLeft: 9 }
: { marginBottom: -4, width: 25, marginLeft: 20 }
}
onPress={() => {
navigation.goBack();
}}
/>
),
title: 'Screen Title'
}
};
It seems to me like a bug. As a solution in this issue is stated
if you use expo you should include the assets from react-navigation in
your assetBundlePatterns so the images are bundled with your app when
you build a standalone app. the easiest way to do this is to just
bundle all assets that your app uses:
https://github.com/expo/new-project-template/blob/6d4c5636de573852dfd2f7715cfa152fd9c84f89/app.json#L20-L22.
to fix it in development mode within expo, you can cache the assets
locally as per this guide. we do this in the navigationplayground
example app, so you can copy that code from here.
There is some workaround also. Try out them to find which one working for you!
I am working on application which is implemented using Expo and react native. I have splash screen in my app.The icon in splash screen loading screen is coming correct in android devices but very small in IOS devices ( in different dpi).I want to make icon resize based on dpi which present under loading .Can anyone help me out here.Thank you.
Config file:-
"expo": {
"name": "ExpoApp",
"description": "No description",
"slug": "evosus",
"privacy": "unlisted", // public
"sdkVersion": "19.0.0",
"version": "1.0.0",
"orientation": "portrait,landscape",
"primaryColor": "green",
"icon": "./assets/icons/ball.png",
//"icon": "./assets/icons/logo_Dark.png",
"notification": {
"icon": "./assets/icons/ball.png",
"color": "#000000"
},
"loading": {
//"icon": "./assets/icons/loading-icon.png",
"icon": "./assets/icons/icon.png",//I want to resize this icon based on screen dpi
"hideExponentText": true
},
"packagerOpts": {
"assetExts": [
"ttf"
]
},
"ios": {
"supportsTablet": true
}
//,"androidStatusBarColor": "#444444"
}
}
App.js component:-
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font, SQLite } from 'expo';
import { Ionicons, EvilIcons } from '#expo/vector-icons';
import RootNavigation from './navigation/RootNavigation';
// Services
import DB from "./src/Services/DBDefinitionService";
import appService from "./src/Services/AppService";
console.ignoredYellowBox = [ 'Setting a timer' ];
export default class App extends React.Component {
state = {
assetsAreLoaded: false,
};
componentWillMount() {
this._loadAssetsAsync();
// Added by Anil G on 23/08/2017
this.db_init();
this.db_device_info_save();
}
render() {
if (!this.state.assetsAreLoaded && !this.props.skipLoadingScreen) {
return <AppLoading/>;
} else {
return (
<View style={styles.container}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
{Platform.OS === 'android' &&
<View style={styles.statusBarUnderlay} />}
<RootNavigation />
</View>
);
}
}
async _loadAssetsAsync() {
try {
await Promise.all([
Asset.loadAsync([
require('./assets/images/robot-dev.png'),
require('./assets/images/robot-prod.png'),
]),
Font.loadAsync([
// This is the font that we are using for our tab bar
Ionicons.font,
EvilIcons.font,
// We include SpaceMono because we use it in HomeScreen.js. Feel free
// to remove this if you are not using it in your app
{ 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
'roboto-regular': require('./assets/fonts/Roboto-Regular.ttf'),
'roboto-medium': require('./assets/fonts/Roboto-Medium.ttf'),
},
]),
]);
} catch (e) {
// In this case, you might want to report the error to your error
// reporting service, for example Sentry
console.warn(
'There was an error caching assets (see: App.js), perhaps due to a ' +
'network timeout, so we skipped caching. Reload the app to try again.'
);
console.log(e);
} finally {
this.setState({ assetsAreLoaded: true });
}
}
async db_init() {
await DB.init();
}
async db_device_info_save() {
await appService.device_info_save();
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
statusBarUnderlay: {
height: 24,
backgroundColor: 'rgba(0,0,0,0.2)',
},
});
If I remember properly you can define it the same way you do in iOS, adding #x2, #x4...to your icon name and defining one for each dpi.