- Tests - Technologies - How to run the tests - More commands - Adding a test - How to mockup a module - How to mockup an endpoint - E2E - Project folder structure - Appspace-pushnotification-api folder - Code folder - Assets - Components - Config - Library - Services - Styles - Themes - Utils - Documentation folder - Img folder - PipelineJobs folder and pipelines files - Spikes folder - Push notifications ??? - Libraries - Handlers - Enable or disable push notifications - appspace-pushnotification-api - Commands - Links to documentation - Troubleshooting - Invariant Violation: Module AppRegistry is not a registered callable module - Notifications not arriving at the phone ## Tests ### Technologies - We use [Jest](https://jestjs.io/) as the testing framework for the application. - [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) ### How to run all the tests In order to run the tests, execute the commands below: ``` $ cd code $ yarn test ``` This will run all the tests ### Run an individual test Execute: ``` $ cd code $ yarn test <path_to_file> ``` For example: ``` $ cd code $ yarn test code/components/molecules/Banner/Banner.test.tsx ``` ### More commands - If you want to execute te tests for the files you are editing, use: ``` $ yarn test:watch ``` This re-runs the tests for a particular file, every time that file is saved. - With the command: ``` $ yarn test:ci ``` Jest will assume it is running in a CI environment (more info [here](https://jestjs.io/docs/en/cli#--ci)). ### Adding a test Create a new file with the name of the file for which the tests are being created and end the file extension with `<file_name>.test.tsx`. This file should be in the same location of the component under test. > In the following example, we assume we are creating tests for a component called `BannerSkeleton.tsx`. In order to be able to test the component, you need to add **testID**s to its child components to later target them in the tests. The `testID` needs to be defined in two places: - **`constants.js`** ```js export const testIDs = { feedsBannerSkeletonViewContent: 'feedsBannerSkeleton-content', }; ``` - **`constants.d.ts`** ```ts interface ITestIDContainer { feedsBannerSkeletonViewContent: string; }; ``` Now, open `BannerSkeleton.tsx` and add the `testID` defined above: ```tsx import { testIDs } from '../../../utils/constants'; export const BannerSkeleton = () => ( <CustomSkeleton> <View testID={testIDs.feedsBannerSkeletonViewContent} style={styles.banner} /> </CustomSkeleton> ); ``` Then paste the following boilerplate code into the file `BannerSkeleton.test.tsx`: ```tsx import React from 'react'; import { cleanup, render } from '@testing-library/react-native'; import { testIDs } from '../../../utils/constants'; import { ApplicationWrapper } from '../../../services/tests'; import { BannerSkeleton } from './BannerSkeleton'; describe('BannerSkeleton tests', () => { const Component = ApplicationWrapper(BannerSkeleton); afterEach(cleanup); it('Renders the correct elements', async () => { const { findByTestId } = render(<Component />); expect(findByTestId(testIDs.feedsBannerSkeletonViewContent)).toBeDefined(); }); }); ``` In the example above, we can highlight the following functions: - **`ApplicationWrapper`**: provides [Context](https://reactjs.org/docs/context.html)s to the component under test. - **`cleanup`**: unmounts React trees that were mounted with render. - **`render`**: renders the given React element and returns helpers to query the output components structure. - **`findTestById`**: finally, we get the component by the its specific `testId` and make an assertion on it. > Also, you can use the function `getByText` returned by `render` in order to search for specific text being rendered on the component. #### References - [Context](https://reactjs.org/docs/context.html) - [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) - [cleanup](https://callstack.github.io/react-native-testing-library/docs/api/#cleanup) - [findByTestId](https://callstack.github.io/react-native-testing-library/docs/api-queries/#bytestid) - [getByText](https://callstack.github.io/react-native-testing-library/docs/api-queries/#bytext) ### How to mockup a module Some modules requires to be mocked because they try to interact with the underlying platform API (Android or iOS). Otherwise, the tests will throw errors. To get around this problem, there is a file called `setupTests.js`. The following code snippet shows the code to mock a module inside `setupTests.js`: ```js jest.mock('react-native-push-notification', () => ({ invokeApp: jest.fn(), setApplicationIconBadgeNumber: jest.fn(), getChannels: jest.fn(), localNotification: jest.fn(), })); ``` Where the first parameter of `jest.mock` is the name of the module and the second parameter is a function that returns an object with the methods to mock. #### References - [jest.mock](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options) ### Global spies ??? ### How to mockup an endpoint Every file inside the `api/endpoints` folder should have a corresponding mock file inside `api/mockEndpoints`. The name for the mock file is the name of the original file prefixed with the word `mock`. For example, if the file is called `feed.api.ts`, its corresponding mock file should be `mock.api.ts`. For every function inside the original file, there should be a mock function with the same signature. All the mocked functions receive an object with the following properties: - **`extraConfig`**: - **`mockedResult`**: data returned by the mock endpoint. - **`queryStringRecord?`**: used for endpoints that allow pagination. - **`offset`**: number of items to skip. - **`itemsPerPage`**: number of items to return. Below there is an example of `mock.feed.api`: ```ts /* ... imports omitted for brevity */ export const getArticle: GetArticleAPICall = async ({ extraConfig = {} }) => { const { mockedResult } = extraConfig as IAPIConfig; return { data: mockedResult || articleRTFContent } as IArticleResponse; }; ``` > Don't forget to export the mocked functions in the corresponding `index.ts` file so they can be later imported on the tests: `export * from './mock.feed.api';` Once the mock endpoints are created, the last step is to add them to the Redux store inside the test: ```tsx /* ... imports omitted for brevity */ import { ComponentUnderTest } from './ComponentUnderTest'; import { APIServiceMock } from '../../../services/api'; describe('ComponentUnderTest component test', () => { afterEach(cleanup); it('Renders correctly', async () => { const getArticle = jest.fn(() => APIServiceMock.getArticle({ articleURL: '', extraConfig: { mockedResult: randomRTF() } }) ); const { store } = configureStore({ ...APIServiceMock, getArticle }, initialAppState, loggerTestingOptions); const Component = ApplicationWrapper(ComponentUnderTest, { store }); const { getByText, getByTestId } = render(<Component />); /* ... assertions omitted for brevity */ }); }); ``` #### References - Redux ??? [X] TODO remover ### Mocking data Last, we'll see how to generate random data for the tests. Inside the folder `mockEndpoints/data/generators` there are files for each entity in the project that needs to be mocked. Each file exports a class that inherits from `BaseGenerator<T>`. These classes are used to generate and export in `mockEndpoints/data/index.ts` that you can the import in your tests. - Generating and exporting the data (`index.ts`) ```ts export const feeds: IFeed[] = FeedGenerator.generate(20); ``` - Importing the mocked data (in a test file): ```tsx import { feeds } from '../../../services/api/mockEndpoints/data'; ``` ## Project folder structure The relevant files and subfolders placed on the root of the project are the following: 📦kaindy ┣ 📂appspace-pushnotification-api ┣ 📂code ┣ 📂documentation ┣ 📂img ┣ 📂pipelineJobs ┣ 📂spikes ┣ 📜e2e.yml ┣ 📜releases.yml ┗ 📜validation.yml ### Appspace-pushnotification-api folder Here we have a serverless API. It uses handlers with an SDK to send **push notifications** to *firebase*. ### Code folder Inside this folder we have our main application. This includes the frontend, with its respective logic, styles, images, tests, configurations and libraries. The most relevant subfolders that it contains are the following: 📂code ┣ 📂assets ┣ 📂components ┣ 📂config ┣ 📂library ┣ 📂services ┣ 📂styles ┣ 📂themes ┗ 📂utils #### Assets In this folder are all the necessary resources for the app, such as images, icons, fonts and animations. #### Components Inside the components directory, the structure is based on atomic design, which is a methodology for creating design systems. The folder structure is as follows: 📂components ┣ 📂atoms ┣ 📂molecules ┣ 📂organisms ┣ 📂templates ┣ 📂views ┣ 📂wrappers ┗ 📜App.tsx `📜App.tsx` is the main component, which returns the whole app embedded in the corresponding wrappers and providers. `📂atoms` is the folder where the basic building blocks of matter are, such as labels, buttons or inputs. The components inside the `📂molecules` folder are group of atoms bonded together. They take on their own properties and serve as the backbone of the design system. For example, we can combine labels, inputs, and buttons to create a form. Inside the `📂organisms` folder, there are groups of molecules joined together to form a relatively complex, distinct section of an interface. For example, Components inside the `📂templates` folder consist mostly of groups of organisms stitched together to form pages. It’s here where we start to see the design coming together and start seeing things like layout in action. Inside `📂views` we have specific instances of templates. In these components, we get the information and content to show on the view. Finally, in the `📂wrappers` folder, we have components that wrap their children in other components to add some behavior or style to them. #### Config Here we have the configs that we prefer to have separately and import from wherever necessary. For example, there is a folder that exports all needed *Jest* configurations. #### Library Inside this directory should go the generic components that are not coupled to anything in the app. All components inside this folder should be usable on any other application without any problem. #### Services In here we have another division between different subfolders, the most relevant are: 📂services ┣ 📂api ┣ 📂cache ┣ 📂navigation ┣ 📂notification ┣ 📂providers ┣ 📂store ┣ 📂tests ┗ 📂theming `📂api` is the directory that handles the connection to the backend. This includes all functions that make API calls, as well as the same functions but returning mock data. Inside the `📂cache` folder we have functions that read and write on the local cache folder, to save temporary data on the device. The `📂navigation` folder handles the logic related to the navigator, such us the associations of views, components, and settings. The logic for handling *push notifications* is in the `📂notification` folder. The `📂providers` folder handles the logic of the providers that wrap the app, such as store, internet, and theming providers. In the `📂store` folder are all the actions, reducers, configurations, and logic of the redux store. Inside the `📂tests` folder we have helpers and wrappers to use on the tests. And finally, we import all the app's colors on the `📂themes` folder to set the different themes with its respective colors. #### Styles In this folder we have styles to import and use on the app, such as combinations of font families, sizes, weight, etc. #### Themes Inside this directory we have all the colors that we use on the app, to prevent having hardcoded colors in the components. These colours are then imported by the theming logic to apply them properly to each component based on the current theme. #### Utils In this folder we have all the constants used on the app and their corresponding types. Also, we have random functions that are imported from different, no related components on the project. ### Documentation folder Here we have documentation of the app, mostly about features. ### Img folder Here you can add images to include on some documentation to achieve a better illustration of a topic or instructions. ### PipelinesJobs folder and pipelines files Inside PipelinesJobs folder we have `📜install.yml`, and in the root directory we have `📜e2e.yml`, `📜releases.yml` and `📜validation.yml`. These are all configuration files for pipelines. They set configurations for automatic scripts, builds, deploys and releases. > For more information about pipelines, please visit our [Pipelines document](./documentation/Pipelines.md) ### Spikes folder In this directory you can add small projects to develop and test a new feature, or to try different approaches to implement some change in the app. ## Push notifications ### Libraries - [react-native-push-notification](https://github.com/zo0r/react-native-push-notification) ### Handlers For each type of notification there is a file in the folder `PushNotificationController/Handlers` that defines a method to handle the notification. ### Enable or disable Push Notifications It is possible to enable and disable the different type of notifications from the app. Follow the steps below to enable or disable the notifications: - Go to the Sidebar Menu. - Click on **Settings**, it'll display a list with an option called **Notifications**. - Click on **Notifications** and it'll display a list with each type of notification to enable or disable. This sets through Redux a state that specifics if the notification (of the given type) will be enable or disabled. ### appspace-pushnotification-api This is a serverless project to mock the API to send notifications. For more info, click [here](appspace-pushnotification-api). #### Commands (ya estan en el link ??) ### Links to more documentation - [Push Notifications Proposal](https://southworks365.sharepoint.com/sites/kaindy/Shared%20Documents/Forms/AllItems.aspx?id[…]Fsites%2Fkaindy%2FShared%20Documents%2FProject%2FSpecs) - [Phase 2 Requirements](https://southworks365.sharepoint.com/sites/kaindy/Shared%20Documents/Forms/AllItems.aspx?id=%2Fsites%2Fkaindy%2FShared%20Documents%2FProject%2FSpecs%2FAppspace%20Portal%20%2D%20Native%20App%20Ph2%20v2%2Epdf&parent=%2Fsites%2Fkaindy%2FShared%20Documents%2FProject%2FSpecs) ## Troubleshooting This secton will comment on the different problems we have encountered when developing the application: - **Invariant Violation: Module AppRegistry is not a registered callable module**: when this happens, try to re-run the application by executing `npm run android` again. If the problem persists, re-install the app and try again. - **Notifications not arriving at the phone**: make sure the notifications are enabled for the application (click [here](https://support.google.com/android/answer/9079661?hl=en#zippy=%2Cturn-notifications-on-or-off-for-certain-apps) to see how to enable notifications). > This could vary on Android depending on the phone vendor. - **Login errors**: when trying to login into the app, the login API could be down. To bypass the login, replace the following code into the file `auth.api.ts`: ```ts export const login: LoginAPICall = async () => { return { data: { token: '', tokenExpiration: 0, refreshToken: '<refresh_token>', refreshTokenExpiration: 0, subjectId: '<subject_id>', }, } as ILoginResponse; }; ``` Where `<refresh_token>` and `<subject_id>` can be obtained from 1password under the key **Bypass Login**. > It also needs to make an import for the interface `ILoginResponse`.