refactor: Developers component

Using React hooks
This commit is contained in:
Juan Picado @jotadeveloper 2019-08-12 07:06:10 +02:00
parent 502c0903ab
commit e46020f9b0
No known key found for this signature in database
GPG Key ID: 15AA875EF3768142
12 changed files with 5738 additions and 57 deletions

View File

@ -16,6 +16,7 @@
"@material-ui/core": "3.9.3", "@material-ui/core": "3.9.3",
"@material-ui/icons": "3.0.2", "@material-ui/icons": "3.0.2",
"@octokit/rest": "16.28.7", "@octokit/rest": "16.28.7",
"@testing-library/react": "9.1.0",
"@types/enzyme": "3.10.3", "@types/enzyme": "3.10.3",
"@types/lodash": "4.14.136", "@types/lodash": "4.14.136",
"@types/material-ui": "0.21.6", "@types/material-ui": "0.21.6",

View File

@ -4579,6 +4579,70 @@
"tarball": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz" "tarball": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz"
}, },
"maintainers": [ "maintainers": [
{
"name": "dmethvin",
"email": "dave.methvin@gmail.com"
},
{
"name": "mgol",
"email": "m.goleb@gmail.com"
},
{
"name": "scott.gonzalez",
"email": "scott.gonzalez@gmail.com"
},
{
"name": "timmywil",
"email": "4timmywil@gmail.com"
},
{
"name": "dmethvin",
"email": "dave.methvin@gmail.com"
},
{
"name": "mgol",
"email": "m.goleb@gmail.com"
},
{
"name": "scott.gonzalez",
"email": "scott.gonzalez@gmail.com"
},
{
"name": "timmywil",
"email": "4timmywil@gmail.com"
},
{
"name": "dmethvin",
"email": "dave.methvin@gmail.com"
},
{
"name": "mgol",
"email": "m.goleb@gmail.com"
},
{
"name": "scott.gonzalez",
"email": "scott.gonzalez@gmail.com"
},
{
"name": "timmywil",
"email": "4timmywil@gmail.com"
},
{
"name": "dmethvin",
"email": "dave.methvin@gmail.com"
},
{
"name": "mgol",
"email": "m.goleb@gmail.com"
},
{
"name": "scott.gonzalez",
"email": "scott.gonzalez@gmail.com"
},
{
"name": "timmywil",
"email": "4timmywil@gmail.com"
},
{ {
"name": "dmethvin", "name": "dmethvin",
"email": "dave.methvin@gmail.com" "email": "dave.methvin@gmail.com"

View File

@ -0,0 +1,32 @@
import React, { FC } from 'react';
import Avatar from '@material-ui/core/Avatar';
import Tooltip from '@material-ui/core/Tooltip';
import { isEmail } from '../../utils/url';
export interface AvatarDeveloper {
name: string;
packageName: string;
version: string;
avatar: string;
email: string;
}
const AvatarTooltip: FC<AvatarDeveloper> = ({ name, packageName, version, avatar, email }) => {
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
function renderLinkForMail(email, avatarComponent, packageName, version): JSX.Element {
if (!email || isEmail(email) === false) {
return avatarComponent;
}
return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
}
return <Tooltip title={name}>{renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
};
export { AvatarTooltip };

View File

@ -0,0 +1,4 @@
import { AvatarTooltip } from './AvatarTooltip';
export { AvatarTooltip };
export default AvatarTooltip;

View File

@ -0,0 +1,103 @@
import React from 'react';
import { mount } from 'enzyme';
import Developers, { DevelopersType } from './Developers';
import { Fab } from './styles';
import { DetailContextProvider } from '../../pages/version/Version';
describe('test Developers', () => {
const packageMeta = {
latest: {
packageName: 'foo',
version: '1.0.0',
maintainers: [
{
name: 'dmethvin',
email: 'dave.methvin@gmail.com',
},
{
name: 'mgol',
email: 'm.goleb@gmail.com',
},
],
contributors: [
{
name: 'dmethvin',
email: 'dave.methvin@gmail.com',
},
{
name: 'mgol',
email: 'm.goleb@gmail.com',
},
],
},
};
test('should render the component with no items', () => {
const type: DevelopersType = 'maintainers';
const packageMeta = {
latest: {},
};
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} />
</DetailContextProvider>
);
expect(wrapper).toMatchSnapshot();
});
test('should render the component for maintainers with items', () => {
const type: DevelopersType = 'maintainers';
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} />
</DetailContextProvider>
);
expect(wrapper).toMatchSnapshot();
});
test('should render the component for contributors with items', () => {
const type: DevelopersType = 'contributors';
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} />
</DetailContextProvider>
);
expect(wrapper).toMatchSnapshot();
});
test('should test onClick the component avatar', () => {
const type: DevelopersType = 'contributors';
const packageMeta = {
latest: {
packageName: 'foo',
version: '1.0.0',
contributors: [
{
name: 'dmethvin',
email: 'dave.methvin@gmail.com',
},
{
name: 'dmethvin2',
email: 'dave2.methvin@gmail.com',
},
],
},
};
const wrapper = mount(
// @ts-ignore
<DetailContextProvider value={{ packageMeta }}>
<Developers type={type} visibleMax={1} />
</DetailContextProvider>
);
const item2 = wrapper.find(Fab);
item2.simulate('click');
});
});

View File

@ -1,79 +1,59 @@
import React, { Component } from 'react'; import React, { FC, Fragment } from 'react';
import Avatar from '@material-ui/core/Avatar';
import Add from '@material-ui/icons/Add'; import Add from '@material-ui/icons/Add';
import Tooltip from '@material-ui/core/Tooltip';
import { DetailContextConsumer } from '../../pages/version/Version'; import { DetailContext } from '../../pages/version/Version';
import { AvatarTooltip } from '../AvatarTooltip';
import { Details, Heading, Content, Fab } from './styles'; import { Details, Heading, Content, Fab } from './styles';
import { isEmail } from '../../utils/url';
export type DevelopersType = 'contributors' | 'maintainers';
interface Props { interface Props {
type: 'contributors' | 'maintainers'; type: DevelopersType;
} visibleMax?: number;
interface State {
visibleDevs: number;
} }
class Developers extends Component<Props, State> { export const VISIBLE_MAX = 6;
public state = {
visibleDevs: 6, const Developers: FC<Props> = ({ type, visibleMax }) => {
const [visibleDevs, setVisibleDevs] = React.useState<number>(visibleMax || VISIBLE_MAX);
const { packageMeta } = React.useContext(DetailContext);
const handleLoadMore = () => {
setVisibleDevs(visibleDevs + VISIBLE_MAX);
}; };
public render(): JSX.Element { const renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => {
return ( const { name: packageName, version } = packageMeta.latest;
<DetailContextConsumer>
{({ packageMeta }) => {
const { type } = this.props;
const developerType = packageMeta && packageMeta.latest[type];
if (!developerType || developerType.length === 0) return null;
return this.renderDevelopers(developerType, packageMeta);
}}
</DetailContextConsumer>
);
}
public handleLoadMore = () => { return <AvatarTooltip avatar={avatar} email={email} name={name} packageName={packageName} version={version} />;
this.setState(prev => ({ visibleDevs: prev.visibleDevs + 6 }));
}; };
private renderDevelopers = (developers, packageMeta) => { const renderDevelopers = (developers, packageMeta) => {
const { type } = this.props; const listVisibleDevelopers = developers.slice(0, visibleDevs);
const { visibleDevs } = this.state;
return ( return (
<> <Fragment>
<Heading variant={'subheading'}>{type}</Heading> <Heading variant={'subheading'}>{type}</Heading>
<Content> <Content>
{developers.slice(0, visibleDevs).map(developer => ( {listVisibleDevelopers.map(developer => (
<Details key={developer.email}>{this.renderDeveloperDetails(developer, packageMeta)}</Details> <Details key={developer.email}>{renderDeveloperDetails(developer, packageMeta)}</Details>
))} ))}
{visibleDevs < developers.length && ( {visibleDevs < developers.length && (
<Fab onClick={this.handleLoadMore} size="small"> <Fab onClick={handleLoadMore} size="small">
<Add /> <Add />
</Fab> </Fab>
)} )}
</Content> </Content>
</> </Fragment>
); );
}; };
private renderLinkForMail(email, avatarComponent, packageName, version): JSX.Element { const developerList = packageMeta && packageMeta.latest[type];
if (!email || isEmail(email) === false) { if (!developerList || developerList.length === 0) {
return avatarComponent; return null;
}
return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
} }
private renderDeveloperDetails = ({ name, avatar, email }, packageMeta) => { return renderDevelopers(developerList, packageMeta);
const { name: packageName, version } = packageMeta.latest;
const avatarComponent = <Avatar aria-label={name} src={avatar} />;
return <Tooltip title={name}>{this.renderLinkForMail(email, avatarComponent, packageName, version)}</Tooltip>;
}; };
}
export default Developers; export default Developers;

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
import styled from 'react-emotion'; import styled from 'react-emotion';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import { default as MuiFab } from '@material-ui/core/Fab'; import { default as MuiFab } from '@material-ui/core/Fab';
import colors from '../../utils/styles/colors'; import colors from '../../utils/styles/colors';
import { fontWeight } from '../../utils/styles/sizes'; import { fontWeight } from '../../utils/styles/sizes';

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Footer /> component should load the initial state of Footer component 1`] = `"<div class=\\"css-i0nj2g ezbsl480\\"><div class=\\"css-hzfs9b ezbsl481\\"><div class=\\"css-d8nsp7 ezbsl482\\"> Made with<span class=\\"css-1so4oe0 ezbsl487\\">♥</span>on<span class=\\"css-1ie354y ezbsl484\\"><svg class=\\"ezbsl485 css-1kgp95j ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-f1ndto ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg></span></span></div><div class=\\"css-1wbzdyy ezbsl483\\">Powered by<span class=\\"ezbsl488 css-i15wza ek145dl1\\" name=\\"verdaccio\\" title=\\"Verdaccio\\"><img alt=\\"Verdaccio\\" src=\\"[object Object]\\" class=\\"css-1ncdhax ek145dl2\\"></span>/ v.1.0.0</div></div></div>"`; exports[`<Footer /> component should load the initial state of Footer component 1`] = `"<div class=\\"css-i0nj2g ezbsl480\\"><div class=\\"css-hzfs9b ezbsl481\\"><div class=\\"css-d8nsp7 ezbsl482\\"> Made with<span class=\\"css-1so4oe0 ezbsl487\\">♥</span>on<span class=\\"css-1ie354y ezbsl484\\"><svg class=\\"ezbsl485 css-tsfgle ek145dl0\\"><title>Earth</title><use xlink:href=\\"[object Object]#earth\\"></use></svg><span class=\\"css-8631ip ezbsl486\\"><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Spain</title><use xlink:href=\\"[object Object]#spain\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Nicaragua</title><use xlink:href=\\"[object Object]#nicaragua\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>India</title><use xlink:href=\\"[object Object]#india\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Brazil</title><use xlink:href=\\"[object Object]#brazil\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>China</title><use xlink:href=\\"[object Object]#china\\"></use></svg><svg class=\\"ezbsl488 css-13b76ay ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg></span></span></div><div class=\\"css-1wbzdyy ezbsl483\\">Powered by<span class=\\"ezbsl488 css-i15wza ek145dl1\\" name=\\"verdaccio\\" title=\\"Verdaccio\\"><img alt=\\"Verdaccio\\" src=\\"[object Object]\\" class=\\"css-1ncdhax ek145dl2\\"></span>/ v.1.0.0</div></div></div>"`;

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Icon /> component should render the component in default state 1`] = `"<svg class=\\"css-3skwlp ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg>"`; exports[`<Icon /> component should render the component in default state 1`] = `"<svg class=\\"css-snirlv ek145dl0\\"><title>Austria</title><use xlink:href=\\"[object Object]#austria\\"></use></svg>"`;

View File

@ -21,7 +21,7 @@ const getSize = (size: Breakpoint): string => {
const commonStyle = ({ size = 'sm' as Breakpoint, pointer, modifiers = null }): string => css` const commonStyle = ({ size = 'sm' as Breakpoint, pointer, modifiers = null }): string => css`
&& { && {
display: inline-block; display: inline-block;
cursor: ${pointer ? 'pointer' : 'default'}; cursor: ${pointer ? 'pointer' : 'Developers'};
${getSize(size)}; ${getSize(size)};
${modifiers && modifiers}; ${modifiers && modifiers};
} }

View File

@ -1009,7 +1009,7 @@
pirates "^4.0.0" pirates "^4.0.0"
source-map-support "^0.5.9" source-map-support "^0.5.9"
"@babel/runtime@7.5.5", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5": "@babel/runtime@7.5.5", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5":
version "7.5.5" version "7.5.5"
resolved "https://registry.verdaccio.org/@babel%2fruntime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" resolved "https://registry.verdaccio.org/@babel%2fruntime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
@ -1628,6 +1628,30 @@
universal-user-agent "^3.0.0" universal-user-agent "^3.0.0"
url-template "^2.0.8" url-template "^2.0.8"
"@sheerun/mutationobserver-shim@^0.3.2":
version "0.3.2"
resolved "https://registry.verdaccio.org/@sheerun%2fmutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b"
integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==
"@testing-library/dom@^6.0.0":
version "6.0.0"
resolved "https://registry.verdaccio.org/@testing-library%2fdom/-/dom-6.0.0.tgz#34e28e69e49bd6347fc64a5dde4c4f9aabbd17d3"
integrity sha512-B5XTz3uMsbqbdR9CZlnwpZjTE3fCWuqRkz/zvDc2Ej/vuHmTM0Ur2v0XPwr7usWfGIBsahEK5HL1E91+4IFiBg==
dependencies:
"@babel/runtime" "^7.5.5"
"@sheerun/mutationobserver-shim" "^0.3.2"
aria-query "3.0.0"
pretty-format "^24.8.0"
wait-for-expect "^1.3.0"
"@testing-library/react@9.1.0":
version "9.1.0"
resolved "https://registry.verdaccio.org/@testing-library%2freact/-/react-9.1.0.tgz#c00c4ff90c5f9015365f63b7759a57c428b6ab56"
integrity sha512-XMtU8AmGX472c7+qXaaVZ2QpDKPA56vRaVEjlFBPDL8im2erM6DF0KJlwzLcc2wrvfYkcMDbreFyL0vdalubrQ==
dependencies:
"@babel/runtime" "^7.5.5"
"@testing-library/dom" "^6.0.0"
"@types/babel__core@^7.1.0": "@types/babel__core@^7.1.0":
version "7.1.0" version "7.1.0"
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51"
@ -2467,7 +2491,7 @@ argv@^0.0.2:
resolved "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" resolved "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas= integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=
aria-query@^3.0.0: aria-query@3.0.0, aria-query@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.verdaccio.org/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" resolved "https://registry.verdaccio.org/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc"
integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=
@ -13943,6 +13967,11 @@ w3c-xmlserializer@^1.1.2:
webidl-conversions "^4.0.2" webidl-conversions "^4.0.2"
xml-name-validator "^3.0.0" xml-name-validator "^3.0.0"
wait-for-expect@^1.3.0:
version "1.3.0"
resolved "https://registry.verdaccio.org/wait-for-expect/-/wait-for-expect-1.3.0.tgz#65241ce355425f907f5d127bdb5e72c412ff830c"
integrity sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA==
walker@^1.0.7, walker@~1.0.5: walker@^1.0.7, walker@~1.0.5:
version "1.0.7" version "1.0.7"
resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"