Refactor: Dependencies - Replaced class with func. comp (#169)

This commit is contained in:
Antoine Chalifour
2019-10-11 15:02:53 +02:00
committed by Priscila Oliveira
parent e0642a9d0d
commit 99621b6baf
5 changed files with 167 additions and 111 deletions

View File

@@ -0,0 +1,92 @@
import React from 'react';
import { render } from '@testing-library/react';
import { HashRouter } from 'react-router-dom';
import { DetailContextProvider } from '../../pages/Version';
import Dependencies from './Dependencies';
describe('<Dependencies /> component', () => {
test('Renders a message when there are no dependencies', () => {
// Given
const packageMeta = {
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {},
devDependencies: {},
peerDependencies: {},
},
_uplinks: {},
};
// When
const { getByText } = render(
<DetailContextProvider value={{ packageMeta }}>
<Dependencies />
</DetailContextProvider>
);
// Then
expect(getByText('verdaccio has no dependencies.')).toBeDefined();
});
test('Renders a link to each dependency', () => {
// Given
const packageMeta = {
latest: {
name: 'verdaccio',
version: '4.0.0',
author: {
name: 'verdaccio user',
email: 'verdaccio.user@verdaccio.org',
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
dependencies: {
react: '16.9.0',
'react-dom': '16.9.0',
},
devDependencies: {
'babel-core': '7.0.0-beta6',
},
peerDependencies: {
'styled-components': '5.0.0',
},
},
_uplinks: {},
};
// When
const { getByText } = render(
<HashRouter>
<DetailContextProvider value={{ packageMeta }}>
<Dependencies />
</DetailContextProvider>
</HashRouter>
);
// Then
// FIXME: currently MaterialUI chips do not support the children
// prop, therefore it is impossible to use proper links for
// dependencies. Those are for now clickable spans
expect(getByText('dependencies (2)')).toBeDefined();
expect(getByText('react@16.9.0').tagName).toBe('SPAN');
expect(getByText('react-dom@16.9.0').tagName).toBe('SPAN');
expect(getByText('devDependencies (1)')).toBeDefined();
expect(getByText('babel-core@7.0.0-beta6').tagName).toBe('SPAN');
expect(getByText('peerDependencies (1)')).toBeDefined();
expect(getByText('styled-components@5.0.0').tagName).toBe('SPAN');
});
});

View File

@@ -1,117 +1,78 @@
import React, { Component, Fragment, ReactElement } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import CardContent from '@material-ui/core/CardContent';
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/Version';
import { PackageDependencies } from '../../../types/packageMeta';
import { DetailContext } from '../../pages/Version';
import NoItems from '../NoItems';
import { CardWrap, Heading, Tags, Tag } from './styles';
type DepDetailProps = {
name: string;
version: string;
onLoading?: () => void;
} & RouteComponentProps;
interface DepDetailState {
name: string;
version: string;
interface DependencyBlockProps {
title: string;
dependencies: PackageDependencies;
}
class DepDetail extends Component<DepDetailProps, DepDetailState> {
constructor(props: DepDetailProps) {
super(props);
const { name, version } = this.props;
const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }) => {
const { enableLoading } = useContext(DetailContext);
const history = useHistory();
this.state = {
name,
version,
};
}
const deps = Object.entries(dependencies);
public render(): ReactElement<HTMLElement> {
const { name, version } = this.state;
const tagText = `${name}@${version}`;
return <Tag className={'dep-tag'} clickable={true} label={tagText} onClick={this.handleOnClick} />;
}
function handleClick(name: string): void {
enableLoading && enableLoading();
private handleOnClick = () => {
const { name } = this.state;
const { onLoading, history } = this.props;
onLoading && onLoading();
history.push(`/-/web/detail/${name}`);
};
}
return (
<CardWrap>
<CardContent>
<Heading variant="subtitle1">{`${title} (${deps.length})`}</Heading>
<Tags>
{deps.map(([name, version]) => (
// eslint-disable-next-line
<Tag className={'dep-tag'} clickable={true} key={name} label={`${name}@${version}`} onClick={() => handleClick(name)} />
))}
</Tags>
</CardContent>
</CardWrap>
);
};
function hasKeys(object?: { [key: string]: any }): boolean {
return !!object && Object.keys(object).length > 0;
}
const WrapperDependencyDetail = withRouter(DepDetail);
const Dependencies: React.FC<{}> = () => {
const { packageMeta } = useContext(DetailContext);
class DependencyBlock extends Component<{ title: string; dependencies: [] }> {
public render(): ReactElement<HTMLElement> {
const { dependencies, title } = this.props;
const deps = Object.entries(dependencies) as [];
if (!packageMeta) {
throw new Error('packageMeta is required at DetailContext');
}
const { latest } = packageMeta;
// FIXME: add dependencies to package meta type
// @ts-ignore
const { dependencies, devDependencies, peerDependencies, name } = latest;
const dependencyMap = { dependencies, devDependencies, peerDependencies };
const hasDependencies = hasKeys(dependencies) || hasKeys(devDependencies) || hasKeys(peerDependencies);
if (hasDependencies) {
return (
<DetailContextConsumer>
{({ enableLoading }) => {
return (
<CardWrap>
<CardContent>
<Heading variant="subtitle1">{`${title} (${deps.length})`}</Heading>
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
</CardContent>
</CardWrap>
);
}}
</DetailContextConsumer>
<>
{Object.entries(dependencyMap).map(([dependencyType, dependencies]) => {
if (!dependencies || Object.keys(dependencies).length === 0) {
return null;
}
return <DependencyBlock dependencies={dependencies} key={dependencyType} title={dependencyType} />;
})}
</>
);
}
private renderTags = (deps: [], enableLoading?: () => void) =>
deps.map(dep => {
const [name, version] = dep as [string, string];
return <WrapperDependencyDetail key={name} name={name} onLoading={enableLoading} version={version} />;
});
}
class Dependencies extends Component {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{packageMeta => {
return this.renderDependencies(packageMeta as VersionPageConsumerProps);
}}
</DetailContextConsumer>
);
}
private checkDependencyLength<T>(dependency: Record<string, T> = {}): boolean {
return Object.keys(dependency).length > 0;
}
private renderDependencies({ packageMeta }): ReactElement<HTMLElement> {
const { latest } = packageMeta;
const { dependencies, devDependencies, peerDependencies, name } = latest;
const dependencyMap = { dependencies, devDependencies, peerDependencies };
const dependencyList = Object.keys(dependencyMap).reduce(
(result, value, key) => {
const selectedDepndency = dependencyMap[value];
if (selectedDepndency && this.checkDependencyLength(selectedDepndency)) {
result.push(<DependencyBlock dependencies={selectedDepndency} key={key} title={value} />);
}
return result;
},
[] as JSX.Element[]
);
if (dependencyList.length) {
return <Fragment>{dependencyList}</Fragment>;
}
return <NoItems className="no-dependencies" text={`${name} has no dependencies.`} />;
}
}
return <NoItems className="no-dependencies" text={`${name} has no dependencies.`} />;
};
export default Dependencies;