Refactor: Moved Search to Header (#1064)

* refactor: Moved Search to Header

feat: added TxtField. WIP

refactor: replaced downshift by react-autosuggest

refactor: moved search's state

refactor: introduced weight 300 & 500

refactor: changed container css margin

refactor: made it more abstract

refactor: replaced name by label. changed css

refactor: removed aria

refactor: removed margin

fix: fixed flow  types

fix: fixed tags overlapping

fix: fixed search. WIP

refactor: removed useless library and added rect-router

refactor: fixed tests

* chore: remove tpm file

* feat: added component Loading

feat: added component Layout

refactor: changed css

refactor: added md prop

refactor: moved Header back to App

* chore: fix flow

* fix: update snapshot

fix: fixed componentDidMount parameter

refactor: added onKeyDown event

fix: fixed bad request

refactor: renamed interfaces files

refactor: refactor: logic display results

refactor: changed minor things

fix: fixed tests

fix: fixed tests
This commit is contained in:
Priscila 2018-10-27 21:19:45 +02:00 committed by Ayush Sharma
parent 98754c03a3
commit 9d265996f9
64 changed files with 3724 additions and 642 deletions

View File

@ -0,0 +1,38 @@
// flow-typed signature: bc8a4aaaeb1e738c5006d06072a9b064
// flow-typed version: <<STUB>>/@material-ui/core/InputAdornment_v3.1.0/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* '@material-ui/core/InputAdornment'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@material-ui/core/InputAdornment' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@material-ui/core/InputAdornment/InputAdornment' {
declare module.exports: any;
}
// Filename aliases
declare module '@material-ui/core/InputAdornment/index' {
declare module.exports: $Exports<'@material-ui/core/InputAdornment'>;
}
declare module '@material-ui/core/InputAdornment/index.js' {
declare module.exports: $Exports<'@material-ui/core/InputAdornment'>;
}
declare module '@material-ui/core/InputAdornment/InputAdornment.js' {
declare module.exports: $Exports<'@material-ui/core/InputAdornment/InputAdornment'>;
}

View File

@ -0,0 +1,38 @@
// flow-typed signature: fd8dc668544eb744d5267a667187804b
// flow-typed version: <<STUB>>/@material-ui/core/MenuItem_v3.1.0/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* '@material-ui/core/MenuItem'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@material-ui/core/MenuItem' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@material-ui/core/MenuItem/MenuItem' {
declare module.exports: any;
}
// Filename aliases
declare module '@material-ui/core/MenuItem/index' {
declare module.exports: $Exports<'@material-ui/core/MenuItem'>;
}
declare module '@material-ui/core/MenuItem/index.js' {
declare module.exports: $Exports<'@material-ui/core/MenuItem'>;
}
declare module '@material-ui/core/MenuItem/MenuItem.js' {
declare module.exports: $Exports<'@material-ui/core/MenuItem/MenuItem'>;
}

View File

@ -0,0 +1,38 @@
// flow-typed signature: 1ac90635766a00f883f3d21d79c9f12e
// flow-typed version: <<STUB>>/@material-ui/core/Paper_v3.1.0/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* '@material-ui/core/Paper'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@material-ui/core/Paper' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@material-ui/core/Paper/Paper' {
declare module.exports: any;
}
// Filename aliases
declare module '@material-ui/core/Paper/index' {
declare module.exports: $Exports<'@material-ui/core/Paper'>;
}
declare module '@material-ui/core/Paper/index.js' {
declare module.exports: $Exports<'@material-ui/core/Paper'>;
}
declare module '@material-ui/core/Paper/Paper.js' {
declare module.exports: $Exports<'@material-ui/core/Paper/Paper'>;
}

View File

@ -0,0 +1,38 @@
// flow-typed signature: 864619754dd206242d851f1d47ddb63f
// flow-typed version: <<STUB>>/@material-ui/core/TextField_v3.0.1/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* '@material-ui/core/TextField'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@material-ui/core/TextField' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@material-ui/core/TextField/TextField' {
declare module.exports: any;
}
// Filename aliases
declare module '@material-ui/core/TextField/index' {
declare module.exports: $Exports<'@material-ui/core/TextField'>;
}
declare module '@material-ui/core/TextField/index.js' {
declare module.exports: $Exports<'@material-ui/core/TextField'>;
}
declare module '@material-ui/core/TextField/TextField.js' {
declare module.exports: $Exports<'@material-ui/core/TextField/TextField'>;
}

View File

@ -1,5 +1,5 @@
// flow-typed signature: 55ef97a6a933779dac4db73eb9147735
// flow-typed version: <<STUB>>/@material-ui/core_v3.x.x/flow_v0.77.0
// flow-typed signature: 412328dad707658ee9e7ff8346800ab2
// flow-typed version: <<STUB>>/@material-ui/core_v3.0.1/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:

View File

@ -1,5 +1,5 @@
// flow-typed signature: d51b6caa61b0a7c4c66d42853d664055
// flow-typed version: <<STUB>>/@material-ui/icons_v3.x.x/flow_v0.77.0
// flow-typed signature: 59e1686415435e183c7c98de573c90d5
// flow-typed version: <<STUB>>/@material-ui/icons_v3.0.1/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:

View File

@ -0,0 +1,33 @@
// flow-typed signature: f4ce515b9395f4f0279d388b18ef59b5
// flow-typed version: <<STUB>>/autosuggest-highlight/match_v3.1.1/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* 'autosuggest-highlight/match'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'autosuggest-highlight/match' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
// Filename aliases
declare module 'autosuggest-highlight/match/index' {
declare module.exports: $Exports<'autosuggest-highlight/match'>;
}
declare module 'autosuggest-highlight/match/index.js' {
declare module.exports: $Exports<'autosuggest-highlight/match'>;
}

View File

@ -0,0 +1,33 @@
// flow-typed signature: 7df3e3914baffd57187e87617a708990
// flow-typed version: <<STUB>>/autosuggest-highlight/parse_v3.1.1/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* 'autosuggest-highlight/parse'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'autosuggest-highlight/parse' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
// Filename aliases
declare module 'autosuggest-highlight/parse/index' {
declare module.exports: $Exports<'autosuggest-highlight/parse'>;
}
declare module 'autosuggest-highlight/parse/index.js' {
declare module.exports: $Exports<'autosuggest-highlight/parse'>;
}

View File

@ -0,0 +1,60 @@
// flow-typed signature: 844045a071365b8f4e9d7d1aac302959
// flow-typed version: <<STUB>>/react-autosuggest_v9.4.2/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* 'react-autosuggest'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'react-autosuggest' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-autosuggest/dist/Autosuggest' {
declare module.exports: any;
}
declare module 'react-autosuggest/dist/index' {
declare module.exports: any;
}
declare module 'react-autosuggest/dist/standalone/autosuggest' {
declare module.exports: any;
}
declare module 'react-autosuggest/dist/standalone/autosuggest.min' {
declare module.exports: any;
}
declare module 'react-autosuggest/dist/theme' {
declare module.exports: any;
}
// Filename aliases
declare module 'react-autosuggest/dist/Autosuggest.js' {
declare module.exports: $Exports<'react-autosuggest/dist/Autosuggest'>;
}
declare module 'react-autosuggest/dist/index.js' {
declare module.exports: $Exports<'react-autosuggest/dist/index'>;
}
declare module 'react-autosuggest/dist/standalone/autosuggest.js' {
declare module.exports: $Exports<'react-autosuggest/dist/standalone/autosuggest'>;
}
declare module 'react-autosuggest/dist/standalone/autosuggest.min.js' {
declare module.exports: $Exports<'react-autosuggest/dist/standalone/autosuggest.min'>;
}
declare module 'react-autosuggest/dist/theme.js' {
declare module.exports: $Exports<'react-autosuggest/dist/theme'>;
}

199
flow-typed/npm/react-router_vx.x.x.js vendored Normal file
View File

@ -0,0 +1,199 @@
// flow-typed signature: 4fb3dfe55b5d1711432e74df5fa80adc
// flow-typed version: <<STUB>>/react-router_v4.3.1/flow_v0.81.0
/**
* This is an autogenerated libdef stub for:
*
* 'react-router'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'react-router' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-router/es/generatePath' {
declare module.exports: any;
}
declare module 'react-router/es/index' {
declare module.exports: any;
}
declare module 'react-router/es/matchPath' {
declare module.exports: any;
}
declare module 'react-router/es/MemoryRouter' {
declare module.exports: any;
}
declare module 'react-router/es/Prompt' {
declare module.exports: any;
}
declare module 'react-router/es/Redirect' {
declare module.exports: any;
}
declare module 'react-router/es/Route' {
declare module.exports: any;
}
declare module 'react-router/es/Router' {
declare module.exports: any;
}
declare module 'react-router/es/RouterContext' {
declare module.exports: any;
}
declare module 'react-router/es/StaticRouter' {
declare module.exports: any;
}
declare module 'react-router/es/Switch' {
declare module.exports: any;
}
declare module 'react-router/es/withRouter' {
declare module.exports: any;
}
declare module 'react-router/generatePath' {
declare module.exports: any;
}
declare module 'react-router/matchPath' {
declare module.exports: any;
}
declare module 'react-router/MemoryRouter' {
declare module.exports: any;
}
declare module 'react-router/Prompt' {
declare module.exports: any;
}
declare module 'react-router/Redirect' {
declare module.exports: any;
}
declare module 'react-router/Route' {
declare module.exports: any;
}
declare module 'react-router/Router' {
declare module.exports: any;
}
declare module 'react-router/StaticRouter' {
declare module.exports: any;
}
declare module 'react-router/Switch' {
declare module.exports: any;
}
declare module 'react-router/umd/react-router' {
declare module.exports: any;
}
declare module 'react-router/umd/react-router.min' {
declare module.exports: any;
}
declare module 'react-router/withRouter' {
declare module.exports: any;
}
// Filename aliases
declare module 'react-router/es/generatePath.js' {
declare module.exports: $Exports<'react-router/es/generatePath'>;
}
declare module 'react-router/es/index.js' {
declare module.exports: $Exports<'react-router/es/index'>;
}
declare module 'react-router/es/matchPath.js' {
declare module.exports: $Exports<'react-router/es/matchPath'>;
}
declare module 'react-router/es/MemoryRouter.js' {
declare module.exports: $Exports<'react-router/es/MemoryRouter'>;
}
declare module 'react-router/es/Prompt.js' {
declare module.exports: $Exports<'react-router/es/Prompt'>;
}
declare module 'react-router/es/Redirect.js' {
declare module.exports: $Exports<'react-router/es/Redirect'>;
}
declare module 'react-router/es/Route.js' {
declare module.exports: $Exports<'react-router/es/Route'>;
}
declare module 'react-router/es/Router.js' {
declare module.exports: $Exports<'react-router/es/Router'>;
}
declare module 'react-router/es/RouterContext.js' {
declare module.exports: $Exports<'react-router/es/RouterContext'>;
}
declare module 'react-router/es/StaticRouter.js' {
declare module.exports: $Exports<'react-router/es/StaticRouter'>;
}
declare module 'react-router/es/Switch.js' {
declare module.exports: $Exports<'react-router/es/Switch'>;
}
declare module 'react-router/es/withRouter.js' {
declare module.exports: $Exports<'react-router/es/withRouter'>;
}
declare module 'react-router/generatePath.js' {
declare module.exports: $Exports<'react-router/generatePath'>;
}
declare module 'react-router/index' {
declare module.exports: $Exports<'react-router'>;
}
declare module 'react-router/index.js' {
declare module.exports: $Exports<'react-router'>;
}
declare module 'react-router/matchPath.js' {
declare module.exports: $Exports<'react-router/matchPath'>;
}
declare module 'react-router/MemoryRouter.js' {
declare module.exports: $Exports<'react-router/MemoryRouter'>;
}
declare module 'react-router/Prompt.js' {
declare module.exports: $Exports<'react-router/Prompt'>;
}
declare module 'react-router/Redirect.js' {
declare module.exports: $Exports<'react-router/Redirect'>;
}
declare module 'react-router/Route.js' {
declare module.exports: $Exports<'react-router/Route'>;
}
declare module 'react-router/Router.js' {
declare module.exports: $Exports<'react-router/Router'>;
}
declare module 'react-router/StaticRouter.js' {
declare module.exports: $Exports<'react-router/StaticRouter'>;
}
declare module 'react-router/Switch.js' {
declare module.exports: $Exports<'react-router/Switch'>;
}
declare module 'react-router/umd/react-router.js' {
declare module.exports: $Exports<'react-router/umd/react-router'>;
}
declare module 'react-router/umd/react-router.min.js' {
declare module.exports: $Exports<'react-router/umd/react-router.min'>;
}
declare module 'react-router/withRouter.js' {
declare module.exports: $Exports<'react-router/withRouter'>;
}

View File

@ -20,6 +20,7 @@
"@verdaccio/streams": "1.0.0",
"JSONStream": "1.3.4",
"async": "2.6.1",
"autosuggest-highlight": "3.1.1",
"body-parser": "1.18.3",
"bunyan": "1.8.12",
"chalk": "2.4.1",
@ -45,6 +46,8 @@
"mkdirp": "0.5.1",
"mv": "2.1.1",
"pkginfo": "0.4.1",
"react-autosuggest": "9.4.2",
"react-router": "4.3.1",
"request": "2.88.0",
"semver": "5.5.1",
"verdaccio-audit": "1.0.0",
@ -85,7 +88,6 @@
"codecov": "3.1.0",
"cross-env": "5.2.0",
"css-loader": "0.28.10",
"element-theme-default": "1.4.13",
"emotion": "9.2.8",
"enzyme": "3.6.0",
"enzyme-adapter-react-16": "1.5.0",

View File

@ -1,18 +1,28 @@
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import isNil from 'lodash/isNil';
import 'element-theme-default';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import SnackbarContent from '@material-ui/core/SnackbarContent';
import ErrorIcon from '@material-ui/icons/Error';
import storage from './utils/storage';
import logo from './utils/logo';
import { makeLogin, isTokenExpire } from './utils/login';
import Header from './components/Header';
import Footer from './components/Footer';
import Loading from './components/Loading';
import LoginModal from './components/Login';
import Header from './components/Header';
import { Container, Content } from './components/Layout';
import Route from './router';
import API from './utils/api';
import { getDetailPageURL } from './utils/url';
import './styles/main.scss';
import classes from "./app.scss";
import 'normalize.css';
export default class App extends Component {
@ -22,30 +32,46 @@ export default class App extends Component {
user: {},
scope: (window.VERDACCIO_SCOPE) ? `${window.VERDACCIO_SCOPE}:` : '',
showLoginModal: false,
isUserLoggedIn: false
};
constructor(props) {
super(props);
this.handleLogout = this.handleLogout.bind(this);
this.toggleLoginModal = this.toggleLoginModal.bind(this);
this.doLogin = this.doLogin.bind(this);
this.loadLogo = this.loadLogo.bind(this);
this.isUserAlreadyLoggedIn = this.isUserAlreadyLoggedIn.bind(this);
isUserLoggedIn: false,
packages: [],
searchPackages: [],
filteredPackages: [],
search: '',
isLoading: true,
showAlertDialog: false,
alertDialogContent: {
title: '',
message: '',
packages: []
},
}
componentDidMount() {
this.loadLogo();
this.isUserAlreadyLoggedIn();
this.loadPackages();
}
isUserAlreadyLoggedIn() {
// eslint-disable-next-line no-unused-vars
componentDidUpdate(_, prevState) {
if (prevState.isUserLoggedIn !== this.state.isUserLoggedIn) {
this.loadPackages();
}
}
loadLogo = async () => {
const logoUrl = await logo();
this.setState({
logoUrl
});
}
isUserAlreadyLoggedIn = () => {
// checks for token validity
const token = storage.getItem('token');
const username = storage.getItem('username');
if (isTokenExpire(token) || isNil(username)) {
this.handleLogout();
this.handleLogout();
} else {
this.setState({
user: { username, token },
@ -54,16 +80,38 @@ export default class App extends Component {
}
}
async loadLogo() {
const logoUrl = await logo();
this.setState({ logoUrl });
loadPackages = async () => {
try {
this.req = await API.request('packages', 'GET');
const transformedPackages = this.req.map(({ name, ...others}) => ({
label: name,
...others
}));
this.setState({
packages: transformedPackages,
filteredPackages: transformedPackages,
isLoading: false
});
} catch (error) {
this.handleShowAlertDialog({
title: 'Warning',
message: `Unable to load package list: ${error.message}`
});
this.setLoading(false);
}
}
setLoading = isLoading => (
this.setState({
isLoading
})
)
/**
* Toggles the login modal
* Required by: <LoginModal /> <Header />
*/
toggleLoginModal() {
toggleLoginModal = () => {
this.setState((prevState) => ({
showLoginModal: !prevState.showLoginModal,
error: {}
@ -74,27 +122,16 @@ export default class App extends Component {
* handles login
* Required by: <Header />
*/
async doLogin(usernameValue, passwordValue) {
doLogin = async (usernameValue, passwordValue) => {
const { username, token, error } = await makeLogin(
usernameValue,
passwordValue
);
if (username && token) {
this.setState({
user: {
username,
token
}
});
this.setLoggedUser(username, token);
storage.setItem('username', username);
storage.setItem('token', token);
// close login modal after successful login
// set isUserLoggedin to true
this.setState({
isUserLoggedIn: true,
showLoginModal: false
});
}
if (error) {
@ -105,11 +142,43 @@ export default class App extends Component {
}
}
setLoggedUser = (username, token) => {
this.setState({
user: {
username,
token,
},
isUserLoggedIn: true, // close login modal after successful login
showLoginModal: false // set isUserLoggedin to true
});
}
handleFetchPackages = async ({ value }) => {
try {
this.req = await API.request(`/search/${encodeURIComponent(value)}`, 'GET');
const transformedPackages = this.req.map(({ name, ...others}) => ({
label: name,
...others
}));
// Implement cancel feature later
if (this.state.search === value) {
this.setState({
searchPackages: transformedPackages
});
}
} catch (error) {
this.handleShowAlertDialog({
title: 'Warning',
message: `Unable to get search result: ${error.message}`
});
}
}
/**
* Logouts user
* Required by: <Header />
*/
handleLogout() {
handleLogout = () => {
storage.removeItem('username');
storage.removeItem('token');
this.setState({
@ -118,48 +187,159 @@ export default class App extends Component {
});
}
renderHeader() {
const {
logoUrl,
user,
scope,
} = this.state;
return <Header
logo={logoUrl}
username={user.username}
scope={scope}
toggleLoginModal={this.toggleLoginModal}
handleLogout={this.handleLogout}
/>;
handlePackagesClearRequested = () => {
this.setState({
searchPackages: []
});
};
// eslint-disable-next-line no-unused-vars
handleSearch = (_, { newValue }) => {
const { filteredPackages, packages, search } = this.state;
const value = newValue.trim();
this.setState({
search: value,
filteredPackages: value.length < search.length ?
packages.filter(pkg => pkg.label.match(value)) : filteredPackages
});
};
handleKeyDown = event => {
if (event.key === 'Enter') {
const { filteredPackages, packages } = this.state;
const value = event.target.value.trim();
this.setState({
filteredPackages: value ?
packages.filter(pkg => pkg.label.match(value)) : filteredPackages
});
}
}
renderLoginModal() {
const {
error,
showLoginModal
} = this.state;
return <LoginModal
visibility={showLoginModal}
error={error}
onChange={this.setUsernameAndPassword}
onCancel={this.toggleLoginModal}
onSubmit={this.doLogin}
/>;
// eslint-disable-next-line no-unused-vars
handleClickSearch = (_, { suggestionValue, method }) => {
const { packages } = this.state;
switch(method) {
case 'click':
window.location.href = getDetailPageURL(suggestionValue);
break;
case 'enter':
this.setState({
filteredPackages: packages.filter(pkg => pkg.label.match(suggestionValue))
});
break;
}
}
handleShowAlertDialog = content => {
this.setState({
showAlertDialog: true,
alertDialogContent: content
});
}
handleDismissAlertDialog = () => {
this.setState({
showAlertDialog: false
});
};
getfilteredPackages = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
if (inputLength === 0) {
return [];
} else {
return this.searchPackage(value);
}
}
renderHeader = () => {
const { logoUrl, user, search, searchPackages } = this.state;
return (
<Header
logo={logoUrl}
username={user.username}
toggleLoginModal={this.toggleLoginModal}
onLogout={this.handleLogout}
onSearch={this.handleSearch}
onSuggestionsFetch={this.handleFetchPackages}
onCleanSuggestions={this.handlePackagesClearRequested}
onClick={this.handleClickSearch}
onKeyDown={this.handleKeyDown}
packages={searchPackages}
search={search}
/>
);
}
renderAlertDialog = () => (
<Dialog
open={this.state.showAlertDialog}
onClose={this.handleDismissAlertDialog}
>
<DialogTitle id="alert-dialog-title">
{this.state.alertDialogContent.title}
</DialogTitle>
<DialogContent>
<SnackbarContent
className={classes.alertError}
message={
<div
id="client-snackbar"
className={classes.alertErrorMsg}
>
<ErrorIcon className={classes.alertIcon} />
<span>
{this.state.alertDialogContent.message}
</span>
</div>
}
/>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleDismissAlertDialog}
color="primary"
autoFocus
>
Ok
</Button>
</DialogActions>
</Dialog>
)
renderLoginModal = () => {
const { error, showLoginModal } = this.state;
return (
<LoginModal
visibility={showLoginModal}
error={error}
onChange={this.setUsernameAndPassword}
onCancel={this.toggleLoginModal}
onSubmit={this.doLogin}
/>
);
}
render() {
const { isUserLoggedIn } = this.state;
const { isLoading, ...others } = this.state;
return (
<div className="page-full-height">
<div id="header">
{this.renderHeader()}
</div>
<Container isLoading={isLoading}>
{isLoading ? (
<Loading />
) : (
<Fragment>
{this.renderHeader()}
<Content>
<Route {...others} />
</Content>
<Footer />
</Fragment>
)}
{this.renderAlertDialog()}
{this.renderLoginModal()}
<Route isUserLoggedIn={isUserLoggedIn} />
<div id="footer">
<Footer />
</div>
</div>
</Container>
);
}
}

16
src/webui/app.scss Normal file
View File

@ -0,0 +1,16 @@
@import './styles/variables';
.alertError {
background-color: $red !important;
min-width: inherit !important;
}
.alertErrorMsg {
display: flex;
align-items: center;
}
.alertIcon {
opacity: 0.9;
margin-right: 8px;
}

View File

@ -0,0 +1,105 @@
/**
* @prettier
* @flow
*/
import React from 'react';
import type { Node } from 'react';
import Autosuggest from 'react-autosuggest';
import match from 'autosuggest-highlight/match';
import parse from 'autosuggest-highlight/parse';
import Paper from '@material-ui/core/Paper';
import MenuItem from '@material-ui/core/MenuItem';
import { fontWeight } from '../../utils/styles/sizes';
import { Wrapper, InputField } from './styles';
import { IProps } from './types';
const renderInputComponent = (inputProps): Node => {
const { ref, startAdornment, disableUnderline, onKeyDown, ...others } = inputProps;
return (
<InputField
fullWidth
InputProps={{
inputRef: node => {
ref(node);
},
startAdornment,
disableUnderline,
onKeyDown,
}}
{...others}
/>
);
};
const getSuggestionValue = (suggestion): string => suggestion.label;
const renderSuggestion = (suggestion, { query, isHighlighted }): Node => {
const matches = match(suggestion.label, query);
const parts = parse(suggestion.label, matches);
return (
<MenuItem selected={isHighlighted} component="div">
<div>
{parts.map((part, index) => {
return part.highlight ? (
<span key={String(index)} href={suggestion.link} style={{ fontWeight: fontWeight.semiBold }}>
{part.text}
</span>
) : (
<span key={String(index)} href={suggestion.link} style={{ fontWeight: fontWeight.light }}>
{part.text}
</span>
);
})}
</div>
</MenuItem>
);
};
const AutoComplete = ({
suggestions,
startAdornment,
onChange,
onSuggestionsFetch,
onCleanSuggestions,
value = '',
placeholder = '',
disableUnderline = false,
color,
onClick,
onKeyDown,
}: IProps): Node => {
const autosuggestProps = {
renderInputComponent,
suggestions,
getSuggestionValue,
renderSuggestion,
onSuggestionsFetchRequested: onSuggestionsFetch,
onSuggestionsClearRequested: onCleanSuggestions,
};
return (
<Wrapper>
<Autosuggest
{...autosuggestProps}
inputProps={{
value,
onChange,
placeholder,
startAdornment,
disableUnderline,
color,
onKeyDown,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
onSuggestionSelected={onClick}
/>
</Wrapper>
);
};
export default AutoComplete;

View File

@ -0,0 +1,52 @@
/**
* @prettier
* @flow
*/
import React from 'react';
import styled, { css } from 'react-emotion';
import TxtField from '../TxtField';
import { IInputField } from './types';
export const Wrapper = styled.div`
&& {
width: 100%;
height: 32px;
position: relative;
z-index: 1;
}
`;
export const InputField = ({ color, ...others }: IInputField) => (
<TxtField
{...others}
classes={{
input: css`
&& {
${color &&
css`
color: ${color};
`};
}
`,
root: css`
&& {
&:before {
content: '';
border: none;
}
&:after {
${color &&
css`
border-color: ${color};
`};
}
&:hover:before {
content: none;
}
}
`,
}}
/>
);

View File

@ -0,0 +1,24 @@
/**
* @prettier
* @flow
*/
import { InputAdornmentProps } from '@material-ui/core/InputAdornment';
export interface IProps {
suggestions: any[];
color?: string;
value?: string;
placeholder?: string;
startAdornment?: React.ComponentType<InputAdornmentProps>;
disableUnderline?: boolean;
onChange?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => void;
onSuggestionsFetch?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => void;
onCleanSuggestions?: () => void;
onClick?: () => void;
onKeyDown?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => void;
}
export interface IInputField {
color: string;
}

View File

@ -8,7 +8,7 @@ import FileCopy from '@material-ui/icons/FileCopy';
import Tooltip from '@material-ui/core/Tooltip/index';
import type { Node } from 'react';
import { IProps } from './interfaces';
import { IProps } from './types';
import { ClipBoardCopy, ClipBoardCopyText, CopyIcon } from './styles';
@ -33,7 +33,7 @@ const CopyToClipBoard = ({ text }: IProps): Node => (
<ClipBoardCopy>
<ClipBoardCopyText>{text}</ClipBoardCopyText>
<Tooltip title="Copy to Clipboard" disableFocusListener>
<CopyIcon aria-label="Copy to Clipboard" onClick={copyToClipBoardUtility(text)}>
<CopyIcon onClick={copyToClipBoardUtility(text)}>
<FileCopy />
</CopyIcon>
</Tooltip>

View File

@ -12,17 +12,21 @@ import Info from '@material-ui/icons/Info';
import Help from '@material-ui/icons/Help';
import Tooltip from '@material-ui/core/Tooltip/index';
import AccountCircle from '@material-ui/icons/AccountCircle';
import InputAdornment from '@material-ui/core/InputAdornment';
import { default as IconSearch } from '@material-ui/icons/Search';
import { getRegistryURL } from '../../utils/url';
import Link from '../Link';
import Logo from '../Logo';
import Label from '../Label';
import CopyToClipBoard from '../CopyToClipBoard/index';
import RegistryInfoDialog from '../RegistryInfoDialog';
import AutoComplete from '../AutoComplete';
import Label from '../Label';
import type { Node } from 'react';
import { IProps, IState } from './interfaces';
import { Wrapper, InnerWrapper, Greetings } from './styles';
import { IProps, IState } from './types';
import colors from '../../utils/styles/colors';
import { Greetings, NavBar, InnerNavBar, MobileNavBar, InnerMobileNavBar, LeftSide, RightSide, Search, IconSearchButton } from './styles';
class Header extends Component<IProps, IState> {
handleLoggedInMenu: Function;
@ -32,20 +36,27 @@ class Header extends Component<IProps, IState> {
handleToggleLogin: Function;
renderInfoDialog: Function;
constructor(props: Object) {
constructor(props: IProps) {
super(props);
this.handleLoggedInMenu = this.handleLoggedInMenu.bind(this);
this.handleLoggedInMenuClose = this.handleLoggedInMenuClose.bind(this);
this.handleOpenRegistryInfoDialog = this.handleOpenRegistryInfoDialog.bind(this);
this.handleCloseRegistryInfoDialog = this.handleCloseRegistryInfoDialog.bind(this);
this.handleToggleLogin = this.handleToggleLogin.bind(this);
this.renderInfoDialog = this.renderInfoDialog.bind(this);
const { packages = [] } = props;
this.state = {
openInfoDialog: false,
registryUrl: '',
packages,
showMobileNavBar: false,
};
}
static getDerivedStateFromProps(nextProps: IProps, prevState: IState) {
if (nextProps.packages !== prevState.packages) {
return {
packages: nextProps.packages,
};
}
return null;
}
componentDidMount() {
const registryUrl = getRegistryURL();
this.setState({
@ -56,65 +67,104 @@ class Header extends Component<IProps, IState> {
/**
* opens popover menu for logged in user.
*/
handleLoggedInMenu(event: SyntheticEvent<HTMLElement>) {
handleLoggedInMenu = (event: SyntheticEvent<HTMLElement>) => {
this.setState({
anchorEl: event.currentTarget,
});
}
};
/**
* closes popover menu for logged in user
*/
handleLoggedInMenuClose() {
handleLoggedInMenuClose = () => {
this.setState({
anchorEl: null,
});
}
};
/**
* opens registry information dialog.
*/
handleOpenRegistryInfoDialog() {
handleOpenRegistryInfoDialog = () => {
this.setState({
openInfoDialog: true,
});
}
};
/**
* closes registry information dialog.
*/
handleCloseRegistryInfoDialog() {
handleCloseRegistryInfoDialog = () => {
this.setState({
openInfoDialog: false,
});
}
};
/**
* close/open popover menu for logged in users.
*/
handleToggleLogin() {
handleToggleLogin = () => {
this.setState(
{
anchorEl: null,
},
this.props.toggleLoginModal
);
}
};
renderLeftSide(): Node {
const { registryUrl } = this.state;
handleToggleMNav = () => {
this.setState({
showMobileNavBar: !this.state.showMobileNavBar,
});
};
handleDismissMNav = () => {
this.setState({
showMobileNavBar: false,
});
};
renderLeftSide = (): Node => {
const { packages } = this.state;
const { onSearch = () => {}, search = '', withoutSearch = false, ...others } = this.props;
return (
<a href={`${registryUrl}/#/`}>
<Logo />
</a>
<LeftSide>
<Link to="/" style={{ marginRight: '1em' }}>
<Logo />
</Link>
{!withoutSearch && (
<Search>
<AutoComplete
suggestions={packages}
onChange={onSearch}
value={search}
placeholder="Search packages"
color={colors.white}
startAdornment={
<InputAdornment position="start" style={{ color: colors.white }}>
<IconSearch />
</InputAdornment>
}
{...others}
/>
</Search>
)}
</LeftSide>
);
}
};
renderRightSide(): Node {
const { username = '' } = this.props;
renderRightSide = (): Node => {
const { username = '', withoutSearch = false } = this.props;
const installationLink = 'https://verdaccio.org/docs/en/installation';
return (
<div>
<RightSide>
{!withoutSearch && (
<Tooltip title="Search packages" disableFocusListener>
<IconSearchButton color="inherit" onClick={this.handleToggleMNav}>
<IconSearch />
</IconSearchButton>
</Tooltip>
)}
<Tooltip title="Documentation" disableFocusListener>
<IconButton color="inherit" component={Link} to={installationLink} blank>
<Help />
@ -132,20 +182,20 @@ class Header extends Component<IProps, IState> {
Login
</Button>
)}
</div>
</RightSide>
);
}
};
/**
* render popover menu
*/
renderMenu(): Node {
const { handleLogout, username = '' } = this.props;
renderMenu = (): Node => {
const { onLogout, username = '' } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
return (
<React.Fragment>
<IconButton id="header--button-account" aria-owns="sidebar-menu" aria-haspopup="true" color="inherit" onClick={this.handleLoggedInMenu}>
<IconButton id="header--button-account" color="inherit" onClick={this.handleLoggedInMenu}>
<AccountCircle />
</IconButton>
<Menu
@ -166,15 +216,15 @@ class Header extends Component<IProps, IState> {
<Greetings>{`Hi,`}</Greetings>
<Label text={username} limit={140} weight="bold" capitalize />
</MenuItem>
<MenuItem onClick={handleLogout} id="header--button-logout">
<MenuItem onClick={onLogout} id="header--button-logout">
Logout
</MenuItem>
</Menu>
</React.Fragment>
);
}
};
renderInfoDialog(): Node {
renderInfoDialog = (): Node => {
const { scope } = this.props;
const { openInfoDialog, registryUrl } = this.state;
return (
@ -185,17 +235,32 @@ class Header extends Component<IProps, IState> {
</div>
</RegistryInfoDialog>
);
}
};
render() {
const { packages, showMobileNavBar } = this.state;
const { onSearch = () => {}, search = '', withoutSearch = false, ...others } = this.props;
return (
<Wrapper position="static">
<InnerWrapper>
{this.renderLeftSide()}
{this.renderRightSide()}
</InnerWrapper>
{this.renderInfoDialog()}
</Wrapper>
<div>
<NavBar position="static">
<InnerNavBar>
{this.renderLeftSide()}
{this.renderRightSide()}
</InnerNavBar>
{this.renderInfoDialog()}
</NavBar>
{showMobileNavBar &&
!withoutSearch && (
<MobileNavBar>
<InnerMobileNavBar>
<AutoComplete suggestions={packages} onChange={onSearch} value={search} placeholder="Search packages" disableUnderline {...others} />
</InnerMobileNavBar>
<Button color="inherit" onClick={this.handleDismissMNav}>
Cancel
</Button>
</MobileNavBar>
)}
</div>
);
}
}

View File

@ -1,17 +0,0 @@
/**
* @prettier
* @flow
*/
export interface IProps {
username?: string;
handleLogout: Function;
toggleLoginModal: Function;
scope: string;
}
export interface IState {
anchorEl?: any;
openInfoDialog: boolean;
registryUrl: string;
}

View File

@ -1,38 +1,102 @@
/**
* @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 colors from '../../utils/styles/colors';
import mq from '../../utils/styles/media';
export const Wrapper = styled(AppBar)`
&& {
background-color: ${colors.primary};
position: fixed;
}
`;
export const InnerWrapper = styled(Toolbar)`
&& {
justify-content: space-between;
align-items: center;
padding: 0 20px;
${mq.medium(css`
min-width: 400px;
max-width: 800px;
width: 100%;
margin: auto;
`)};
${mq.large(css`
max-width: 1240px;
`)};
}
`;
export const Greetings = styled.span`
margin: 0 5px 0 0;
`;
/**
* @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 colors from '../../utils/styles/colors';
import mq from '../../utils/styles/media';
export const NavBar = styled(AppBar)`
&& {
background-color: ${colors.primary};
min-height: 60px;
display: flex;
justify-content: center;
}
`;
export const InnerNavBar = styled(Toolbar)`
&& {
justify-content: space-between;
align-items: center;
padding: 0 20px;
${mq.medium(css`
min-width: 400px;
max-width: 800px;
width: 100%;
margin: auto;
`)};
${mq.large(css`
max-width: 1240px;
`)};
}
`;
export const Greetings = styled.span`
&& {
margin: 0 5px 0 0;
}
`;
export const RightSide = styled(Toolbar)`
&& {
display: flex;
padding: 0;
}
`;
export const LeftSide = styled(RightSide)`
&& {
flex: 1;
}
`;
export const MobileNavBar = styled.div`
&& {
align-items: center;
display: flex;
border-bottom: 1px solid ${colors.greyLight};
padding: 8px;
position: relative;
${mq.medium(css`
display: none;
`)};
}
`;
export const InnerMobileNavBar = styled.div`
&& {
border-radius: 4px;
background-color: ${colors.greyLight};
color: ${colors.white};
width: 100%;
padding: 0px 5px;
margin: 0 10px 0 0;
}
`;
export const Search = styled.div`
&& {
display: none;
max-width: 393px;
width: 100%;
display: none;
${mq.medium(css`
display: flex;
`)};
}
`;
export const IconSearchButton = styled(IconButton)`
&& {
display: block;
${mq.medium(css`
display: none;
`)};
}
`;

View File

@ -0,0 +1,23 @@
/**
* @prettier
* @flow
*/
export interface IProps {
username?: string;
onLogout?: Function;
toggleLoginModal: Function;
scope: string;
search?: string;
packages?: any[];
withoutSearch?: boolean;
onSearch?: (event: SyntheticKeyboardEvent<HTMLInputElement>) => void;
}
export interface IState {
anchorEl?: any;
openInfoDialog: boolean;
registryUrl: string;
packages: any[];
showMobileNavBar: boolean;
}

View File

@ -9,7 +9,7 @@ import { fontWeight } from '../../utils/styles/sizes';
import ellipsis from '../../utils/styles/ellipsis';
import type { Node } from 'react';
import { IProps } from './interfaces';
import { IProps } from './types';
const Wrapper = styled.span`
font-weight: ${({ weight }) => fontWeight[weight]};

View File

@ -0,0 +1,29 @@
/**
* @prettier
* @flow
*/
import styled, { css } from 'react-emotion';
export const Content = styled.div`
&& {
background-color: #fff;
flex: 1;
position: relative;
}
`;
export const Container = styled.div`
&& {
display: flex;
flex-direction: column;
min-height: 100vh;
overflow: hidden;
${({ isLoading }) =>
isLoading &&
css`
${Content} {
background-color: #f5f6f8;
}
`}
`;

View File

@ -5,7 +5,7 @@
import React from 'react';
import type { Node } from 'react';
import { IProps } from './interfaces';
import { IProps } from './types';
const Link = ({ children, to = '#', blank = false, ...props }: IProps): Node => (
<a href={to} target={blank ? '_blank' : '_self'} {...props}>

View File

@ -0,0 +1,23 @@
/**
* @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 => (
<Wrapper>
<Badge>
<Logo md />
</Badge>
<Spinner />
</Wrapper>
);
export default Loading;

View File

@ -0,0 +1,24 @@
/**
* @prettier
* @flow
*/
import styled from 'react-emotion';
export const Wrapper = styled.div`
&& {
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
position: absolute;
}
`;
export const Badge = styled.div`
&& {
margin: 0 0 30px 0;
border-radius: 25px;
box-shadow: 0 10px 20px 0 rgba(69, 58, 100, 0.2);
background: #f7f8f6;
}
`;

View File

@ -103,7 +103,6 @@ export default class LoginModal extends Component {
return type === 'error' && (
<SnackbarContent
className={classes.loginError}
aria-describedby="client-snackbar"
message={
<div
id="client-snackbar"
@ -129,7 +128,6 @@ export default class LoginModal extends Component {
open={visibility}
id="login--form-container"
maxWidth="xs"
aria-labelledby="login-dialog"
fullWidth
>
<DialogTitle>Login</DialogTitle>
@ -137,7 +135,6 @@ export default class LoginModal extends Component {
{this.renderLoginError(error)}
<FormControl
error={!username.value && !username.pristine}
aria-describedby='username'
required={username.required}
fullWidth
>
@ -156,7 +153,6 @@ export default class LoginModal extends Component {
</FormControl>
<FormControl
error={!password.value && !password.pristine}
aria-describedby='password'
required={password.required}
style={{ marginTop: '8px' }}
fullWidth

View File

@ -3,19 +3,27 @@
* @flow
*/
import styled from 'react-emotion';
import styled, { css } from 'react-emotion';
import logo from './img/logo.svg';
const Logo = styled.div`
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
background-position: center;
background-size: contain;
background-image: url(${logo});
background-repeat: no-repeat;
width: 40px;
height: 40px;
&& {
display: inline-block;
vertical-align: middle;
box-sizing: border-box;
background-position: center;
background-size: contain;
background-image: url(${logo});
background-repeat: no-repeat;
width: 40px;
height: 40px;
${props =>
props.md &&
css`
width: 90px;
height: 90px;
`};
}
`;
export default Logo;

View File

@ -1,5 +1,4 @@
@import '../../styles/variables';
@import '../../styles/mixins';
.notFound {
width: 100%;
@ -7,9 +6,6 @@
line-height: $line-height-xl;
border: none;
outline: none;
@include border-bottom-default($grey-light);
&:focus {
@include border-bottom-default($grey);
}
flex-direction: column;
align-items: center;
}

View File

@ -6,7 +6,7 @@ import classes from './404.scss';
const NotFound = (props) => {
return (
<div className={classes.notFound}>
<div className={`container content ${classes.notFound}`}>
<h1>Error 404 - {props.pkg}</h1>
<hr/>
<p>

View File

@ -21,7 +21,6 @@
.tags {
margin: 0 0.5em 0.5em 0;
white-space: nowrap;
font-size: $font-size-sm;
:global {
.el-tag {

View File

@ -1,6 +1,5 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import isEmpty from 'lodash/isEmpty';
import Package from '../Package';
import Help from '../Help';
@ -15,63 +14,49 @@ export default class PackageList extends React.Component {
help: PropTypes.bool
};
renderPackges = () => {
const { packages } = this.props;
return (
packages.length > 0 ? (
<Fragment>
<h1 className={classes.listTitle}>Available Packages</h1>
{this.renderList()}
</Fragment>
) : (
<NoItems
className="package-no-items"
text={'No items were found with that query'}
/>
)
);
}
renderList = () => {
const { packages } = this.props;
return (
<ul>
{packages.map((pkg, i) => {
const { label: name, version, description, time, keywords } = pkg;
const author = formatAuthor(pkg.author);
const license = formatLicense(pkg.license);
return (
<li key={i}>
<Package {...{ name, version, author, description, license, time, keywords }} />
</li>
);
})}
</ul>
);
}
render() {
const { help } = this.props;
return (
<div className="package-list-items">
<div className={classes.pkgContainer}>
{this.renderTitle()}
{this.isTherePackages() ? this.renderList() : this.renderOptions()}
{help ? <Help /> : this.renderPackges()}
</div>
</div>
);
}
renderTitle() {
if (this.isTherePackages() === false) {
return;
}
return <h1 className={classes.listTitle}>Available Packages</h1>;
}
renderList() {
return this.props.packages.map((pkg, i) => {
const {name, version, description, time, keywords} = pkg;
const author = formatAuthor(pkg.author);
const license = formatLicense(pkg.license);
return (
<li key={i}>
<Package {...{name, version, author, description, license, time, keywords}} />
</li>
);
});
}
renderOptions() {
if (this.isTherePackages() === false && this.props.help) {
return this.renderHelp();
} else {
return this.renderNoItems();
}
}
renderNoItems() {
return (
<NoItems
className="package-no-items"
text={'No items were found with that query'}
/>
);
}
renderHelp() {
if (this.props.help === false) {
return;
}
return <Help />;
}
isTherePackages() {
return isEmpty(this.props.packages) === false;
}
}

View File

@ -17,7 +17,6 @@
.listTitle {
font-weight: $font-weight-regular;
font-size: $font-size-xl;
margin-top: 30px;
margin-bottom: 0;
margin: 0;
}
}

View File

@ -11,7 +11,7 @@ import { Title, Content } from './styles';
import type { Node } from 'react';
import { IProps } from './interfaces';
import { IProps } from './types';
const RegistryInfoDialog = ({ open = false, children, onClose }: IProps): Node => (
<Dialog id="registryInfo--dialog-container" open={open} onClose={onClose}>

View File

@ -1,35 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classes from './search.scss';
const noSubmit = (e) => {
e.preventDefault();
};
const Search = (props) => {
return (
<form autoComplete="off" onSubmit={noSubmit}>
<input
name="search-box"
type="text"
placeholder={props.placeHolder}
className={classes.searchBox}
onChange={props.handleSearchInput}
autoComplete="off"
/>
</form>
);
};
Search.defaultProps = {
placeHolder: 'Type to search...'
};
Search.propTypes = {
handleSearchInput: PropTypes.func.isRequired,
placeHolder: PropTypes.string,
};
export default Search;

View File

@ -1,5 +0,0 @@
@import '../../styles/mixins';
.searchBox {
@include searchBox;
}

View File

@ -6,10 +6,10 @@
import React from 'react';
import type { Node } from 'react';
import { IProps } from './interfaces';
import { IProps } from './types';
import { Wrapper, Circular } from './styles';
const Spinner = ({ size = 50, centered = true }: IProps): Node => (
const Spinner = ({ size = 50, centered = false }: IProps): Node => (
<Wrapper centered={centered}>
<Circular size={size} />
</Wrapper>

View File

@ -1,15 +1,25 @@
import styled from 'react-emotion';
import CircularProgress from '@material-ui/core/CircularProgress';
/**
* @prettier
* @flow
*/
import styled, { css } from 'react-emotion';
import CircularProgress from '@material-ui/core/CircularProgress/index';
import colors from '../../utils/styles/colors';
export const Wrapper = styled.div`
&& {
${({ centered }) => centered && `
flex: 1;
display: flex;
justify-content: center;
align-items: center;
`}
display: flex;
align-items: center;
justify-content: center;
${props =>
props.centered &&
css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`}
`;
export const Circular = styled(CircularProgress)`

View File

@ -0,0 +1,19 @@
/**
* @prettier
* @flow
*/
import React from 'react';
import TextField, { TextFieldProps } from '@material-ui/core/TextField';
const TxtField = ({ InputProps, classes, ...other }: TextFieldProps) => (
<TextField
{...other}
InputProps={{
...InputProps,
classes,
}}
/>
);
export default TxtField;

View File

@ -1,193 +0,0 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import SnackbarContent from '@material-ui/core/SnackbarContent';
import ErrorIcon from '@material-ui/icons/Error';
import isEmpty from 'lodash/isEmpty';
import debounce from 'lodash/debounce';
import API from '../../utils/api';
import PackageList from '../../components/PackageList';
import Search from '../../components/Search';
import Spinner from '../../components/Spinner';
import classes from "./home.scss";
class Home extends Component {
static propTypes = {
children: PropTypes.element,
isUserLoggedIn: PropTypes.bool
};
state = {
showAlertDialog: false,
alertDialogContent: {
title: '',
message: ''
},
loading: true,
fistTime: true,
query: ''
};
constructor(props) {
super(props);
this.handleSearchInput = this.handleSearchInput.bind(this);
this.handleShowAlertDialog = this.handleShowAlertDialog.bind(this);
this.handleCloseAlertDialog = this.handleCloseAlertDialog.bind(this);
this.searchPackage = debounce(this.searchPackage, 800);
}
componentDidMount() {
this.loadPackages();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.query !== this.state.query) {
if (this.req && this.req.abort) this.req.abort();
this.setState({
loading: true
});
if (prevState.query !== '' && this.state.query === '') {
this.loadPackages();
} else {
this.searchPackage(this.state.query);
}
}
if (prevProps.isUserLoggedIn !== this.props.isUserLoggedIn) {
this.loadPackages();
}
}
async loadPackages() {
try {
this.req = await API.request('packages', 'GET');
if (this.state.query === '') {
this.setState({
packages: this.req,
loading: false
});
}
} catch (error) {
this.handleShowAlertDialog({
title: 'Warning',
message: `Unable to load package list: ${error.error}`
});
}
}
async searchPackage(query) {
try {
this.req = await API.request(`/search/${query}`, 'GET');
// Implement cancel feature later
if (this.state.query === query) {
this.setState({
packages: this.req,
fistTime: false,
loading: false
});
}
} catch (err) {
this.handleShowAlertDialog({
title: 'Warning',
message: 'Unable to get search result, please try again later.'
});
}
}
renderAlertDialog() {
return (
<Dialog
open={this.state.showAlertDialog}
onClose={this.handleCloseAlertDialog}
aria-labelledby="alert-dialog-title"
>
<DialogTitle id="alert-dialog-title">
{this.state.alertDialogContent.title}
</DialogTitle>
<DialogContent>
<SnackbarContent
className={classes.alertError}
aria-describedby="client-snackbar"
message={
<div
id="client-snackbar"
className={classes.alertErrorMsg}
>
<ErrorIcon className={classes.alertIcon} />
<span>
{this.state.alertDialogContent.message}
</span>
</div>
}
/>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleCloseAlertDialog}
color="primary"
autoFocus
>
Ok
</Button>
</DialogActions>
</Dialog>
);
}
handleShowAlertDialog(content) {
this.setState({
showAlertDialog: true,
alertDialogContent: content
});
};
handleCloseAlertDialog() {
this.setState({
showAlertDialog: false
});
};
handleSearchInput(e) {
this.setState({
query: e.target.value.trim()
});
}
isTherePackages() {
return isEmpty(this.state.packages);
}
render() {
const { packages, loading } = this.state;
return (
<Fragment>
{this.renderSearchBar()}
{loading ? (
<Spinner centered />
) : (
<PackageList help={isEmpty(packages) === true} packages={packages} />
)}
{this.renderAlertDialog()}
</Fragment>
);
}
renderSearchBar() {
if (this.isTherePackages() && this.state.fistTime) {
return;
}
return <Search handleSearchInput={this.handleSearchInput} />;
}
}
export default Home;

View File

@ -3,7 +3,6 @@
.twoColumn {
@include container-size;
margin: 0 10px;
display: flex;
> div {

View File

@ -67,12 +67,14 @@ export default class Detail extends Component {
const { notFound, readMe } = this.state;
if (notFound) {
return <NotFound pkg={this.packageName} />;
return (
<NotFound pkg={this.packageName} />
);
} else if (isEmpty(readMe)) {
return <Spinner centered />;
}
return (
<div className={classes.twoColumn}>
<div className={`container content ${classes.twoColumn}`}>
<PackageDetail readMe={readMe} packageName={this.packageName} />
<PackageSidebar packageName={this.packageName} />
</div>

View File

@ -0,0 +1,47 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import PackageList from '../../components/PackageList';
class Home extends Component {
static propTypes = {
children: PropTypes.element,
isUserLoggedIn: PropTypes.bool,
packages: PropTypes.array,
filteredPackages: PropTypes.array,
};
constructor(props) {
super(props);
this.state = {
fistTime: true,
packages: props.packages,
filteredPackages: props.filteredPackages
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.packages !== prevState.packages) {
return {
packages: nextProps.packages,
};
}
if (nextProps.filteredPackages !== prevState.filteredPackages) {
return {
filteredPackages: nextProps.filteredPackages,
};
}
return null;
}
render() {
const { filteredPackages, packages } = this.state;
return (
<div className="container content">
<PackageList help={!packages.length > 0} packages={filteredPackages} />
</div>
);
}
}
export default Home;

View File

@ -4,40 +4,40 @@ import {HashRouter as Router, Route, Switch} from 'react-router-dom';
import {asyncComponent} from './utils/asyncComponent';
const DetailPackage = asyncComponent(() => import('./modules/detail'));
const HomePage = asyncComponent(() => import('./modules/home'));
const DetailPackage = asyncComponent(() => import('./pages/detail'));
import HomePage from './pages/home';
class RouterApp extends Component {
static propTypes = {
isUserLoggedIn: PropTypes.bool
};
render() {
const {isUserLoggedIn} = this.props;
return (
<Router>
<div className="container content">
<Switch>
<Route
exact
path="/(search/:keyword)?"
render={() => <HomePage isUserLoggedIn={isUserLoggedIn} />}
path="/"
render={() => (
<HomePage {...this.props} />
)}
/>
<Route
exact
path="/detail/@:scope/:package"
render={(props) => (
<DetailPackage {...props} isUserLoggedIn={isUserLoggedIn} />
<DetailPackage {...props} {...this.props} />
)}
/>
<Route
exact
path="/detail/:package"
render={(props) => (
<DetailPackage {...props} isUserLoggedIn={isUserLoggedIn} />
<DetailPackage {...props} {...this.props} />
)}
/>
</Switch>
</div>
</Router>
);
}

View File

@ -3,7 +3,7 @@
:global {
.container {
margin-top: 94px;
margin-top: 20px;
flex: 1;
@include container-size;
@ -15,13 +15,13 @@
.content {
display: flex;
flex-direction: column;
}
.page-full-height {
display: flex;
flex-direction: column;
min-height: 100vh;
overflow: hidden;
}
.el-button {
@ -40,4 +40,8 @@
.el-dialog__headerbtn:hover .el-dialog__close {
color: $eclipse;
}
.package-list-items {
width: 100%;
}
}

View File

@ -16,8 +16,8 @@ export const lineHeight = {
};
export const fontWeight = {
light: 400,
light: 300,
regular: 400,
semiBold: 600,
semiBold: 500,
bold: 700
};

View File

@ -27,8 +27,6 @@ jest.mock('../../../src/webui/utils/storage', () => {
return new LocalStorageMock();
});
jest.mock('element-theme-default', () => ({}));
jest.mock('../../../src/webui/utils/api', () => ({
request: require('./components/__mocks__/api').default.request
}));
@ -66,15 +64,14 @@ describe('App', () => {
expect(wrapper.state('user').username).toEqual('verdaccio');
});
it('handleLogout - logouts the user and clear localstorage', () => {
it('handleLogout - logouts the user and clear localstorage', async () => {
const { handleLogout } = wrapper.instance();
storage.setItem('username', 'verdaccio');
storage.setItem('token', 'xxxx.TOKEN.xxxx');
handleLogout();
expect(handleLogout()).toBeUndefined();
await handleLogout();
expect(wrapper.state('user')).toEqual({});
expect(wrapper.state('isLoggedIn')).toBeFalsy();
expect(wrapper.state('isUserLoggedIn')).toBeFalsy();
});
it('doLogin - login the user successfully', async () => {
@ -84,11 +81,11 @@ describe('App', () => {
username: 'sam',
token: 'TEST_TOKEN'
};
expect(wrapper.state('user')).toEqual(result);
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);
});
it('doLogin - authentication failure', async () => {

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CopyToClipBoard /> component render the component 1`] = `"<p class=\\"css-1cxz858 eduq2bv0\\"><span class=\\"css-1m8aenu eduq2bv1\\">copy text</span><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-14 MuiIconButton-root-8 css-56v3u0 eduq2bv2\\" type=\\"button\\" aria-label=\\"Copy to Clipboard\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-13\\"><svg class=\\"MuiSvgIcon-root-17\\" 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><span class=\\"MuiTouchRipple-root-26\\"></span></button></p>"`;
exports[`<CopyToClipBoard /> component render the component 1`] = `"<p class=\\"css-1cxz858 eduq2bv0\\"><span class=\\"css-1m8aenu eduq2bv1\\">copy text</span><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-14 MuiIconButton-root-8 css-56v3u0 eduq2bv2\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label-13\\"><svg class=\\"MuiSvgIcon-root-17\\" 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><span class=\\"MuiTouchRipple-root-26\\"></span></button></p>"`;

View File

@ -1,5 +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`] = `"<header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-6njnwn e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-ioebmw e1ctrp121\\"><a href=\\"http://localhost/#/\\"><div class=\\"css-guljh6 e18wxr160\\"></div></a><div><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></a><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" type=\\"button\\" id=\\"header--button-account\\" aria-owns=\\"sidebar-menu\\" aria-haspopup=\\"true\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></button></div></div></header>"`;
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-1rvhilz e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-ioebmw e1ctrp121\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1ctrp124\\"><a href=\\"/\\" target=\\"_self\\" style=\\"margin-right: 1em;\\"><div class=\\"css-12nq0oo e18wxr160\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1ctrp123\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></a><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" type=\\"button\\" id=\\"header--button-account\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></button></div></div></header></div>"`;
exports[`<Header /> component with logged out state should load the component in logged out state 1`] = `"<header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-6njnwn e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-ioebmw e1ctrp121\\"><a href=\\"http://localhost/#/\\"><div class=\\"css-guljh6 e18wxr160\\"></div></a><div><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></a><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiButton-root-108 MuiButton-text-110 MuiButton-flat-113 MuiButton-colorInherit-129\\" type=\\"button\\" id=\\"header--button-login\\"><span class=\\"MuiButton-label-109\\">Login</span><span class=\\"MuiTouchRipple-root-66\\"></span></button></div></div></header>"`;
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-1rvhilz e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-ioebmw e1ctrp121\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1ctrp124\\"><a href=\\"/\\" target=\\"_self\\" style=\\"margin-right: 1em;\\"><div class=\\"css-12nq0oo e18wxr160\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1ctrp123\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></a><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiIconButton-root-48 MuiIconButton-colorInherit-49\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-53\\"><svg class=\\"MuiSvgIcon-root-57\\" 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><span class=\\"MuiTouchRipple-root-66\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-54 MuiButton-root-108 MuiButton-text-110 MuiButton-flat-113 MuiButton-colorInherit-129\\" type=\\"button\\" id=\\"header--button-login\\"><span class=\\"MuiButton-label-109\\">Login</span><span class=\\"MuiTouchRipple-root-66\\"></span></button></div></div></header></div>"`;

View File

@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LoginModal /> should load the component in default state 1`] = `"<div class=\\"MuiModal-root-13 MuiDialog-root-1 MuiDialog-scrollPaper-2\\" role=\\"dialog\\" id=\\"login--form-container\\" aria-labelledby=\\"login-dialog\\"><div class=\\"MuiBackdrop-root-15\\" aria-hidden=\\"true\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div class=\\"MuiPaper-root-17 MuiPaper-elevation24-43 MuiPaper-rounded-18 MuiDialog-paper-4 MuiDialog-paperScrollPaper-5 MuiDialog-paperWidthXs-7 MuiDialog-paperFullWidth-11\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"document\\" tabindex=\\"-1\\"><div class=\\"MuiDialogTitle-root-44\\"><h2 class=\\"MuiTypography-root-45 MuiTypography-title-51\\">Login</h2></div><div class=\\"MuiDialogContent-root-71\\"><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\" aria-describedby=\\"username\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\" aria-describedby=\\"password\\" style=\\"margin-top: 8px;\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98 MuiInputBase-inputType-116 MuiInput-inputType-101\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root-120 dialog-footer\\"><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label-123\\">Cancel</span><span class=\\"MuiTouchRipple-root-151\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label-123\\">Login</span><span class=\\"MuiTouchRipple-root-151\\"></span></button></div></div></div>"`;
exports[`<LoginModal /> should load the component in default state 1`] = `"<div class=\\"MuiModal-root-13 MuiDialog-root-1 MuiDialog-scrollPaper-2\\" role=\\"dialog\\" id=\\"login--form-container\\"><div class=\\"MuiBackdrop-root-15\\" aria-hidden=\\"true\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div class=\\"MuiPaper-root-17 MuiPaper-elevation24-43 MuiPaper-rounded-18 MuiDialog-paper-4 MuiDialog-paperScrollPaper-5 MuiDialog-paperWidthXs-7 MuiDialog-paperFullWidth-11\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"document\\" tabindex=\\"-1\\"><div class=\\"MuiDialogTitle-root-44\\"><h2 class=\\"MuiTypography-root-45 MuiTypography-title-51\\">Login</h2></div><div class=\\"MuiDialogContent-root-71\\"><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\" style=\\"margin-top: 8px;\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98 MuiInputBase-inputType-116 MuiInput-inputType-101\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root-120 dialog-footer\\"><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label-123\\">Cancel</span><span class=\\"MuiTouchRipple-root-151\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label-123\\">Login</span><span class=\\"MuiTouchRipple-root-151\\"></span></button></div></div></div>"`;
exports[`<LoginModal /> should load the component with props 1`] = `"<div class=\\"MuiModal-root-13 MuiDialog-root-1 MuiDialog-scrollPaper-2\\" role=\\"dialog\\" id=\\"login--form-container\\" aria-labelledby=\\"login-dialog\\"><div class=\\"MuiBackdrop-root-15\\" aria-hidden=\\"true\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div class=\\"MuiPaper-root-17 MuiPaper-elevation24-43 MuiPaper-rounded-18 MuiDialog-paper-4 MuiDialog-paperScrollPaper-5 MuiDialog-paperWidthXs-7 MuiDialog-paperFullWidth-11\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"document\\" tabindex=\\"-1\\"><div class=\\"MuiDialogTitle-root-44\\"><h2 class=\\"MuiTypography-root-45 MuiTypography-title-51\\">Login</h2></div><div class=\\"MuiDialogContent-root-71\\"><div class=\\"MuiTypography-root-45 MuiTypography-body1-54 MuiPaper-root-17 MuiPaper-elevation6-25 MuiSnackbarContent-root-158 loginError\\" role=\\"alertdialog\\" aria-describedby=\\"client-snackbar\\"><div class=\\"MuiSnackbarContent-message-159\\"><div id=\\"client-snackbar\\" class=\\"loginErrorMsg\\"><svg class=\\"MuiSvgIcon-root-161 loginIcon\\" 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-2h2v2zm0-4h-2V7h2v6z\\"></path></svg><span><div><strong>Error Title</strong></div><div>Error Description</div></span></div></div></div><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\" aria-describedby=\\"username\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\" aria-describedby=\\"password\\" style=\\"margin-top: 8px;\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98 MuiInputBase-inputType-116 MuiInput-inputType-101\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root-120 dialog-footer\\"><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label-123\\">Cancel</span><span class=\\"MuiTouchRipple-root-151\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label-123\\">Login</span><span class=\\"MuiTouchRipple-root-151\\"></span></button></div></div></div>"`;
exports[`<LoginModal /> should load the component with props 1`] = `"<div class=\\"MuiModal-root-13 MuiDialog-root-1 MuiDialog-scrollPaper-2\\" role=\\"dialog\\" id=\\"login--form-container\\"><div class=\\"MuiBackdrop-root-15\\" aria-hidden=\\"true\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div class=\\"MuiPaper-root-17 MuiPaper-elevation24-43 MuiPaper-rounded-18 MuiDialog-paper-4 MuiDialog-paperScrollPaper-5 MuiDialog-paperWidthXs-7 MuiDialog-paperFullWidth-11\\" style=\\"opacity: 1; -webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"document\\" tabindex=\\"-1\\"><div class=\\"MuiDialogTitle-root-44\\"><h2 class=\\"MuiTypography-root-45 MuiTypography-title-51\\">Login</h2></div><div class=\\"MuiDialogContent-root-71\\"><div class=\\"MuiTypography-root-45 MuiTypography-body1-54 MuiPaper-root-17 MuiPaper-elevation6-25 MuiSnackbarContent-root-158 loginError\\" role=\\"alertdialog\\"><div class=\\"MuiSnackbarContent-message-159\\"><div id=\\"client-snackbar\\" class=\\"loginErrorMsg\\"><svg class=\\"MuiSvgIcon-root-161 loginIcon\\" 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-2h2v2zm0-4h-2V7h2v6z\\"></path></svg><span><div><strong>Error Title</strong></div><div>Error Description</div></span></div></div></div><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root-72 MuiFormControl-fullWidth-75\\" style=\\"margin-top: 8px;\\"><label class=\\"MuiFormLabel-root-83 MuiFormLabel-required-88 MuiInputLabel-root-76 MuiInputLabel-formControl-77 MuiInputLabel-animated-80\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk-89\\">*</span></label><div class=\\"MuiInputBase-root-103 MuiInput-root-90 MuiInput-underline-94 MuiInputBase-formControl-104 MuiInput-formControl-91\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input-113 MuiInput-input-98 MuiInputBase-inputType-116 MuiInput-inputType-101\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root-120 dialog-footer\\"><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label-123\\">Cancel</span><span class=\\"MuiTouchRipple-root-151\\"></span></button><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-148 MuiButton-root-122 MuiButton-text-124 MuiButton-flat-127 MuiButton-colorInherit-143 MuiDialogActions-action-121\\" type=\\"button\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label-123\\">Login</span><span class=\\"MuiTouchRipple-root-151\\"></span></button></div></div></div>"`;

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NotFound /> component should set html from props 1`] = `"<div class=\\"notFound\\"><h1>Error 404 - verdaccio</h1><hr/><p>Oops, The package you are trying to access does not exist.</p></div>"`;
exports[`<NotFound /> component should set html from props 1`] = `"<div class=\\"container content notFound\\"><h1>Error 404 - verdaccio</h1><hr/><p>Oops, The package you are trying to access does not exist.</p></div>"`;

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<PackageList /> component should load the component with packages 1`] = `"<div class=\\"package-list-items\\"><div class=\\"pkgContainer\\"><h1 class=\\"listTitle\\">Available Packages</h1><li><section class=\\"package\\"><a href=\\"detail/verdaccio\\"><div class=\\"header\\"><div class=\\"title\\"><h1>verdaccio <div role=\\"button\\" class=\\"MuiChip-root-1\\" tabindex=\\"-1\\"><span class=\\"MuiChip-label-20\\">v1.0.0</span></div></h1></div><div role=\\"author\\" class=\\"author\\">By: Sam</div></div><div class=\\"footer\\"><p class=\\"description\\">Private NPM repository</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\">Published less than a minute ago</div><div class=\\"license\\"></div></div></a></section></li><li><section class=\\"package\\"><a href=\\"detail/abc\\"><div class=\\"header\\"><div class=\\"title\\"><h1>abc <div role=\\"button\\" class=\\"MuiChip-root-1\\" tabindex=\\"-1\\"><span class=\\"MuiChip-label-20\\">v1.0.1</span></div></h1></div><div role=\\"author\\" class=\\"author\\">By: Rose</div></div><div class=\\"footer\\"><p class=\\"description\\">abc description</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\">Published less than a minute ago</div><div class=\\"license\\"></div></div></a></section></li><li><section class=\\"package\\"><a href=\\"detail/xyz\\"><div class=\\"header\\"><div class=\\"title\\"><h1>xyz <div role=\\"button\\" class=\\"MuiChip-root-1\\" tabindex=\\"-1\\"><span class=\\"MuiChip-label-20\\">v1.1.0</span></div></h1></div><div role=\\"author\\" class=\\"author\\">By: Martin</div></div><div class=\\"footer\\"><p class=\\"description\\">xyz description</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\"></div><div class=\\"license\\"></div></div></a></section></li></div></div>"`;
exports[`<PackageList /> component should load the component with packages 1`] = `"<div class=\\"package-list-items\\"><div class=\\"pkgContainer\\"><h1 class=\\"listTitle\\">Available Packages</h1><ul><li><section class=\\"package\\"><a href=\\"detail/undefined\\"><div class=\\"header\\"><div class=\\"title\\"><h1> <div role=\\"button\\" class=\\"MuiChip-root-1\\" tabindex=\\"-1\\"><span class=\\"MuiChip-label-20\\">v1.0.0</span></div></h1></div><div role=\\"author\\" class=\\"author\\">By: Sam</div></div><div class=\\"footer\\"><p class=\\"description\\">Private NPM repository</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\">Published less than a minute ago</div><div class=\\"license\\"></div></div></a></section></li><li><section class=\\"package\\"><a href=\\"detail/undefined\\"><div class=\\"header\\"><div class=\\"title\\"><h1> <div role=\\"button\\" class=\\"MuiChip-root-1\\" tabindex=\\"-1\\"><span class=\\"MuiChip-label-20\\">v1.0.1</span></div></h1></div><div role=\\"author\\" class=\\"author\\">By: Rose</div></div><div class=\\"footer\\"><p class=\\"description\\">abc description</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\">Published less than a minute ago</div><div class=\\"license\\"></div></div></a></section></li><li><section class=\\"package\\"><a href=\\"detail/undefined\\"><div class=\\"header\\"><div class=\\"title\\"><h1> <div role=\\"button\\" class=\\"MuiChip-root-1\\" tabindex=\\"-1\\"><span class=\\"MuiChip-label-20\\">v1.1.0</span></div></h1></div><div role=\\"author\\" class=\\"author\\">By: Martin</div></div><div class=\\"footer\\"><p class=\\"description\\">xyz description</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\"></div><div class=\\"license\\"></div></div></a></section></li></ul></div></div>"`;

View File

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Search /> component should match the snapshot 1`] = `"<form autoComplete=\\"off\\"><input type=\\"text\\" name=\\"search-box\\" placeholder=\\"Type to search...\\" class=\\"searchBox\\" autoComplete=\\"off\\"/></form>"`;

View File

@ -17,12 +17,18 @@ describe('<Header /> component with logged in state', () => {
handleLogout: jest.fn(),
toggleLoginModal: jest.fn(),
scope: 'test scope',
withoutSearch: true,
};
wrapper = mount(<Header {...props} />);
});
test('should load the component in logged in state', () => {
const state = { openInfoDialog: false, registryUrl: 'http://localhost' };
const state = {
openInfoDialog: false,
packages: undefined,
registryUrl: 'http://localhost',
showMobileNavBar: false,
};
expect(wrapper.state()).toEqual(state);
expect(wrapper.html()).toMatchSnapshot();
});
@ -53,12 +59,18 @@ describe('<Header /> component with logged out state', () => {
handleLogout: jest.fn(),
toggleLoginModal: jest.fn(),
scope: 'test scope',
withoutSearch: true,
};
wrapper = mount(<Header {...props} />);
});
test('should load the component in logged out state', () => {
const state = { openInfoDialog: false, registryUrl: 'http://localhost' };
const state = {
openInfoDialog: false,
packages: undefined,
registryUrl: 'http://localhost',
showMobileNavBar: false,
};
expect(wrapper.state()).toEqual(state);
expect(wrapper.html()).toMatchSnapshot();
});

View File

@ -5,8 +5,6 @@
import React from 'react';
import { mount } from 'enzyme';
import PackageList from '../../../../src/webui/components/PackageList/index';
import Help from '../../../../src/webui/components/Help/index';
import NoItems from '../../../../src/webui/components/NoItems/index';
import { BrowserRouter } from 'react-router-dom';
describe('<PackageList /> component', () => {
@ -18,12 +16,10 @@ describe('<PackageList /> component', () => {
const wrapper = mount(
<PackageList packages={props.packages} help={props.help} />
);
expect(wrapper.find('Help')).toHaveLength(1);
expect(wrapper.find('h1').text()).toEqual('No Package Published Yet');
const instance = wrapper.instance();
expect(instance.isTherePackages()).toBeFalsy();
expect(instance.renderHelp()).toBeTruthy();
expect(instance.renderOptions()).toEqual(<Help />);
expect(instance.renderTitle()).toBeUndefined();
});
it('should load the component with packages', () => {
@ -52,23 +48,16 @@ describe('<PackageList /> component', () => {
],
help: false
};
const wrapper = mount(
<BrowserRouter>
<PackageList packages={props.packages} help={props.help} />
</BrowserRouter>
);
const instance = wrapper.find(PackageList).instance();
expect(instance.isTherePackages()).toBeTruthy();
expect(instance.renderHelp()).toBeUndefined();
expect(instance.renderTitle().props.children).toEqual('Available Packages');
expect(instance.renderNoItems()).toEqual(
<NoItems className="package-no-items" text="No items were found with that query" />
);
expect(instance.renderOptions()).toEqual(
<NoItems className="package-no-items" text="No items were found with that query" />
);
expect(wrapper.find('.listTitle').text()).toContain('Available Packages');
// package count
expect(wrapper.find('Package')).toHaveLength(3);
// match snapshot

View File

@ -1,45 +0,0 @@
/**
* Search component
*/
import React from 'react';
import { shallow } from 'enzyme';
import Search from '../../../../src/webui/components/Search/index';
console.error = jest.fn();
describe('<Search /> component', () => {
it('should give error for the required fields', () => {
const wrapper = shallow(<Search />);
expect(console.error).toBeCalled();
expect(wrapper.find('input').prop('placeholder')).toEqual(
'Type to search...'
);
});
it('should have <input /> element with correct properties', () => {
const props = {
handleSearchInput: () => {},
placeHolder: 'Test placeholder'
};
const wrapper = shallow(<Search {...props} />);
expect(wrapper.find('input')).toHaveLength(1);
expect(wrapper.find('input').prop('placeholder')).toEqual(
'Test placeholder'
);
});
it('should call the handleSearchInput function', () => {
const props = {
handleSearchInput: jest.fn()
};
const wrapper = shallow(<Search {...props} />);
wrapper.find('input').simulate('change');
expect(props.handleSearchInput).toBeCalled();
});
it('should match the snapshot', () => {
const props = { handleSearchInput: () => {} };
const wrapper = shallow(<Search {...props} />);
expect(wrapper.html()).toMatchSnapshot();
});
});

View File

@ -1,39 +0,0 @@
/**
* Home Component
*/
import React from 'react';
import { mount } from 'enzyme';
import Home from '../../../../src/webui/modules/home/index';
describe('<Home /> Component', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<Home />);
});
it('handleSearchInput - should match the search query', () => {
const { handleSearchInput } = wrapper.instance();
const result = 'test query string one';
const input = {
target: {
value: result
}
};
handleSearchInput(input);
expect(wrapper.state('query')).toBe(result);
});
it('handleSearchInput - should match the trimmed search query', () => {
const { handleSearchInput } = wrapper.instance();
const result = ' ';
const input = {
target: {
value: result
}
};
handleSearchInput(input);
expect(wrapper.state('query')).toBe(result.trim());
});
});

2285
yarn.lock

File diff suppressed because it is too large Load Diff