Skip to content

Commit 80627f8

Browse files
Ewa Wydraewydra
Ewa Wydra
authored andcommitted
Added json-server. Created tests for reducers and actions
1 parent 69c6e39 commit 80627f8

23 files changed

+412
-108
lines changed

db.json

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"articles": [
3+
{
4+
"id": 1,
5+
"title": "Article one",
6+
"date": "December 17, 2018 15:23:00",
7+
"abstract": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus tincidunt suscipit dui, non auctor metus tincidunt sed.",
8+
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu sollicitudin libero, vitae lacinia orci. Maecenas sed egestas ipsum. Duis euismod tincidunt pretium. Cras consequat risus id fermentum elementum. Nullam a lorem sed risus hendrerit rhoncus. Proin lacus lectus, ultrices gravida venenatis nec, eleifend quis erat. Donec et blandit nunc. Donec id ultricies eros, sed pretium velit. Etiam augue mi, posuere et facilisis auctor, placerat eget turpis. Suspendisse augue nisl, ultricies sit amet nisl ut, tempus sodales lorem. Vivamus non tincidunt metus. Integer lectus sapien, laoreet ut placerat ut, aliquam et purus. Integer in semper mi. Nunc tempor eget urna a laoreet. Phasellus a dapibus enim. Nam malesuada ex a massa convallis mattis. Etiam volutpat lacus vitae odio molestie, eget interdum orci venenatis. Ut non placerat diam, nec blandit nibh. Vestibulum ut sapien sed orci ornare commodo. Nam id neque in dui tristique venenatis ac sed lorem. Nulla aliquam turpis id lorem molestie egestas. Etiam semper sodales libero, sed varius ante. Phasellus mollis euismod est vel gravida. Cras bibendum neque eget odio laoreet, ac feugiat quam egestas. Maecenas feugiat erat facilisis lacus ornare dictum. Aliquam quis porttitor enim. Nunc quis consequat elit. Donec pellentesque, purus eu varius condimentum, enim enim vestibulum erat, ut scelerisque dolor nulla et mi. Praesent tincidunt ante vitae neque fermentum pulvinar. Maecenas fermentum placerat consectetur."
9+
},
10+
{
11+
"id": 2,
12+
"title": "Article two",
13+
"date": "December 20, 2018 17:31:00",
14+
"abstract": "Phasellus a dapibus enim. Nam malesuada ex a massa convallis mattis. Etiam volutpat lacus vitae odio molestie, eget interdum orci venenatis.",
15+
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu sollicitudin libero, vitae lacinia orci. Maecenas sed egestas ipsum. Duis euismod tincidunt pretium. Cras consequat risus id fermentum elementum. Nullam a lorem sed risus hendrerit rhoncus. Proin lacus lectus, ultrices gravida venenatis nec, eleifend quis erat. Donec et blandit nunc. Donec id ultricies eros, sed pretium velit. Etiam augue mi, posuere et facilisis auctor, placerat eget turpis. Suspendisse augue nisl, ultricies sit amet nisl ut, tempus sodales lorem. Vivamus non tincidunt metus. Integer lectus sapien, laoreet ut placerat ut, aliquam et purus. Integer in semper mi. Nunc tempor eget urna a laoreet. Phasellus a dapibus enim. Nam malesuada ex a massa convallis mattis. Etiam volutpat lacus vitae odio molestie, eget interdum orci venenatis. Ut non placerat diam, nec blandit nibh. Vestibulum ut sapien sed orci ornare commodo. Nam id neque in dui tristique venenatis ac sed lorem. Nulla aliquam turpis id lorem molestie egestas. Etiam semper sodales libero, sed varius ante. Phasellus mollis euismod est vel gravida. Cras bibendum neque eget odio laoreet, ac feugiat quam egestas. Maecenas feugiat erat facilisis lacus ornare dictum. Aliquam quis porttitor enim. Nunc quis consequat elit. Donec pellentesque, purus eu varius condimentum, enim enim vestibulum erat, ut scelerisque dolor nulla et mi. Praesent tincidunt ante vitae neque fermentum pulvinar. Maecenas fermentum placerat consectetur."
16+
},
17+
{
18+
"id": 3,
19+
"title": "Article three",
20+
"date": "December 21, 2018 19:31:00",
21+
"abstract": "Vestibulum ut sapien sed orci ornare commodo. Nam id neque in dui tristique venenatis ac sed lorem. Nulla aliquam turpis id lorem molestie egestas.",
22+
"text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu sollicitudin libero, vitae lacinia orci. Maecenas sed egestas ipsum. Duis euismod tincidunt pretium. Cras consequat risus id fermentum elementum. Nullam a lorem sed risus hendrerit rhoncus. Proin lacus lectus, ultrices gravida venenatis nec, eleifend quis erat. Donec et blandit nunc. Donec id ultricies eros, sed pretium velit. Etiam augue mi, posuere et facilisis auctor, placerat eget turpis. Suspendisse augue nisl, ultricies sit amet nisl ut, tempus sodales lorem. Vivamus non tincidunt metus. Integer lectus sapien, laoreet ut placerat ut, aliquam et purus. Integer in semper mi. Nunc tempor eget urna a laoreet. Phasellus a dapibus enim. Nam malesuada ex a massa convallis mattis. Etiam volutpat lacus vitae odio molestie, eget interdum orci venenatis. Ut non placerat diam, nec blandit nibh. Vestibulum ut sapien sed orci ornare commodo. Nam id neque in dui tristique venenatis ac sed lorem. Nulla aliquam turpis id lorem molestie egestas. Etiam semper sodales libero, sed varius ante. Phasellus mollis euismod est vel gravida. Cras bibendum neque eget odio laoreet, ac feugiat quam egestas. Maecenas feugiat erat facilisis lacus ornare dictum. Aliquam quis porttitor enim. Nunc quis consequat elit. Donec pellentesque, purus eu varius condimentum, enim enim vestibulum erat, ut scelerisque dolor nulla et mi. Praesent tincidunt ante vitae neque fermentum pulvinar. Maecenas fermentum placerat consectetur."
23+
}
24+
]
25+
}

src/App.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import ArticleList from './components/ArticleList';
88
import ArticleDetails from './components/ArticleDetails';
99
import Navbar from './components/Navbar';
1010
import AddArticlePage from './components/AddArticlePage';
11-
import { SnackbarProvider } from './components/Snackbar/SnackbarContext';
11+
import SnackbarProvider from './components/Snackbar/SnackbarProvider';
1212

1313
function App() {
1414
return (

src/actions/__mocks__/axios.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const mockAxios = jest.genMockFromModule('axios')
2+
3+
mockAxios.create = jest.fn(() => mockAxios)
4+
5+
export default mockAxios

src/actions/articles.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
import * as types from '../types/articles';
2+
import axios from '../utils/axios';
23

3-
export const addArticle = payload => ({
4-
type: types.ADD_ARTICLE,
5-
payload: payload
6-
});
4+
export const fetchArticles = () => {
5+
return (dispatch) => {
6+
dispatch({type: types.GET_ARTICLES});
7+
axios.get('/articles')
8+
.then(response => {
9+
dispatch({ type: types.GET_ARTICLES_SUCCESS, payload: response.data });
10+
})
11+
.catch(error => {
12+
dispatch({ type: types.GET_ARTICLES_ERROR, payload: error });
13+
});
14+
}
15+
}
16+
17+
export const addArticle = (payload, history) => {
18+
return (dispatch) => {
19+
dispatch({type: types.ADD_ARTICLE});
20+
axios.post('/articles', payload)
21+
.then(response => {
22+
dispatch({ type: types.ADD_ARTICLE_SUCCESS, payload: response.data });
23+
history.push(`/article/${response.data.id}`);
24+
})
25+
.catch(error => {
26+
dispatch({ type: types.ADD_ARTICLE_ERROR, payload: error });
27+
});
28+
}
29+
}

src/actions/articles.test.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import configureMockStore from 'redux-mock-store';
2+
import thunk from 'redux-thunk';
3+
import mockAxios from 'axios';
4+
import * as types from '../types/articles';
5+
import * as actions from './articles';
6+
7+
const middlewares = [thunk]
8+
const mockStore = configureMockStore(middlewares)
9+
const store = mockStore()
10+
11+
const fetchArticlesData = [
12+
{
13+
id: 1,
14+
title: 'Title',
15+
date: 'May 6, 2019 13:30:00',
16+
abstract: 'Abstract',
17+
text: 'Text'
18+
},
19+
{
20+
id: 2,
21+
title: 'Title 2',
22+
date: 'May 7, 2019 13:30:00',
23+
abstract: 'Abstract 2',
24+
text: 'Text 2'
25+
}
26+
];
27+
28+
const addArticleData = {
29+
id: 3,
30+
title: 'New article title',
31+
date: 'August 23, 2019 21:39:00',
32+
abstract: 'New article abstract',
33+
text: 'New article content'
34+
};
35+
36+
describe('articles actions', () => {
37+
beforeEach(() => {
38+
store.clearActions();
39+
});
40+
41+
it('creates GET_ARTICLES_SUCCESS after succesfully fetching articles', async () => {
42+
mockAxios.get.mockImplementationOnce(() => Promise.resolve({ data: fetchArticlesData }));
43+
44+
const expectedActions = [
45+
{ type: types.GET_ARTICLES },
46+
{ type: types.GET_ARTICLES_SUCCESS, payload: fetchArticlesData }
47+
];
48+
49+
await store.dispatch(actions.fetchArticles())
50+
51+
expect(store.getActions()).toEqual(expectedActions);
52+
expect(mockAxios.get).toHaveBeenCalledTimes(1);
53+
});
54+
55+
it('creates ADD_ARTICLE_SUCCESS after succesfully adding article', async () => {
56+
mockAxios.post.mockImplementationOnce(() => Promise.resolve({ data: addArticleData }));
57+
58+
const expectedActions = [
59+
{ type: types.ADD_ARTICLE },
60+
{ type: types.ADD_ARTICLE_SUCCESS, payload: addArticleData }
61+
];
62+
63+
await store.dispatch(actions.addArticle())
64+
65+
expect(store.getActions()).toEqual(expectedActions);
66+
expect(mockAxios.post).toHaveBeenCalledTimes(1);
67+
});
68+
});

src/components/ArticleForm.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { Formik, Field, Form } from "formik";
44
import { format } from 'date-fns';
55
import { withRouter } from 'react-router-dom';
66
import TextField from "./utils/TextField";
7+
import SnackbarContext from "./Snackbar/SnackbarContext";
78
import { addArticle } from "../actions/articles";
8-
import { SnackbarContext } from "./Snackbar/SnackbarContext";
99

1010
const initialValues = {
1111
title: "",

src/components/ArticleList.jsx

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import React, {Component} from 'react';
22
import { connect } from 'react-redux';
3+
import { getArticles, isLoading } from '../selectors/articles';
4+
import { fetchArticles } from '../actions/articles';
35
import ArticleListItem from './ArticleListItem';
4-
import { getArticles } from '../selectors/articles';
6+
import Spinner from './utils/Spinner';
57

68
export class ArticleList extends Component {
9+
componentDidMount() {
10+
this.props.fetchArticles();
11+
}
12+
713
listArticles = () =>
814
this.props.articles.map(article => {
915
return <ArticleListItem key={article.id} article={article} />
1016
});
1117

1218
render() {
13-
return (
19+
if (this.props.isLoading) return <Spinner />
20+
return (
1421
<ul className="Article-list">
1522
{this.listArticles()}
1623
</ul>
@@ -20,6 +27,11 @@ export class ArticleList extends Component {
2027

2128
const mapStateToProps = (state) => ({
2229
articles: getArticles(state),
30+
isLoading: isLoading(state),
2331
});
2432

25-
export default connect(mapStateToProps)(ArticleList);
33+
const mapDispatchToProps = {
34+
fetchArticles
35+
};
36+
37+
export default connect(mapStateToProps, mapDispatchToProps)(ArticleList);

src/components/ArticleList.test.jsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ const setup = () => {
1919
abstract: 'Phasellus a dapibus enim. Nam malesuada ex a massa convallis mattis.',
2020
text: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eu sollicitudin libero, vitae lacinia orci.`
2121
}];
22-
return { articles };
22+
return {
23+
articles,
24+
fetchArticles: jest.fn(),
25+
};
2326
}
2427

2528
describe('ArticleList', () => {

src/components/Snackbar/Snackbar.jsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import classnames from 'classnames';
3-
import { SnackbarConsumer } from './SnackbarContext';
3+
import SnackbarContext from './SnackbarContext';
44

55
const getClasses = status => (
66
classnames(
@@ -11,13 +11,13 @@ const getClasses = status => (
1111
);
1212

1313
const Snackbar = () => (
14-
<SnackbarConsumer>
14+
<SnackbarContext.Consumer>
1515
{({ isOpen, message, status }) => isOpen && (
1616
<div className={getClasses(status)}>
1717
{message}
1818
</div>
1919
)}
20-
</SnackbarConsumer>
20+
</SnackbarContext.Consumer>
2121
);
2222

2323
export default Snackbar;
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
import Snackbar from './Snackbar';
4+
5+
const mockContext = jest.fn();
6+
jest.mock('./SnackbarContext', () => ({
7+
Consumer: ({ children }) => children(mockContext()),
8+
}));
9+
10+
const setup = () => {
11+
const isOpen = true;
12+
const message = 'Test';
13+
const status = 'success';
14+
return { isOpen, message, status };
15+
}
16+
17+
describe('Snackbar', () => {
18+
beforeEach(() => {
19+
mockContext.mockReset();
20+
});
21+
22+
it('should pass proper context to Snackbar div', () => {
23+
const contextValues = setup();
24+
mockContext.mockReturnValue(contextValues);
25+
26+
const component = mount(<Snackbar />);
27+
28+
expect(component.find('div').prop('children')).toEqual('Test');
29+
expect(component.find('div').hasClass('Snackbar--success')).toEqual(true);
30+
})
31+
32+
it('should not be displayed when isOpen is false', () => {
33+
const contextValues = setup();
34+
contextValues.isOpen = false;
35+
mockContext.mockReturnValue(contextValues);
36+
37+
const component = mount(<Snackbar />);
38+
39+
expect(component.find('div')).not.toExist();
40+
})
41+
})
+3-33
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,10 @@
1-
import React, { Component } from 'react';
2-
import Snackbar from './Snackbar';
1+
import React from 'react';
32

4-
export const SnackbarContext = React.createContext({
3+
const SnackbarContext = React.createContext({
54
message: '',
65
isOpen: false,
76
showSnackbar: () => {},
87
status: 'success',
98
});
109

11-
export class SnackbarProvider extends Component {
12-
showSnackbar = (message, status) => {
13-
this.setState({
14-
message,
15-
status,
16-
isOpen: true,
17-
});
18-
setTimeout(() => this.setState({ isOpen: false}), 3000);
19-
};
20-
21-
state = {
22-
isOpen: false,
23-
message: '',
24-
status: 'success',
25-
showSnackbar: this.showSnackbar,
26-
};
27-
28-
render() {
29-
const { children } = this.props;
30-
31-
return (
32-
<SnackbarContext.Provider value={this.state}>
33-
<Snackbar />
34-
{children}
35-
</SnackbarContext.Provider>
36-
);
37-
}
38-
}
39-
40-
export const SnackbarConsumer = SnackbarContext.Consumer;
10+
export default SnackbarContext;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, { Component } from 'react';
2+
import SnackbarContext from './SnackbarContext';
3+
import Snackbar from './Snackbar';
4+
5+
export class SnackbarProvider extends Component {
6+
constructor(props) {
7+
super(props);
8+
this.state = {
9+
isOpen: false,
10+
message: '',
11+
status: 'success',
12+
};
13+
}
14+
15+
showSnackbar = (message, status) => {
16+
this.setState({
17+
message,
18+
status,
19+
isOpen: true,
20+
});
21+
setTimeout(() => this.setState({ isOpen: false}), 3000);
22+
};
23+
24+
render() {
25+
const { children } = this.props;
26+
27+
return (
28+
<SnackbarContext.Provider
29+
value={{
30+
showSnackbar: this.showSnackbar,
31+
isOpen: this.state.isOpen,
32+
message: this.state.message,
33+
status: this.state.status,
34+
}}
35+
>
36+
<Snackbar />
37+
{children}
38+
</SnackbarContext.Provider>
39+
);
40+
}
41+
}
42+
43+
export default SnackbarProvider;

src/components/utils/Spinner.jsx

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
const Spinner = () => (
4+
<div className="Spinner" />
5+
);
6+
7+
export default Spinner;

src/components/utils/Spinner.test.jsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import Spinner from './Spinner';
4+
5+
describe('Spinner', () => {
6+
it('should render correctly', () =>{
7+
const component = shallow(<Spinner />);
8+
expect(component).toMatchSnapshot();
9+
})
10+
})

src/components/utils/TextField.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import classnames from 'classnames';
33

4-
const InputFeedback = ({ type, children }) => {
4+
export const InputFeedback = ({ type, children }) => {
55
const classes = classnames(
66
'Text-field__message',
77
{ 'Text-field__message--error': type === 'error' }

0 commit comments

Comments
 (0)