Skip to content

Commit

Permalink
refactor: Add info and container
Browse files Browse the repository at this point in the history
Signed-off-by: Chris <christin@wednesday.is>
  • Loading branch information
christin-wednesday committed May 30, 2022
1 parent 05501ed commit bfdedc9
Show file tree
Hide file tree
Showing 26 changed files with 1,116 additions and 1,767 deletions.
12 changes: 4 additions & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ const fs = require('fs');
const path = require('path');

const prettierOptions = JSON.parse(fs.readFileSync(path.resolve(__dirname, '.prettierrc'), 'utf8'));

module.exports = {
parser: 'babel-eslint',
env: {
browser: true,
es6: true,
amd: true,
'jest/globals': true,
commonjs: true
es2021: true,
'jest/globals': true
},
plugins: ['react', 'react-hooks', 'jest'],

extends: ['prettier', 'prettier/react', 'prettier-standard', 'plugin:react/recommended', 'eslint:recommended'],
extends: ['eslint:recommended', 'prettier', 'next'],
plugins: ['prettier', 'jest'],
rules: {
'prettier/prettier': ['error', prettierOptions],
'import/no-webpack-loader-syntax': 0,
Expand Down
5 changes: 1 addition & 4 deletions app/components/Meta/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ import { useIntl } from 'react-intl';
import favicon from '@images/favicon.ico';

function Meta({ title, description, useTranslation }) {
let intl;
if (useTranslation) {
intl = useIntl();
}
const intl = useIntl();

return (
<Head>
Expand Down
2 changes: 1 addition & 1 deletion app/configureStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ export default function configureStore(initialState = {}) {
return store;
}

export const wrapper = createWrapper(configureStore, { debug: true });
export const wrapper = createWrapper(configureStore, { debug: process.env.NODE_ENV === 'development' });
3 changes: 3 additions & 0 deletions app/containers/Info/Loadable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import loadable from '@utils/loadable';

export default loadable(() => import('./index'));
89 changes: 89 additions & 0 deletions app/containers/Info/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
*
* Info Container
*
*/
import Text from '@app/components/Text';
import fonts from '@app/themes/fonts';
import { useInjectSaga } from '@app/utils/injectSaga';
import { Container } from '@components/styled';
import Title from '@components/Title';
import { Card, Col, Row, Skeleton } from 'antd';
import isEmpty from 'lodash/isEmpty';
import { useRouter } from 'next/router';
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { compose } from 'redux';
import injectSaga from '@utils/injectSaga';
import { createStructuredSelector } from 'reselect';

import { infoCreators } from './reducer';
import saga from './saga';
import { selectInfoData, selectInfoLoading } from './selectors';

export function Info({ details, params, loading, dispatchRequestInfo, fallBackDetails }) {
const router = useRouter();
const { query } = router;
const { name, description, stargazersCount } = { ...(details || {}), ...(fallBackDetails || {}) } || {};
useInjectSaga({ key: 'info', saga });
useEffect(() => {
if (isEmpty(details) && !!params?.name && !!query?.owner) {
dispatchRequestInfo(params.name, query.owner);
}
}, [params]);

const shouldLoad = loading || isEmpty({ ...fallBackDetails, ...details });

return (
<Row justify="center" align="middle" style={{ height: '100vh' }} flex="1 1 90%">
<Col>
<Container style={{ minWidth: '30rem' }} padding={20}>
<Card
title={React.createElement(Title, {
name,
loading: shouldLoad,
stargazersCount
})}
>
<Skeleton loading={shouldLoad} active>
<Text styles={fonts.style.subheading()}>{description}</Text>
</Skeleton>
</Card>
</Container>
</Col>
</Row>
);
}

Info.propTypes = {
details: PropTypes.shape({
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
stargazersCount: PropTypes.number.isRequired
}),
params: PropTypes.shape({
name: PropTypes.string.isRequired
}),
loading: PropTypes.bool.isRequired,
dispatchRequestInfo: PropTypes.func.isRequired,
fallBackDetails: PropTypes.object
};

const mapStateToProps = createStructuredSelector({
loading: selectInfoLoading(),
fallBackDetails: selectInfoData()
});

function mapDispatchToProps(dispatch) {
return {
dispatchRequestInfo: (repo, owner) => dispatch(infoCreators.requestInfo(repo, owner))
};
}

const withConnect = connect(mapStateToProps, mapDispatchToProps);

export const InfoTest = compose(injectIntl)(Info);

export default compose(withConnect, injectSaga({ key: 'info', saga }))(Info);
42 changes: 42 additions & 0 deletions app/containers/Info/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
*
* /info reducer
*
*/
import produce from 'immer';
import { createActions } from 'reduxsauce';
import { setError, setData, startLoading, stopLoading, PAYLOAD } from '../../utils/reducer';

export const INFO_PAYLOAD = {
REPO: 'repo',
OWNER: 'owner'
};

export const initialState = { [PAYLOAD.LOADING]: false, [PAYLOAD.DATA]: {}, [PAYLOAD.ERROR]: null };

export const { Types: infoTypes, Creators: infoCreators } = createActions({
requestInfo: [INFO_PAYLOAD.REPO, INFO_PAYLOAD.OWNER],
successInfo: [PAYLOAD.DATA],
failureInfo: [PAYLOAD.ERROR]
});

export const infoReducer = (state = initialState, action) =>
produce(state, (draft) => {
switch (action.type) {
case infoTypes.REQUEST_INFO:
startLoading(draft);
break;
case infoTypes.SUCCESS_INFO:
stopLoading(draft);
setData(draft, action);
break;
case infoTypes.FAILURE_INFO:
stopLoading(draft);
setError(draft);
break;
default:
draft = initialState;
}
});

export default infoReducer;
21 changes: 21 additions & 0 deletions app/containers/Info/saga.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { put, call, takeLatest } from 'redux-saga/effects';
import { getRepo } from '@services/info';
import { ERRORS } from '@app/utils/constants';
import { infoTypes, infoCreators, INFO_PAYLOAD } from './reducer';

export function* requestInfo(action) {
try {
if (action[INFO_PAYLOAD.REPO] || action[INFO_PAYLOAD.OWNER]) {
throw new Error(ERRORS.INSUFFICIENT_INFO);
}
const response = yield call(getRepo, action[INFO_PAYLOAD.REPO], action[INFO_PAYLOAD.OWNER]);
yield put(infoCreators.successInfo(response));
} catch (error) {
console.error(error.message);
yield put(infoCreators.failureInfo(error.message));
}
}

export default function* appSaga() {
yield takeLatest(infoTypes.REQUEST_INFO, requestInfo);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { PAYLOAD } from '@app/utils/reducer';
import { createSelector } from 'reselect';
import { initialState } from '../reducers/info';
import get from 'lodash/get';
import { initialState } from './reducer';

const selectInfoDomain = (state) => state.info || initialState;

export const selectInfoLoading = () => createSelector(selectInfoDomain, (substate) => substate.get('loading'));
export const selectInfoData = () => createSelector(selectInfoDomain, (substate) => substate.get('data'));
export const selectInfoLoading = () => createSelector(selectInfoDomain, (substate) => get(substate, PAYLOAD.LOADING));
export const selectInfoData = () => createSelector(selectInfoDomain, (substate) => get(substate, PAYLOAD.DATA, {}));
24 changes: 24 additions & 0 deletions app/containers/Info/tests/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
*
* Tests for Info container
*
*
*/

import React from 'react';
// import { fireEvent } from '@testing-library/dom';
import { renderProvider } from '@utils/testUtils';
import { InfoTest as Info } from '../index';

describe('<Info /> container tests', () => {
// let submitSpy

beforeEach(() => {
// submitSpy = jest.fn();
});

it('should render and match the snapshot', () => {
const { baseElement } = renderProvider(<Info />);
expect(baseElement).toMatchSnapshot();
});
});
17 changes: 17 additions & 0 deletions app/containers/Info/tests/reducer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { infoReducer, infoTypes, initialState } from '../reducer';

describe('Info reducer tests', () => {
it('should return the initial state by default', () => {
expect(infoReducer(undefined, {})).toEqual(initialState);
});

it('should return the updated state when an action of type DEFAULT is dispatched', () => {
const expectedResult = { ...initialState, somePayLoad: 'Mohammed Ali Chherawalla' };
expect(
infoReducer(initialState, {
type: infoTypes.DEFAULT_ACTION,
somePayLoad: 'Mohammed Ali Chherawalla'
})
).toEqual(expectedResult);
});
});
15 changes: 15 additions & 0 deletions app/containers/Info/tests/saga.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Test info sagas
*/

import { takeLatest } from 'redux-saga/effects';
import infoSaga, { defaultFunction } from '../saga';
import { infoTypes } from '../reducer';

describe('Info saga tests', () => {
const generator = infoSaga();

it('should start task to watch for DEFAULT_ACTION action', () => {
expect(generator.next().value).toEqual(takeLatest(infoTypes.DEFAULT_ACTION, defaultFunction));
});
});
19 changes: 19 additions & 0 deletions app/containers/Info/tests/selectors.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { selectInfo, selectSomePayLoad } from '../selectors';

describe('Info selector tests', () => {
const mockedState = {
info: {
somePayLoad: 'W.S'
}
};

it('should select the info state', () => {
const infoSelector = selectInfo();
expect(infoSelector(mockedState)).toEqual(mockedState.info);
});

it('should select the somePayLoad state', () => {
const somePayLoadSelector = selectSomePayLoad();
expect(somePayLoadSelector(mockedState)).toEqual(mockedState.info.somePayLoad);
});
});
4 changes: 2 additions & 2 deletions app/containers/Repos/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ Repos.defaultProps = {
maxwidth: 500
};
const mapStateToProps = createStructuredSelector({
reposData: selectReposData(),
reposError: selectReposError(),
repos: selectReposData(),
error: selectReposError(),
searchKey: selectReposSearchKey()
});

Expand Down
17 changes: 12 additions & 5 deletions app/containers/Repos/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Repos reducer
*
*/
import { PAYLOAD, chainDraftSetters, startLoading, stopLoading, setError } from '@app/utils/reducer';
import { PAYLOAD, startLoading, stopLoading, setError, setData } from '@app/utils/reducer';
import produce from 'immer';
import { createActions } from 'reduxsauce';

Expand All @@ -13,8 +13,8 @@ export const REPOS_PAYLOAD = {

export const initialState = {
[REPOS_PAYLOAD.SEARCH_KEY]: null,
data: [],
error: null
[PAYLOAD.DATA]: {},
[PAYLOAD.ERROR]: null
};

export const { Types: reposActionTypes, Creators: reposActionCreators } = createActions({
Expand All @@ -24,18 +24,25 @@ export const { Types: reposActionTypes, Creators: reposActionCreators } = create
clearGithubRepos: null
});

export const reposReducer = (state, action) =>
export const reposReducer = (state = initialState, action) =>
produce(state, (draft) => {
switch (action.type) {
case reposActionTypes.REQUEST_GET_GITHUB_REPOS:
startLoading(draft);
break;
case reposActionTypes.SUCCESS_GET_GITHUB_REPOS:
stopLoading(draft);
setData(draft, action);
break;
case reposActionTypes.FAILURE_GET_GITHUB_REPOS:
chainDraftSetters(draft, [stopLoading, [setError, [action]]]);
stopLoading(draft);
setError(draft);
break;
case reposActionTypes.CLEAR_GITHUB_REPOS:
draft = initialState;
break;
default:
draft = initialState;
}
});

Expand Down
10 changes: 5 additions & 5 deletions app/containers/Repos/saga.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { put, takeLatest } from 'redux-saga/effects';
// import { getRepos } from '@services/repoApi';
import { reposActionTypes, reposActionCreators } from './reducer';
import { call, put, takeLatest } from 'redux-saga/effects';
import { getRepos } from '@services/repoApi';
import { reposActionTypes, reposActionCreators, REPOS_PAYLOAD } from './reducer';

const { REQUEST_GET_GITHUB_REPOS } = reposActionTypes;
const { successGetGithubRepos, failureGetGithubRepos } = reposActionCreators;

export function* getGithubRepos(action) {
// const response = yield call(getRepos, action[REPOS_PAYLOAD.SEARCH_KEY]);
const { data, ok } = {};
const response = yield call(getRepos, action[REPOS_PAYLOAD.SEARCH_KEY]);
const { data, ok } = response;
if (ok) {
yield put(successGetGithubRepos(data));
} else {
Expand Down
15 changes: 8 additions & 7 deletions app/reducers.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
/*
Combine all reducers in this file and export the combined reducers.
Combine all reducers in this file and export the combined reducers.
*/

import { combineReducers } from 'redux';
// import { reposReducer } from './containers/Repos/reducer';
import { enableAllPlugins } from 'immer';
// import infoReducer from './store/reducers/info';
import { combineReducers } from 'redux';

import repos from './containers/Repos/reducer';
import info from './containers/Info/reducer';

enableAllPlugins();

export default function createReducer(injectedReducer = {}) {
const rootReducer = combineReducers({
...injectedReducer
// repos: reposReducer,
// info: infoReducer
...injectedReducer,
repos,
info
});

return rootReducer;
Expand Down
Loading

0 comments on commit bfdedc9

Please sign in to comment.