diff --git a/src/components/ActionBar/ActionBar.test.tsx b/src/components/ActionBar/ActionBar.test.tsx
index 31ed136..daf8b0a 100644
--- a/src/components/ActionBar/ActionBar.test.tsx
+++ b/src/components/ActionBar/ActionBar.test.tsx
@@ -1,89 +1,71 @@
import React from 'react';
-import { mount } from '../../utils/test-enzyme';
-import api from '../../utils/api';
+import { render, cleanup } from '../../utils/test-react-testing-library';
+import { DetailContext, DetailContextProps } from '../../pages/Version';
-import { ActionBar } from './ActionBar';
+import ActionBar from './ActionBar';
-const mockPackageMeta: jest.Mock = jest.fn(() => ({
- latest: {
- homepage: 'https://verdaccio.tld',
- bugs: {
- url: 'https://verdaccio.tld/bugs',
- },
- dist: {
- tarball: 'https://verdaccio.tld/download',
+const detailContextValue: DetailContextProps = {
+ packageName: 'foo',
+ readMe: 'test',
+ enableLoading: () => {},
+ isLoading: false,
+ hasNotBeenFound: false,
+ packageMeta: {
+ _uplinks: {},
+ latest: {
+ name: '@verdaccio/local-storage',
+ version: '8.0.1-next.1',
+ dist: { fileCount: 0, unpackedSize: 0, tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz' },
+ homepage: 'https://verdaccio.org',
+ bugs: {
+ url: 'https://github.com/verdaccio/monorepo/issues',
+ },
},
},
-}));
+};
-jest.mock('../../pages/Version', () => ({
- DetailContextConsumer: component => {
- return component.children({ packageMeta: mockPackageMeta() });
- },
-}));
+const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({ contextValue }) => (
+
+
+
+);
describe(' component', () => {
- beforeEach(() => {
- jest.resetModules();
- jest.resetAllMocks();
+ afterEach(() => {
+ cleanup();
});
test('should render the component in default state', () => {
- const wrapper = mount();
- expect(wrapper.html()).toMatchSnapshot();
+ const { container } = render();
+ expect(container.firstChild).toMatchSnapshot();
});
test('when there is no action bar data', () => {
- mockPackageMeta.mockImplementation(() => ({
- latest: {},
- }));
+ const packageMeta = {
+ ...detailContextValue.packageMeta,
+ latest: {
+ ...detailContextValue.packageMeta.latest,
+ homepage: undefined,
+ bugs: undefined,
+ dist: {
+ ...detailContextValue.packageMeta.latest.dist,
+ tarball: undefined,
+ },
+ },
+ };
- const wrapper = mount();
- // FIXME: this only renders the DetailContextConsumer, thus
- // the wrapper will be always empty
- expect(wrapper.html()).toEqual('');
- });
-
- test('when there is no latest property in package meta', () => {
- mockPackageMeta.mockImplementation(() => ({}));
- const wrapper = mount();
- expect(wrapper.html()).toEqual('');
+ const { container } = render();
+ expect(container.firstChild).toMatchSnapshot();
});
test('when there is a button to download a tarball', () => {
- mockPackageMeta.mockImplementation(() => ({
- latest: {
- dist: {
- tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
- },
- },
- }));
-
- const wrapper = mount();
- expect(wrapper.html()).toMatchSnapshot();
-
- const button = wrapper.find('button');
- expect(button).toHaveLength(1);
-
- const spy = jest.spyOn(api, 'request');
- button.simulate('click');
- expect(spy).toHaveBeenCalled();
+ const { getByTitle } = render();
+ expect(getByTitle('Download tarball')).toBeTruthy();
});
test('when there is a button to open an issue', () => {
- mockPackageMeta.mockImplementation(() => ({
- latest: {
- bugs: {
- url: 'https://verdaccio.tld/bugs',
- },
- },
- }));
-
- const wrapper = mount();
- expect(wrapper.html()).toMatchSnapshot();
-
- const button = wrapper.find('button');
- expect(button).toHaveLength(1);
+ const { getByTitle } = render();
+ expect(getByTitle('Open an issue')).toBeTruthy();
});
});
diff --git a/src/components/ActionBar/ActionBar.tsx b/src/components/ActionBar/ActionBar.tsx
index 6a731e4..700c225 100644
--- a/src/components/ActionBar/ActionBar.tsx
+++ b/src/components/ActionBar/ActionBar.tsx
@@ -1,133 +1,44 @@
-import React, { Component, ReactElement } from 'react';
-import BugReportIcon from '@material-ui/icons/BugReport';
-import DownloadIcon from '@material-ui/icons/CloudDownload';
-import HomeIcon from '@material-ui/icons/Home';
+import React from 'react';
-import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/Version';
-import { isURL, extractFileName, downloadFile } from '../../utils/url';
-import api from '../../utils/api';
-import Tooltip from '../../muiComponents/Tooltip';
-import List from '../../muiComponents/List';
+import { DetailContext } from '../../pages/Version';
+import { isURL } from '../../utils/url';
+import Box from '../../muiComponents/Box';
-import { Fab, ActionListItem } from './styles';
+import ActionBarAction, { ActionBarActionProps } from './ActionBarAction';
-export interface Action {
- icon: string;
- title: string;
- handler?: Function;
-}
+/* eslint-disable verdaccio/jsx-spread */
+const ActionBar: React.FC = () => {
+ const detailContext = React.useContext(DetailContext);
-export async function downloadHandler(link: string): Promise {
- const fileStream: Blob = await api.request(link, 'GET', {
- headers: {
- ['accept']:
- 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
- },
- credentials: 'include',
- });
- const fileName = extractFileName(link);
- downloadFile(fileStream, fileName);
-}
+ const { packageMeta } = detailContext;
-const ACTIONS = {
- homepage: {
- icon: ,
- title: 'Visit homepage',
- },
- issue: {
- icon: ,
- title: 'Open an issue',
- },
- tarball: {
- icon: ,
- title: 'Download tarball',
- handler: downloadHandler,
- },
+ if (!packageMeta?.latest) {
+ return null;
+ }
+
+ const { homepage, bugs, dist } = packageMeta.latest;
+
+ const actions: Array = [];
+
+ if (homepage && isURL(homepage)) {
+ actions.push({ type: 'VISIT_HOMEPAGE', link: homepage });
+ }
+
+ if (bugs?.url && isURL(bugs.url)) {
+ actions.push({ type: 'OPEN_AN_ISSUE', link: bugs.url });
+ }
+
+ if (dist?.tarball && isURL(dist.tarball)) {
+ actions.push({ type: 'DOWNLOAD_TARBALL', link: dist.tarball });
+ }
+
+ return (
+
+ {actions.map(action => (
+
+ ))}
+
+ );
};
-class ActionBar extends Component {
- public render(): ReactElement {
- return (
-
- {context => {
- const { packageMeta } = context;
-
- if (!packageMeta) {
- return null;
- }
-
- return this.renderActionBar(context as VersionPageConsumerProps);
- }}
-
- );
- }
-
- private renderIconsWithLink(link: string, component: JSX.Element): ReactElement {
- return (
-
- {component}
-
- );
- }
-
- private renderActionBar = ({ packageMeta }) => {
- const { latest } = packageMeta;
-
- if (!latest) {
- return null;
- }
-
- const { homepage, bugs, dist } = latest;
-
- const actionsMap = {
- homepage,
- issue: bugs ? bugs.url : null,
- tarball: dist ? dist.tarball : null,
- };
-
- const renderList = Object.keys(actionsMap).reduce((component: React.ReactElement[], value, key) => {
- const link = actionsMap[value];
- if (link && isURL(link)) {
- const actionItem: Action = ACTIONS[value];
- if (actionItem.handler) {
- const fab = (
-
- {
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
- actionItem.handler!(link);
- }}
- size={'small'}>
- {actionItem['icon']}
-
-
- );
- component.push(fab);
- } else {
- const fab = {actionItem['icon']};
- component.push(
-
- <>{this.renderIconsWithLink(link, fab)}>
-
- );
- }
- }
- return component;
- }, []);
-
- if (renderList.length > 0) {
- return (
-
-
- {renderList}
-
-
- );
- }
-
- return null;
- };
-}
-
-export { ActionBar };
+export default ActionBar;
diff --git a/src/components/ActionBar/ActionBarAction.tsx b/src/components/ActionBar/ActionBarAction.tsx
new file mode 100644
index 0000000..9fb6fec
--- /dev/null
+++ b/src/components/ActionBar/ActionBarAction.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import styled from '@emotion/styled';
+import BugReportIcon from '@material-ui/icons/BugReport';
+import DownloadIcon from '@material-ui/icons/CloudDownload';
+import HomeIcon from '@material-ui/icons/Home';
+
+import Tooltip from '../../muiComponents/Tooltip';
+import Link from '../Link';
+import FloatingActionButton from '../../muiComponents/FloatingActionButton';
+import { Theme } from '../../design-tokens/theme';
+
+import downloadTarball from './download-tarball';
+
+export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
+ backgroundColor: props.theme && props.theme.palette.primary.main,
+ color: props.theme && props.theme.palette.white,
+ marginRight: 10,
+}));
+
+type ActionType = 'VISIT_HOMEPAGE' | 'OPEN_AN_ISSUE' | 'DOWNLOAD_TARBALL';
+
+export interface ActionBarActionProps {
+ type: ActionType;
+ link: string;
+}
+
+/* eslint-disable react/jsx-no-bind */
+/* eslint-disable react/jsx-max-depth */
+const ActionBarAction: React.FC = ({ type, link }) => {
+ switch (type) {
+ case 'VISIT_HOMEPAGE':
+ return (
+
+
+
+
+
+
+
+ );
+ case 'OPEN_AN_ISSUE':
+ return (
+
+
+
+
+
+
+
+ );
+ case 'DOWNLOAD_TARBALL':
+ return (
+
+
+
+
+
+ );
+ }
+};
+
+export default ActionBarAction;
diff --git a/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap b/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap
index cb1abdf..52b26ca 100644
--- a/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap
+++ b/src/components/ActionBar/__snapshots__/ActionBar.test.tsx.snap
@@ -1,7 +1,118 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[` component should render the component in default state 1`] = `""`;
+exports[` component should render the component in default state 1`] = `
+.emotion-0 {
+ background-color: #4b5e40;
+ color: #fff;
+ margin-right: 10px;
+}
-exports[` component when there is a button to download a tarball 1`] = `""`;
+
+`;
-exports[` component when there is a button to open an issue 1`] = `""`;
+exports[` component when there is no action bar data 1`] = `
+
+`;
diff --git a/src/components/ActionBar/download-tarball.ts b/src/components/ActionBar/download-tarball.ts
new file mode 100644
index 0000000..ab6384f
--- /dev/null
+++ b/src/components/ActionBar/download-tarball.ts
@@ -0,0 +1,18 @@
+import api from '../../utils/api';
+import { extractFileName, downloadFile } from '../../utils/url';
+
+function downloadTarball(link: string) {
+ return async function downloadHandler(): Promise {
+ const fileStream: Blob = await api.request(link, 'GET', {
+ headers: {
+ ['accept']:
+ 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
+ },
+ credentials: 'include',
+ });
+ const fileName = extractFileName(link);
+ downloadFile(fileStream, fileName);
+ };
+}
+
+export default downloadTarball;
diff --git a/src/components/ActionBar/index.ts b/src/components/ActionBar/index.ts
index d08aed0..518dc24 100644
--- a/src/components/ActionBar/index.ts
+++ b/src/components/ActionBar/index.ts
@@ -1 +1,2 @@
export { default } from './ActionBar';
+export { default as downloadTarball } from './download-tarball';
diff --git a/src/components/ActionBar/styles.ts b/src/components/ActionBar/styles.ts
deleted file mode 100644
index ebe2fd7..0000000
--- a/src/components/ActionBar/styles.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import styled from '@emotion/styled';
-
-import ListItem from '../../muiComponents/ListItem';
-import FloatingActionButton from '../../muiComponents/FloatingActionButton';
-import { Theme } from '../../design-tokens/theme';
-
-export const ActionListItem = styled(ListItem)({
- paddingTop: 0,
- paddingLeft: 0,
- paddingRight: 0,
-});
-
-export const Fab = styled(FloatingActionButton)<{ theme?: Theme }>(props => ({
- backgroundColor: props.theme && props.theme.palette.primary.main,
- color: props.theme && props.theme.palette.white,
- marginRight: '10px',
-}));
diff --git a/src/components/DetailSidebar/DetailSidebar.tsx b/src/components/DetailSidebar/DetailSidebar.tsx
index fccb2e7..379a896 100644
--- a/src/components/DetailSidebar/DetailSidebar.tsx
+++ b/src/components/DetailSidebar/DetailSidebar.tsx
@@ -1,6 +1,6 @@
import React, { ReactElement } from 'react';
-import { ActionBar } from '../ActionBar/ActionBar';
+import ActionBar from '../ActionBar';
import Author from '../Author';
import Developers from '../Developers';
import Dist from '../Dist/Dist';
diff --git a/src/components/Link/Link.tsx b/src/components/Link/Link.tsx
index d0a5039..310cc4c 100644
--- a/src/components/Link/Link.tsx
+++ b/src/components/Link/Link.tsx
@@ -7,20 +7,26 @@ interface Props extends Pick {
external?: boolean;
className?: string;
to: string;
+ children?: React.ReactNode;
}
+type LinkRef = HTMLAnchorElement;
+
/* eslint-disable verdaccio/jsx-spread */
-const Link: React.FC = ({ external, to, children, variant, className, ...props }) => {
+const Link = React.forwardRef(function Link(
+ { external, to, children, variant, className, ...props },
+ ref
+) {
const LinkTextContent = {children};
return external ? (
-
+
{LinkTextContent}
) : (
-
+
{LinkTextContent}
);
-};
+});
export default Link;
diff --git a/src/components/Package/Package.tsx b/src/components/Package/Package.tsx
index b0b4fd3..0e84d6e 100644
--- a/src/components/Package/Package.tsx
+++ b/src/components/Package/Package.tsx
@@ -9,7 +9,7 @@ import fileSizeSI from '../../utils/file-size';
import { formatDate, formatDateDistance } from '../../utils/package';
import Tooltip from '../../muiComponents/Tooltip';
import { isURL } from '../../utils/url';
-import { downloadHandler } from '../ActionBar/ActionBar';
+import { downloadTarball } from '../ActionBar';
import ListItem from '../../muiComponents/ListItem';
import Grid from '../../muiComponents/Grid';
@@ -140,7 +140,7 @@ const Package: React.FC = ({
dist.tarball &&
isURL(dist.tarball) && (
// eslint-disable-next-line
- downloadHandler(dist.tarball.replace(`https://registry.npmjs.org/`, window.location.href))} target={'_blank'}>
+
{/* eslint-disable-next-line react/jsx-max-depth */}
diff --git a/types/packageMeta.ts b/types/packageMeta.ts
index 3bf1853..0030e8f 100644
--- a/types/packageMeta.ts
+++ b/types/packageMeta.ts
@@ -8,6 +8,7 @@ export interface PackageMetaInterface {
dist: {
fileCount: number;
unpackedSize: number;
+ tarball?: string;
};
engines?: {
node?: string;
@@ -15,6 +16,10 @@ export interface PackageMetaInterface {
};
license?: Partial | string;
version: string;
+ homepage?: string;
+ bugs?: {
+ url: string;
+ };
repository?: {
type?: string;
url?: string;