diff --git a/app/navigators/AppNavigator.js b/app/navigators/AppNavigator.js index d3baa1c..5a3f924 100644 --- a/app/navigators/AppNavigator.js +++ b/app/navigators/AppNavigator.js @@ -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. @@ -12,7 +12,7 @@ const Stack = createStackNavigator(); */ export default function AppNavigator() { return ( - + diff --git a/app/scenes/ExampleScreen/saga.js b/app/scenes/ExampleScreen/saga.js index 618d25a..a0eefa1 100644 --- a/app/scenes/ExampleScreen/saga.js +++ b/app/scenes/ExampleScreen/saga.js @@ -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'; /** diff --git a/app/scenes/ExampleScreen/tests/saga.test.js b/app/scenes/ExampleScreen/tests/saga.test.js index 5745a97..5001a40 100644 --- a/app/scenes/ExampleScreen/tests/saga.test.js +++ b/app/scenes/ExampleScreen/tests/saga.test.js @@ -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'; diff --git a/app/scenes/RootScreen/tests/saga.test.js b/app/scenes/RootScreen/tests/saga.test.js index 0bed1dc..10e90c9 100644 --- a/app/scenes/RootScreen/tests/saga.test.js +++ b/app/scenes/RootScreen/tests/saga.test.js @@ -5,21 +5,22 @@ /* 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) ); @@ -27,19 +28,18 @@ describe('Tests for RootScreen sagas', () => { 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(); }); }); diff --git a/app/services/NavigationService.js b/app/services/NavigationService.js index 49c90a2..147f778 100644 --- a/app/services/NavigationService.js +++ b/app/services/NavigationService.js @@ -1,19 +1,21 @@ 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. @@ -22,7 +24,7 @@ function setTopLevelNavigator(navigatorRef) { * @param params Route parameters. */ function navigate(routeName, params) { - navigator.dispatch( + navigatorObject.navigator.dispatch( NavigationActions.navigate({ routeName, params @@ -40,7 +42,7 @@ function navigate(routeName, params) { * @param params Route parameters. */ function navigateAndReset(routeName, params) { - navigator.dispatch( + navigatorObject.navigator.dispatch( StackActions.replace({ routeName, params @@ -48,8 +50,4 @@ function navigateAndReset(routeName, params) { ); } -export default { - navigate, - navigateAndReset, - setTopLevelNavigator -}; +export { navigate, navigateAndReset, setTopLevelNavigator }; diff --git a/app/services/tests/navigate.test.js b/app/services/tests/navigate.test.js new file mode 100644 index 0000000..23fa7a6 --- /dev/null +++ b/app/services/tests/navigate.test.js @@ -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 } + }); + }); +}); diff --git a/app/services/tests/navigateAndReset.test.js b/app/services/tests/navigateAndReset.test.js new file mode 100644 index 0000000..edbc7b7 --- /dev/null +++ b/app/services/tests/navigateAndReset.test.js @@ -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 } + }); + }); +}); diff --git a/app/services/tests/setTopLevelNavigation.test.js b/app/services/tests/setTopLevelNavigation.test.js new file mode 100644 index 0000000..4d4e62a --- /dev/null +++ b/app/services/tests/setTopLevelNavigation.test.js @@ -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 + ); + }); +}); diff --git a/app/services/UserService.test.js b/app/services/tests/userService.test.js similarity index 94% rename from app/services/UserService.test.js rename to app/services/tests/userService.test.js index 53cbead..fc453f5 100644 --- a/app/services/UserService.test.js +++ b/app/services/tests/userService.test.js @@ -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 () => {