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
4 changes: 2 additions & 2 deletions app/scenes/RootScreen/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
import NavigationService from '@services/NavigationService';
import { setTopLevelNavigator } from '@services/NavigationService';
import AppNavigator from '@navigators/AppNavigator';
import Container from '@atoms/Container';

Expand All @@ -13,8 +13,8 @@
this.props.startup();
}

setRefForTopLevelNavigtor = navigatorRef => {

Check warning on line 16 in app/scenes/RootScreen/index.js

View workflow job for this annotation

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

🕹 Function is not covered

Warning! Not covered function
NavigationService.setTopLevelNavigator(navigatorRef);
setTopLevelNavigator(navigatorRef);

Check warning on line 17 in app/scenes/RootScreen/index.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
};

render() {
Expand All @@ -30,8 +30,8 @@
startup: PropTypes.func
};

const mapDispatchToProps = dispatch => ({

Check warning on line 33 in app/scenes/RootScreen/index.js

View workflow job for this annotation

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

🕹 Function is not covered

Warning! Not covered function
startup: () => dispatch(rootScreenActions.startup())

Check warning on line 34 in app/scenes/RootScreen/index.js

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 34 in app/scenes/RootScreen/index.js

View workflow job for this annotation

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

🕹 Function is not covered

Warning! Not covered function
});

Check warning on line 35 in app/scenes/RootScreen/index.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 connect(null, mapDispatchToProps)(RootScreen);
4 changes: 2 additions & 2 deletions app/scenes/RootScreen/saga.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { takeLatest } from 'redux-saga/effects';
import NavigationService from '@app/services/NavigationService';
import { navigateAndReset } from '@app/services/NavigationService';
import { rootScreenTypes } from './reducer';

/**
* The startup saga is the place to define behavior to execute when the application starts.
*/
export function* startup() {
setTimeout(() => NavigationService.navigateAndReset('MainScreen'), 1000);
setTimeout(() => navigateAndReset('MainScreen'), 1000);
himanshu-wedensday marked this conversation as resolved.
Show resolved Hide resolved
}

export default function* startUpSaga() {
Expand Down
30 changes: 20 additions & 10 deletions app/scenes/RootScreen/tests/saga.test.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
/* eslint-disable sonarjs/no-duplicate-string */
/**
* Test sagas
*/

/* eslint-disable redux-saga/yield-effects */

import { takeLatest } from 'redux-saga/effects';
import NavigationService from 'app/services/NavigationService';
// import NavigationService from 'app/services/NavigationService';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove unused imports

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been resolved in rootscreen refactor pr

import { navigateAndReset } from '@app/services/NavigationService';
import { timeout } from 'app/utils/testUtils';
import set from 'lodash/set';
import rootScreenSaga, { startup } from '../saga';
import { rootScreenTypes } from '../reducer';

const NavigationService = '@app/services/NavigationService';
jest.mock('@app/services/NavigationService', () => ({
...jest.requireActual('@app/services/NavigationService'),
navigateAndReset: jest.fn()
}));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mock setup for NavigationService is potentially incorrect.

The mock setup for NavigationService uses a string instead of the actual module. This could lead to issues where the mock does not behave as expected because it's not linked to the actual module being tested. Consider importing the module and then mocking it.

- const NavigationService = '@app/services/NavigationService';
+ import NavigationService from '@app/services/NavigationService';

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const NavigationService = '@app/services/NavigationService';
jest.mock('@app/services/NavigationService', () => ({
...jest.requireActual('@app/services/NavigationService'),
navigateAndReset: jest.fn()
}));
import NavigationService from '@app/services/NavigationService';
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();
});
const setupTests = () => ({
generator: rootScreenSaga(),
submitSpy: jest.fn()
});

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

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 { submitSpy } = setupTests();
const method = startup();
NavigationService.navigateAndReset = submitSpy;
set(NavigationService, 'navigateAndReset', submitSpy);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of set from Lodash to modify NavigationService might not affect the mock.

The use of set to modify NavigationService in the test might not have the intended effect because the NavigationService is a mock and its properties might not be settable in this way. Consider directly assigning the spy to the mocked function.

- set(NavigationService, 'navigateAndReset', submitSpy);
+ NavigationService.navigateAndReset = submitSpy;

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
set(NavigationService, 'navigateAndReset', submitSpy);
NavigationService.navigateAndReset = submitSpy;

method.next();
await timeout(650);
expect(submitSpy).not.toHaveBeenCalled();
Expand Down
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 '../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