Skip to content

Commit 54cdf36

Browse files
authored
feature: addition of lint verification, unit testing and code analysis in ci/cd
* fix: eslint and linting issues, addition of PR and testing workflows * fix: failing test * feat: sonar scan addition
1 parent a38506e commit 54cdf36

11 files changed

Lines changed: 595 additions & 477 deletions

File tree

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
22
root: true,
33
extends: '@react-native',
4+
ignorePatterns: ['e2e/'],
45
};

.github/workflows/pr-check.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Check PR title
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, synchronize, reopened]
6+
7+
jobs:
8+
lint:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: aslafy-z/conventional-pr-title-action@v3
12+
env:
13+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/unit-testing.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Unit Testing
2+
env:
3+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
5+
on:
6+
push:
7+
branches:
8+
- main
9+
pull_request:
10+
types: [opened, synchronize, reopened]
11+
12+
jobs:
13+
install-lint-test-scan:
14+
runs-on: ubuntu-latest
15+
steps:
16+
# Checkout the repository
17+
- uses: actions/checkout@v3
18+
with:
19+
fetch-depth: 0
20+
# Setup Node environment
21+
- name: Setup Node
22+
uses: actions/setup-node@v3
23+
with:
24+
node-version: 18.x
25+
cache: 'yarn'
26+
# Load previous cache
27+
- name: ESLint Cache
28+
uses: actions/cache@v3
29+
with:
30+
path: './.eslintcache'
31+
key: ${{ runner.os }}-eslintcache-${{ github.ref_name }}-${{ hashFiles('.eslintcache') }}
32+
33+
- name: Install Dependencies
34+
run: yarn install --immutable
35+
# Verify linting
36+
- name: Lint
37+
run: yarn lint
38+
# Run unit tests with coverage
39+
- name: test
40+
run: yarn test:unit:coverage
41+
# Run Code Analysis Scan
42+
- name: SonarCloud Scan
43+
uses: SonarSource/sonarcloud-github-action@master

__mocks__/react-native-video.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
import {View} from 'react-native'
1+
import {View} from 'react-native';
22

3-
export default View
3+
export default View;

__tests__/ListWithFetch.test.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React from 'react';
2-
import {cleanup, render, screen} from '@testing-library/react-native';
2+
import {
3+
cleanup,
4+
render,
5+
screen,
6+
waitForElementToBeRemoved,
7+
} from '@testing-library/react-native';
38
import ListWithFetch from '../src/components/ListWithFetch';
49
import {server} from '../src/test/mocks/server';
510
import {rest} from 'msw';
@@ -15,16 +20,19 @@ test('displays images from the server', async () => {
1520

1621
// Loader is initially visible
1722
expect(screen.getByLabelText(/loader/i)).toBeOnTheScreen();
18-
23+
await waitForElementToBeRemoved(() => screen.getByLabelText(/loader/i), {
24+
timeout: 1500,
25+
});
1926
// Verify that users are fetched and rendered
2027
expect(await screen.findAllByLabelText(/user-container/i)).toHaveLength(10);
2128

2229
// Verifying that the loader is no longer visible
2330
// There are 2 ways to verify that a component is not in the UI tree
24-
// 1. Use getBy* methods and expect them to throw an error with a corresponding message
25-
// 2. Use queryBy* methods and expect them to return null (See the next expect statement)
31+
// 1. Use waitForElementToBeRemoved to wait for the element to be removed from the DOM
32+
// 2. Use getBy* methods and expect them to throw an error with a corresponding message
33+
// 3. Use queryBy* methods and expect them to return null (See the next expect statement)
2634
expect(() => screen.getByLabelText(/loader/i)).toThrow(
27-
'Unable to find an element with accessibilityLabel: /loader/i',
35+
'Unable to find an element with accessibility label: /loader/i',
2836
);
2937

3038
// Verifying that there are no errors

package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,27 @@
2727
"react-native-video": "6.0.0-alpha.7"
2828
},
2929
"devDependencies": {
30-
"@babel/core": "^7.22.11",
31-
"@babel/preset-env": "^7.22.14",
32-
"@babel/runtime": "^7.22.11",
30+
"@babel/core": "^7.20.0",
31+
"@babel/preset-env": "^7.20.0",
32+
"@babel/runtime": "^7.20.0",
3333
"@react-native/eslint-config": "^0.72.2",
3434
"@react-native/metro-config": "^0.72.11",
35-
"@tsconfig/react-native": "^3.0.2",
36-
"@testing-library/jest-native": "^5.4.2",
37-
"@testing-library/react-native": "^12.2.2",
38-
"@types/jest": "^29.5.4",
39-
"@types/react": "^18.2.21",
40-
"@types/react-native-video": "^5.0.15",
35+
"@tsconfig/react-native": "^3.0.0",
36+
"@types/react": "^18.0.24",
4137
"@types/react-test-renderer": "^18.0.0",
42-
"axios": "^1.5.0",
43-
"babel-jest": "^29.6.4",
44-
"eslint": "^8.48.0",
45-
"jest": "^29.6.4",
38+
"babel-jest": "^29.2.1",
39+
"eslint": "^8.19.0",
40+
"jest": "^29.2.1",
4641
"metro-react-native-babel-preset": "0.76.8",
47-
"msw": "^1.2.5",
48-
"prettier": "^3.0.3",
42+
"prettier": "^2.4.1",
4943
"react-test-renderer": "18.2.0",
50-
"typescript": "5.2.2"
44+
"typescript": "4.8.4",
45+
"@testing-library/jest-native": "^5.4.3",
46+
"@testing-library/react-native": "^12.3.0",
47+
"@types/jest": "^29.2.1",
48+
"@types/react-native-video": "^5.0.15",
49+
"axios": "^1.5.0",
50+
"msw": "^1.3.0"
5151
},
5252
"engines": {
5353
"node": ">=16"

sonar-project.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sonar.organization=vangalilea
2+
sonar.projectKey=vanGalilea_react-native-testing
3+
sonar.javascript.lcov.reportPaths=coverage/lcov.info
4+
sonar.sources=src/
5+
sonar.test.exclusions=**/__tests__/**
6+
sonar.coverage.exclusions=**/__tests__/**, **/__mocks__/**
7+
sonar.coverage.inclusions=**/src/**/**.ts|tsx
8+
sonar.exclusions=**/android/**, **/ios/**
9+
sonar.verbose=true
10+
sonar.host.url=https://sonarcloud.io

src/components/Home.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ export default () => {
1818
return (
1919
<View style={styles.body}>
2020
<StatusBar barStyle="dark-content" />
21-
<SafeAreaView style={{flex: 1}}>
21+
<SafeAreaView style={styles.flex1}>
2222
<ScrollView
23-
style={{flex: 1}}
23+
style={styles.flex1}
2424
contentInsetAdjustmentBehavior="automatic">
2525
<View style={styles.innerScrollView}>
2626
<Text>Go to component...</Text>
@@ -49,6 +49,9 @@ export default () => {
4949
};
5050

5151
const styles = StyleSheet.create({
52+
flex1: {
53+
flex: 1,
54+
},
5255
body: {
5356
backgroundColor: Colors.white,
5457
...StyleSheet.absoluteFillObject,

src/components/Modal.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default () => {
1414
<View style={styles.modalView}>
1515
<Text style={styles.modalText}>Hello World!</Text>
1616
<Pressable
17-
style={{...styles.openButton, backgroundColor: '#2196F3'}}
17+
style={[styles.openButton, styles.specialBGColor]}
1818
onPress={() => {
1919
setModalVisible(!modalVisible);
2020
}}>
@@ -74,4 +74,7 @@ const styles = StyleSheet.create({
7474
marginBottom: 15,
7575
textAlign: 'center',
7676
},
77+
specialBGColor: {
78+
backgroundColor: '#2196F3',
79+
},
7780
});

src/utils/theme.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ import React, {
44
SetStateAction,
55
useContext,
66
useState,
7-
} from 'react'
7+
} from 'react';
88

99
export type ThemeType = 'dark' | 'light';
1010
type ThemeContextType = {
1111
theme: ThemeType;
1212
setTheme: Dispatch<SetStateAction<ThemeType>>;
1313
};
14-
const ThemeContext = createContext<ThemeContextType | null>(null)
14+
const ThemeContext = createContext<ThemeContextType | null>(null);
1515

1616
const useTheme = () => {
17-
const context = useContext(ThemeContext)
17+
const context = useContext(ThemeContext);
1818
if (!context) {
19-
throw new Error('useTheme should be used within a ThemeProvider')
19+
throw new Error('useTheme should be used within a ThemeProvider');
2020
}
21-
return context
21+
return context;
2222
};
2323

2424
const ThemeProvider = ({
@@ -28,8 +28,8 @@ const ThemeProvider = ({
2828
initialTheme: ThemeType;
2929
children: React.ReactNode;
3030
}): JSX.Element => {
31-
const [theme, setTheme] = useState(initialTheme)
32-
return <ThemeContext.Provider value={{theme, setTheme}} {...props} />
31+
const [theme, setTheme] = useState(initialTheme);
32+
return <ThemeContext.Provider value={{theme, setTheme}} {...props} />;
3333
};
3434

35-
export {useTheme, ThemeProvider}
35+
export {useTheme, ThemeProvider};

0 commit comments

Comments
 (0)