feat: migrating flow to typescript (#47)
This PR convert the code base to Typescript, the changes are the following: - migrate code base to Typescript (3.4.x) - enable `eslint` and `@typescript-eslint/eslint-plugin` (warnings still need to be addressed in future pull request - update relevant dependencies for this PR (linting, etc) - enable `bundlezise` (it was disabled for some reason) * refactor: refactoring to typescript * refactor: migrating to typescript * refactor: applied feedbacks * fix: fixed conflicts * refactored: changed registry * refactor: updated registry & removed unnecessary lib * fix: fixed registry ur * fix: fixed page load * refactor: refactored footer wip * refactor: converting to ts..wip * refactor: converting to ts. wip * refactor: converting to ts. wip * refactor: converting to ts * refactor: converting to ts * fix: fixed load errors * refactor: converted files to ts * refactor: removed flow from tests * fix: removed transpiled files * refactor: added ts-ignore * fix: fixed errors * fix: fixed types * fix: fixing jest import -.- * fix: fixing lint errors * fix: fixing lint errors * fix: fixed lint errors * refactor: removed unnecessary tsconfig's config * fix: fixing errors * fix: fixed warning * fix: fixed test * refactor: wip * refactor: wip * refactor: wip * fix: fixing tests: wip * wip * wip * fix: fixed search test * wip * fix: fixing lint errors * fix: re-added stylelint * refactor: updated stylelint script * fix: fixed: 'styles.js' were found. * fix: fixed Search tests * chore: enable eslint eslint needs expecitely to know which file has to lint, by default is JS, in this case we need also ts,tsx files eslint . --ext .js,.ts * chore: vcode eslint settings * chore: restore eslint previous conf * chore: clean jest config * chore: fix eslint warnings * chore: eslint errors cleared chore: clean warnings chore: remove github actions test phases chore: remove dupe rule * chore: update handler name * chore: restore logo from img to url css prop - loading images with css is more performant than using img html tags, switching this might be a breaking change - restore no-empty-source seems the linting do not accept false - update snapshots - remove @material-ui/styles * chore: update stylelint linting * chore: update stylelint linting * chore: fix a mistake on move tabs to a function * chore: eanble bundlezie * chore: use default_executor in circleci * chore: update readme
94
src/App/App.test.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import storage from '../utils/storage';
|
||||
import App from './App';
|
||||
|
||||
import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__/token';
|
||||
|
||||
jest.mock('../utils/storage', () => {
|
||||
class LocalStorageMock {
|
||||
store: object;
|
||||
public constructor() {
|
||||
this.store = {};
|
||||
}
|
||||
public clear(): void {
|
||||
this.store = {};
|
||||
}
|
||||
public getItem(key): unknown {
|
||||
return this.store[key] || null;
|
||||
}
|
||||
public setItem(key, value): void {
|
||||
this.store[key] = value.toString();
|
||||
}
|
||||
public removeItem(key): void {
|
||||
delete this.store[key];
|
||||
}
|
||||
}
|
||||
return new LocalStorageMock();
|
||||
});
|
||||
|
||||
jest.mock('../utils/api', () => ({
|
||||
request: require('../../jest/unit/components/__mocks__/api').default.request,
|
||||
}));
|
||||
|
||||
describe('App', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<App />);
|
||||
});
|
||||
|
||||
test('toggleLoginModal: should toggle the value in state', () => {
|
||||
const { handleToggleLoginModal } = wrapper.instance();
|
||||
expect(wrapper.state().showLoginModal).toBeFalsy();
|
||||
handleToggleLoginModal();
|
||||
expect(wrapper.state('showLoginModal')).toBeTruthy();
|
||||
expect(wrapper.state('error')).toEqual({});
|
||||
});
|
||||
|
||||
test('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
||||
storage.setItem('username', 'verdaccio');
|
||||
storage.setItem('token', generateTokenWithTimeRange(24));
|
||||
const { isUserAlreadyLoggedIn } = wrapper.instance();
|
||||
|
||||
isUserAlreadyLoggedIn();
|
||||
|
||||
expect(wrapper.state('user').username).toEqual('verdaccio');
|
||||
});
|
||||
|
||||
test('handleLogout - logouts the user and clear localstorage', async () => {
|
||||
const { handleLogout } = wrapper.instance();
|
||||
storage.setItem('username', 'verdaccio');
|
||||
storage.setItem('token', 'xxxx.TOKEN.xxxx');
|
||||
|
||||
await handleLogout();
|
||||
expect(wrapper.state('user')).toEqual({});
|
||||
expect(wrapper.state('isUserLoggedIn')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('handleDoLogin - login the user successfully', async () => {
|
||||
const { handleDoLogin } = wrapper.instance();
|
||||
await handleDoLogin('sam', '1234');
|
||||
const result = {
|
||||
username: 'sam',
|
||||
token: 'TEST_TOKEN',
|
||||
};
|
||||
expect(wrapper.state('isUserLoggedIn')).toBeTruthy();
|
||||
expect(wrapper.state('showLoginModal')).toBeFalsy();
|
||||
expect(storage.getItem('username')).toEqual('sam');
|
||||
expect(storage.getItem('token')).toEqual('TEST_TOKEN');
|
||||
expect(wrapper.state('user')).toEqual(result);
|
||||
});
|
||||
|
||||
test('handleDoLogin - authentication failure', async () => {
|
||||
const { handleDoLogin } = wrapper.instance();
|
||||
await handleDoLogin('sam', '12345');
|
||||
const result = {
|
||||
description: 'bad username/password, access denied',
|
||||
title: 'Unable to login',
|
||||
type: 'error',
|
||||
};
|
||||
expect(wrapper.state('user')).toEqual({});
|
||||
expect(wrapper.state('error')).toEqual(result);
|
||||
});
|
||||
});
|
||||
@@ -1,33 +1,31 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import isNil from 'lodash/isNil';
|
||||
|
||||
import storage from './utils/storage';
|
||||
import { makeLogin, isTokenExpire } from './utils/login';
|
||||
import storage from '../utils/storage';
|
||||
import { makeLogin, isTokenExpire } from '../utils/login';
|
||||
|
||||
import Loading from './components/Loading';
|
||||
import LoginModal from './components/Login';
|
||||
import Header from './components/Header';
|
||||
import { Container, Content } from './components/Layout';
|
||||
import RouterApp from './router';
|
||||
import API from './utils/api';
|
||||
import './styles/typeface-roboto.scss';
|
||||
import './styles/main.scss';
|
||||
import Loading from '../components/Loading';
|
||||
import LoginModal from '../components/Login';
|
||||
import Header from '../components/Header';
|
||||
import { Container, Content } from '../components/Layout';
|
||||
import RouterApp from '../router';
|
||||
import API from '../utils/api';
|
||||
import '../styles/typeface-roboto.scss';
|
||||
import '../styles/main.scss';
|
||||
import 'normalize.css';
|
||||
import Footer from './components/Footer';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
export const AppContext = React.createContext();
|
||||
export const AppContext = React.createContext<null>(null);
|
||||
export const AppContextProvider = AppContext.Provider;
|
||||
export const AppContextConsumer = AppContext.Consumer;
|
||||
|
||||
export default class App extends Component {
|
||||
state = {
|
||||
export default class App extends Component<any, any> {
|
||||
public state = {
|
||||
error: {},
|
||||
// @ts-ignore
|
||||
logoUrl: window.VERDACCIO_LOGO,
|
||||
user: {},
|
||||
// @ts-ignore
|
||||
scope: window.VERDACCIO_SCOPE ? `${window.VERDACCIO_SCOPE}:` : '',
|
||||
showLoginModal: false,
|
||||
isUserLoggedIn: false,
|
||||
@@ -35,37 +33,40 @@ export default class App extends Component {
|
||||
isLoading: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
this.isUserAlreadyLoggedIn();
|
||||
this.loadOnHandler();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
componentDidUpdate(_, prevState) {
|
||||
public componentDidUpdate(_, prevState): void {
|
||||
const { isUserLoggedIn } = this.state;
|
||||
if (prevState.isUserLoggedIn !== isUserLoggedIn) {
|
||||
this.loadOnHandler();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
public render(): React.ReactElement<HTMLDivElement> {
|
||||
const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state;
|
||||
|
||||
const context: any = { isUserLoggedIn, packages, logoUrl, user, scope };
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Container isLoading={isLoading}>
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<Fragment>
|
||||
<AppContextProvider value={{ isUserLoggedIn, packages, logoUrl, user, scope }}>{this.renderContent()}</AppContextProvider>
|
||||
</Fragment>
|
||||
<>
|
||||
<AppContextProvider value={context}>{this.renderContent()}</AppContextProvider>
|
||||
</>
|
||||
)}
|
||||
{this.renderLoginModal()}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
isUserAlreadyLoggedIn = () => {
|
||||
public isUserAlreadyLoggedIn = () => {
|
||||
// checks for token validity
|
||||
const token = storage.getItem('token');
|
||||
const username = storage.getItem('username');
|
||||
@@ -79,10 +80,12 @@ export default class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
loadOnHandler = async () => {
|
||||
public loadOnHandler = async () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
this.req = await API.request('packages', 'GET');
|
||||
this.setState({
|
||||
// @ts-ignore
|
||||
packages: this.req,
|
||||
isLoading: false,
|
||||
});
|
||||
@@ -96,7 +99,7 @@ export default class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
setLoading = isLoading =>
|
||||
public setLoading = isLoading =>
|
||||
this.setState({
|
||||
isLoading,
|
||||
});
|
||||
@@ -105,8 +108,9 @@ export default class App extends Component {
|
||||
* Toggles the login modal
|
||||
* Required by: <LoginModal /> <Header />
|
||||
*/
|
||||
handleToggleLoginModal = () => {
|
||||
public handleToggleLoginModal = () => {
|
||||
this.setState(prevState => ({
|
||||
// @ts-ignore
|
||||
showLoginModal: !prevState.showLoginModal,
|
||||
error: {},
|
||||
}));
|
||||
@@ -116,7 +120,8 @@ export default class App extends Component {
|
||||
* handles login
|
||||
* Required by: <Header />
|
||||
*/
|
||||
handleDoLogin = async (usernameValue, passwordValue) => {
|
||||
public handleDoLogin = async (usernameValue, passwordValue) => {
|
||||
// @ts-ignore
|
||||
const { username, token, error } = await makeLogin(usernameValue, passwordValue);
|
||||
|
||||
if (username && token) {
|
||||
@@ -133,7 +138,7 @@ export default class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
setLoggedUser = (username, token) => {
|
||||
public setLoggedUser = (username, token) => {
|
||||
this.setState({
|
||||
user: {
|
||||
username,
|
||||
@@ -148,7 +153,7 @@ export default class App extends Component {
|
||||
* Logouts user
|
||||
* Required by: <Header />
|
||||
*/
|
||||
handleLogout = () => {
|
||||
public handleLogout = () => {
|
||||
storage.removeItem('username');
|
||||
storage.removeItem('token');
|
||||
this.setState({
|
||||
@@ -157,34 +162,31 @@ export default class App extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
renderLoginModal = () => {
|
||||
public renderLoginModal = (): ReactElement<HTMLElement> => {
|
||||
const { error, showLoginModal } = this.state;
|
||||
return (
|
||||
<LoginModal
|
||||
error={error}
|
||||
onCancel={this.handleToggleLoginModal}
|
||||
onChange={this.handleSetUsernameAndPassword}
|
||||
onSubmit={this.handleDoLogin}
|
||||
visibility={showLoginModal}
|
||||
/>
|
||||
);
|
||||
return <LoginModal error={error} onCancel={this.handleToggleLoginModal} onSubmit={this.handleDoLogin} visibility={showLoginModal} />;
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
public renderContent = (): ReactElement<HTMLElement> => {
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Content>
|
||||
<RouterApp onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal}>
|
||||
{this.renderHeader()}
|
||||
</RouterApp>
|
||||
</Content>
|
||||
<Footer />
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderHeader = () => {
|
||||
const { logoUrl, user: { username } = {}, scope } = this.state;
|
||||
public renderHeader = (): ReactElement<HTMLElement> => {
|
||||
const {
|
||||
logoUrl,
|
||||
// @ts-ignore
|
||||
user: { username },
|
||||
scope,
|
||||
} = this.state;
|
||||
|
||||
return <Header logo={logoUrl} onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal} scope={scope} username={username} />;
|
||||
};
|
||||
1
src/App/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './App';
|
||||
5
src/components/.eslintrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-invalid-this": 0
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
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 List from '@material-ui/core/List/index';
|
||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||
import List from '@material-ui/core/List';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
|
||||
import { Fab, ActionListItem } from './styles';
|
||||
import { isURL } from '../../utils/url';
|
||||
|
||||
@@ -30,17 +26,17 @@ const ACTIONS = {
|
||||
};
|
||||
|
||||
class ActionBar extends Component<any, any> {
|
||||
render() {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderActionBar(context);
|
||||
return this.renderActionBar(context as VersionPageConsumerProps);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderIconsWithLink(link, component) {
|
||||
private renderIconsWithLink(link: string, component: any): ReactElement<HTMLElement> {
|
||||
return (
|
||||
<a href={link} target={'_blank'}>
|
||||
{component}
|
||||
@@ -48,7 +44,8 @@ class ActionBar extends Component<any, any> {
|
||||
);
|
||||
}
|
||||
|
||||
renderActionBarListItems = packageMeta => {
|
||||
private renderActionBarListItems = packageMeta => {
|
||||
// @ts-ignore
|
||||
const { latest: { bugs: { url: issue } = {}, homepage, dist: { tarball } = {} } = {} } = packageMeta;
|
||||
|
||||
const actionsMap = {
|
||||
@@ -62,8 +59,9 @@ class ActionBar extends Component<any, any> {
|
||||
if (link && isURL(link)) {
|
||||
const fab = <Fab size={'small'}>{ACTIONS[value]['icon']}</Fab>;
|
||||
component.push(
|
||||
// @ts-ignore
|
||||
<Tooltip key={key} title={ACTIONS[value]['title']}>
|
||||
{this.renderIconsWithLink(link, fab)}
|
||||
<>{this.renderIconsWithLink(link, fab)}</>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -77,7 +75,7 @@ class ActionBar extends Component<any, any> {
|
||||
);
|
||||
};
|
||||
|
||||
renderActionBar = ({ packageMeta = {} }) => {
|
||||
private renderActionBar = ({ packageMeta = {} }) => {
|
||||
return <List>{this.renderActionBarListItems(packageMeta)}</List>;
|
||||
};
|
||||
}
|
||||
1
src/components/ActionBar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './ActionBar';
|
||||
@@ -1,9 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import styled from 'react-emotion';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
11
src/components/Author/Author.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Author from './Author';
|
||||
|
||||
describe('<Author /> component', () => {
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Author />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,10 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import React, { Component, ReactNode } from 'react';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import { DetailContextConsumer } from '../../pages/version/Version';
|
||||
import { Heading, AuthorListItem } from './styles';
|
||||
import { isEmail } from '../../utils/url';
|
||||
|
||||
@@ -16,14 +12,14 @@ class Authors extends Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderAuthor(context);
|
||||
{(context: any) => {
|
||||
return context && context.packageMeta && this.renderAuthor(context.packageMeta);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderLinkForMail(email, avatarComponent, packageName, version) {
|
||||
renderLinkForMail(email: string, avatarComponent: ReactNode, packageName: string, version: string) {
|
||||
if (!email || isEmail(email) === false) {
|
||||
return avatarComponent;
|
||||
}
|
||||
@@ -35,7 +31,7 @@ class Authors extends Component<any, any> {
|
||||
);
|
||||
}
|
||||
|
||||
renderAuthor = ({ packageMeta }) => {
|
||||
renderAuthor = packageMeta => {
|
||||
const { author, name: packageName, version } = packageMeta.latest;
|
||||
|
||||
if (!author) {
|
||||
3
src/components/Author/__snapshots__/Author.test.tsx.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Author /> component should render the component in default state 1`] = `""`;
|
||||
1
src/components/Author/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Author';
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
@@ -1,10 +1,4 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import React, { KeyboardEvent } from 'react';
|
||||
import Autosuggest from 'react-autosuggest';
|
||||
import match from 'autosuggest-highlight/match';
|
||||
import parse from 'autosuggest-highlight/parse';
|
||||
@@ -12,9 +6,27 @@ import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
import { fontWeight } from '../../utils/styles/sizes';
|
||||
import { Wrapper, InputField, SuggestionContainer } from './styles';
|
||||
import { IProps } from './types';
|
||||
|
||||
const renderInputComponent = (inputProps): Node => {
|
||||
export interface Props {
|
||||
suggestions: any[];
|
||||
suggestionsLoading?: boolean;
|
||||
suggestionsLoaded?: boolean;
|
||||
suggestionsError?: boolean;
|
||||
apiLoading?: boolean;
|
||||
color?: string;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
startAdornment?: any;
|
||||
disableUnderline?: boolean;
|
||||
onChange?: (event: KeyboardEvent<HTMLInputElement>, { newValue, method }: { newValue: string; method: string }) => void;
|
||||
onSuggestionsFetch?: ({ value: string }) => Promise<void>;
|
||||
onCleanSuggestions?: () => void;
|
||||
onClick?: (event: KeyboardEvent<HTMLInputElement>, { suggestionValue, method }: { suggestionValue: any[]; method: string }) => void;
|
||||
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const renderInputComponent = inputProps => {
|
||||
const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps;
|
||||
return (
|
||||
<InputField
|
||||
@@ -34,21 +46,21 @@ const renderInputComponent = (inputProps): Node => {
|
||||
|
||||
const getSuggestionValue = (suggestion): string => suggestion.name;
|
||||
|
||||
const renderSuggestion = (suggestion, { query, isHighlighted }): Node => {
|
||||
const renderSuggestion = (suggestion, { query, isHighlighted }) => {
|
||||
const matches = match(suggestion.name, query);
|
||||
const parts = parse(suggestion.name, matches);
|
||||
return (
|
||||
<MenuItem component={'div'} selected={isHighlighted}>
|
||||
<MenuItem component="div" selected={isHighlighted}>
|
||||
<div>
|
||||
{parts.map((part, index) => {
|
||||
return part.highlight ? (
|
||||
<span href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.semiBold }}>
|
||||
<a href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.semiBold }}>
|
||||
{part.text}
|
||||
</span>
|
||||
</a>
|
||||
) : (
|
||||
<span href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.light }}>
|
||||
<a href={suggestion.link} key={String(index)} style={{ fontWeight: fontWeight.light }}>
|
||||
{part.text}
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -56,9 +68,9 @@ const renderSuggestion = (suggestion, { query, isHighlighted }): Node => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderMessage = (message): Node => {
|
||||
const renderMessage = message => {
|
||||
return (
|
||||
<MenuItem component={'div'} selected={false}>
|
||||
<MenuItem component="div" selected={false}>
|
||||
<div>{message}</div>
|
||||
</MenuItem>
|
||||
);
|
||||
@@ -86,7 +98,7 @@ const AutoComplete = ({
|
||||
suggestionsLoading = false,
|
||||
suggestionsLoaded = false,
|
||||
suggestionsError = false,
|
||||
}: IProps): Node => {
|
||||
}: Props) => {
|
||||
const autosuggestProps = {
|
||||
renderInputComponent,
|
||||
suggestions,
|
||||
1
src/components/AutoComplete/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './AutoComplete';
|
||||
@@ -1,16 +1,14 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled, { css } from 'react-emotion';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
|
||||
import TextField from '../TextField';
|
||||
import { IInputField } from './types';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
export interface InputFieldProps {
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const Wrapper = styled('div')`
|
||||
&& {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
@@ -19,10 +17,11 @@ export const Wrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const InputField = ({ color, ...others }: IInputField) => (
|
||||
export const InputField: React.FC<InputFieldProps> = ({ color, ...others }) => (
|
||||
<TextField
|
||||
{...others}
|
||||
classes={{
|
||||
// @ts-ignore
|
||||
input: css`
|
||||
&& {
|
||||
${color &&
|
||||
38
src/components/CopyToClipBoard/CopyToClipBoard.test.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import CopyToClipBoard from './CopyToClipBoard';
|
||||
import { CopyIcon } from './styles';
|
||||
|
||||
describe('<CopyToClipBoard /> component', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<CopyToClipBoard text={'copy text'} />);
|
||||
});
|
||||
|
||||
test('render the component', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call the DOM APIs for copy to clipboard utility', () => {
|
||||
const event = {
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
|
||||
// @ts-ignore: Property 'getSelection' does not exist on type 'Global'.
|
||||
global.getSelection = jest.fn(() => ({
|
||||
removeAllRanges: () => {},
|
||||
addRange: () => {},
|
||||
}));
|
||||
|
||||
// @ts-ignore: Property 'document/getSelection' does not exist on type 'Global'.
|
||||
const { document, getSelection } = global;
|
||||
|
||||
wrapper.find(CopyIcon).simulate('click', event);
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
expect(document.createRange).toHaveBeenCalled();
|
||||
expect(getSelection).toHaveBeenCalled();
|
||||
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||
});
|
||||
});
|
||||
40
src/components/CopyToClipBoard/CopyToClipBoard.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import FileCopy from '@material-ui/icons/FileCopy';
|
||||
import React from 'react';
|
||||
|
||||
import { copyToClipBoardUtility } from '../../utils/cli-utils';
|
||||
import { TEXT } from '../../utils/constants';
|
||||
|
||||
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const renderText: React.FC<any> = (text: string, children: React.ReactNode): React.ReactElement<HTMLElement> => {
|
||||
if (children) {
|
||||
return <ClipBoardCopyText>{children}</ClipBoardCopyText>;
|
||||
}
|
||||
|
||||
return <ClipBoardCopyText>{text}</ClipBoardCopyText>;
|
||||
};
|
||||
|
||||
const renderToolTipFileCopy = (text: string): React.ReactElement<HTMLElement> => (
|
||||
<Tooltip disableFocusListener={true} title={TEXT.CLIPBOARD_COPY}>
|
||||
<CopyIcon onClick={copyToClipBoardUtility(text)}>
|
||||
<FileCopy />
|
||||
</CopyIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const CopyToClipBoard: React.FC<Props> = ({ text, children }) => {
|
||||
return (
|
||||
<ClipBoardCopy>
|
||||
{renderText(text, children)}
|
||||
{renderToolTipFileCopy(text)}
|
||||
</ClipBoardCopy>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyToClipBoard;
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<CopyToClipBoard /> component render the component 1`] = `"<div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-1m8aenu eb8w2fo1\\">copy text</span><button class=\\"MuiButtonBase-root-15 MuiIconButton-root-9 css-56v3u0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-14\\"><svg class=\\"MuiSvgIcon-root-18\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span></button></div>"`;
|
||||
1
src/components/CopyToClipBoard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './CopyToClipBoard';
|
||||
@@ -1,7 +1,7 @@
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import styled from 'react-emotion';
|
||||
import IconButton from '@material-ui/core/IconButton/index';
|
||||
|
||||
export const ClipBoardCopy = styled.div`
|
||||
export const ClipBoardCopy = styled('div')`
|
||||
&& {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -9,7 +9,7 @@ export const ClipBoardCopy = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ClipBoardCopyText = styled.span`
|
||||
export const ClipBoardCopyText = styled('span')`
|
||||
&& {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
@@ -1,13 +1,8 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component, Fragment, ReactElement } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import CardContent from '@material-ui/core/CardContent/index';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version';
|
||||
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
|
||||
|
||||
import { CardWrap, Heading, Tags, Tag } from './styles';
|
||||
import NoItems from '../NoItems';
|
||||
@@ -23,13 +18,13 @@ class DepDetail extends Component<any, any> {
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
const { name, version } = this.state;
|
||||
const tagText = `${name}@${version}`;
|
||||
return <Tag className={'dep-tag'} clickable={true} component={'div'} label={tagText} onClick={this.handleOnClick} />;
|
||||
}
|
||||
|
||||
handleOnClick = () => {
|
||||
private handleOnClick = () => {
|
||||
const { name } = this.state;
|
||||
const { onLoading, history } = this.props;
|
||||
|
||||
@@ -41,18 +36,17 @@ class DepDetail extends Component<any, any> {
|
||||
const WrapperDependencyDetail = withRouter(DepDetail);
|
||||
|
||||
class DependencyBlock extends Component<any, any> {
|
||||
render() {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
const { dependencies, title } = this.props;
|
||||
const deps = Object.entries(dependencies);
|
||||
|
||||
return (
|
||||
// $FlowFixMe
|
||||
<DetailContextConsumer>
|
||||
{({ enableLoading }) => {
|
||||
{({ enableLoading }: any) => {
|
||||
return (
|
||||
<CardWrap>
|
||||
<CardContent>
|
||||
<Heading variant={'subheading'}>{`${title} (${deps.length})`}</Heading>
|
||||
<Heading variant="subheading">{`${title} (${deps.length})`}</Heading>
|
||||
<Tags>{this.renderTags(deps, enableLoading)}</Tags>
|
||||
</CardContent>
|
||||
</CardWrap>
|
||||
@@ -62,7 +56,7 @@ class DependencyBlock extends Component<any, any> {
|
||||
);
|
||||
}
|
||||
|
||||
renderTags = (deps: any, enableLoading: any) =>
|
||||
private renderTags = (deps: any, enableLoading: any) =>
|
||||
deps.map(dep => {
|
||||
const [name, version] = dep;
|
||||
|
||||
@@ -71,26 +65,25 @@ class DependencyBlock extends Component<any, any> {
|
||||
}
|
||||
|
||||
class Dependencies extends Component<any, any> {
|
||||
state = {
|
||||
public state = {
|
||||
tabPosition: 0,
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{packageMeta => {
|
||||
return this.renderDependencies(packageMeta);
|
||||
return this.renderDependencies(packageMeta as VersionPageConsumerProps);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
checkDependencyLength(dependency: Object = {}) {
|
||||
private checkDependencyLength(dependency: Record<string, any> = {}): boolean {
|
||||
return Object.keys(dependency).length > 0;
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
renderDependencies({ packageMeta }) {
|
||||
private renderDependencies({ packageMeta }): ReactElement<HTMLElement> {
|
||||
const { latest } = packageMeta;
|
||||
const { dependencies, devDependencies, peerDependencies, name } = latest;
|
||||
|
||||
@@ -99,7 +92,8 @@ class Dependencies extends Component<any, any> {
|
||||
const dependencyList = Object.keys(dependencyMap).reduce((result, value, key) => {
|
||||
const selectedDepndency = dependencyMap[value];
|
||||
if (selectedDepndency && this.checkDependencyLength(selectedDepndency)) {
|
||||
result.push(<DependencyBlock className={'dependency-block'} dependencies={selectedDepndency} key={key} title={value} />);
|
||||
// @ts-ignore
|
||||
result.push(<DependencyBlock className="dependency-block" dependencies={selectedDepndency} key={key} title={value} />);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
@@ -107,7 +101,7 @@ class Dependencies extends Component<any, any> {
|
||||
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.`} />;
|
||||
}
|
||||
}
|
||||
|
||||
1
src/components/Dependencies/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Dependencies';
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Card from '@material-ui/core/Card/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import Chip from '@material-ui/core/Chip/index';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
|
||||
export const CardWrap = styled(Card)`
|
||||
&& {
|
||||
7
src/components/Dependencies/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface Props {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
72
src/components/DetailContainer/DetailContainer.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React, { Component, ReactElement, Fragment } from 'react';
|
||||
|
||||
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
|
||||
import Readme from '../Readme';
|
||||
import Versions from '../Versions';
|
||||
import { preventXSS } from '../../utils/sec-utils';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import { Content } from './styles';
|
||||
import Dependencies from '../Dependencies';
|
||||
import UpLinks from '../UpLinks';
|
||||
|
||||
interface DetailContainerState {
|
||||
tabPosition: number;
|
||||
}
|
||||
|
||||
class DetailContainer extends Component<any, DetailContainerState> {
|
||||
public state = {
|
||||
tabPosition: 0,
|
||||
};
|
||||
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderTabs(context as VersionPageConsumerProps);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
private handleChange = (event: any, tabPosition: number) => {
|
||||
event.preventDefault();
|
||||
this.setState({ tabPosition });
|
||||
};
|
||||
|
||||
private renderListTabs(tabPosition: number): React.ReactElement<HTMLElement> {
|
||||
return (
|
||||
<Tabs indicatorColor={'primary'} onChange={this.handleChange} textColor={'primary'} value={tabPosition} variant={'fullWidth'}>
|
||||
<Tab id={'readme-tab'} label={'Readme'} />
|
||||
<Tab id={'dependencies-tab'} label={'Dependencies'} />
|
||||
<Tab id={'versions-tab'} label={'Versions'} />
|
||||
<Tab id={'uplinks-tab'} label={'Uplinks'} />
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
private renderTabs = ({ readMe }) => {
|
||||
const { tabPosition } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Content>
|
||||
{this.renderListTabs(tabPosition)}
|
||||
<br />
|
||||
{tabPosition === 0 && this.renderReadme(readMe)}
|
||||
{tabPosition === 1 && <Dependencies />}
|
||||
{tabPosition === 2 && <Versions />}
|
||||
{tabPosition === 3 && <UpLinks />}
|
||||
</Content>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private renderReadme = (readMe: string): ReactElement<HTMLElement> => {
|
||||
const encodedReadme = preventXSS(readMe);
|
||||
|
||||
return <Readme description={encodedReadme} />;
|
||||
};
|
||||
}
|
||||
|
||||
export default DetailContainer;
|
||||
1
src/components/DetailContainer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './DetailContainer';
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
|
||||
export const Content = styled.div`
|
||||
export const Content = styled('div')`
|
||||
&& {
|
||||
padding: 15px;
|
||||
}
|
||||
7
src/components/DetailContainer/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface Props {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
87
src/components/DetailSidebar/DetailSidebar.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import List from '@material-ui/core/List';
|
||||
|
||||
import ActionBar from '../ActionBar/ActionBar';
|
||||
import Author from '../Author';
|
||||
import Developers from '../Developers';
|
||||
import Dist from '../Dist/Dist';
|
||||
import Engine from '../Engines/Engines';
|
||||
import Install from '../Install';
|
||||
import Repository from '../Repository/Repository';
|
||||
|
||||
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
|
||||
|
||||
import { TitleListItem, TitleListItemText } from './styles';
|
||||
|
||||
class DetailSidebar extends Component {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
return <DetailContextConsumer>{context => this.renderSideBar(context as VersionPageConsumerProps)}</DetailContextConsumer>;
|
||||
}
|
||||
|
||||
private renderSideBar = ({ packageName, packageMeta }): ReactElement<HTMLElement> => {
|
||||
return (
|
||||
<div className={'sidebar-info'}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
{this.renderTitle(packageName, packageMeta)}
|
||||
{this.renderActionBar()}
|
||||
{this.renderCopyCLI()}
|
||||
{this.renderRepository()}
|
||||
{this.renderEngine()}
|
||||
{this.renderDist()}
|
||||
{this.renderAuthor()}
|
||||
{this.renderMaintainers()}
|
||||
{this.renderContributors()}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private renderTitle = (packageName, packageMeta) => {
|
||||
return (
|
||||
<List className="detail-info">
|
||||
<TitleListItem alignItems="flex-start">
|
||||
<TitleListItemText primary={<b>{packageName}</b>} secondary={packageMeta.latest.description} />
|
||||
</TitleListItem>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
private renderCopyCLI = () => {
|
||||
return <Install />;
|
||||
};
|
||||
|
||||
private renderMaintainers = () => {
|
||||
return <Developers type="maintainers" />;
|
||||
};
|
||||
|
||||
private renderContributors = () => {
|
||||
return <Developers type="contributors" />;
|
||||
};
|
||||
|
||||
private renderRepository = () => {
|
||||
return <Repository />;
|
||||
};
|
||||
|
||||
private renderAuthor = () => {
|
||||
return <Author />;
|
||||
};
|
||||
|
||||
private renderEngine = () => {
|
||||
return <Engine />;
|
||||
};
|
||||
|
||||
private renderDist = () => {
|
||||
return <Dist />;
|
||||
};
|
||||
|
||||
private renderActionBar = () => {
|
||||
return <ActionBar />;
|
||||
};
|
||||
}
|
||||
|
||||
export default DetailSidebar;
|
||||
1
src/components/DetailSidebar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './DetailSidebar';
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Add from '@material-ui/icons/Add';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version';
|
||||
import { DetailContextConsumer } from '../../pages/version/Version';
|
||||
import { Details, Heading, Content, Fab } from './styles';
|
||||
import { isEmail } from '../../utils/url';
|
||||
|
||||
@@ -24,7 +20,7 @@ class Developers extends Component<Props, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{({ packageMeta }) => {
|
||||
{({ packageMeta }: any) => {
|
||||
const { type } = this.props;
|
||||
const developerType = packageMeta.latest[type];
|
||||
if (!developerType || developerType.length === 0) return null;
|
||||
@@ -49,7 +45,7 @@ class Developers extends Component<Props, any> {
|
||||
<Details key={developer.email}>{this.renderDeveloperDetails(developer, packageMeta)}</Details>
|
||||
))}
|
||||
{visibleDevs < developers.length && (
|
||||
<Fab onClick={this.handleLoadMore} size={'small'}>
|
||||
<Fab onClick={this.handleLoadMore} size="small">
|
||||
<Add />
|
||||
</Fab>
|
||||
)}
|
||||
1
src/components/Developers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Developers';
|
||||
@@ -1,7 +1,3 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||
@@ -1,12 +1,8 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import List from '@material-ui/core/List/index';
|
||||
import List from '@material-ui/core/List';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import { DetailContextConsumer } from '../../pages/version/Version';
|
||||
import { Heading, DistListItem, DistChips } from './styles';
|
||||
import fileSizeSI from '../../utils/file-size';
|
||||
|
||||
@@ -14,14 +10,14 @@ class Dist extends Component<any, any> {
|
||||
render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
{(context: any) => {
|
||||
return this.renderDist(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderChips(dist, license) {
|
||||
renderChips(dist: any, license: string) {
|
||||
const distDict = {
|
||||
'file-count': dist.fileCount,
|
||||
size: dist.unpackedSize && fileSizeSI(dist.unpackedSize),
|
||||
@@ -29,6 +25,7 @@ class Dist extends Component<any, any> {
|
||||
};
|
||||
|
||||
const chipsList = Object.keys(distDict).reduce((componentList, title, key) => {
|
||||
// @ts-ignore
|
||||
const value = distDict[title];
|
||||
if (value) {
|
||||
const label = (
|
||||
@@ -37,6 +34,7 @@ class Dist extends Component<any, any> {
|
||||
<b>{title.split('-').join(' ')}</b>:{value}
|
||||
</span>
|
||||
);
|
||||
// @ts-ignore is not assignable to parameter of type 'never'
|
||||
componentList.push(<DistChips key={key} label={label} />);
|
||||
}
|
||||
return componentList;
|
||||
@@ -45,11 +43,11 @@ class Dist extends Component<any, any> {
|
||||
return chipsList;
|
||||
}
|
||||
|
||||
renderDist = ({ packageMeta }) => {
|
||||
renderDist = ({ packageMeta }: any) => {
|
||||
const { dist = {}, license } = packageMeta.latest;
|
||||
|
||||
return (
|
||||
<List subheader={<Heading variant={'subheading'}>{'Latest Distribution'}</Heading>}>
|
||||
<List subheader={<Heading variant="subheading">{'Latest Distribution'}</Heading>}>
|
||||
<DistListItem>{this.renderChips(dist, license)}</DistListItem>
|
||||
</List>
|
||||
);
|
||||
1
src/components/Dist/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Dist';
|
||||
@@ -1,12 +1,8 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab/index';
|
||||
import Chip from '@material-ui/core/Chip/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import { default as MuiFab } from '@material-ui/core/Fab';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
import Grid from '@material-ui/core/Grid/index';
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
|
||||
import { Heading, EngineListItem } from './styles';
|
||||
// @ts-ignore
|
||||
import node from './img/node.png';
|
||||
import npm from '../Install/img/npm.svg';
|
||||
|
||||
@@ -20,17 +17,17 @@ const ICONS = {
|
||||
};
|
||||
|
||||
class Engine extends Component {
|
||||
render() {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderEngine(context);
|
||||
return this.renderEngine(context as VersionPageConsumerProps);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderEngine = ({ packageMeta }) => {
|
||||
private renderEngine = ({ packageMeta }): ReactElement<HTMLElement> | null => {
|
||||
const { engines } = packageMeta.latest;
|
||||
if (!engines) {
|
||||
return null;
|
||||
@@ -41,6 +38,7 @@ class Engine extends Component {
|
||||
'NPM-version': engines.npm,
|
||||
};
|
||||
|
||||
const accumulator: React.ReactNode[] = [];
|
||||
const items = Object.keys(engineDict).reduce((markup, text, key) => {
|
||||
const heading = engineDict[text];
|
||||
if (heading) {
|
||||
@@ -51,7 +49,7 @@ class Engine extends Component {
|
||||
);
|
||||
}
|
||||
return markup;
|
||||
}, []);
|
||||
}, accumulator);
|
||||
|
||||
if (items.length < 1) {
|
||||
return null;
|
||||
@@ -60,7 +58,7 @@ class Engine extends Component {
|
||||
return <Grid container={true}>{items}</Grid>;
|
||||
};
|
||||
|
||||
renderListItems = (heading, text) => {
|
||||
private renderListItems = (heading, text) => {
|
||||
return (
|
||||
<List subheader={<Heading variant={'subheading'}>{text.split('-').join(' ')}</Heading>}>
|
||||
<EngineListItem>
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
1
src/components/Engines/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Engines';
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
23
src/components/Footer/Footer.test.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import Footer from './Footer';
|
||||
|
||||
jest.mock('../../../package.json', () => ({
|
||||
version: '4.0.0-alpha.3',
|
||||
}));
|
||||
|
||||
describe('<Footer /> component', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
// @ts-ignore : Property 'VERDACCIO_VERSION' does not exist on type 'Window'
|
||||
window.VERDACCIO_VERSION = 'v.1.0.0';
|
||||
wrapper = mount(<Footer />);
|
||||
// @ts-ignore : Property 'VERDACCIO_VERSION' does not exist on type 'Window'
|
||||
delete window.VERDACCIO_VERSION;
|
||||
});
|
||||
|
||||
test('should load the initial state of Footer component', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
53
src/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Wrapper, Left, Right, Earth, Flags, Love, Flag, Logo, Inner, ToolTip } from './styles';
|
||||
import { goToVerdaccioWebsite } from '../../utils/windows';
|
||||
|
||||
const renderTooltip = () => (
|
||||
<ToolTip>
|
||||
<Earth name="earth" size="md" />
|
||||
<Flags>
|
||||
<Flag name="spain" size="md" />
|
||||
<Flag name="nicaragua" size="md" />
|
||||
<Flag name="india" size="md" />
|
||||
<Flag name="brazil" size="md" />
|
||||
<Flag name="china" size="md" />
|
||||
<Flag name="austria" size="md" />
|
||||
</Flags>
|
||||
</ToolTip>
|
||||
);
|
||||
const POWERED_LABEL = 'Powered by';
|
||||
const MADEWITH_LABEL = ' Made with';
|
||||
const ON_LABEL = 'on';
|
||||
const HEARTH_EMOJI = '♥';
|
||||
|
||||
// @ts-ignore
|
||||
const renderRight = (version = window.VERDACCIO_VERSION) => {
|
||||
return (
|
||||
<Right>
|
||||
{POWERED_LABEL}
|
||||
<Logo img={true} name="verdaccio" onClick={goToVerdaccioWebsite} pointer={true} size="md" />
|
||||
{`/ ${version}`}
|
||||
</Right>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLeft = () => (
|
||||
<Left>
|
||||
{MADEWITH_LABEL}
|
||||
<Love>{HEARTH_EMOJI}</Love>
|
||||
{ON_LABEL}
|
||||
{renderTooltip()}
|
||||
</Left>
|
||||
);
|
||||
|
||||
const Footer: React.FC = () => (
|
||||
<Wrapper>
|
||||
<Inner>
|
||||
{renderLeft()}
|
||||
{renderRight()}
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export default Footer;
|
||||
3
src/components/Footer/__snapshots__/Footer.test.tsx.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// 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>"`;
|
||||
1
src/components/Footer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Footer';
|
||||
@@ -1,14 +1,9 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import mq from '../../utils/styles/media';
|
||||
import Icon from '../Icon';
|
||||
import Icon from '../Icon/Icon';
|
||||
import colors from '../../utils/styles/colors';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
export const Wrapper = styled('div')`
|
||||
&& {
|
||||
background: ${colors.snow};
|
||||
border-top: 1px solid ${colors.greyGainsboro};
|
||||
@@ -18,31 +13,40 @@ export const Wrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Inner = styled.div`
|
||||
export const Inner = styled('div')`
|
||||
&& {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
${mq.medium(css`
|
||||
min-width: 400px;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
justify-content: space-between;
|
||||
`)};
|
||||
${mq.large(css`
|
||||
max-width: 1240px;
|
||||
`)};
|
||||
${() => {
|
||||
// @ts-ignore
|
||||
return mq.medium(css`
|
||||
min-width: 400px;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
justify-content: space-between;
|
||||
`);
|
||||
}};
|
||||
${() => {
|
||||
// @ts-ignore
|
||||
return mq.large(css`
|
||||
max-width: 1240px;
|
||||
`);
|
||||
}};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Left = styled.div`
|
||||
export const Left = styled('div')`
|
||||
&& {
|
||||
align-items: center;
|
||||
display: none;
|
||||
${mq.medium(css`
|
||||
display: flex;
|
||||
`)};
|
||||
${() => {
|
||||
// @ts-ignore
|
||||
return mq.medium(css`
|
||||
display: flex;
|
||||
`);
|
||||
}};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -52,7 +56,7 @@ export const Right = styled(Left)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const ToolTip = styled.span`
|
||||
export const ToolTip = styled('span')`
|
||||
&& {
|
||||
position: relative;
|
||||
height: 18px;
|
||||
@@ -65,7 +69,7 @@ export const Earth = styled(Icon)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Flags = styled.span`
|
||||
export const Flags = styled('span')`
|
||||
&& {
|
||||
position: absolute;
|
||||
background: ${colors.greyAthens};
|
||||
@@ -92,7 +96,7 @@ export const Flags = styled.span`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Love = styled.span`
|
||||
export const Love = styled('span')`
|
||||
&& {
|
||||
color: ${colors.love};
|
||||
padding: 0 5px;
|
||||
124
src/components/Header/Header.test.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { shallow } from 'enzyme';
|
||||
import Header from './Header';
|
||||
|
||||
describe('<Header /> component with logged in state', () => {
|
||||
let wrapper;
|
||||
let routerWrapper;
|
||||
let instance;
|
||||
let props;
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
username: 'test user',
|
||||
handleLogout: jest.fn(),
|
||||
logo: '',
|
||||
onToggleLoginModal: jest.fn(),
|
||||
scope: 'test scope',
|
||||
withoutSearch: true,
|
||||
};
|
||||
routerWrapper = shallow(
|
||||
<Router>
|
||||
<Header
|
||||
logo={props.logo}
|
||||
onLogout={props.handleLogout}
|
||||
onToggleLoginModal={props.onToggleLoginModal}
|
||||
scope={props.scope}
|
||||
username={props.username}
|
||||
withoutSearch={props.withoutSearch}
|
||||
/>
|
||||
</Router>
|
||||
);
|
||||
wrapper = routerWrapper.find(Header).dive();
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
test('should load the component in logged in state', () => {
|
||||
const state = {
|
||||
openInfoDialog: false,
|
||||
packages: undefined,
|
||||
registryUrl: 'http://localhost',
|
||||
showMobileNavBar: false,
|
||||
};
|
||||
|
||||
expect(wrapper.state()).toEqual(state);
|
||||
expect(routerWrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('handleLoggedInMenu: set anchorEl to html element value in state', () => {
|
||||
// creates a sample menu
|
||||
const div = document.createElement('div');
|
||||
const text = document.createTextNode('sample menu');
|
||||
div.appendChild(text);
|
||||
|
||||
const event = {
|
||||
currentTarget: div,
|
||||
};
|
||||
|
||||
instance.handleLoggedInMenu(event);
|
||||
expect(wrapper.state('anchorEl')).toEqual(div);
|
||||
});
|
||||
});
|
||||
|
||||
describe('<Header /> component with logged out state', () => {
|
||||
let wrapper;
|
||||
let routerWrapper;
|
||||
let instance;
|
||||
let props;
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
handleLogout: jest.fn(),
|
||||
onToggleLoginModal: jest.fn(),
|
||||
scope: 'test scope',
|
||||
logo: '',
|
||||
withoutSearch: true,
|
||||
};
|
||||
routerWrapper = shallow(
|
||||
<Router>
|
||||
<Header
|
||||
logo={props.logo}
|
||||
onLogout={props.handleLogout}
|
||||
onToggleLoginModal={props.onToggleLoginModal}
|
||||
scope={props.scope}
|
||||
withoutSearch={props.withoutSearch}
|
||||
/>
|
||||
</Router>
|
||||
);
|
||||
wrapper = routerWrapper.find(Header).dive();
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
test('should load the component in logged out state', () => {
|
||||
const state = {
|
||||
openInfoDialog: false,
|
||||
packages: undefined,
|
||||
registryUrl: 'http://localhost',
|
||||
showMobileNavBar: false,
|
||||
};
|
||||
expect(wrapper.state()).toEqual(state);
|
||||
expect(routerWrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('handleLoggedInMenuClose: set anchorEl value to null in state', () => {
|
||||
instance.handleLoggedInMenuClose();
|
||||
expect(wrapper.state('anchorEl')).toBeNull();
|
||||
});
|
||||
|
||||
test('handleOpenRegistryInfoDialog: set openInfoDialog to be truthy in state', () => {
|
||||
instance.handleOpenRegistryInfoDialog();
|
||||
expect(wrapper.state('openInfoDialog')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('handleCloseRegistryInfoDialog: set openInfoDialog to be falsy in state', () => {
|
||||
instance.handleCloseRegistryInfoDialog();
|
||||
expect(wrapper.state('openInfoDialog')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('handleToggleLogin: close/open popover menu', () => {
|
||||
instance.handleToggleLogin();
|
||||
expect(wrapper.state('anchorEl')).toBeNull();
|
||||
expect(props.onToggleLoginModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,43 +1,46 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Node } from 'react';
|
||||
import React, { SyntheticEvent, Component, Fragment, ReactElement } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Button from '@material-ui/core/Button/index';
|
||||
import IconButton from '@material-ui/core/IconButton/index';
|
||||
import MenuItem from '@material-ui/core/MenuItem/index';
|
||||
import Menu from '@material-ui/core/Menu/index';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import Info from '@material-ui/icons/Info';
|
||||
import Help from '@material-ui/icons/Help';
|
||||
import Tooltip from '@material-ui/core/Tooltip/index';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import { default as IconSearch } from '@material-ui/icons/Search';
|
||||
|
||||
import { getRegistryURL } from '../../utils/url';
|
||||
import ExternalLink from '../Link';
|
||||
import Logo from '../Logo';
|
||||
import RegistryInfoDialog from '../RegistryInfoDialog';
|
||||
import Label from '../Label';
|
||||
import Search from '../Search';
|
||||
import RegistryInfoContent from '../RegistryInfoContent';
|
||||
import RegistryInfoDialog from '../RegistryInfoDialog/RegistryInfoDialog';
|
||||
import Label from '../Label/Label';
|
||||
import Search from '../Search/Search';
|
||||
import RegistryInfoContent from '../RegistryInfoContent/RegistryInfoContent';
|
||||
|
||||
import { IProps, IState } from './types';
|
||||
import type { ToolTipType } from './types';
|
||||
import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, IconSearchButton, SearchWrapper } from './styles';
|
||||
|
||||
class Header extends Component<IProps, IState> {
|
||||
handleLoggedInMenu: Function;
|
||||
handleLoggedInMenuClose: Function;
|
||||
handleOpenRegistryInfoDialog: Function;
|
||||
handleCloseRegistryInfoDialog: Function;
|
||||
handleToggleLogin: Function;
|
||||
renderInfoDialog: Function;
|
||||
interface Props {
|
||||
logo: string;
|
||||
username?: string;
|
||||
onLogout: () => void;
|
||||
onToggleLoginModal: () => void;
|
||||
scope: string;
|
||||
withoutSearch?: boolean;
|
||||
}
|
||||
|
||||
constructor(props: IProps) {
|
||||
interface State {
|
||||
anchorEl?: any;
|
||||
openInfoDialog: boolean;
|
||||
registryUrl: string;
|
||||
showMobileNavBar: boolean;
|
||||
}
|
||||
|
||||
type ToolTipType = 'search' | 'help' | 'info';
|
||||
|
||||
class Header extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
openInfoDialog: false,
|
||||
@@ -46,10 +49,36 @@ class Header extends Component<IProps, IState> {
|
||||
};
|
||||
}
|
||||
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
const { showMobileNavBar } = this.state;
|
||||
const { withoutSearch = false } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<NavBar position="static">
|
||||
<InnerNavBar>
|
||||
{this.renderLeftSide()}
|
||||
{this.renderRightSide()}
|
||||
</InnerNavBar>
|
||||
{this.renderInfoDialog()}
|
||||
</NavBar>
|
||||
{showMobileNavBar && !withoutSearch && (
|
||||
<MobileNavBar>
|
||||
<InnerMobileNavBar>
|
||||
<Search />
|
||||
</InnerMobileNavBar>
|
||||
<Button color="inherit" onClick={this.handleDismissMNav}>
|
||||
{'Cancel'}
|
||||
</Button>
|
||||
</MobileNavBar>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* opens popover menu for logged in user.
|
||||
*/
|
||||
handleLoggedInMenu = (event: SyntheticEvent<HTMLElement>) => {
|
||||
public handleLoggedInMenu = (event: SyntheticEvent<HTMLElement>) => {
|
||||
this.setState({
|
||||
anchorEl: event.currentTarget,
|
||||
});
|
||||
@@ -58,7 +87,7 @@ class Header extends Component<IProps, IState> {
|
||||
/**
|
||||
* closes popover menu for logged in user
|
||||
*/
|
||||
handleLoggedInMenuClose = () => {
|
||||
public handleLoggedInMenuClose = () => {
|
||||
this.setState({
|
||||
anchorEl: null,
|
||||
});
|
||||
@@ -67,7 +96,7 @@ class Header extends Component<IProps, IState> {
|
||||
/**
|
||||
* opens registry information dialog.
|
||||
*/
|
||||
handleOpenRegistryInfoDialog = () => {
|
||||
public handleOpenRegistryInfoDialog = () => {
|
||||
this.setState({
|
||||
openInfoDialog: true,
|
||||
});
|
||||
@@ -76,7 +105,7 @@ class Header extends Component<IProps, IState> {
|
||||
/**
|
||||
* closes registry information dialog.
|
||||
*/
|
||||
handleCloseRegistryInfoDialog = () => {
|
||||
public handleCloseRegistryInfoDialog = () => {
|
||||
this.setState({
|
||||
openInfoDialog: false,
|
||||
});
|
||||
@@ -85,7 +114,7 @@ class Header extends Component<IProps, IState> {
|
||||
/**
|
||||
* close/open popover menu for logged in users.
|
||||
*/
|
||||
handleToggleLogin = () => {
|
||||
public handleToggleLogin = () => {
|
||||
const { onToggleLoginModal } = this.props;
|
||||
this.setState(
|
||||
{
|
||||
@@ -95,20 +124,20 @@ class Header extends Component<IProps, IState> {
|
||||
);
|
||||
};
|
||||
|
||||
handleToggleMNav = () => {
|
||||
public handleToggleMNav = () => {
|
||||
const { showMobileNavBar } = this.state;
|
||||
this.setState({
|
||||
showMobileNavBar: !showMobileNavBar,
|
||||
});
|
||||
};
|
||||
|
||||
handleDismissMNav = () => {
|
||||
public handleDismissMNav = () => {
|
||||
this.setState({
|
||||
showMobileNavBar: false,
|
||||
});
|
||||
};
|
||||
|
||||
renderLeftSide = (): Node => {
|
||||
public renderLeftSide = () => {
|
||||
const { withoutSearch = false } = this.props;
|
||||
return (
|
||||
<LeftSide>
|
||||
@@ -124,21 +153,22 @@ class Header extends Component<IProps, IState> {
|
||||
);
|
||||
};
|
||||
|
||||
renderLogo = (): Node => {
|
||||
public renderLogo = () => {
|
||||
const { logo } = this.props;
|
||||
|
||||
if (logo !== '') {
|
||||
return <img alt={'logo'} height={'40px'} src={logo} />;
|
||||
if (logo) {
|
||||
return <img alt="logo" height="40px" src={logo} />;
|
||||
} else {
|
||||
return <Logo />;
|
||||
}
|
||||
};
|
||||
|
||||
renderToolTipIcon = (title: string, type: ToolTipType) => {
|
||||
public renderToolTipIcon = (title: string, type: ToolTipType) => {
|
||||
let content;
|
||||
switch (type) {
|
||||
case 'help':
|
||||
content = (
|
||||
// @ts-ignore
|
||||
<IconButton blank={true} color={'inherit'} component={ExternalLink} to={'https://verdaccio.org/docs/en/installation'}>
|
||||
<Help />
|
||||
</IconButton>
|
||||
@@ -146,14 +176,14 @@ class Header extends Component<IProps, IState> {
|
||||
break;
|
||||
case 'info':
|
||||
content = (
|
||||
<IconButton color={'inherit'} id={'header--button-registryInfo'} onClick={this.handleOpenRegistryInfoDialog}>
|
||||
<IconButton color="inherit" id="header--button-registryInfo" onClick={this.handleOpenRegistryInfoDialog}>
|
||||
<Info />
|
||||
</IconButton>
|
||||
);
|
||||
break;
|
||||
case 'search':
|
||||
content = (
|
||||
<IconSearchButton color={'inherit'} onClick={this.handleToggleMNav}>
|
||||
<IconSearchButton color="inherit" onClick={this.handleToggleMNav}>
|
||||
<IconSearch />
|
||||
</IconSearchButton>
|
||||
);
|
||||
@@ -166,7 +196,7 @@ class Header extends Component<IProps, IState> {
|
||||
);
|
||||
};
|
||||
|
||||
renderRightSide = (): Node => {
|
||||
public renderRightSide = () => {
|
||||
const { username = '', withoutSearch = false } = this.props;
|
||||
return (
|
||||
<RightSide>
|
||||
@@ -176,7 +206,7 @@ class Header extends Component<IProps, IState> {
|
||||
{username ? (
|
||||
this.renderMenu()
|
||||
) : (
|
||||
<Button color={'inherit'} id={'header--button-login'} onClick={this.handleToggleLogin}>
|
||||
<Button color="inherit" id="header--button-login" onClick={this.handleToggleLogin}>
|
||||
{'Login'}
|
||||
</Button>
|
||||
)}
|
||||
@@ -184,26 +214,26 @@ class Header extends Component<IProps, IState> {
|
||||
);
|
||||
};
|
||||
|
||||
renderGreetings = () => {
|
||||
private renderGreetings = () => {
|
||||
const { username = '' } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Greetings>{`Hi,`}</Greetings>
|
||||
<Label capitalize={true} limit={140} text={username} weight={'bold'} />
|
||||
</>
|
||||
<Fragment>
|
||||
<Greetings>{'Hi,'}</Greetings>
|
||||
<Label capitalize={true} text={username} weight="bold" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* render popover menu
|
||||
*/
|
||||
renderMenu = (): Node => {
|
||||
private renderMenu = () => {
|
||||
const { onLogout } = this.props;
|
||||
const { anchorEl } = this.state;
|
||||
const open = Boolean(anchorEl);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IconButton color={'inherit'} id={'header--button-account'} onClick={this.handleLoggedInMenu}>
|
||||
<>
|
||||
<IconButton color="inherit" id="header--button-account" onClick={this.handleLoggedInMenu}>
|
||||
<AccountCircle />
|
||||
</IconButton>
|
||||
<Menu
|
||||
@@ -212,7 +242,7 @@ class Header extends Component<IProps, IState> {
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
id={'sidebar-menu'}
|
||||
id="sidebar-menu"
|
||||
onClose={this.handleLoggedInMenuClose}
|
||||
open={open}
|
||||
transformOrigin={{
|
||||
@@ -220,15 +250,15 @@ class Header extends Component<IProps, IState> {
|
||||
horizontal: 'right',
|
||||
}}>
|
||||
<MenuItem disabled={true}>{this.renderGreetings()}</MenuItem>
|
||||
<MenuItem id={'header--button-logout'} onClick={onLogout}>
|
||||
<MenuItem id="header--button-logout" onClick={onLogout}>
|
||||
{'Logout'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderInfoDialog = (): Node => {
|
||||
private renderInfoDialog = () => {
|
||||
const { scope } = this.props;
|
||||
const { openInfoDialog, registryUrl } = this.state;
|
||||
return (
|
||||
@@ -237,33 +267,6 @@ class Header extends Component<IProps, IState> {
|
||||
</RegistryInfoDialog>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showMobileNavBar } = this.state;
|
||||
const { withoutSearch = false } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<NavBar position={'static'}>
|
||||
<InnerNavBar>
|
||||
{this.renderLeftSide()}
|
||||
{this.renderRightSide()}
|
||||
</InnerNavBar>
|
||||
{this.renderInfoDialog()}
|
||||
</NavBar>
|
||||
{showMobileNavBar &&
|
||||
!withoutSearch && (
|
||||
<MobileNavBar>
|
||||
<InnerMobileNavBar>
|
||||
<Search />
|
||||
</InnerMobileNavBar>
|
||||
<Button color={'inherit'} onClick={this.handleDismissMNav}>
|
||||
{'Cancel'}
|
||||
</Button>
|
||||
</MobileNavBar>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Header;
|
||||
5
src/components/Header/__snapshots__/Header.test.tsx.snap
Normal file
@@ -0,0 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Header /> component with logged in state should load the component in logged in state 1`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-rfunvc e1jf5lit8\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1jf5lit0\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1jf5lit3\\"><a style=\\"margin-right:1em\\" href=\\"/\\"><div class=\\"css-1tnu3ib em793ed0\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1jf5lit2\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span></button><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-account\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z\\"></path><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path></svg></span></button></div></div></header></div>"`;
|
||||
|
||||
exports[`<Header /> component with logged out state should load the component in logged out state 1`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-rfunvc e1jf5lit8\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1jf5lit0\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1jf5lit3\\"><a style=\\"margin-right:1em\\" href=\\"/\\"><div class=\\"css-1tnu3ib em793ed0\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1jf5lit2\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span></button><button class=\\"MuiButtonBase-root-55 MuiButton-root-85 MuiButton-text-87 MuiButton-flat-90 MuiButton-colorInherit-106\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-login\\"><span class=\\"MuiButton-label-86\\">Login</span></button></div></div></header></div>"`;
|
||||
1
src/components/Header/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Header';
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import AppBar from '@material-ui/core/AppBar/index';
|
||||
import Toolbar from '@material-ui/core/Toolbar/index';
|
||||
import IconButton from '@material-ui/core/IconButton/index';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
import mq from '../../utils/styles/media';
|
||||
@@ -19,7 +14,7 @@ export const InnerNavBar = styled(Toolbar)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Greetings = styled.span`
|
||||
export const Greetings = styled('span')`
|
||||
&& {
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
@@ -38,7 +33,7 @@ export const LeftSide = styled(RightSide)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const MobileNavBar = styled.div`
|
||||
export const MobileNavBar = styled('div')`
|
||||
&& {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -48,7 +43,7 @@ export const MobileNavBar = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const InnerMobileNavBar = styled.div`
|
||||
export const InnerMobileNavBar = styled('div')`
|
||||
&& {
|
||||
border-radius: 4px;
|
||||
background-color: ${colors.greyLight};
|
||||
@@ -65,7 +60,7 @@ export const IconSearchButton = styled(IconButton)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchWrapper = styled.div`
|
||||
export const SearchWrapper = styled('div')`
|
||||
&& {
|
||||
display: none;
|
||||
max-width: 393px;
|
||||
@@ -79,28 +74,37 @@ export const NavBar = styled(AppBar)`
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
${mq.medium(css`
|
||||
${SearchWrapper} {
|
||||
display: flex;
|
||||
}
|
||||
${IconSearchButton} {
|
||||
display: none;
|
||||
}
|
||||
${MobileNavBar} {
|
||||
display: none;
|
||||
}
|
||||
`)};
|
||||
${mq.large(css`
|
||||
${InnerNavBar} {
|
||||
padding: 0 20px;
|
||||
}
|
||||
`)};
|
||||
${mq.xlarge(css`
|
||||
${InnerNavBar} {
|
||||
max-width: 1240px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`)};
|
||||
${() => {
|
||||
// @ts-ignore
|
||||
return mq.medium(css`
|
||||
${SearchWrapper} {
|
||||
display: flex;
|
||||
}
|
||||
${IconSearchButton} {
|
||||
display: none;
|
||||
}
|
||||
${MobileNavBar} {
|
||||
display: none;
|
||||
}
|
||||
`);
|
||||
}};
|
||||
${() => {
|
||||
// @ts-ignore
|
||||
return mq.large(css`
|
||||
${InnerNavBar} {
|
||||
padding: 0 20px;
|
||||
}
|
||||
`);
|
||||
}};
|
||||
${() => {
|
||||
// @ts-ignore
|
||||
return mq.xlarge(css`
|
||||
${InnerNavBar} {
|
||||
max-width: 1240px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`);
|
||||
}};
|
||||
}
|
||||
`;
|
||||
10
src/components/Help/Help.test.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import Help from './Help';
|
||||
|
||||
describe('<Help /> component', () => {
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Help />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,15 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import React, { Fragment } from 'react';
|
||||
import type { Node } from 'react';
|
||||
import CardActions from '@material-ui/core/CardActions/index';
|
||||
import CardContent from '@material-ui/core/CardContent/index';
|
||||
import Button from '@material-ui/core/Button/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
import CopyToClipBoard from '../CopyToClipBoard/index';
|
||||
import { getRegistryURL } from '../../utils/url';
|
||||
import CopyToClipBoard from '../CopyToClipBoard';
|
||||
|
||||
import { CardStyled as Card, HelpTitle } from './styles';
|
||||
|
||||
function renderHeadingClipboardSegments(title: string, text: string): Node {
|
||||
function renderHeadingClipboardSegments(title: string, text: string): React.ReactNode {
|
||||
return (
|
||||
<Fragment>
|
||||
<Typography variant={'body2'}>{title}</Typography>
|
||||
@@ -23,24 +18,24 @@ function renderHeadingClipboardSegments(title: string, text: string): Node {
|
||||
);
|
||||
}
|
||||
|
||||
const Help = (): Node => {
|
||||
const Help: React.FC = () => {
|
||||
const registryUrl = getRegistryURL();
|
||||
|
||||
return (
|
||||
<Card id={'help-card'}>
|
||||
<Card id="help-card">
|
||||
<CardContent>
|
||||
<Typography component={'h2'} gutterBottom={true} id={'help-card__title'} variant={'headline'}>
|
||||
<Typography component="h2" gutterBottom={true} id="help-card__title" variant="headline">
|
||||
{'No Package Published Yet.'}
|
||||
</Typography>
|
||||
<HelpTitle color={'textSecondary'} gutterBottom={true}>
|
||||
<HelpTitle color="textSecondary" gutterBottom={true}>
|
||||
{'To publish your first package just:'}
|
||||
</HelpTitle>
|
||||
{renderHeadingClipboardSegments('1. Login', `npm adduser --registry ${registryUrl}`)}
|
||||
{renderHeadingClipboardSegments('2. Publish', `npm publish --registry ${registryUrl}`)}
|
||||
<Typography variant={'body2'}>{'3. Refresh this page.'}</Typography>
|
||||
<Typography variant="body2">{'3. Refresh this page.'}</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button color={'primary'} href={'https://verdaccio.org/docs/en/installation'} size={'small'} target={'_blank'}>
|
||||
<Button color="primary" href="https://verdaccio.org/docs/en/installation" size="small" target="_blank">
|
||||
{'Learn More'}
|
||||
</Button>
|
||||
</CardActions>
|
||||
3
src/components/Help/__snapshots__/Help.test.tsx.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Help /> component should render the component in default state 1`] = `"<div class=\\"MuiPaper-root-2 MuiPaper-elevation1-5 MuiPaper-rounded-3 MuiCard-root-1 css-ryznli e1wgaou60\\" id=\\"help-card\\"><div class=\\"MuiCardContent-root-29\\"><h2 class=\\"MuiTypography-root-30 MuiTypography-headline-35 MuiTypography-gutterBottom-57\\" id=\\"help-card__title\\">No Package Published Yet.</h2><p class=\\"MuiTypography-root-30 MuiTypography-body1-39 MuiTypography-colorTextSecondary-63 MuiTypography-gutterBottom-57 css-zg2fwz e1wgaou61\\">To publish your first package just:</p><p class=\\"MuiTypography-root-30 MuiTypography-body2-38\\">1. Login</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-1m8aenu eb8w2fo1\\">npm adduser --registry http://localhost</span><button class=\\"MuiButtonBase-root-80 MuiIconButton-root-74 css-56v3u0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-79\\"><svg class=\\"MuiSvgIcon-root-83\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span></button></div><p class=\\"MuiTypography-root-30 MuiTypography-body2-38\\">2. Publish</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-1m8aenu eb8w2fo1\\">npm publish --registry http://localhost</span><button class=\\"MuiButtonBase-root-80 MuiIconButton-root-74 css-56v3u0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-79\\"><svg class=\\"MuiSvgIcon-root-83\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span></button></div><p class=\\"MuiTypography-root-30 MuiTypography-body2-38\\">3. Refresh this page.</p></div><div class=\\"MuiCardActions-root-92\\"><a class=\\"MuiButtonBase-root-80 MuiButton-root-95 MuiButton-text-97 MuiButton-textPrimary-98 MuiButton-flat-100 MuiButton-flatPrimary-101 MuiButton-sizeSmall-118 MuiCardActions-action-94\\" tabindex=\\"0\\" role=\\"button\\" href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\"><span class=\\"MuiButton-label-96\\">Learn More</span></a></div></div>"`;
|
||||
1
src/components/Help/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Help';
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import styled from 'react-emotion';
|
||||
import Card from '@material-ui/core/Card/index';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
|
||||
export const CardStyled = styled(Card)`
|
||||
&& {
|
||||
14
src/components/Icon/Icon.test.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Icon from './Icon';
|
||||
|
||||
describe('<Icon /> component', () => {
|
||||
const props = {
|
||||
name: 'austria',
|
||||
};
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Icon name={props.name} />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
import React, { MouseEvent } from 'react';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
import { Svg, Img, ImgWrapper } from './styles';
|
||||
import { IProps, IIconsMap } from './types';
|
||||
|
||||
import brazil from './img/brazil.svg';
|
||||
import china from './img/china.svg';
|
||||
@@ -25,8 +18,25 @@ import license from './img/license.svg';
|
||||
import time from './img/time.svg';
|
||||
import version from './img/version.svg';
|
||||
|
||||
export const Icons: $Shape<IIconsMap> = {
|
||||
// flags
|
||||
export interface IconsMap {
|
||||
brazil: string;
|
||||
spain: string;
|
||||
china: string;
|
||||
nicaragua: string;
|
||||
pakistan: string;
|
||||
austria: string;
|
||||
india: string;
|
||||
earth: string;
|
||||
verdaccio: string;
|
||||
license: string;
|
||||
time: string;
|
||||
law: string;
|
||||
version: string;
|
||||
filebinary: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export const Icons: IconsMap = {
|
||||
brazil,
|
||||
spain,
|
||||
china,
|
||||
@@ -36,7 +46,6 @@ export const Icons: $Shape<IIconsMap> = {
|
||||
austria,
|
||||
earth,
|
||||
verdaccio,
|
||||
// other icons
|
||||
filebinary,
|
||||
law,
|
||||
license,
|
||||
@@ -44,13 +53,25 @@ export const Icons: $Shape<IIconsMap> = {
|
||||
version,
|
||||
};
|
||||
|
||||
const Icon = ({ className, name, size = 'sm', img = false, pointer = false, ...props }: IProps): Node => {
|
||||
export interface Props {
|
||||
name: keyof IconsMap;
|
||||
className?: string;
|
||||
onClick?: (event: MouseEvent<SVGElement | HTMLSpanElement>) => void;
|
||||
size?: 'sm' | 'md';
|
||||
pointer?: boolean;
|
||||
img?: boolean;
|
||||
modifiers?: any;
|
||||
}
|
||||
|
||||
const Icon: React.FC<Props> = ({ className, name, size = 'sm', img = false, pointer = false, ...props }) => {
|
||||
// @ts-ignore
|
||||
const title = capitalize(name);
|
||||
return img ? (
|
||||
<ImgWrapper className={className} pointer={pointer} size={size} title={title} {...props}>
|
||||
<ImgWrapper className={className} name={name} pointer={pointer} size={size} title={title} {...props}>
|
||||
<Img alt={title} src={Icons[name]} />
|
||||
</ImgWrapper>
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Svg className={className} pointer={pointer} size={size} {...props}>
|
||||
<title>{title}</title>
|
||||
<use xlinkHref={`${Icons[name]}#${name}`} />
|
||||
3
src/components/Icon/__snapshots__/Icon.test.tsx.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// 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>"`;
|
||||
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 898 B After Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 977 B After Width: | Height: | Size: 977 B |
|
Before Width: | Height: | Size: 851 B After Width: | Height: | Size: 851 B |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
1
src/components/Icon/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Icon';
|
||||
@@ -1,23 +1,12 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
import { IProps } from './types';
|
||||
|
||||
const getSize = (size: string) => {
|
||||
const getSize = (size?: 'md' | 'sm') => {
|
||||
switch (size) {
|
||||
case 'md':
|
||||
return `
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
`;
|
||||
case 'lg':
|
||||
return `
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`;
|
||||
default:
|
||||
return `
|
||||
width: 14px;
|
||||
@@ -26,7 +15,7 @@ const getSize = (size: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const commonStyle = ({ size = 'sm', pointer, modifiers }: IProps) => css`
|
||||
const commonStyle = ({ size = 'sm', pointer, modifiers }: any) => css`
|
||||
&& {
|
||||
display: inline-block;
|
||||
cursor: ${pointer ? 'pointer' : 'default'};
|
||||
@@ -35,19 +24,19 @@ const commonStyle = ({ size = 'sm', pointer, modifiers }: IProps) => css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Svg = styled.svg`
|
||||
export const Svg = styled('svg')`
|
||||
&& {
|
||||
${commonStyle};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ImgWrapper = styled.span`
|
||||
export const ImgWrapper = styled('span')`
|
||||
&& {
|
||||
${commonStyle};
|
||||
}
|
||||
`;
|
||||
|
||||
export const Img = styled.img`
|
||||
export const Img = styled('img')`
|
||||
&& {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
11
src/components/Install/Install.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Install from './Install';
|
||||
|
||||
describe('<Install /> component', () => {
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Install />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,33 +1,30 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import List from '@material-ui/core/List/index';
|
||||
import ListItemText from '@material-ui/core/ListItemText/index';
|
||||
|
||||
import { DetailContextConsumer } from '../../pages/version/index';
|
||||
// @ts-ignore
|
||||
import { DetailContextConsumer } from '../../pages/version/Version';
|
||||
import CopyToClipBoard from '../CopyToClipBoard';
|
||||
|
||||
import { Heading, InstallItem, PackageMangerAvatar } from './styles';
|
||||
// logos of package managers
|
||||
import npm from './img/npm.svg';
|
||||
import pnpm from './img/pnpm.svg';
|
||||
import yarn from './img/yarn.svg';
|
||||
|
||||
import { Heading, InstallItem, PackageMangerAvatar } from './styles';
|
||||
|
||||
class Install extends Component {
|
||||
render() {
|
||||
public render() {
|
||||
return (
|
||||
<DetailContextConsumer>
|
||||
{context => {
|
||||
return this.renderCopyCLI(context);
|
||||
{(context: any) => {
|
||||
return context && context.packageName && this.renderCopyCLI(context);
|
||||
}}
|
||||
</DetailContextConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
renderCopyCLI = ({ packageName }) => {
|
||||
public renderCopyCLI = ({ packageName }: { packageName: string }) => {
|
||||
return (
|
||||
<>
|
||||
<List subheader={<Heading variant={'subheading'}>{'Installation'}</Heading>}>{this.renderListItems(packageName)}</List>
|
||||
@@ -35,7 +32,7 @@ class Install extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
renderListItems = packageName => {
|
||||
public renderListItems = (packageName: string) => {
|
||||
return (
|
||||
<>
|
||||
<InstallItem>
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Install /> component should render the component in default state 1`] = `""`;
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
1
src/components/Install/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Install';
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import styled from 'react-emotion';
|
||||
import Typography from '@material-ui/core/Typography/index';
|
||||
import ListItem from '@material-ui/core/ListItem/index';
|
||||
import Avatar from '@material-ui/core/Avatar/index';
|
||||
|
||||
export const Heading = styled(Typography)`
|
||||
&& {
|
||||
14
src/components/Label/Label.test.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Label from './Label';
|
||||
|
||||
describe('<Label /> component', () => {
|
||||
const props = {
|
||||
text: 'test',
|
||||
};
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Label text={props.text} />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
30
src/components/Label/Label.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import styled from 'react-emotion';
|
||||
import { fontWeight } from '../../utils/styles/sizes';
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
capitalize?: boolean;
|
||||
weight?: string;
|
||||
modifiers?: any;
|
||||
}
|
||||
|
||||
const Wrapper = styled('div')`
|
||||
font-weight: ${({ weight }) => {
|
||||
// @ts-ignore
|
||||
return fontWeight[weight];
|
||||
}};
|
||||
text-transform: ${({ capitalize }) => (capitalize ? 'capitalize' : 'none')};
|
||||
${({ modifiers }: Props) => modifiers && modifiers};
|
||||
`;
|
||||
|
||||
const Label: React.FC<Props> = ({ text = '', capitalize = false, weight = 'regular', ...props }) => {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Wrapper capitalize={capitalize} weight={weight} {...props}>
|
||||
{text}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Label;
|
||||
3
src/components/Label/__snapshots__/Label.test.tsx.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Label /> component should render the component in default state 1`] = `"<div class=\\"css-1xfhjjm esyufg60\\">test</div>"`;
|
||||
1
src/components/Label/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Label';
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled, { css } from 'react-emotion';
|
||||
|
||||
export const Content = styled.div`
|
||||
export const Content = styled('div')`
|
||||
&& {
|
||||
background-color: #ffffff;
|
||||
flex: 1;
|
||||
@@ -15,14 +10,15 @@ export const Content = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
export const Container = styled('div')`
|
||||
&& {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
${({ isLoading }) =>
|
||||
isLoading &&
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
props.isLoading &&
|
||||
css`
|
||||
${Content} {
|
||||
background-color: #f5f6f8;
|
||||
1
src/components/Layout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { Content, Container } from './Layout';
|
||||
14
src/components/Link/Link.test.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Link from './Link';
|
||||
|
||||
describe('<Link /> component', () => {
|
||||
const props = {
|
||||
to: 'https://github.com/verdaccio/ui',
|
||||
};
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Link blank={true} to={props.to} />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
15
src/components/Link/Link.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode;
|
||||
to: string;
|
||||
blank?: boolean;
|
||||
}
|
||||
|
||||
const Link: React.FC<Props> = ({ children, to = '#', blank = false, ...props }) => (
|
||||
<a href={to} target={blank ? '_blank' : '_self'} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
export default Link;
|
||||
3
src/components/Link/__snapshots__/Link.test.tsx.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Link /> component should render the component in default state 1`] = `"<a href=\\"https://github.com/verdaccio/ui\\" target=\\"_blank\\"></a>"`;
|
||||
1
src/components/Link/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Link';
|
||||
11
src/components/Loading/Loading.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Loading from './Loading';
|
||||
|
||||
describe('<Loading /> component', () => {
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Loading />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,14 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Node } from 'react';
|
||||
|
||||
import Logo from '../Logo';
|
||||
import Spinner from '../Spinner';
|
||||
|
||||
import { Wrapper, Badge } from './styles';
|
||||
|
||||
const Loading = (): Node => (
|
||||
const Loading: React.FC = () => (
|
||||
<Wrapper>
|
||||
<Badge>
|
||||
<Logo md={true} />
|
||||
<Logo />
|
||||
</Badge>
|
||||
<Spinner />
|
||||
</Wrapper>
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Loading /> component should render the component in default state 1`] = `"<div class=\\"css-1221txa eimgwje0\\"><div class=\\"css-bxochs eimgwje1\\"><div class=\\"css-1tnu3ib em793ed0\\"></div></div><div class=\\"css-vqrgi e1ag4h8b0\\"><div class=\\"MuiCircularProgress-root-1 MuiCircularProgress-colorPrimary-4 MuiCircularProgress-indeterminate-3 css-15gl0ho e1ag4h8b1\\" style=\\"width:50px;height:50px\\" role=\\"progressbar\\"><svg class=\\"MuiCircularProgress-svg-6\\" viewBox=\\"22 22 44 44\\"><circle class=\\"MuiCircularProgress-circle-7 MuiCircularProgress-circleIndeterminate-9\\" cx=\\"44\\" cy=\\"44\\" r=\\"20.2\\" fill=\\"none\\" stroke-width=\\"3.6\\"></circle></svg></div></div></div>"`;
|
||||
1
src/components/Loading/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Loading';
|
||||
@@ -1,11 +1,6 @@
|
||||
/**
|
||||
* @prettier
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import styled from 'react-emotion';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
export const Wrapper = styled('div')`
|
||||
&& {
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
@@ -14,7 +9,7 @@ export const Wrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export const Badge = styled.div`
|
||||
export const Badge = styled('div')`
|
||||
&& {
|
||||
margin: 0 0 30px 0;
|
||||
border-radius: 25px;
|
||||
128
src/components/Login/Login.test.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import LoginModal from './Login';
|
||||
|
||||
const eventUsername = {
|
||||
target: {
|
||||
value: 'xyz',
|
||||
},
|
||||
};
|
||||
|
||||
const eventPassword = {
|
||||
target: {
|
||||
value: '1234',
|
||||
},
|
||||
};
|
||||
|
||||
const event = {
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
|
||||
describe('<LoginModal />', () => {
|
||||
test('should load the component in default state', () => {
|
||||
const wrapper = mount(<LoginModal />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should load the component with props', () => {
|
||||
const props = {
|
||||
visibility: true,
|
||||
error: {
|
||||
type: 'error',
|
||||
title: 'Error Title',
|
||||
description: 'Error Description',
|
||||
},
|
||||
onCancel: () => {},
|
||||
onSubmit: () => {},
|
||||
};
|
||||
const wrapper = mount(<LoginModal {...props} />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('onCancel: should close the login modal', () => {
|
||||
const props = {
|
||||
visibility: true,
|
||||
error: {
|
||||
type: 'error',
|
||||
title: 'Error Title',
|
||||
description: 'Error Description',
|
||||
},
|
||||
onCancel: jest.fn(),
|
||||
onSubmit: () => {},
|
||||
};
|
||||
const wrapper = mount(<LoginModal {...props} />);
|
||||
wrapper.find('button[id="login--form-cancel"]').simulate('click');
|
||||
expect(props.onCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('setCredentials - should set username and password in state', () => {
|
||||
const props = {
|
||||
visibility: true,
|
||||
error: {},
|
||||
onCancel: () => {},
|
||||
onSubmit: () => {},
|
||||
};
|
||||
const wrapper = mount<LoginModal>(<LoginModal {...props} />);
|
||||
const { setCredentials } = wrapper.instance();
|
||||
|
||||
expect(setCredentials('username', eventUsername)).toBeUndefined();
|
||||
expect(wrapper.state('form').username.value).toEqual('xyz');
|
||||
|
||||
expect(setCredentials('password', eventPassword)).toBeUndefined();
|
||||
expect(wrapper.state('form').password.value).toEqual('1234');
|
||||
});
|
||||
|
||||
test('validateCredentials: should validate credentials', async () => {
|
||||
const props = {
|
||||
visibility: true,
|
||||
error: {},
|
||||
onCancel: () => {},
|
||||
onSubmit: jest.fn(),
|
||||
};
|
||||
|
||||
const wrapper = mount<LoginModal>(<LoginModal {...props} />);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.submitCredentials = jest.fn();
|
||||
const { validateCredentials, setCredentials, submitCredentials } = instance;
|
||||
|
||||
expect(setCredentials('username', eventUsername)).toBeUndefined();
|
||||
expect(wrapper.state('form').username.value).toEqual('xyz');
|
||||
|
||||
expect(setCredentials('password', eventPassword)).toBeUndefined();
|
||||
expect(wrapper.state('form').password.value).toEqual('1234');
|
||||
|
||||
validateCredentials(event);
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
expect(wrapper.state('form').username.pristine).toEqual(false);
|
||||
expect(wrapper.state('form').password.pristine).toEqual(false);
|
||||
|
||||
expect(submitCredentials).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('submitCredentials: should submit credentials', async () => {
|
||||
const props = {
|
||||
onSubmit: jest.fn(),
|
||||
};
|
||||
|
||||
const wrapper = mount<LoginModal>(<LoginModal {...props} />);
|
||||
const { setCredentials, submitCredentials } = wrapper.instance();
|
||||
expect(setCredentials('username', eventUsername)).toBeUndefined();
|
||||
expect(wrapper.state('form').username.value).toEqual('xyz');
|
||||
|
||||
expect(setCredentials('password', eventPassword)).toBeUndefined();
|
||||
expect(wrapper.state('form').password.value).toEqual('1234');
|
||||
|
||||
await submitCredentials();
|
||||
expect(props.onSubmit).toHaveBeenCalledWith('xyz', '1234');
|
||||
expect(wrapper.state('form').username.value).toEqual('');
|
||||
expect(wrapper.state('form').username.pristine).toEqual(true);
|
||||
expect(wrapper.state('form').password.value).toEqual('');
|
||||
expect(wrapper.state('form').password.pristine).toEqual(true);
|
||||
});
|
||||
});
|
||||