Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/services refactoring #76

Closed
wants to merge 8 commits into from
4 changes: 2 additions & 2 deletions app/navigators/AppNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createStackNavigator } from '@react-navigation/stack';
import SplashScreen from '@scenes/SplashScreen/';
import ExampleScreen from '@scenes/ExampleScreen';
import { NavigationContainer } from '@react-navigation/native';
import NavigationService from '../services/NavigationService';
import { setTopLevelNavigator } from '@app/services/NavigationService';
const Stack = createStackNavigator();
/**
* The root screen contains the application's navigation.
Expand All @@ -12,7 +12,7 @@ const Stack = createStackNavigator();
*/
export default function AppNavigator() {
return (
<NavigationContainer ref={NavigationService.setTopLevelNavigator}>
<NavigationContainer ref={setTopLevelNavigator}>
<Stack.Navigator headerMode="none" initialRouteName="SplashScreen">
<Stack.Screen name="SplashScreen" component={SplashScreen} />
<Stack.Screen name="MainScreen" component={ExampleScreen} />
Expand Down
2 changes: 1 addition & 1 deletion app/scenes/ExampleScreen/saga.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { put, call, takeLatest } from 'redux-saga/effects';
import { get } from 'lodash';
import { getUser } from '@app/services/UserService';
import { getUser } from '@app/services/userService';
import { exampleScreenActions, exampleScreenTypes } from './reducer';

/**
Expand Down
2 changes: 1 addition & 1 deletion app/scenes/ExampleScreen/tests/saga.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/* eslint-disable redux-saga/yield-effects */

import { takeLatest, call, put } from 'redux-saga/effects';
import { getUser } from 'app/services/UserService';
import { getUser } from '@app/services/userService';
import { apiResponseGenerator } from 'app/utils/testUtils';
import exampleScreenSaga, { fetchUser } from '../saga';
import { exampleScreenTypes } from '../reducer';
Expand Down
24 changes: 12 additions & 12 deletions app/scenes/RootScreen/tests/saga.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,41 @@
/* eslint-disable redux-saga/yield-effects */

import { takeLatest } from 'redux-saga/effects';
import NavigationService from 'app/services/NavigationService';
import { navigateAndReset } from '@app/services/NavigationService';
import { timeout } from 'app/utils/testUtils';
import rootScreenSaga, { startup } from '../saga';
import { rootScreenTypes } from '../reducer';

jest.mock('@app/services/NavigationService', () => ({
...jest.requireActual('@app/services/NavigationService'),
navigateAndReset: jest.fn()
}));
describe('Tests for RootScreen sagas', () => {
let generator;
let submitSpy;

beforeEach(() => {
generator = rootScreenSaga();
submitSpy = jest.fn();
afterEach(() => {
jest.clearAllMocks();
});

it('should start task to watch for STARTUP action', () => {
const generator = rootScreenSaga();
expect(generator.next().value).toEqual(
takeLatest(rootScreenTypes.STARTUP, startup)
);
});

Check failure on line 28 in app/scenes/RootScreen/tests/saga.test.js

View workflow job for this annotation

GitHub Actions / Tests' annotations (🧪 jest-coverage-report-action)

Tests for RootScreen sagas > should ensure that the navigation service is called after waiting for 1000ms

TypeError: (0 , _NavigationService.navigateAndReset) is not a function at Timeout._onTimeout (/home/runner/work/react-native-template/react-native-template/app/scenes/RootScreen/saga.js:9:20) at listOnTimeout (node:internal/timers:569:17) at processTimers (node:internal/timers:512:7) Error: expect(jest.fn()).toHaveBeenCalled() Expected number of calls: >= 1 Received number of calls: 0 at call (/home/runner/work/react-native-template/react-native-template/app/scenes/RootScreen/tests/saga.test.js:33:23) at tryCatch (/home/runner/work/react-native-template/react-native-template/node_modules/regenerator-runtime/runtime.js:63:40) at Generator._invoke (/home/runner/work/react-native-template/react-native-template/node_modules/regenerator-runtime/runtime.js:293:22) at Generator.call (/home/runner/work/react-native-template/react-native-template/node_modules/regenerator-runtime/runtime.js:118:21) at tryCatch (/home/runner/work/react-native-template/react-native-template/node_modules/regenerator-runtime/runtime.js:63:40) at invoke (/home/runner/work/react-native-template/react-native-template/node_modules/regenerator-runtime/runtime.js:154:20) at fn (/home/runner/work/react-native-template/react-native-template/node_modules/regenerator-runtime/runtime.js:164:13) at tryCallOne (/home/runner/work/react-native-template/react-native-template/node_modules/react-native/node_modules/promise/lib/core.js:37:12) at call (/home/runner/work/react-native-template/react-native-template/node_modules/react-native/node_modules/promise/lib/core.js:123:15) at flush (/home/runner/work/react-native-template/react-native-template/node_modules/asap/raw.js:50:29)
it('should ensure that the navigation service is called after waiting for 1000ms', async () => {
const method = startup();
NavigationService.navigateAndReset = submitSpy;
method.next();
await timeout(1000);
expect(submitSpy).toHaveBeenCalled();
expect(navigateAndReset).toHaveBeenCalled();
expect(navigateAndReset).toHaveBeenCalledWith('MainScreen');
});

it('should ensure that the navigation service is called after waiting for 1000ms', async () => {
const method = startup();
NavigationService.navigateAndReset = submitSpy;
method.next();
await timeout(650);
expect(submitSpy).not.toHaveBeenCalled();
expect(navigateAndReset).not.toHaveBeenCalled();
await timeout(200);
expect(submitSpy).not.toHaveBeenCalled();
expect(navigateAndReset).not.toHaveBeenCalled();
});
});
22 changes: 10 additions & 12 deletions app/services/NavigationService.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
import { NavigationActions, StackActions } from '@react-navigation/compat';

import set from 'lodash/set';
/**
* The navigation is implemented as a service so that it can be used outside of components, for example in sagas.
*
* @see https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
*/

let navigator;
const navigatorObject = {
navigator: null
};

/**
* This function is called when the RootScreen is created to set the navigator instance to use.
*/
function setTopLevelNavigator(navigatorRef) {
navigator = navigatorRef;
}
const setTopLevelNavigator = navigatorRef => {
set(navigatorObject, 'navigator', navigatorRef);
};

/**
* Call this function when you want to navigate to a specific route.
*
* @param routeName The name of the route to navigate to. Routes are defined in RootScreen using createStackNavigator()
* @param params Route parameters.

Check warning on line 24 in app/services/NavigationService.js

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹 Function is not covered

Warning! Not covered function
*/
function navigate(routeName, params) {
navigator.dispatch(
navigatorObject.navigator.dispatch(
NavigationActions.navigate({
routeName,
params

Check warning on line 30 in app/services/NavigationService.js

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
himanshu-wedensday marked this conversation as resolved.
Show resolved Hide resolved
})
);
}
Expand All @@ -37,19 +39,15 @@
* the main screen: the user should not be able to go back to the splashscreen.
*
* @param routeName The name of the route to navigate to. Routes are defined in RootScreen using createStackNavigator()
* @param params Route parameters.

Check warning on line 42 in app/services/NavigationService.js

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹 Function is not covered

Warning! Not covered function
*/
function navigateAndReset(routeName, params) {
navigator.dispatch(
navigatorObject.navigator.dispatch(
StackActions.replace({
routeName,
params

Check warning on line 48 in app/services/NavigationService.js

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
})
);
}

export default {
navigate,
navigateAndReset,
setTopLevelNavigator
};
export { navigate, navigateAndReset, setTopLevelNavigator };
32 changes: 32 additions & 0 deletions app/services/tests/navigate.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NavigationActions } from '@react-navigation/compat';
import { navigate, setTopLevelNavigator } from '../NavigationService';
jest.mock('@react-navigation/compat', () => ({
NavigationActions: {
navigate: jest.fn()
}
}));
const navigatorRef = { goBack: 'goBack', dispatch: jest.fn() };
setTopLevelNavigator(navigatorRef);
describe('navigate', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('dispatches navigation action with the correct routeName and params', () => {
const routeName = '/test';
const params = { screen: 'MainScreen' };
NavigationActions.navigate.mockReturnValueOnce({
type: 'NAVIGATE_ACTION',
payload: { routeName, params }
});
navigate(routeName, params);
expect(NavigationActions.navigate).toHaveBeenCalledWith({
routeName,
params
});
expect(navigatorRef.dispatch).toHaveBeenCalledWith({
type: 'NAVIGATE_ACTION',
payload: { routeName, params }
});
});
});
34 changes: 34 additions & 0 deletions app/services/tests/navigateAndReset.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { StackActions } from '@react-navigation/compat';
import { setTopLevelNavigator, navigateAndReset } from '../NavigationService';

jest.mock('@react-navigation/compat', () => ({
StackActions: {
replace: jest.fn()
}
}));
const navigatorRef = { goBack: 'goBack', dispatch: jest.fn() };
setTopLevelNavigator(navigatorRef);
describe('test navigateAndReset', () => {
afterEach(() => {
// Reset mocks after each test
jest.clearAllMocks();
});

it('dispatches stack action with the correct routeName and params', () => {
const routeName = '/test';
const params = { screen: 'MainScreen' };
StackActions.replace.mockReturnValueOnce({
type: 'NAVIGATE_ACTION',
payload: { routeName, params }
});
navigateAndReset(routeName, params);
expect(StackActions.replace).toHaveBeenCalledWith({
routeName,
params
});
expect(navigatorRef.dispatch).toHaveBeenCalledWith({
type: 'NAVIGATE_ACTION',
payload: { routeName, params }
});
});
});
20 changes: 20 additions & 0 deletions app/services/tests/setTopLevelNavigation.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import set from 'lodash/set';
import { setTopLevelNavigator } from '@app/services/NavigationService';
jest.mock('lodash/set', () => jest.fn());
describe('setTopLevelNavigator', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('sets the navigator object with the provided reference', () => {
const navigatorObject = {
navigator: null
};
const navigatorRef = { goBack: 'goBack' };
setTopLevelNavigator(navigatorRef);
expect(set).toHaveBeenCalledWith(
navigatorObject,
'navigator',
navigatorRef
);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import { getApiClient } from 'app/utils/apiUtils';
import { getUser } from './UserService';
import { getUser } from '../userService';

describe('UserService tests', () => {
it('should make the api call to "/quotes?count=1"', async () => {
Expand Down
Loading