Skip to content
This repository was archived by the owner on Jan 16, 2022. It is now read-only.
Merged
48 changes: 18 additions & 30 deletions src/components/Author/Author.test.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
import React from 'react';
import { mount } from 'enzyme';
import Authors from './Author';

const mockPackageMeta = jest.fn(() => ({
latest: {
homepage: 'https://verdaccio.tld',
bugs: {
url: 'https://verdaccio.tld/bugs',
},
dist: {
tarball: 'https://verdaccio.tld/download',
},
},
}));
import { DetailContext } from '../../pages/Version';

jest.mock('../../pages/Version', () => ({
DetailContextConsumer: component => {
return component.children({ packageMeta: mockPackageMeta() });
},
}));
import Authors from './Author';

describe('<Author /> component', () => {
beforeEach(() => {
jest.resetAllMocks();
});

const component = (packageMeta: React.ContextType<typeof DetailContext>['packageMeta']): JSX.Element => (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, as it is a component, it must be capitalize and have a more descriptive name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also write this component outside of the describe<'Author .....

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

component ==> mockAuthorComponent . It has to be lower case since is a function. Other popular naming way might be withAuthorComponent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm unfortunately I don't agree with you @juanpicado it returns an JSX element and according to the standards it should be capitalize...Please see that also Function Components are functions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main difference is that this doesn't take props, you can't use it as a JSX element.

That's why I didn't consider it a component and why I used lowercase, it's more of a function that returns JSX.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priscilawebdev any last comment here? or can we merge?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juanpicado no...We can merge :-)

<DetailContext.Provider value={{ packageMeta }}>
<Authors />
</DetailContext.Provider>
);

test('should render the component in default state', () => {
const packageMeta = {
latest: {
Expand All @@ -36,13 +27,12 @@ describe('<Author /> component', () => {
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
},
_uplinks: {},
};

// @ts-ignore
mockPackageMeta.mockImplementation(() => packageMeta);

const wrapper = mount(<Authors />);
const wrapper = mount(component(packageMeta));
expect(wrapper.html()).toMatchSnapshot();
});

Expand All @@ -51,14 +41,13 @@ describe('<Author /> component', () => {
latest: {
name: 'verdaccio',
version: '4.0.0',
dist: { fileCount: 0, unpackedSize: 0 },
},
_uplinks: {},
Comment on lines +44 to +46
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not needed for the test but were missing before. The type error was hidden by using jest.fn

};

// @ts-ignore
mockPackageMeta.mockImplementation(() => packageMeta);

const wrapper = mount(<Authors />);
expect(wrapper.html()).toEqual('');
const wrapper = mount(component(packageMeta));
expect(wrapper.html()).toBeNull();
});

test('should render the component when there is no author email', () => {
Expand All @@ -71,13 +60,12 @@ describe('<Author /> component', () => {
url: '',
avatar: 'https://www.gravatar.com/avatar/000000',
},
dist: { fileCount: 0, unpackedSize: 0 },
},
_uplinks: {},
};

// @ts-ignore
mockPackageMeta.mockImplementation(() => packageMeta);

const wrapper = mount(<Authors />);
const wrapper = mount(component(packageMeta));
expect(wrapper.html()).toMatchSnapshot();
});
});
74 changes: 30 additions & 44 deletions src/components/Author/Author.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,44 @@
import React, { Component, ReactNode, ReactElement } from 'react';
import React, { FC, useContext } from 'react';

import Avatar from '@material-ui/core/Avatar';
import List from '@material-ui/core/List';

import { DetailContextConsumer } from '../../pages/Version';
import { DetailContext } from '../../pages/Version';
import { Heading, AuthorListItem, AuthorListItemText } from './styles';
import { isEmail } from '../../utils/url';

class Authors extends Component {
public render(): ReactElement<HTMLElement> {
return (
<DetailContextConsumer>
{context => {
const { packageMeta } = context;

if (!packageMeta) {
return null;
}

return this.renderAuthor(packageMeta);
}}
</DetailContextConsumer>
);
const Authors: FC = () => {
const { packageMeta } = useContext(DetailContext);

if (!packageMeta) {
return null;
}

public renderLinkForMail(email: string, avatarComponent: ReactNode, packageName: string, version: string): ReactElement<HTMLElement> | ReactNode {
if (!email || isEmail(email) === false) {
return avatarComponent;
}
const { author, name: packageName, version } = packageMeta.latest;

return (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
);
if (!author) {
return null;
}

public renderAuthor = ({ latest }) => {
const { author, name: packageName, version } = latest;

if (!author) {
return null;
}

const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;
return (
<List subheader={<Heading variant={'subtitle1'}>{'Author'}</Heading>}>
<AuthorListItem button={true}>
{this.renderLinkForMail(author.email, avatarComponent, packageName, version)}
<AuthorListItemText primary={author.name} />
</AuthorListItem>
</List>
);
};
}
const { email, name } = author;

const avatarComponent = <Avatar alt={author.name} src={author.avatar} />;

return (
<List subheader={<Heading variant={'subtitle1'}>{'Author'}</Heading>}>
<AuthorListItem button={true}>
{!email || !isEmail(email) ? (
<>{avatarComponent}</>
) : (
<a href={`mailto:${email}?subject=${packageName}@${version}`} target={'_top'}>
{avatarComponent}
</a>
)}

<AuthorListItemText primary={name} />
</AuthorListItem>
</List>
);
};

export default Authors;
7 changes: 1 addition & 6 deletions src/components/Package/Package.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ import {
WrapperLink,
} from './styles';
import { isURL } from '../../utils/url';
interface Author {
name: string;
avatar?: string;
email?: string;
}

interface Bugs {
url: string;
Expand All @@ -45,7 +40,7 @@ export interface PackageInterface {
name: string;
version: string;
time?: number | string;
author: Author;
author: Required<PackageMetaInterface['latest']>['author'];
description?: string;
keywords?: string[];
license?: PackageMetaInterface['latest']['license'];
Expand Down
8 changes: 8 additions & 0 deletions types/packageMeta.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
export interface Author {
name: string;
avatar?: string;
email?: string;
}

export interface PackageMetaInterface {
latest: {
author?: Author;
name: string;
dist: {
fileCount: number;
unpackedSize: number;
};
license?: Partial<LicenseInterface> | string;
version: string;
};
_uplinks: {};
}
Expand Down