Skip to content

Commit

Permalink
Merge dc74356 into 5e445d0
Browse files Browse the repository at this point in the history
  • Loading branch information
espoirMur committed May 7, 2019
2 parents 5e445d0 + dc74356 commit 76f054f
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 30 deletions.
14 changes: 13 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@
},
"plugins": ["react", "prettier"],
"rules": {
"prettier/prettier": "error",
"newlines-between": "always",
"no-unused-vars": [
"error",
{
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_"
}
],
"react/prefer-stateless-function": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/no-static-element-interactions": "off",
Expand Down Expand Up @@ -51,6 +57,7 @@
"no-named-as-default": "off",
"arrow-body-style": ["error", "as-needed"],
"import/no-dynamic-require": "off",
"prettier/prettier":0,
"no-shadow": [
"error",
{
Expand Down Expand Up @@ -91,6 +98,11 @@
"specialLink": ["to"],
"aspects": ["noHref", "invalidHref", "preferButton"]
}
],
"react/forbid-prop-types": [2,
{
"forbid": ["object", "array"], "checkContextTypes": false, "checkChildContextTypes": false
}
]
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@
"dotenv-webpack": "^1.7.0",
"isomorphic-fetch": "^2.2.1",
"jest": "^24.7.1",
"jest-localstorage-mock": "^2.4.0",
"jsonwebtoken": "^8.5.1",
"node-sass": "^4.11.0",
"prop-types": "^15.7.2",
"query-string": "^6.5.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-html-parser": "^2.0.2",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/__actions__/createArticle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dotenv.config();
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

const BASE_URL = process.env.API_BASE_URL;
const BASE_URL = process.env.API_URL;
describe("article action creators", () => {
test("should return the on input change action", () => {
const data = {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/__actions__/readArticleAction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

const BASE_URL = process.env.API_BASE_URL;
const BASE_URL = process.env.API_URL;
describe("fetching article", () => {
test("should return fetching article action", () => {
expect(fetchingArticle()).toEqual({ type: FETCHING_ARTICLE });
Expand Down
53 changes: 53 additions & 0 deletions src/__tests__/__utils__/checkAuth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { mount } from "enzyme";
import React from "react";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import { checkAuth } from "../../utils/checkAuth";
import { EditArticle } from "../../views/EditArticle";
import { article } from "../__mocks__/testData";

describe.only("should test the check authenticated component", () => {
const initialState = { article };
const mockStore = configureStore();
let wrapper;
let store;
let ConditionalComponent;
const nextUrl = "/articles/new";
beforeEach(() => {
const props = {
article,
match: {
params: {
slug: "hello-world"
}
},
response: "",
location: { pathname: nextUrl },
history: []
};
store = mockStore(initialState);
ConditionalComponent = checkAuth(EditArticle);
wrapper = mount(
<Provider store={store}>
<MemoryRouter>
<ConditionalComponent {...props} />
</MemoryRouter>
</Provider>
).find("AuthenticatedComponent");
});
afterEach(() => {});
test("should redirect to next url if not logged in", () => {
expect(wrapper.props().history).toContain(`/sign_in?next=${nextUrl}`);
});

test("should return next component if the user is authenticated", () => {
// this test is giving me headaches
const localStorageProto = Object.getPrototypeOf(localStorage);
jest
.spyOn(localStorageProto, "getItem")
.mockImplementation(() => "fake- from test token");
expect(wrapper.isEmptyRender()).not.toBeTruthy();
localStorageProto.getItem.mockRestore();
});
});
26 changes: 24 additions & 2 deletions src/__tests__/__views__/Login.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const props = {
token: null,
handleSignIn,
handleTextInput,
socialAuth
socialAuth,
location: {},
history: []
};
const warper = shallow(<Login {...props} />);

Expand Down Expand Up @@ -101,12 +103,32 @@ describe("Login component", () => {
warper.setProps({
loginSuccess: true
});
expect(instance.handleNavigation).toBeCalledWith("");
expect(instance.props.history).toContain("/");
expect(handleSignIn).toHaveBeenCalledWith({
email: formData.email.value,
password: formData.password.value
});
});
it("should redirect the user to the next url on log in", () => {
const localStorageProto = Object.getPrototypeOf(localStorage);
jest
.spyOn(localStorageProto, "getItem")
.mockImplementation(() => "fake-1-token");
mockedFormData.mockReturnValue({});
Validator.formData = mockedFormData.bind(Validator);
warper.setProps({
password: formData.password.value,
email: formData.email.value
});
findElement(FormButton, 0).simulate("click");
warper.setProps({
loginSuccess: true,
location: { search: "?next=/articles/new" }
});
expect(instance.props.history).toContain("/articles/new");
localStorageProto.getItem.mockRestore();
});

it("should not signIn user if email and password are not provided", () => {
const errors = { email: "Email is required" };
mockedFormData.mockReturnValue({ ...errors });
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/Logo/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import PropTypes from "prop-types";

const Logo = ({ className, ...props }) => (
const Logo = ({ ...props }) => (
<img
src={require("../../../assets/img/quill-drawing-a-line.svg")}
alt="logo"
Expand Down
2 changes: 1 addition & 1 deletion src/redux/reducers/authReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default (state = INITIAL_STATE, action) => {
};
case SET_CURRENT_USER:
return {
...INITIAL_STATE,
...state,
currentUser: payload
};
default:
Expand Down
34 changes: 34 additions & 0 deletions src/utils/checkAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";

const checkAuth = Component => {
class AuthenticatedComponent extends React.Component {
componentWillMount() {
this.checkAuth();
}

async checkAuth() {
const { location, history } = this.props;
const token = await localStorage.getItem("token");
const currentUrl = location.pathname;
if (!token) {
history.push(`/sign_in?next=${currentUrl}`);
}
}

render() {
const token = localStorage.getItem("token");
return <div>{token ? <Component {...this.props} /> : ""}</div>;
}
}

AuthenticatedComponent.propTypes = {
location: PropTypes.shape({
pathname: PropTypes.string.isRequired
}).isRequired,
history: PropTypes.any.isRequired
};
return connect()(AuthenticatedComponent);
};
export { checkAuth };
26 changes: 22 additions & 4 deletions src/views/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Link, Redirect } from "react-router-dom";
import queryString from "query-string";
import {
handleTextInput,
handleSignIn,
Expand Down Expand Up @@ -53,10 +54,23 @@ export class Login extends Component {
}

render() {
const { email, password, message, isSubmitting, loginSuccess } = this.props;
const {
email,
password,
message,
isSubmitting,
loginSuccess,
history,
location
} = this.props;
const { errors } = this.state;
if (loginSuccess) {
return this.handleNavigation("");
if (loginSuccess && location) {
const { next: nextUrl } = queryString.parse(location.search);
if (!nextUrl) {
history.push("/");
} else if (localStorage.getItem("token")) {
history.push(nextUrl);
}
}
return (
<div className="auth" data-test="login">
Expand Down Expand Up @@ -155,7 +169,11 @@ Login.propTypes = {
handleTextInput: PropTypes.func.isRequired,
message: PropTypes.string,
loginSuccess: PropTypes.bool.isRequired,
socialAuth: PropTypes.func.isRequired
socialAuth: PropTypes.func.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired
}).isRequired,
history: PropTypes.arrayOf(PropTypes.string).isRequired
};
Login.defaultProps = {
message: ""
Expand Down
35 changes: 26 additions & 9 deletions src/views/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import {
BrowserRouter as Router,
Route,
Switch,
Redirect
} from "react-router-dom";
import MainRoutes from "./routes/MainRoutes";
import AuthRoutes from "./routes/AuthRoutes";

export default class Routers extends Component {
render() {
const token = localStorage.getItem("token");
return (
<Router>
<Switch>
{AuthRoutes.map((route, key) => (
<Route
exact
path={route.path}
component={route.component}
key={Number(key)}
/>
))}
{AuthRoutes.map((route, key) =>
route.path !== "/update_password" ? (
<Route
exact
path={route.path}
key={Number(key)}
render={props =>
token ? <Redirect to="/" /> : <route.component {...props} />
}
/>
) : (
<Route
exact
path={route.path}
component={route.component}
key={Number(key)}
/>
)
)}
<MainRoutes />
</Switch>
</Router>
Expand Down
32 changes: 22 additions & 10 deletions src/views/routes/MainRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import NotFound from "../NotFound";
import EditProfile from "../EditProfile";
import Profile from "../Profile";
import Settings from "../Settings";
import { checkAuth } from "../../utils/checkAuth";

export const routes = [
{
Expand All @@ -17,15 +18,17 @@ export const routes = [
},
{
path: "/articles/new",
component: NewArticle
component: NewArticle,
requireAuth: true
},
{
path: "/articles/:slug",
component: ReadArticle
},
{
path: "/articles/:slug/edit",
component: EditArticle
component: EditArticle,
requireAuth: true
},
{
path: "/profiles/:username",
Expand All @@ -47,14 +50,23 @@ export default class MainRoutes extends Component {
<div>
<Navbar />
<Switch>
{routes.map((route, index) => (
<Route
exact
path={route.path}
component={route.component}
key={Number(index)}
/>
))}
{routes.map((route, index) =>
route.requireAuth ? (
<Route
exact
path={route.path}
component={checkAuth(route.component)}
key={Number(index)}
/>
) : (
<Route
exact
path={route.path}
component={route.component}
key={Number(index)}
/>
)
)}
<Route component={NotFound} />
</Switch>
</div>
Expand Down

0 comments on commit 76f054f

Please sign in to comment.