forked from sombochea/verdaccio-ui
Refactor: Dependencies - Replaced class with func. comp (#169)
This commit is contained in:
parent
e0642a9d0d
commit
99621b6baf
@ -27,7 +27,7 @@
|
|||||||
"@types/node": "12.7.8",
|
"@types/node": "12.7.8",
|
||||||
"@types/react": "16.9.2",
|
"@types/react": "16.9.2",
|
||||||
"@types/react-dom": "16.9.0",
|
"@types/react-dom": "16.9.0",
|
||||||
"@types/react-router-dom": "4.3.5",
|
"@types/react-router-dom": "5.1.0",
|
||||||
"@types/validator": "10.11.3",
|
"@types/validator": "10.11.3",
|
||||||
"@typescript-eslint/parser": "2.3.2",
|
"@typescript-eslint/parser": "2.3.2",
|
||||||
"@verdaccio/babel-preset": "2.0.0",
|
"@verdaccio/babel-preset": "2.0.0",
|
||||||
@ -85,10 +85,9 @@
|
|||||||
"react": "16.10.0",
|
"react": "16.10.0",
|
||||||
"react-autosuggest": "9.4.3",
|
"react-autosuggest": "9.4.3",
|
||||||
"react-dom": "16.10.0",
|
"react-dom": "16.10.0",
|
||||||
|
"react-router-dom": "5.1.2",
|
||||||
"react-emotion": "9.2.12",
|
"react-emotion": "9.2.12",
|
||||||
"react-hot-loader": "4.12.11",
|
"react-hot-loader": "4.12.11",
|
||||||
"react-router": "5.0.1",
|
|
||||||
"react-router-dom": "5.0.1",
|
|
||||||
"resolve-url-loader": "3.1.0",
|
"resolve-url-loader": "3.1.0",
|
||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.0",
|
||||||
"source-map-loader": "0.2.4",
|
"source-map-loader": "0.2.4",
|
||||||
|
92
src/components/Dependencies/Dependencies.test.tsx
Normal file
92
src/components/Dependencies/Dependencies.test.tsx
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
@ -1,117 +1,78 @@
|
|||||||
import React, { Component, Fragment, ReactElement } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import CardContent from '@material-ui/core/CardContent';
|
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 NoItems from '../NoItems';
|
||||||
|
|
||||||
import { CardWrap, Heading, Tags, Tag } from './styles';
|
import { CardWrap, Heading, Tags, Tag } from './styles';
|
||||||
|
|
||||||
type DepDetailProps = {
|
interface DependencyBlockProps {
|
||||||
name: string;
|
title: string;
|
||||||
version: string;
|
dependencies: PackageDependencies;
|
||||||
onLoading?: () => void;
|
|
||||||
} & RouteComponentProps;
|
|
||||||
|
|
||||||
interface DepDetailState {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DepDetail extends Component<DepDetailProps, DepDetailState> {
|
const DependencyBlock: React.FC<DependencyBlockProps> = ({ title, dependencies }) => {
|
||||||
constructor(props: DepDetailProps) {
|
const { enableLoading } = useContext(DetailContext);
|
||||||
super(props);
|
const history = useHistory();
|
||||||
const { name, version } = this.props;
|
|
||||||
|
|
||||||
this.state = {
|
const deps = Object.entries(dependencies);
|
||||||
name,
|
|
||||||
version,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): ReactElement<HTMLElement> {
|
function handleClick(name: string): void {
|
||||||
const { name, version } = this.state;
|
enableLoading && enableLoading();
|
||||||
const tagText = `${name}@${version}`;
|
|
||||||
return <Tag className={'dep-tag'} clickable={true} label={tagText} onClick={this.handleOnClick} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleOnClick = () => {
|
|
||||||
const { name } = this.state;
|
|
||||||
const { onLoading, history } = this.props;
|
|
||||||
|
|
||||||
onLoading && onLoading();
|
|
||||||
history.push(`/-/web/detail/${name}`);
|
history.push(`/-/web/detail/${name}`);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const WrapperDependencyDetail = withRouter(DepDetail);
|
|
||||||
|
|
||||||
class DependencyBlock extends Component<{ title: string; dependencies: [] }> {
|
|
||||||
public render(): ReactElement<HTMLElement> {
|
|
||||||
const { dependencies, title } = this.props;
|
|
||||||
const deps = Object.entries(dependencies) as [];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DetailContextConsumer>
|
|
||||||
{({ enableLoading }) => {
|
|
||||||
return (
|
return (
|
||||||
<CardWrap>
|
<CardWrap>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Heading variant="subtitle1">{`${title} (${deps.length})`}</Heading>
|
<Heading variant="subtitle1">{`${title} (${deps.length})`}</Heading>
|
||||||
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
|
<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>
|
</CardContent>
|
||||||
</CardWrap>
|
</CardWrap>
|
||||||
);
|
);
|
||||||
}}
|
};
|
||||||
</DetailContextConsumer>
|
|
||||||
);
|
function hasKeys(object?: { [key: string]: any }): boolean {
|
||||||
|
return !!object && Object.keys(object).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTags = (deps: [], enableLoading?: () => void) =>
|
const Dependencies: React.FC<{}> = () => {
|
||||||
deps.map(dep => {
|
const { packageMeta } = useContext(DetailContext);
|
||||||
const [name, version] = dep as [string, string];
|
|
||||||
|
|
||||||
return <WrapperDependencyDetail key={name} name={name} onLoading={enableLoading} version={version} />;
|
if (!packageMeta) {
|
||||||
});
|
throw new Error('packageMeta is required at DetailContext');
|
||||||
}
|
}
|
||||||
|
|
||||||
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 { latest } = packageMeta;
|
||||||
|
// FIXME: add dependencies to package meta type
|
||||||
|
// @ts-ignore
|
||||||
const { dependencies, devDependencies, peerDependencies, name } = latest;
|
const { dependencies, devDependencies, peerDependencies, name } = latest;
|
||||||
|
|
||||||
const dependencyMap = { dependencies, devDependencies, peerDependencies };
|
const dependencyMap = { dependencies, devDependencies, peerDependencies };
|
||||||
|
const hasDependencies = hasKeys(dependencies) || hasKeys(devDependencies) || hasKeys(peerDependencies);
|
||||||
|
|
||||||
const dependencyList = Object.keys(dependencyMap).reduce(
|
if (hasDependencies) {
|
||||||
(result, value, key) => {
|
return (
|
||||||
const selectedDepndency = dependencyMap[value];
|
<>
|
||||||
if (selectedDepndency && this.checkDependencyLength(selectedDepndency)) {
|
{Object.entries(dependencyMap).map(([dependencyType, dependencies]) => {
|
||||||
result.push(<DependencyBlock dependencies={selectedDepndency} key={key} title={value} />);
|
if (!dependencies || Object.keys(dependencies).length === 0) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
},
|
return <DependencyBlock dependencies={dependencies} key={dependencyType} title={dependencyType} />;
|
||||||
[] 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;
|
export default Dependencies;
|
||||||
|
@ -48,3 +48,7 @@ export interface Author {
|
|||||||
url?: string;
|
url?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PackageDependencies {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
26
yarn.lock
26
yarn.lock
@ -1676,10 +1676,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-router-dom@4.3.5":
|
"@types/react-router-dom@5.1.0":
|
||||||
version "4.3.5"
|
version "5.1.0"
|
||||||
resolved "https://registry.verdaccio.org/@types%2freact-router-dom/-/react-router-dom-4.3.5.tgz#72f229967690c890d00f96e6b85e9ee5780db31f"
|
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.0.tgz#8baa84a7fa8c8e7797fb3650ca51f93038cb4caf"
|
||||||
integrity sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA==
|
integrity sha512-YCh8r71pL5p8qDwQf59IU13hFy/41fDQG/GeOI3y+xmD4o0w3vEPxE8uBe+dvOgMoDl0W1WUZsWH0pxc1mcZyQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
@ -10946,23 +10946,23 @@ react-lifecycles-compat@^3.0.4:
|
|||||||
resolved "https://registry.verdaccio.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.verdaccio.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
|
||||||
react-router-dom@5.0.1:
|
react-router-dom@5.1.2:
|
||||||
version "5.0.1"
|
version "5.1.2"
|
||||||
resolved "https://registry.verdaccio.org/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"
|
||||||
integrity sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==
|
integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.1.2"
|
"@babel/runtime" "^7.1.2"
|
||||||
history "^4.9.0"
|
history "^4.9.0"
|
||||||
loose-envify "^1.3.1"
|
loose-envify "^1.3.1"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
react-router "5.0.1"
|
react-router "5.1.2"
|
||||||
tiny-invariant "^1.0.2"
|
tiny-invariant "^1.0.2"
|
||||||
tiny-warning "^1.0.0"
|
tiny-warning "^1.0.0"
|
||||||
|
|
||||||
react-router@5.0.1:
|
react-router@5.1.2:
|
||||||
version "5.0.1"
|
version "5.1.2"
|
||||||
resolved "https://registry.verdaccio.org/react-router/-/react-router-5.0.1.tgz#04ee77df1d1ab6cb8939f9f01ad5702dbadb8b0f"
|
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418"
|
||||||
integrity sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==
|
integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.1.2"
|
"@babel/runtime" "^7.1.2"
|
||||||
history "^4.9.0"
|
history "^4.9.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user