Skip to content

Commit

Permalink
feature(delete-book): refactor utitlity functions
Browse files Browse the repository at this point in the history
  • Loading branch information
segunolalive committed Dec 10, 2017
1 parent e0a2994 commit 24e2948
Show file tree
Hide file tree
Showing 24 changed files with 462 additions and 119 deletions.
2 changes: 1 addition & 1 deletion .babelrc
@@ -1,6 +1,6 @@
{
"presets": ["stage-2", "env", "react"],
"plugins": ["transform-object-rest-spread"],
"plugins": ["transform-object-rest-spread", "transform-class-properties"],
"env": {
"production": {
"plugins": [
Expand Down
35 changes: 21 additions & 14 deletions .eslintrc
Expand Up @@ -8,14 +8,15 @@
"mocha": true,
"jest": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": { "jsx": true }
"ecmaVersion": 8,
"ecmaFeatures": { "jsx": true, "classes": true }
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js",".jsx"]
"extensions": [".js", ".jsx"]
}
}
},
Expand All @@ -34,19 +35,25 @@
"no-shadow": ["error", { "allow": ["req", "res", "err"] }],
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"valid-jsdoc": ["error", {
"requireReturn": true,
"requireReturnType": true,
"requireParamDescription": false,
"requireReturnDescription": true
}],
"valid-jsdoc": [
"error",
{
"requireReturn": true,
"requireReturnType": true,
"requireParamDescription": false,
"requireReturnDescription": true
}
],
"class-methods-use-this": 0,
"require-jsdoc": ["error", {
"require-jsdoc": [
"error",
{
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true
}
}]
}
]
}
}
59 changes: 40 additions & 19 deletions README.md
@@ -1,26 +1,28 @@
# helloBooks

[![License](http://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT)
[![Build Status](https://travis-ci.org/segunolalive/helloBooks.svg?branch=development)](https://travis-ci.org/segunolalive/helloBooks)
[![Coverage Status](https://coveralls.io/repos/github/segunolalive/helloBooks/badge.svg?branch=development)](https://coveralls.io/github/segunolalive/helloBooks?branch=development)
[![Code Climate](https://codeclimate.com/github/segunolalive/helloBooks/badges/gpa.svg)](https://codeclimate.com/github/segunolalive/helloBooks?branch=development)

# helloBooks
## A Library app

### A Library app
Hello books is an application that provides users with access to books from wherever they are.
Being a virtual library, users can borrow and read their favorite books using any device.
HelloBooks exposes RESTful API endpoints such that anyone can customize their method of consuming
the resources.

#### Built With
### Built With

* [NodeJS](https://nodejs.org/en/) - A JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
* [PostgreSQL](https://www.postgresql.org/) - A powerful, open source object-relational database system.
* [Sequelize](http://docs.sequelizejs.com/) - A promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and more.
* [ExpressJS](http://expressjs.com/) - Fast, unopinionated, minimalist web framework for Node.js.
* [Reactjs](https://reactjs.org/) - A declarative component-based JavaScript library for building user interfaces


#### Getting Started
```

```markdown
# Clone your fork of this repository

# Ensure NodeJS, PostgreSQL and Sequelize cli are globally installed
Expand Down Expand Up @@ -51,30 +53,47 @@ npm run start
```

#### Features
- Authentication is via [**JSON Web Tokens**](https://jwt.io/)
- Login/Sign up to gain access to routes
- A library of books from different categories
- Ability to borrow books repeatedly
- Track your reading/borrowing history
- Admin access to add and modify book details

* Authentication is via [**JSON Web Tokens**](https://jwt.io/)
* Login/Sign up to gain access to routes
* A library of books from different categories
* Ability to borrow books repeatedly
* Track your reading/borrowing history
* Admin access to add and modify book details

#### API Documentation
- <https://www.segunolalive-hellobooks.com.herokuapp/api-docs>

* <https://www.segunolalive-hellobooks.com.herokuapp/api-docs>

#### Testing
Run `npm test`

For client-side tests, run `npm run test:client`

For server-side tests, run `npm run test:server`

For both, run `npm test`

For end to end tests, start by running `npm run e2e-setup`

Then start the client by running `npm run start:client`.

In another terminal window, run `npm run start:server:e2e` to start the application server in test-mode.

In a third terminal window run `npm run e2e:server` to start the selenium server.

In a fourth terminal window run `npm run test:e2e`

#### Contributing

Hello books is open source and contributions are highly welcomed.

If you would like to contribute, follow the instructions below.

- Fork this project.
- Checkout a new branch
- Make your changes and commit.
- Keep commit messages atomic.
- Raise a pull request against development.
* Fork this project.
* Checkout a new branch
* Make your changes and commit.
* Keep commit messages atomic.
* Raise a pull request against development.

**NB:** All Pull requests must be made against development branch. Pull Requests against master would be rejected.

Expand All @@ -84,8 +103,10 @@ See project wiki for coding style guide, commit message, pull request and branch

#### Acknowledgments

* Andela Fellowship (https://andela.com/)
* [Andela Fellowship](https://andela.com/)

---

#### License

MIT License
67 changes: 65 additions & 2 deletions client/__tests__/actions/authActions.spec.js
Expand Up @@ -5,8 +5,13 @@ import configureMockStore from 'redux-mock-store';
import mockData from '../__mocks__/mockData';
import mockLocalStorage from '../__mocks__/mockLocalStorage';
import { login } from '../../actions/authActions/login';
import logout from '../../actions/authActions/logout';
import { signUp } from '../../actions/authActions/signup';
import requestResetPassword from '../../actions/authActions/requestResetPassword';
import resetPassword from '../../actions/authActions/resetPassword';
import actionTypes from '../../actions/actionTypes';
import notify from '../__mocks__/notify';
import { request } from 'https';

const middleware = [thunk];
const mockStore = configureMockStore(middleware);
Expand Down Expand Up @@ -76,7 +81,7 @@ describe('Auth Actions', () => {
});
it('creates AUTH_LOADING on signup failure', () => {
const { authResponse } = mockData;
moxios.stubRequest('/api/v1/users/signin', {
moxios.stubRequest('/api/v1/users/signup', {
status: 400,
response: authResponse
});
Expand All @@ -85,9 +90,67 @@ describe('Auth Actions', () => {
{ type: actionTypes.AUTH_LOADING, state: false }
];
const store = mockStore({});
return store.dispatch(login({})).then(() => {
return store.dispatch(signUp({})).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});

describe('logout', () => {
it('creates LOGOUT when logout action is successful', (done) => {
const store = mockStore({});
const expectedActions = [{ type: actionTypes.LOGOUT }];
store.dispatch(logout());
expect(store.getActions()).toEqual(expectedActions);
done();
});
});

describe('requestResetPassword', () => {
it('provides a notification on success', () => {
moxios.stubRequest('/api/v1/users/forgot-password', {
status: 200,
response: mockData.authResponse
});
expect(notify.success).toHaveBeenCalled();
});

it("provides a notification on success", () => {
window.requestResetPassword = () => () => Promise.resolve(1);
requestResetPassword("password", "token");
expect(notify.success).toHaveBeenCalled();
});

it('provides a notification on failure', () => {
moxios.stubRequest('/api/v1/users/forgot-password', {
status: 500,
response: mockData.authResponse
});
expect(notify.error).toHaveBeenCalled();
});
});

describe('resetPassword', () => {
it('provides a notification on success', () => {
moxios.stubRequest('/api/v1/users/reset-password/1234yyjhkopi123', {
status: 200,
response: mockData.authResponse
});
expect(notify.success).toHaveBeenCalled();
});

it('provides a notification on success', () => {
window.resetPassword = () => () => Promise.resolve(1);
resetPassword('password', 'token');
expect(notify.success).toHaveBeenCalled();
});

it('provides a notification on failure', () => {
moxios.stubRequest('/api/v1/users/reset-password/1234yyjhkopi123', {
status: 500,
response: mockData.authResponse
});
expect(notify.error).toHaveBeenCalled();
});
});
});
17 changes: 16 additions & 1 deletion client/__tests__/components/Dashboard/index.spec.jsx
Expand Up @@ -15,9 +15,10 @@ const store = mockStore(mockStoreData);
let props = {
fetchBorrowedBooks: jest.fn(),
returnBook: jest.fn(),
readBook: jest.fn(),
...mockStoreData.authReducer,
...mockStoreData.bookReducer.borrowedBooks,
fetchingBorrowedBooks: false,
fetchingBorrowedBooks: false
};
const setUp = () => (shallow(<Dashboard { ...props } />));

Expand Down Expand Up @@ -53,6 +54,20 @@ describe('Dashboard Component', () => {
expect(componentDidMountSpy).toHaveBeenCalledTimes(1);
});

it("should call the handleReturnBook method", () => {
const wrapper = shallow(<Dashboard {...props} />);
const handleReturnBookSpy = jest.spyOn(wrapper.instance(), "handleReturnBook");
wrapper.instance().handleReturnBook(1);
expect(handleReturnBookSpy).toHaveBeenCalledTimes(1);
});

it("should call the readBook method", () => {
const wrapper = shallow(<Dashboard {...props} />);
const readBookSpy = jest.spyOn(wrapper.instance(), "readBook");
wrapper.instance().readBook(1);
expect(readBookSpy).toHaveBeenCalledTimes(1);
});

it('should redirect to login page if user is not logged in', () => {
props = { ...props, isLoggedIn: false };
const wrapper = shallow(<Dashboard { ...props } />);
Expand Down
2 changes: 1 addition & 1 deletion client/__tests__/components/common/Modal.spec.jsx
Expand Up @@ -22,7 +22,7 @@ describe('Modal', () => {
});

it("calls modalAction prop function when a confirm button is clicked", () => {
const confirmButton = wrapper.find("button").at(1);
const confirmButton = wrapper.find("button").at(0);
confirmButton.simulate("click");
expect(wrapper.instance().props.modalAction).toHaveBeenCalled();
});
Expand Down
1 change: 1 addition & 0 deletions client/__tests__/setupTest.js
Expand Up @@ -6,6 +6,7 @@ global.$ = $;
$.prototype.sideNav = () => { };
$.prototype.material_select = () => { };
$.prototype.modal = () => { };
$.prototype.ready = fn => fn();

global.Materialize = {
toast: () => {}
Expand Down
18 changes: 18 additions & 0 deletions client/__tests__/utils/requestImageUrl.spec.js
@@ -0,0 +1,18 @@
import requestImageUrl from '../../utils/requestImageUrl';

describe('requestImageUrl', () => {
it('returns the url if no configuration object is passed', () => {
const baseUrl = "localhost:300";
const result = requestImageUrl(baseUrl);
expect(result).toEqual(baseUrl);
});

it('adds width and height configuration the url', () => {
const baseUrl = "localhost:300";
const result = requestImageUrl(baseUrl, {width: 10, height: 10 });
expect(result).not.toEqual(baseUrl);
expect(result).toContain('w_10');
expect(result).toContain("h_10");
});
});

17 changes: 16 additions & 1 deletion client/__tests__/utils/saveLocally.spec.js
Expand Up @@ -16,14 +16,29 @@ describe('saveState', () => {
expect(localStorage.getItem('state').id).toEqual(2);
}, 1000);
});

it('fails silently', () => {
localStorage.setItem = () => {throw new Error('something broke')};
saveState(state);
setTimeout(() => {
expect(localStorage.getItem("state").id).toEqual(false);
}, 1000);
})
});

describe('loadState', () => {
it('reads state from localStorage', () => {
const loadedState = loadState();
setTimeout(() => {
expect(loadedState.id).toEqual(2);
console.log(localStorage);
}, 1000);
});

it('returns false if state is null', () => {
const nullState = null
saveState(nullState);
setTimeout(() => {
expect(loadState()).toEqual(false);
}, 5000);
});
});
5 changes: 2 additions & 3 deletions client/actions/api.js
@@ -1,8 +1,7 @@
let api = '/api/v1';

if (process.env.NODE_ENV === ('development' || 'test')) {
api = 'http://localhost:4000/api/v1';
}
api = process.env.NODE_ENV === ('development' || 'test') ?
`http://localhost:4000${api}` : api;

/**
* api url
Expand Down
7 changes: 2 additions & 5 deletions client/actions/authActions/login.js
Expand Up @@ -42,12 +42,9 @@ export const login = data => (dispatch) => {
dispatch(authLoading(false));
notify.success(response.data.message);
return response.data;
}, (error) => {
notify.error(error.response.data.message);
return dispatch(authLoading(false));
})
.catch(() => {
notify.error('Something terrible happened. We\'ll fix that');
.catch((error) => {
notify.error(error.response.data.message);
return dispatch(authLoading(false));
});
};

0 comments on commit 24e2948

Please sign in to comment.