From 2a384e9d2780a7aa6307b279eae11f85b15ec9f7 Mon Sep 17 00:00:00 2001 From: Alvin Mugambi Date: Tue, 18 Jun 2019 11:09:34 +0300 Subject: [PATCH] feat(pagination): implement pagionation support for articles - ensure articles are paginated and page numbers are displayed [Delivers #165305229] --- src/assets/styles/articles.scss | 29 ++++++-- src/components/articles/Pagination.js | 55 +++++++++++++++ src/components/articles/TagsSearch.js | 8 +-- src/components/comments/Comments.js | 1 - src/redux/actions/FetchArticlesActions.js | 8 +-- src/redux/constants/index.js | 1 + src/views/AllArticles.js | 83 +++++++++++++++++------ tests/FetchDeleteArticlesActions.test.js | 10 +-- tests/Pagination.test.js | 45 ++++++++++++ 9 files changed, 199 insertions(+), 41 deletions(-) create mode 100644 src/components/articles/Pagination.js create mode 100644 tests/Pagination.test.js diff --git a/src/assets/styles/articles.scss b/src/assets/styles/articles.scss index ad87935..0c7ab32 100644 --- a/src/assets/styles/articles.scss +++ b/src/assets/styles/articles.scss @@ -60,6 +60,7 @@ height: 400px; border: 1px solid $bordergray; margin-bottom: 20px; + margin: 10px; } .flip-box-inner { @@ -83,11 +84,6 @@ backface-visibility: hidden; } -.flip-box-front { - background-color: $cardgray; - color: $black; -} - .flip-box-back { overflow: hidden !important; transform: rotateY(180deg); @@ -95,6 +91,11 @@ .home-card { height: 100% !important; + -ms-flex-direction: column; + flex-direction: column; + margin-right: 0px !important; + margin-bottom: 0 !important; + margin-left: 0px !important; } .homepage-card-image { @@ -113,11 +114,12 @@ font-weight: 900; } -.socialShare{ +.socialShare { margin-top: 1%; text-align: right; - &-twitter, &-facebook { + &-twitter, + &-facebook { height: 5%; width: 5%; margin-right: 1%; @@ -127,3 +129,16 @@ width: 4.7%; } } +.pagination { + display: flex; + justify-content: center; +} + +.page-link { + cursor: pointer; +} + +.active-page { + color: white !important; + background-color: blue !important; +} diff --git a/src/components/articles/Pagination.js b/src/components/articles/Pagination.js new file mode 100644 index 0000000..f606e1f --- /dev/null +++ b/src/components/articles/Pagination.js @@ -0,0 +1,55 @@ +import React, { Component } from 'react'; + +class Pagination extends Component { + render() { + const { + handleLink, handleClick, pageNumbers, next, previous, currentPage, + } = this.props; + return ( +
+ +
+ ); + } +} + +export default Pagination; diff --git a/src/components/articles/TagsSearch.js b/src/components/articles/TagsSearch.js index 5bf7aa8..ae5f1ef 100644 --- a/src/components/articles/TagsSearch.js +++ b/src/components/articles/TagsSearch.js @@ -3,10 +3,10 @@ import PropTypes from 'prop-types'; import '../../assets/styles/searchComponent.scss'; /* -* Tags search Component -* Display tags that contain keyword searched -*@return {jsx} -*/ + * Tags search Component + * Display tags that contain keyword searched + *@return {jsx} + */ class TagsSearch extends Component { render() { const { tag } = this.props; diff --git a/src/components/comments/Comments.js b/src/components/comments/Comments.js index aa7490d..301eb29 100644 --- a/src/components/comments/Comments.js +++ b/src/components/comments/Comments.js @@ -26,7 +26,6 @@ class Comments extends Component { const { slug } = this.props; const { isLoading, data } = this.props.myState; const comments = data.Comments; - console.log(comments); return (
diff --git a/src/redux/actions/FetchArticlesActions.js b/src/redux/actions/FetchArticlesActions.js index e184289..8e2b188 100644 --- a/src/redux/actions/FetchArticlesActions.js +++ b/src/redux/actions/FetchArticlesActions.js @@ -2,7 +2,6 @@ import axios from 'axios'; import { FETCH_ARTICLES, DELETE_ARTICLE, BASE_URL } from '../constants'; import { successToast } from '../../helpers'; - /* *Defines the action types for successful all articles fetch */ @@ -64,16 +63,15 @@ export const deleteArticleFailure = error => ({ error, }); - /* *Defines the fetchArticles actions and dispatches the right *action for either success *failure */ -export const fetchArticles = () => dispatch => { +export const fetchArticles = url => dispatch => { dispatch({ type: FETCH_ARTICLES }); return axios - .get(`${BASE_URL}/articles/`) + .get(url) .then(response => { dispatch(fetchSuccess(response)); }) @@ -105,7 +103,7 @@ export const fetchOneArticle = (slug, history) => dispatch => { *action for either success *failure */ -export const fetchByAuthor = (author) => dispatch => { +export const fetchByAuthor = author => dispatch => { dispatch({ type: FETCH_ARTICLES }); return axios .get(`${BASE_URL}/article/search/?author=${author}`) diff --git a/src/redux/constants/index.js b/src/redux/constants/index.js index d345ddb..7e69bbc 100644 --- a/src/redux/constants/index.js +++ b/src/redux/constants/index.js @@ -27,3 +27,4 @@ export const DELETE_ARTICLE = 'DELETE_ARTICLE'; export const CREATE_ARTICLE = 'CREATE_ARTICLE'; export const UPDATE_ARTICLE = 'UPDATE_ARTICLE'; export const GET_ONE_ARTICLE = 'GET_ONE_ARTICLE'; +export const ARTICLES_URL = `${BASE_URL}/articles/`; diff --git a/src/views/AllArticles.js b/src/views/AllArticles.js index 60d756f..2837a3a 100644 --- a/src/views/AllArticles.js +++ b/src/views/AllArticles.js @@ -1,19 +1,44 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; -import { CardColumns } from 'react-bootstrap'; +import { CardColumns, CardDeck } from 'react-bootstrap'; import { connect } from 'react-redux'; import parse from 'html-react-parser'; import { fetchArticles } from '../redux/actions/FetchArticlesActions'; import Article from '../components/articles/Article'; import { Loader } from '../components/layout/Loader'; +import { ARTICLES_URL } from '../redux/constants'; +import Pagination from '../components/articles/Pagination'; class AllArticles extends Component { + state = { + ArticlesPerPage: 9, + currentPage: '', + }; + componentWillMount() { const { fetchArticles: fetchAllArticles } = this.props; - fetchAllArticles(); + this.setState({ currentPage: 1 }); + fetchAllArticles(ARTICLES_URL); } + handleClick = e => { + const { fetchArticles: fetchAllArticles } = this.props; + const id = Number(e.target.id); + fetchAllArticles(`${ARTICLES_URL}?page=${id}`); + this.setState({ + currentPage: id, + }); + }; + + handleLink = e => { + const { fetchArticles: fetchAllArticles } = this.props; + const url = e.target.id; + const id = url.substring(url.lastIndexOf('=') + 1); + this.setState({ currentPage: Number(id) }); + fetchAllArticles(url); + }; + render() { const { articles: { isLoading }, @@ -26,27 +51,47 @@ class AllArticles extends Component { }, } = this.props; - const { results } = { ...data }; + const { + results, previous, next, count, + } = { ...data }; + + const { ArticlesPerPage, currentPage } = this.state; + // Logic for displaying page numbers + const pageNumbers = []; + for (let i = 1; i <= Math.ceil(count / ArticlesPerPage); i++) { + pageNumbers.push(i); + } return (

Latest Articles

- - {results && results.map((values) => ( -
- ))} - + + {results + && results.map(values => ( +
+ ))} + + + +
); diff --git a/tests/FetchDeleteArticlesActions.test.js b/tests/FetchDeleteArticlesActions.test.js index c314b82..d4bce8b 100644 --- a/tests/FetchDeleteArticlesActions.test.js +++ b/tests/FetchDeleteArticlesActions.test.js @@ -8,7 +8,7 @@ import { fetchByAuthor, deleteArticle, } from '../src/redux/actions/FetchArticlesActions'; -import { FETCH_ARTICLES, DELETE_ARTICLE } from '../src/redux/constants'; +import { FETCH_ARTICLES, DELETE_ARTICLE, ARTICLES_URL } from '../src/redux/constants'; /* * Defines tests for search action: @@ -49,6 +49,8 @@ const middlewares = [thunk]; const mockStore = configureMockStore(middlewares); +const url = ARTICLES_URL; + describe('test get all articles actions', () => { beforeEach(() => { moxios.install(); @@ -76,7 +78,7 @@ describe('test get all articles actions', () => { const store = mockStore({}); return store - .dispatch(fetchArticles()) + .dispatch(fetchArticles(url)) .then(() => { expect(store.getActions()[1].type).toEqual(expectedActions[1].type); }) @@ -102,12 +104,11 @@ describe('test get all articles actions', () => { const store = mockStore({}); - return store.dispatch(fetchArticles()).catch(() => { + return store.dispatch(fetchArticles(url)).catch(() => { expect(error.response.data).toEqual(expectedActions.type); }); }); - it('tests for successful get one action', () => { const { response } = mockData; moxios.wait(() => { @@ -158,7 +159,6 @@ describe('test get all articles actions', () => { }); }); - it('tests for successful get author articles', () => { const { response } = mockData; moxios.wait(() => { diff --git a/tests/Pagination.test.js b/tests/Pagination.test.js new file mode 100644 index 0000000..0912e29 --- /dev/null +++ b/tests/Pagination.test.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import store from '../src/redux/store'; +import Pagination from '../src/components/articles/Pagination'; + +describe('should mount Pagination component when it is called', () => { + it('tests for successful mount of pagination', () => { + const props = { + handleLink: jest.fn(), + handleClick: jest.fn(), + pageNumbers: [], + next: '', + previous: '', + currentPage: 1, + }; + const wrapper = mount( + + + + + , + ); + }); + + it('renders Pagination component', () => { + const props = { + handleLink: jest.fn(), + handleClick: jest.fn(), + pageNumbers: [], + next: '', + previous: 'a', + currentPage: 1, + }; + const event = { + preventDefault() {}, + target: { value: 'the-value' }, + }; + const wrapper = shallow(); + + wrapper.find('.page-link').simulate('click', event); + expect(props.handleLink).toHaveBeenCalled(); + }); +});