Skip to content

Commit

Permalink
Merge pull request #37 from andela/ft/167190403-articles-from-tag
Browse files Browse the repository at this point in the history
Display all Articles With a Specific Tag
  • Loading branch information
chuxmykel committed Sep 13, 2019
2 parents c038120 + 95e2cc5 commit c2bccac
Show file tree
Hide file tree
Showing 15 changed files with 352 additions and 18 deletions.
13 changes: 13 additions & 0 deletions src/actions/__tests__/__snapshots__/tagAction.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`tag action tag action test tagAction should return expected value 1`] = `[Function]`;

exports[`tag action test view articles with tag action should display articles with tag 1`] = `
Array [
Object {
"payload": Array [],
"tag": Object {},
"type": "GET_ARTICLES_WITH_TAG",
},
]
`;
51 changes: 51 additions & 0 deletions src/actions/__tests__/tagAction.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import nock from 'nock';
import { getArticlesWithTagFromDb } from '@Actions/tagAction';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const url = 'https://a-haven-staging.herokuapp.com/api/v1';


describe('tag action', () => {
describe('tag action test', () => {
const tag = {
articleTag: 'javascript',
};

const history = {
push: jest.fn(),
};

it('tagAction should return expected value', () => {
expect(getArticlesWithTagFromDb(tag, history)).toMatchSnapshot();
});
});

describe('test view articles with tag action', () => {
let store;
const response = {
articles: [],
};

beforeEach(() => {
store = mockStore({});
});

afterEach(() => {
nock.cleanAll();
});

it('should display articles with tag', () => {
nock(url)
.post('/articles/tag/get-article')
.reply(200, response);

return store.dispatch(getArticlesWithTagFromDb({}, { push: jest.fn() }))
.then(() => {
expect(store.getActions()).toMatchSnapshot();
});
});
});
});
28 changes: 28 additions & 0 deletions src/actions/tagAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { GET_ARTICLES_WITH_TAG } from '@Actions/types';
import swal from '@sweetalert/with-react';
import { axiosInstance } from '@Utils/';


export const getArticlesWithTag = (payload, tag) => ({
type: GET_ARTICLES_WITH_TAG,
payload,
tag,
});

export const getArticlesWithTagFromDb = (tag, history) => async dispatch => {
try {
const response = await axiosInstance.post('/articles/tag/get-article', { tag: { articleTag: tag } });

if (response.status === 200) {
dispatch(getArticlesWithTag(response.data.articles, tag));
history.push('/tags');
}
} catch (err) {
swal({
text: err.response.data.error,
icon: 'error',
buttons: false,
timer: 4000,
});
}
};
2 changes: 2 additions & 0 deletions src/actions/types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ export const UPDATE_SUB = 'UPDATE_SUB';
export const UPDATE_CATEGORY_ARTICLES = 'UPDATE_CATEGORY_ARTICLES';
export const UPDATE_CATEGORY = 'UPDATE_CATEGORY';
export const UPDATE_MENU_ITEM = 'UPDATE_MENU_ITEM';

export const GET_ARTICLES_WITH_TAG = 'GET_ARTICLES_WITH_TAG';
18 changes: 18 additions & 0 deletions src/e2e/articlesTag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
'@tag': ['articlesTag'],
'User should be able to see articles list on clicking a tag': (browser) => {
browser
.url('http://localhost:9090/articles/Regional-Group-Manager')
.pause(3000)
.assert.visible('div[class="read-article"]')
.pause(500)
.moveToElement('div[class="comment-delete"]', 10, 10)
.waitForElementVisible('div[class="article-tag-div"]')
.pause(3000)
.click('li[class="liTag"]')
.pause(3000)
.waitForElementVisible('articlesList')
.pause(3000)
.end();
},
};
10 changes: 10 additions & 0 deletions src/reducers/__snapshots__/tagReducer.spec.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`tag reducer test should return new state 1`] = `
Object {
"articles": Array [
Object {},
],
"suppliedTag": undefined,
}
`;
2 changes: 2 additions & 0 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import searchReducer from './searchReducer';
import notifications from './notifications';
import { articleReducer } from './Articles';
import categoryReducer from './categoriesReducer';
import tagReducer from './tagReducer';

const reducers = combineReducers({
ui: uiReducer,
Expand All @@ -19,6 +20,7 @@ const reducers = combineReducers({
article: articleReducer,
notifications,
category: categoryReducer,
tag: tagReducer,
});

export default reducers;
19 changes: 19 additions & 0 deletions src/reducers/tagReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const initState = {
articles: [],
suppliedTag: '',
};

const tagReducer = (state = initState, action) => {
switch (action.type) {
case 'GET_ARTICLES_WITH_TAG':
return {
...state,
articles: action.payload,
suppliedTag: action.tag,
};
default:
return state;
}
};

export default tagReducer;
16 changes: 16 additions & 0 deletions src/reducers/tagReducer.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GET_ARTICLES_WITH_TAG } from '@Actions/types';
import tagReducer, { initState } from './tagReducer';

describe('tag reducer test', () => {
it('should return initial state', () => {
expect(tagReducer(undefined, {})).toEqual(initState);
});

it('should return new state', () => {
expect(tagReducer(initState, {
type: GET_ARTICLES_WITH_TAG,
payload: [{}],
suppliedTag: 'travel',
})).toMatchSnapshot();
});
});
2 changes: 2 additions & 0 deletions src/views/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ReadArticle from '@Views/Articles/ReadArticle';
import EditArticle from '@Views/Articles/EditArticle';
import Profile from '@Views/ProfilePage';
import NotFound from '@Views/404';
import ArticleTags from './ArticlesTag/ArticlesTag';
import './app.scss';

const App = () => (
Expand All @@ -32,6 +33,7 @@ const App = () => (
<Route exact path="/articles/:slug" component={ReadArticle} />
<Route exact path="/articles/:slug/edit" component={EditArticle} />
<Route exact path="/articles" component={CategoryPage} />
<Route exact path="/tags" component={ArticleTags} />
<Route component={NotFound} />
</Switch>
</div>
Expand Down
49 changes: 31 additions & 18 deletions src/views/Articles/ReadArticle/ReadArticle.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable react/no-unused-prop-types */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
Expand All @@ -6,6 +8,7 @@ import { withRouter } from 'react-router-dom';
import swal from '@sweetalert/with-react';
import Loader from 'react-loader-spinner';
import { readArticle, deleteAnArticle } from '@Actions/Articles';
import { getArticlesWithTagFromDb } from '@Actions/tagAction';
import connectComponent from '@Lib/connect-component';
import Icon from '@Components/Icon';
import { convertToHtml, isEmpty } from '@Utils/';
Expand Down Expand Up @@ -70,6 +73,11 @@ export class ReadArticle extends Component {
});
}

handleTagClick = async (tag) => {
const { getArticlesTag, history } = this.props;
await getArticlesTag(tag, history);
}

render = () => {
const {
ui: { loading },
Expand All @@ -81,15 +89,18 @@ export class ReadArticle extends Component {
readTime,
image,
articleBody,
tagList,
Tags,
likesCount,
dislikesCount,
comment,
},
} = this.props;
const tags = tagList ? tagList.split(' ')
.filter(tag => tag.length > 0)
.map((tag, i) => (<li key={i}><p>{tag}</p></li>)) : null;

const tags = Tags ? Tags.map((tag, i) => (
<li className="liTag" key={i}>
<p onClick={() => this.handleTagClick(tag.name)}>{tag.name}</p>
</li>
)) : null;
const body = this.parseArticleBody(articleBody);

const loader = (
Expand Down Expand Up @@ -129,13 +140,13 @@ export class ReadArticle extends Component {
<div className="vertical-center read-time">
<p>{`${readTime || 0} min${readTime > 1 ? 's' : ''} read`}</p>
{this.isMyArticle() && (
<button
data-test="edit-article"
type="button"
onClick={this.editArticle}
>
Edit Article
</button>
<button
data-test="edit-article"
type="button"
onClick={this.editArticle}
>
Edit Article
</button>
)}
</div>
</div>
Expand Down Expand Up @@ -169,13 +180,13 @@ export class ReadArticle extends Component {
<Icon name="comments" />
<p className="icon-label">{comment ? comment.length : 0}</p>
{this.isMyArticle() && (
<button
className="delete-icon"
type="button"
onClick={this.deleteArticle}
>
<Icon name="trash" />
</button>
<button
className="delete-icon"
type="button"
onClick={this.deleteArticle}
>
<Icon name="trash" />
</button>
)}
</div>
</div>
Expand All @@ -194,11 +205,13 @@ ReadArticle.propTypes = {
history: PropTypes.shape().isRequired,
auth: PropTypes.shape().isRequired,
ui: PropTypes.shape().isRequired,
getArticlesTag: PropTypes.func.isRequired,
};

export default connectComponent(
withRouter(ReadArticle), {
getSingleArticle: slug => readArticle(slug),
deleteArticle: (slug, history) => deleteAnArticle(slug, history),
getArticlesTag: getArticlesWithTagFromDb,
},
);
5 changes: 5 additions & 0 deletions src/views/Articles/ReadArticle/ReadArticle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,8 @@
align-items: center;
}
}

.liTag :hover {
cursor: pointer;
transform: scale(1.2);
}
Loading

0 comments on commit c2bccac

Please sign in to comment.