Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect store hook #24

Merged
merged 30 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d0a8f59
refactor(footer): replace 'connect' HOC with useSelector
aneurysmjs Aug 12, 2019
8cbbeb6
Merge branch 'master' into connect-store-hook
aneurysmjs Aug 13, 2019
6eb3802
fix(footer): resolve conflicts
aneurysmjs Aug 14, 2019
bcd2b66
refactor(home): connect to store using hooks
aneurysmjs Aug 16, 2019
72cf431
fix(fetproducts): apply properly Flow's typing
aneurysmjs Aug 18, 2019
7e293c1
feat(core): create <UserMenu /> component
aneurysmjs Aug 19, 2019
f0195ae
refactor(header): user <UserMenu />
aneurysmjs Aug 19, 2019
2f87233
refactor(header): move <Sidebar /> into <UserMenu />
aneurysmjs Aug 20, 2019
d8007aa
test(usermenu): test when toggling sidebar
aneurysmjs Aug 20, 2019
fbb491c
style(plopfile): apply eslint formatting rules
aneurysmjs Aug 20, 2019
64eac60
feat(store): add cart state
aneurysmjs Aug 20, 2019
3738ea9
fix(reducers/cart): type for getter
aneurysmjs Aug 20, 2019
d533941
feat(usermenu): connect to store and get cart state
aneurysmjs Aug 20, 2019
8797114
test(usermenu.test): wrapped <UserMenu /> in a <Provider />
aneurysmjs Aug 20, 2019
2dd2622
test(usermenu.test): test for cart's quantity
aneurysmjs Aug 20, 2019
7806634
test(header): fix error when not using <Provider />
aneurysmjs Aug 21, 2019
bd5de94
test(usermenu): use renderWithRedux
aneurysmjs Aug 21, 2019
f6e3076
test(home.test): fix Error Actions may not have an undefined "type" p…
aneurysmjs Aug 22, 2019
0b7c442
chore(jest): create axios mock
aneurysmjs Aug 22, 2019
191e492
test(__mocks__): create axios mock
aneurysmjs Aug 26, 2019
2a07323
test(middlewares): add test for apiMiddleware
aneurysmjs Aug 26, 2019
7a4b1da
test(middlewares/logger): add test
aneurysmjs Aug 27, 2019
88535db
test(actions/fetchproducts): test async action creator
aneurysmjs Aug 27, 2019
1336f26
Merge pull request #27 from aneurysmjs/issue/26/test-async-actions-an…
aneurysmjs Aug 27, 2019
8e79c43
refactor(*): remove flow's pragma from test
aneurysmjs Aug 28, 2019
282b46c
refactor(store): create selectors folder
aneurysmjs Aug 28, 2019
ea12e70
feat(commontype): add ResponseError type
aneurysmjs Aug 28, 2019
dc0be66
test(reducers/products): add missing test
aneurysmjs Aug 28, 2019
e5ff11c
test(home): skip test
aneurysmjs Aug 28, 2019
7497747
feat(home): display spinner while loading and error when failing
aneurysmjs Aug 28, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"purgecss-webpack-plugin": "1.5.0",
"react-dev-utils": "9.0.3",
"react-test-renderer": "16.9.0",
"redux-mock-store": "1.5.3",
"rimraf": "3.0.0",
"run-sequence": "2.2.1",
"sass-loader": "7.2.0",
Expand Down
4 changes: 2 additions & 2 deletions plopfile.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const components = './src/app/components';

const isFunctional = componentType => componentType === 'functional';
const isFunctional = (componentType) => componentType === 'functional';

module.exports = function(plop) {
module.exports = function plopFn(plop) {
plop.setGenerator('React Component', {
description: 'Create a new React component',
prompts: [
Expand Down
9 changes: 9 additions & 0 deletions src/app/__mocks__/axios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @link https://medium.com/@kilgarenone/use-jest-to-test-redux-async-action-creator-with-axios-in-a-create-react-app-app-d9c9b52eba5e
*/
const axiosMock = jest.genMockFromModule('axios');

// this is the key to fix the axios.create() undefined error!
axiosMock.create = jest.fn(() => axiosMock);

export default axiosMock;
77 changes: 29 additions & 48 deletions src/app/components/core/Footer/Footer.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,45 @@
// @flow strict
import React, { Component } from 'react';
import React from 'react';
// $FlowIgnore
import { connect } from 'react-redux';
import { useSelector } from 'react-redux';

import { footerData } from '@/store/actions';
import { getFooter } from '@/store/reducers/footer';
import type { FooterType } from '@/store/types/FooterType';

import Icon from '@/components/base/Icon/Icon';

import './Footer.scss';

type PropsType = {
footer: FooterType
};

class Footer extends Component<PropsType> {
render() {
const {
footer: {
social,
},
} = this.props;

return (
<footer className="footer">
<div className="container">
<div className="footer__top">
<div className="row">
<div className="col">
<div data-testid="social">
{social && social.map((s) => (
<Icon
key={s.id}
path={`social/${s.icon}`}
/>
))}
</div>
const Footer = () => {
const { social }: FooterType = useSelector(getFooter);

return (
<footer className="footer">
<div className="container">
<div className="footer__top">
<div className="row">
<div className="col">
<div data-testid="social">
{social && social.map((s) => (
<Icon
key={s.id}
path={`social/${s.icon}`}
/>
))}
</div>
</div>
</div>
</div>
<div className="footer__bottom">
<div className="footer__copyright">
<span data-testid="copyright">Copyright © {new Date().getFullYear()}. All Rights Reserved</span>
</div>
</div>
<div className="footer__bottom">
<div className="footer__copyright">
<span data-testid="copyright">
Copyright © {new Date().getFullYear()}. All Rights Reserved
</span>
</div>
</footer>
);
}
}

const mapStateToProps = (state) => ({
footer: state.footer,
});

const mapDispatchToProps = {
footerData,
</div>
</footer>
);
};

export default connect(
mapStateToProps,
mapDispatchToProps,
)(Footer);
export default Footer;
1 change: 0 additions & 1 deletion src/app/components/core/Footer/Footer.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow strict
import React from 'react';
// $FlowFixMe
import { cleanup } from '@testing-library/react';
Expand Down
54 changes: 8 additions & 46 deletions src/app/components/core/Header/Header.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,15 @@
// @flow strict
import React, { useState } from 'react';
import React from 'react';

import Icon from '@/components/base/Icon/Icon';
import Navigation from '@/components/core/Navigation/Navigation';
import { useLazy } from '@/hooks/useLazy';
import { UserMenu } from '@/components/core/UserMenu';

import './Header.scss';

const Header = () => {
const [open, setOpen] = useState(false);

const handleOpen = () => setOpen(!open);

const Sidebar = useLazy(
() => import(/* webpackChunkName: "Sidebar" */'@/components/shared/Sidebar/Sidebar'),
open,
);

return (
<div className="header">
{Sidebar
? (
<Sidebar
title="Cart"
side="right"
isOpen={open}
onClose={handleOpen}
>
<p className="lead">
You have nothing, let&apos;s shop!
</p>
</Sidebar>)
: null}
<Navigation />
<div className="header__user-menu">
<span
tabIndex="-1"
role="button"
onKeyPress={() => {}}
onClick={handleOpen}
>
<Icon
size="20"
path="icons/cart"
/>
</span>
</div>
</div>
);
};

const Header = () => (
<div className="header">
<Navigation />
<UserMenu />
</div>
);
export default Header;
6 changes: 0 additions & 6 deletions src/app/components/core/Header/Header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,4 @@
background: var(--white);
border-bottom: 1px solid #6c6c6c1f;
width: 100%;

@include element(user-menu) {
align-items: center;
display: flex;
padding: 0 px-to-rem(16);
}
}
7 changes: 3 additions & 4 deletions src/app/components/core/Header/Header.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// @flow strict
import React from 'react';
import { act } from 'react-dom/test-utils';
import { BrowserRouter as Router } from 'react-router-dom';
// $FlowFixMe
import { render } from '@testing-library/react';

import renderWithRedux from '@/utils/testing/renderWithRedux';

import Header from './Header';

Expand All @@ -12,7 +11,7 @@ describe('Header', () => {
let testRenderer = {};

await act(async () => {
testRenderer = render(
testRenderer = renderWithRedux(
<Router>
<Header />
</Router>,
Expand Down
59 changes: 59 additions & 0 deletions src/app/components/core/UserMenu/UserMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @flow strict
import React, { useState } from 'react';
// $FlowFixMe
import { useSelector } from 'react-redux';

import Icon from '@/components/base/Icon/Icon';
import { useLazy } from '@/hooks/useLazy';
import { getCart } from '@/store/reducers/cart';

import type { CartType } from '@/store/types/CartType';

import './UserMenu.scss';

const UserMenu = () => {
const [open, setOpen] = useState(false);
const cart: CartType = useSelector(getCart);

const handleOpen = () => setOpen(!open);

const Sidebar = useLazy(
() => import(/* webpackChunkName: "Sidebar" */'@/components/shared/Sidebar/Sidebar'),
open,
);

return (
<div className="user-menu">
{Sidebar
? (
<Sidebar
title="Cart"
side="right"
isOpen={open}
onClose={handleOpen}
>
<p className="lead">
You have nothing, let&apos;s shop!
</p>
</Sidebar>)
: null}
<span
className="user-menu__cart-icon"
tabIndex="-1"
role="button"
onKeyPress={() => {}}
onClick={handleOpen}
>
<Icon
size="20"
path="icons/cart"
/>
<span className="user-menu__cart-quantity">
({ cart.quantity })
</span>
</span>
</div>
);
};

export default UserMenu;
18 changes: 18 additions & 0 deletions src/app/components/core/UserMenu/UserMenu.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@import '~styles/functions/px-to-rem';
@import '~styles/mixins';
@import '~styles/variables';

.user-menu {
align-items: center;
display: flex;
padding: 0 px-to-rem(16);

@include element(cart-icon) {
align-items: center;
display: flex;
}

@include element(cart-quantity) {
margin-left: px-to-rem(5);
}
}
53 changes: 53 additions & 0 deletions src/app/components/core/UserMenu/UserMenu.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
// $FlowFixMe
import { fireEvent } from '@testing-library/react';

import renderWithRedux from '@/utils/testing/renderWithRedux';

import UserMenu from './UserMenu';

describe('UserMenu', () => {
it('should toggle <Sidebar /> when clicking icon', async () => {
let testRenderer = {};

await act(async () => {
testRenderer = renderWithRedux(<UserMenu />);
});

const { queryByRole, queryByTestId } = testRenderer;
const button = queryByRole('button');
const sidebar = queryByTestId('sidebar');

expect(sidebar).toBe(null);

await act(async () => {
fireEvent.click(button);
});

const sidebarOpened = queryByTestId('sidebar');

expect(sidebarOpened).not.toBe(null);

await act(async () => {
fireEvent.click(button);
});

const sidebarClosed = queryByTestId('sidebar');

expect(sidebarClosed).toBe(null);
});

it('should display cart\'s quantity', async () => {
let testRenderer = {};

await act(async () => {
testRenderer = renderWithRedux(<UserMenu />);
});

const { queryByRole } = testRenderer;
const button = queryByRole('button');

expect(button.textContent).toBe('(0)');
});
});
3 changes: 3 additions & 0 deletions src/app/components/core/UserMenu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow strict
// eslint-disable-next-line import/prefer-default-export
export { default as UserMenu } from './UserMenu';
2 changes: 2 additions & 0 deletions src/app/constants/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @flow strict

export const ASYNC_ACTION_TYPE = 'ASYNC_ACTION_TYPE';

export const BASE_URL = 'http://localhost:3000/api';

export const COUNTRIES = 'https://restcountries.eu/rest/v2';
Expand Down
2 changes: 0 additions & 2 deletions src/app/hooks/useLazy/useLazy.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const useLazy = (
return;
}
const module = await getModule();
// eslint-disable-next-line no-console
console.log('module', module);
setAsyncModule(() => module.default);
} catch (err) {
throw new Error(`LazyComponent error: ${err}`);
Expand Down
Loading