Skip to content

Commit 0cdc6f0

Browse files
(feat): userLogin
- user should login
1 parent bcf9180 commit 0cdc6f0

File tree

22 files changed

+11016
-13
lines changed

22 files changed

+11016
-13
lines changed

assets/HomePic.jpg

2.97 MB
Loading
820 KB
Loading

package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
"coverage": "codeclimate-test-reporter < ./coverage/lcov.info"
1414
},
1515
"engines": {
16-
"yarn": "1.x",
17-
"node": "14.x"
16+
"yarn": "1.x"
1817
},
1918
"repository": {
2019
"type": "git",
@@ -34,11 +33,23 @@
3433
"homepage": "https://github.com/Stackup-Rwanda/stackup2-barefoot-frontend#readme",
3534
"dependencies": {
3635
"@babel/core": "^7.9.6",
36+
"@babel/plugin-transform-runtime": "^7.10.1",
37+
"@babel/polyfill": "^7.10.1",
38+
"@babel/preset-env": "^7.9.6",
3739
"@babel/preset-react": "^7.9.4",
40+
"@babel/runtime": "^7.10.2",
41+
"@fortawesome/fontawesome-svg-core": "^1.2.28",
42+
"@fortawesome/free-brands-svg-icons": "^5.13.0",
43+
"@fortawesome/free-solid-svg-icons": "^5.13.0",
44+
"@fortawesome/react-fontawesome": "^0.1.9",
45+
"@material-ui/core": "^4.10.0",
46+
"@material-ui/icons": "^4.9.1",
47+
"@material-ui/styles": "^4.10.0",
3848
"@storybook/addon-actions": "^5.3.18",
3949
"@storybook/addon-links": "^5.3.18",
4050
"@storybook/addons": "^5.3.18",
4151
"@storybook/react": "^5.3.18",
52+
"babel-eslint": "^10.1.0",
4253
"babel-jest": "^26.0.1",
4354
"babel-loader": "^8.1.0",
4455
"babel-plugin-transform-class-properties": "^6.24.1",
@@ -66,6 +77,7 @@
6677
"redux-devtools-extension": "^2.13.8",
6778
"redux-mock-store": "^1.5.4",
6879
"redux-thunk": "^2.3.0",
80+
"regenerator-runtime": "^0.13.5",
6981
"style-loader": "^1.2.1",
7082
"webpack": "^4.43.0",
7183
"webpack-cli": "^3.3.11",

src/__tests__/App.test.js

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
11
/* eslint-disable no-undef */
22
import { mount, shallow } from 'enzyme';
33
import React from 'react';
4+
import 'regenerator-runtime/runtime';
45
import { Provider } from 'react-redux';
56
import renderer from 'react-test-renderer';
67
import configureStore from 'redux-mock-store';
8+
import TextField from '@material-ui/core/TextField';
79
import customMessage from '../utils/customMessages';
810
import App from '../entry/App';
911
import HelloComponent from '../components/HelloComponent';
1012
import FrontPage from '../views/frontPage';
1113
import reducer from '../reducers/reducer';
1214
import firstMessage from '../actions/actions';
15+
import Login, {
16+
UserLogin, mapDispatchToProps,
17+
mapStateToProps,
18+
} from '../components/forms/UserLogin';
19+
import InputField from '../components/forms/InputField';
20+
import Lines from '../components/forms/Lines';
21+
import Links from '../components/forms/Link';
22+
import SubmitButton from '../components/forms/SubmitButton';
23+
import SocialLogin from '../components/forms/SocialLogin';
24+
import userLogin from '../helpers/userLogin';
1325

1426
let component = '';
15-
27+
let loginComponent = '';
28+
const defaultProps = {
29+
email: '',
30+
errors: '',
31+
password: '',
32+
success: false,
33+
token: '',
34+
classes: '',
35+
};
1636
const mockStore = configureStore([]);
1737
const store = mockStore({
1838
message: 'Welcome',
@@ -23,6 +43,22 @@ component = renderer.create(
2343
<HelloComponent />
2444
</Provider>,
2545
);
46+
loginComponent = renderer.create(
47+
<Provider store={store}>
48+
<Login />
49+
</Provider>,
50+
);
51+
const initialObj = {
52+
errors: '',
53+
message: 'Welcome',
54+
success: false,
55+
token: '',
56+
};
57+
const handleLoginChange = jest.fn();
58+
const props = {
59+
name: 'password',
60+
handleLoginChange,
61+
};
2662
describe('App tests', () => {
2763
it('Will prove that the app is rendered from App component', () => {
2864
const appRender = shallow(<App />);
@@ -40,7 +76,7 @@ describe('App tests', () => {
4076
});
4177

4278
it('should return welcome when no action provided', () => {
43-
expect(reducer(undefined, {})).toEqual({ message: 'Welcome' });
79+
expect(reducer(undefined, {})).toEqual(initialObj);
4480
});
4581
it('should return Redux when action is provided with value', () => {
4682
expect(reducer(undefined, { ...firstMessage, value: 'Redux' })).toEqual({ message: 'Redux' });
@@ -51,4 +87,88 @@ describe('App tests', () => {
5187
});
5288
expect(store.dispatch).toHaveBeenCalledTimes(1);
5389
});
90+
it('rendering the user login component', () => {
91+
const login = shallow(<loginComponent />);
92+
expect(login.contains(<InputField />, <SubmitButton />, <Links />, <Lines />, <SocialLogin />));
93+
});
94+
it('password should hold what a user entered', () => {
95+
const wrapper = shallow(<InputField {...props} />);
96+
wrapper
97+
.find(TextField)
98+
.simulate('change', { target: { value: 'Example@2020' } });
99+
expect(handleLoginChange).toBeCalledTimes(1);
100+
});
101+
it('email should hold what a user entered', () => {
102+
const props = {
103+
name: 'email',
104+
handleLoginChange,
105+
};
106+
const wrapper = shallow(<InputField {...props} />);
107+
wrapper
108+
.find(TextField)
109+
.simulate('change', { target: { value: '[email protected]' } });
110+
expect(handleLoginChange).toBeCalledTimes(1);
111+
});
112+
it('email should hold what a user entered', () => {
113+
const mockFn = jest.fn();
114+
const highWrapper = mount(<Provider store={store}><Login /></Provider>);
115+
const wrapper = highWrapper.find(TextField);
116+
console.log('hh', wrapper);
117+
wrapper
118+
.at(0)
119+
.props()
120+
.handleLoginChange = mockFn;
121+
wrapper
122+
.at(0)
123+
.props()
124+
.handleLoginChange();
125+
expect(mockFn).toBeCalledTimes(1);
126+
});
127+
it('rendering the user login component', () => {
128+
const login = mount(<Provider store={store}><Login /></Provider>);
129+
expect(login.contains(<InputField />, <SubmitButton />, <Links />, <Lines />, <SocialLogin />));
130+
});
131+
it('login', () => {
132+
const handleLoginChange = jest.fn();
133+
const dispatch = jest.fn();
134+
mapDispatchToProps(dispatch).login();
135+
mapStateToProps({ defaultProps });
136+
const wrapper = mount(<Provider store={store}><Login /></Provider>);
137+
138+
wrapper
139+
.find(TextField)
140+
.at(0)
141+
.simulate('change', { target: { value: '[email protected]' } });
142+
wrapper
143+
.find(TextField)
144+
.at(1)
145+
.simulate('change', { target: { value: 'Example@2020' } });
146+
wrapper
147+
.find(SubmitButton)
148+
.simulate('click');
149+
150+
expect(dispatch).toBeCalledTimes(1);
151+
});
152+
it('login functionality success', async () => {
153+
const credentials = {
154+
155+
password: 'TravelAdmin2@',
156+
};
157+
global.fetch = jest.fn(() => Promise.resolve({
158+
json: () => Promise.resolve({}),
159+
}));
160+
const result = await userLogin(credentials);
161+
console.log(result);
162+
expect(fetch).toHaveBeenCalledTimes(1);
163+
});
164+
it('login functionality fail', async () => {
165+
const credentials = {
166+
167+
password: 'TravelAdmin2@',
168+
};
169+
global.fetch = jest.fn(() => Promise.reject());
170+
const result = await userLogin(credentials);
171+
console.log(result);
172+
expect(fetch).toHaveBeenCalledTimes(1);
173+
});
54174
});

src/actions/loginActions.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {
2+
LOGIN_HANDLE,
3+
} from './loginTypes';
4+
import login from '../helpers/userLogin';
5+
6+
const loginError = (data) => ({ type: LOGIN_HANDLE, payload: data });
7+
8+
const handleLogin = (credentials) => async (dispatch) => {
9+
let output = '';
10+
console.log('credentials', credentials);
11+
const { email } = credentials;
12+
if (email.length === 0 || !(new RegExp(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,15}/g).test(email))) {
13+
dispatch(loginError({ error: 'Invalid email' }));
14+
return;
15+
}
16+
const result = await login(credentials);
17+
const { error, token } = result;
18+
output = token
19+
? { success: true, error: '', token }
20+
: { error };
21+
dispatch(loginError(output));
22+
};
23+
24+
export default handleLogin;

src/actions/loginTypes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const LOGIN_PROCESS = 'LOGIN_PROCESS';
2+
export const LOGIN_HANDLE = 'LOGIN_HANDLE';

src/components/forms/InputField.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import TextField from '@material-ui/core/TextField';
3+
import { makeStyles } from '@material-ui/core/styles';
4+
import PropTypes from 'prop-types';
5+
6+
const useStyles = makeStyles({
7+
textField: {
8+
marginBottom: '7%',
9+
marginTop: '1%',
10+
'& div': {
11+
borderRadius: 18,
12+
},
13+
},
14+
input: {
15+
color: 'black',
16+
backgroundColor: 'white',
17+
},
18+
});
19+
20+
const InputField = (props) => {
21+
const classes = useStyles();
22+
const { handleLoginChange, name } = props;
23+
return (
24+
<TextField
25+
variant="outlined"
26+
required
27+
fullWidth
28+
id={name}
29+
label={name}
30+
name={name}
31+
type={name}
32+
className={classes.textField}
33+
InputProps={{
34+
className: classes.input,
35+
}}
36+
size="small"
37+
onChange={handleLoginChange}
38+
/>
39+
);
40+
};
41+
InputField.propTypes = {
42+
name: PropTypes.string.isRequired,
43+
handleLoginChange: PropTypes.func.isRequired,
44+
};
45+
export default InputField;

src/components/forms/Lines.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import Divider from '@material-ui/core/Divider';
3+
import Grid from '@material-ui/core/Grid';
4+
import { makeStyles } from '@material-ui/core/styles';
5+
6+
const useStyles = makeStyles({
7+
divider: {
8+
marginTop: '7%',
9+
backgroundColor: 'white',
10+
},
11+
});
12+
const Lines = () => {
13+
const classes = useStyles();
14+
return (
15+
<Grid container>
16+
<Grid item xs={5}>
17+
<Divider variant="middle" className={classes.divider} light />
18+
</Grid>
19+
<Grid item>
20+
OR
21+
</Grid>
22+
<Grid item xs={5}>
23+
<Divider variant="middle" className={classes.divider} />
24+
</Grid>
25+
</Grid>
26+
);
27+
};
28+
29+
export default Lines;

src/components/forms/Link.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
import Link from '@material-ui/core/Link';
3+
import { makeStyles } from '@material-ui/core/styles';
4+
import PropTypes from 'prop-types';
5+
6+
const useStyles = makeStyles({
7+
link: {
8+
textAlign: 'center',
9+
margin: '5%',
10+
},
11+
});
12+
const Links = (props) => {
13+
const classes = useStyles();
14+
const { children } = props;
15+
return (
16+
<div className={classes.link}>
17+
<Link href="/" variant="body2" style={{ color: 'white', fontSize: '14px' }}>
18+
{children}
19+
</Link>
20+
</div>
21+
);
22+
};
23+
Links.propTypes = {
24+
children: PropTypes.string.isRequired,
25+
};
26+
export default Links;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import FacebookIcon from '@material-ui/icons/Facebook';
3+
import Button from '@material-ui/core/Button';
4+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5+
import { faGoogle } from '@fortawesome/free-brands-svg-icons';
6+
import { makeStyles } from '@material-ui/core/styles';
7+
import Aux from '../../hoc/Auxiliary/Auxiliary';
8+
9+
const useStyles = makeStyles({
10+
textField: {
11+
marginBottom: '7%',
12+
marginTop: '1%',
13+
textTransform: 'none',
14+
borderRadius: 15,
15+
},
16+
});
17+
const element = <FontAwesomeIcon icon={faGoogle} />;
18+
const SocialLogin = () => {
19+
const classes = useStyles();
20+
return (
21+
<Aux>
22+
<Button
23+
variant="contained"
24+
color="primary"
25+
className={classes.textField}
26+
startIcon={<FacebookIcon />}
27+
type="button"
28+
fullWidth
29+
>
30+
Login With Facebook
31+
</Button>
32+
<Button
33+
variant="contained"
34+
className={classes.textField}
35+
startIcon={element}
36+
type="button"
37+
style={{ backgroundColor: 'white' }}
38+
fullWidth
39+
>
40+
Login With Google
41+
</Button>
42+
</Aux>
43+
);
44+
};
45+
46+
export default SocialLogin;

0 commit comments

Comments
 (0)