mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-02-21 07:29:37 +01:00
* feat: added material-ui refactor: replaced element-react by material-ui refactor: updated snapshots refactor: updated tests * fix: modified validation.WIP * refactor: modified tests.WIP * test(fix): unit test for login and validat ecredentials * chore(fix): e2e update css selectors * test(fix): replace Object.values by supported syntax on node6
This commit is contained in:
parent
ec186794c6
commit
3639557118
@ -15,6 +15,8 @@
|
|||||||
"verdaccio": "./bin/verdaccio"
|
"verdaccio": "./bin/verdaccio"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@material-ui/core": "3.0.2",
|
||||||
|
"@material-ui/icons": "2.0.3",
|
||||||
"@verdaccio/file-locking": "0.0.7",
|
"@verdaccio/file-locking": "0.0.7",
|
||||||
"@verdaccio/local-storage": "1.2.0",
|
"@verdaccio/local-storage": "1.2.0",
|
||||||
"@verdaccio/streams": "1.0.0",
|
"@verdaccio/streams": "1.0.0",
|
||||||
@ -82,7 +84,6 @@
|
|||||||
"codecov": "3.0.4",
|
"codecov": "3.0.4",
|
||||||
"cross-env": "5.1.4",
|
"cross-env": "5.1.4",
|
||||||
"css-loader": "0.28.10",
|
"css-loader": "0.28.10",
|
||||||
"element-react": "1.4.8",
|
|
||||||
"element-theme-default": "1.4.13",
|
"element-theme-default": "1.4.13",
|
||||||
"enzyme": "3.3.0",
|
"enzyme": "3.3.0",
|
||||||
"enzyme-adapter-react-16": "1.1.1",
|
"enzyme-adapter-react-16": "1.1.1",
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react';
|
||||||
import isNil from 'lodash/isNil';
|
import isNil from 'lodash/isNil';
|
||||||
import 'element-theme-default';
|
import 'element-theme-default';
|
||||||
import {i18n} from 'element-react';
|
|
||||||
import locale from 'element-react/src/locale/lang/en';
|
|
||||||
|
|
||||||
import storage from './utils/storage';
|
import storage from './utils/storage';
|
||||||
import logo from './utils/logo';
|
import logo from './utils/logo';
|
||||||
import {makeLogin, isTokenExpire} from './utils/login';
|
import { makeLogin, isTokenExpire } from './utils/login';
|
||||||
|
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import LoginModal from './components/Login';
|
import LoginModal from './components/Login';
|
||||||
|
|
||||||
i18n.use(locale);
|
|
||||||
|
|
||||||
import Route from './router';
|
import Route from './router';
|
||||||
|
|
||||||
import './styles/main.scss';
|
import './styles/main.scss';
|
||||||
@ -52,7 +48,7 @@ export default class App extends Component {
|
|||||||
this.handleLogout();
|
this.handleLogout();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
user: {username, token},
|
user: { username, token },
|
||||||
isUserLoggedIn: true
|
isUserLoggedIn: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -60,7 +56,7 @@ export default class App extends Component {
|
|||||||
|
|
||||||
async loadLogo() {
|
async loadLogo() {
|
||||||
const logoUrl = await logo();
|
const logoUrl = await logo();
|
||||||
this.setState({logoUrl});
|
this.setState({ logoUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,7 +75,7 @@ export default class App extends Component {
|
|||||||
* Required by: <Header />
|
* Required by: <Header />
|
||||||
*/
|
*/
|
||||||
async doLogin(usernameValue, passwordValue) {
|
async doLogin(usernameValue, passwordValue) {
|
||||||
const {username, token, error} = await makeLogin(
|
const { username, token, error } = await makeLogin(
|
||||||
usernameValue,
|
usernameValue,
|
||||||
passwordValue
|
passwordValue
|
||||||
);
|
);
|
||||||
@ -152,12 +148,12 @@ export default class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {isUserLoggedIn} = this.state;
|
const { isUserLoggedIn } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="page-full-height">
|
<div className="page-full-height">
|
||||||
{this.renderHeader()}
|
{this.renderHeader()}
|
||||||
{this.renderLoginModal()}
|
{this.renderLoginModal()}
|
||||||
<Route isUserLoggedIn={isUserLoggedIn} />
|
<Route isUserLoggedIn={isUserLoggedIn} />
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Button} from 'element-react';
|
import Button from '@material-ui/core/Button';
|
||||||
import capitalize from 'lodash/capitalize';
|
import capitalize from 'lodash/capitalize';
|
||||||
import {getRegistryURL} from '../../utils/url';
|
import { getRegistryURL } from '../../utils/url';
|
||||||
import classes from './header.scss';
|
import classes from './header.scss';
|
||||||
import './logo.png';
|
import './logo.png';
|
||||||
|
|
||||||
@ -10,8 +10,8 @@ const Header = ({
|
|||||||
logo = '',
|
logo = '',
|
||||||
scope = '',
|
scope = '',
|
||||||
username = '',
|
username = '',
|
||||||
handleLogout = () => {},
|
handleLogout = () => { },
|
||||||
toggleLoginModal = () => {}
|
toggleLoginModal = () => { }
|
||||||
}) => {
|
}) => {
|
||||||
const registryUrl = getRegistryURL();
|
const registryUrl = getRegistryURL();
|
||||||
return (
|
return (
|
||||||
@ -37,20 +37,21 @@ const Header = ({
|
|||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
className={`${classes.headerButton} header-button-logout`}
|
className={`${classes.headerButton} header-button-logout`}
|
||||||
type="danger"
|
color="primary"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
className={`${classes.headerButton} header-button-login`}
|
className={`${classes.headerButton} header-button-login`}
|
||||||
onClick={toggleLoginModal}
|
color="primary"
|
||||||
>
|
onClick={toggleLoginModal}
|
||||||
Login
|
>
|
||||||
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
import React, {Component, createRef} from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Form, Button, Dialog, Input, Alert} from 'element-react';
|
import Button from '@material-ui/core/Button';
|
||||||
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
|
import Dialog from '@material-ui/core/Dialog';
|
||||||
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
|
import SnackbarContent from '@material-ui/core/SnackbarContent';
|
||||||
|
import ErrorIcon from '@material-ui/icons/Error';
|
||||||
|
import InputLabel from '@material-ui/core/InputLabel';
|
||||||
|
import Input from '@material-ui/core/Input';
|
||||||
|
import FormControl from '@material-ui/core/FormControl';
|
||||||
|
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||||
|
|
||||||
|
import classes from "./login.scss";
|
||||||
|
|
||||||
export default class LoginModal extends Component {
|
export default class LoginModal extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -15,141 +27,174 @@ export default class LoginModal extends Component {
|
|||||||
error: {},
|
error: {},
|
||||||
onCancel: () => {},
|
onCancel: () => {},
|
||||||
onSubmit: () => {}
|
onSubmit: () => {}
|
||||||
};
|
}
|
||||||
|
|
||||||
state = {
|
|
||||||
form: {
|
|
||||||
username: '',
|
|
||||||
password: ''
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
username: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: 'Please input the username',
|
|
||||||
trigger: 'change'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
password: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: 'Please input the password',
|
|
||||||
trigger: 'change'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.formRef = createRef();
|
|
||||||
this.submitCredentials = this.submitCredentials.bind(this);
|
this.submitCredentials = this.submitCredentials.bind(this);
|
||||||
this.setCredentials = this.setCredentials.bind(this);
|
this.setCredentials = this.setCredentials.bind(this);
|
||||||
|
this.validateCredentials = this.validateCredentials.bind(this);
|
||||||
|
this.state = {
|
||||||
|
form: {
|
||||||
|
username: {
|
||||||
|
required: true,
|
||||||
|
pristine: true,
|
||||||
|
helperText: 'Field required',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
required: true,
|
||||||
|
pristine: true,
|
||||||
|
helperText: 'Field required',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
error: props.error
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set login modal's username and password to current state
|
* set login modal's username and password to current state
|
||||||
* Required by: <LoginModal />
|
* Required to login
|
||||||
*/
|
*/
|
||||||
setCredentials(key, value) {
|
setCredentials(name, e) {
|
||||||
this.setState(
|
this.setState({
|
||||||
(prevState) => ({
|
form: {
|
||||||
form: {...prevState.form, [key]: value}
|
...this.state.form,
|
||||||
})
|
[name]: {
|
||||||
);
|
...this.state.form[name],
|
||||||
}
|
value: e.target.value,
|
||||||
|
pristine: false
|
||||||
/**
|
}
|
||||||
* Clears the username and password field.
|
|
||||||
*/
|
|
||||||
handleReset() {
|
|
||||||
this.formRef.current.resetFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
submitCredentials(event) {
|
|
||||||
// prevents default submit behavior
|
|
||||||
event.preventDefault();
|
|
||||||
this.formRef.current.validate((valid) => {
|
|
||||||
if (valid) {
|
|
||||||
const {username, password} = this.state.form;
|
|
||||||
this.props.onSubmit(username, password);
|
|
||||||
this.setState({
|
|
||||||
form: {username}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLoginError({type, title, description} = {}) {
|
validateCredentials(event) {
|
||||||
return type ? (
|
// prevents default submit behavior
|
||||||
<Alert
|
event.preventDefault();
|
||||||
title={title}
|
|
||||||
type={type}
|
this.setState({
|
||||||
description={description}
|
form: Object.keys(this.state.form).reduce((acc, key) => ({
|
||||||
showIcon={true}
|
...acc,
|
||||||
closable={false}
|
...{ [key]: {...this.state.form[key], pristine: false } }
|
||||||
style={{lineHeight: '10px'}}
|
}), {})
|
||||||
|
}, () => {
|
||||||
|
if (!Object.keys(this.state.form).some(id => !this.state.form[id])) {
|
||||||
|
this.submitCredentials();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitCredentials() {
|
||||||
|
const { form: { username, password } } = this.state;
|
||||||
|
await this.props.onSubmit(username.value, password.value);
|
||||||
|
// let's wait for API response and then set
|
||||||
|
// username and password filed to empty state
|
||||||
|
this.setState({
|
||||||
|
form: Object.keys(this.state.form).reduce((acc, key) => ({
|
||||||
|
...acc,
|
||||||
|
...{ [key]: {...this.state.form[key], value: "", pristine: true } }
|
||||||
|
}), {})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLoginError({ type, title, description } = {}) {
|
||||||
|
return type === 'error' && (
|
||||||
|
<SnackbarContent
|
||||||
|
className={classes.loginError}
|
||||||
|
aria-describedby="client-snackbar"
|
||||||
|
message={
|
||||||
|
<div
|
||||||
|
id="client-snackbar"
|
||||||
|
className={classes.loginErrorMsg}
|
||||||
|
>
|
||||||
|
<ErrorIcon className={classes.loginIcon} />
|
||||||
|
<span>
|
||||||
|
<div><strong>{title}</strong></div>
|
||||||
|
<div>{description}</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
''
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {visibility, onCancel, error} = this.props;
|
const { visibility, onCancel, error } = this.props;
|
||||||
const {username, password} = this.state.form;
|
const { form: { username, password } } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="login-dialog">
|
<div className="login">
|
||||||
<Dialog
|
<Dialog
|
||||||
title="Login"
|
onClose={onCancel}
|
||||||
size="tiny"
|
open={visibility}
|
||||||
visible={visibility}
|
className="login-dialog"
|
||||||
onCancel={onCancel}
|
maxWidth="xs"
|
||||||
|
aria-labelledby="login-dialog"
|
||||||
|
fullWidth
|
||||||
>
|
>
|
||||||
<Dialog.Body>
|
<DialogTitle id="login-dialog">Login</DialogTitle>
|
||||||
<Form
|
<DialogContent>
|
||||||
className="login-form"
|
{this.renderLoginError(error)}
|
||||||
ref={this.formRef}
|
<FormControl
|
||||||
model={this.state.form}
|
error={!username.value && !username.pristine}
|
||||||
rules={this.state.rules}
|
aria-describedby='username'
|
||||||
|
required={username.required}
|
||||||
|
fullWidth
|
||||||
>
|
>
|
||||||
<Form.Item>
|
<InputLabel htmlFor="username">Username</InputLabel>
|
||||||
{this.renderLoginError(error)}
|
<Input
|
||||||
</Form.Item>
|
id="username"
|
||||||
<Form.Item prop="username" labelPosition="top">
|
value={username.value}
|
||||||
<Input
|
onChange={this.setCredentials.bind(this, 'username')}
|
||||||
name="username"
|
placeholder="Your username"
|
||||||
placeholder="Type your username"
|
/>
|
||||||
value={username}
|
{!username.value && !username.pristine && (
|
||||||
onChange={this.setCredentials.bind(this, 'username')}
|
<FormHelperText id='username-error'>
|
||||||
/>
|
{username.helperText}
|
||||||
</Form.Item>
|
</FormHelperText>
|
||||||
<Form.Item prop="password">
|
)}
|
||||||
<Input
|
</FormControl>
|
||||||
name="password"
|
<FormControl
|
||||||
type="password"
|
error={!password.value && !password.pristine}
|
||||||
placeholder="Type your password"
|
aria-describedby='password'
|
||||||
value={password}
|
required={password.required}
|
||||||
onChange={this.setCredentials.bind(this, 'password')}
|
style={{ marginTop: '8px' }}
|
||||||
/>
|
fullWidth
|
||||||
</Form.Item>
|
>
|
||||||
<Form.Item style={{float: 'right'}}>
|
<InputLabel htmlFor="password">Password</InputLabel>
|
||||||
<Button onClick={onCancel} className="cancel-login-button">
|
<Input
|
||||||
Cancel
|
id="password"
|
||||||
</Button>
|
type="password"
|
||||||
<Button
|
value={password.value}
|
||||||
nativeType="submit"
|
onChange={this.setCredentials.bind(this, 'password')}
|
||||||
className="login-button"
|
placeholder="Your strong password"
|
||||||
onClick={this.submitCredentials}
|
/>
|
||||||
>
|
{!password.value && !password.pristine && (
|
||||||
Login
|
<FormHelperText id='password-error'>
|
||||||
</Button>
|
{password.helperText}
|
||||||
</Form.Item>
|
</FormHelperText>
|
||||||
</Form>
|
)}
|
||||||
</Dialog.Body>
|
</FormControl>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions className="dialog-footer">
|
||||||
|
<Button
|
||||||
|
onClick={onCancel}
|
||||||
|
className="cancel-login-button"
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="login-button"
|
||||||
|
onClick={this.validateCredentials.bind(this)}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
src/webui/components/Login/login.scss
Normal file
22
src/webui/components/Login/login.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
|
.loginDialog {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginError {
|
||||||
|
background-color: $red !important;
|
||||||
|
min-width: inherit !important;
|
||||||
|
margin-bottom: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginErrorMsg {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginIcon {
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Tag} from 'element-react';
|
import Chip from '@material-ui/core/Chip';
|
||||||
import {Link} from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import {formatDateDistance} from '../../utils/package';
|
import { formatDateDistance } from '../../utils/package';
|
||||||
|
|
||||||
import classes from './package.scss';
|
import classes from './package.scss';
|
||||||
|
|
||||||
const Package = ({name, version, author, description, license, time, keywords}) => {
|
const Package = ({ name, version, author, description, license, time, keywords }) => {
|
||||||
return (<section className={classes.package}>
|
return (<section className={classes.package}>
|
||||||
<Link to={`detail/${name}`}>
|
<Link to={`detail/${name}`}>
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
<div className={classes.title}>
|
<div className={classes.title}>
|
||||||
<h1>
|
<h1>
|
||||||
{name} <Tag type="gray">v{version}</Tag>
|
{name} <Chip label={`v${version}`} />
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div role="author" className={classes.author}>
|
<div role="author" className={classes.author}>
|
||||||
{ author ? `By: ${author}`: ''}
|
{author ? `By: ${author}` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.footer}>
|
<div className={classes.footer}>
|
||||||
@ -27,9 +27,11 @@ const Package = ({name, version, author, description, license, time, keywords})
|
|||||||
</div>
|
</div>
|
||||||
<div className={classes.tags}>
|
<div className={classes.tags}>
|
||||||
{keywords && keywords.map((keyword, index) => (
|
{keywords && keywords.map((keyword, index) => (
|
||||||
<Tag key={index} type="gray">
|
<Chip
|
||||||
{keyword}
|
key={index}
|
||||||
</Tag>
|
label={keyword}
|
||||||
|
className={classes.tag}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.details}>
|
<div className={classes.details}>
|
||||||
|
@ -27,6 +27,9 @@ .package {
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tag {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, {Component} from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Loading} from 'element-react';
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import PackageDetail from '../../components/PackageDetail';
|
import PackageDetail from '../../components/PackageDetail';
|
||||||
@ -10,8 +10,6 @@ import API from '../../utils/api';
|
|||||||
import classes from './detail.scss';
|
import classes from './detail.scss';
|
||||||
import PackageSidebar from '../../components/PackageSidebar/index';
|
import PackageSidebar from '../../components/PackageSidebar/index';
|
||||||
|
|
||||||
const loadingMessage = 'Loading...';
|
|
||||||
|
|
||||||
export default class Detail extends Component {
|
export default class Detail extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
@ -66,12 +64,12 @@ export default class Detail extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {notFound, readMe} = this.state;
|
const { notFound, readMe } = this.state;
|
||||||
|
|
||||||
if (notFound) {
|
if (notFound) {
|
||||||
return <NotFound pkg={this.packageName} />;
|
return <NotFound pkg={this.packageName} />;
|
||||||
} else if (isEmpty(readMe)) {
|
} else if (isEmpty(readMe)) {
|
||||||
return <Loading text={loadingMessage} />;
|
return <CircularProgress size={50} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={classes.twoColumn}>
|
<div className={classes.twoColumn}>
|
||||||
|
16
src/webui/modules/home/home.scss
Normal file
16
src/webui/modules/home/home.scss
Normal 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;
|
||||||
|
}
|
@ -1,6 +1,13 @@
|
|||||||
import React, {Component, Fragment} from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Loading, MessageBox} from 'element-react';
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
|
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 isEmpty from 'lodash/isEmpty';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
@ -9,13 +16,20 @@ import API from '../../utils/api';
|
|||||||
import PackageList from '../../components/PackageList';
|
import PackageList from '../../components/PackageList';
|
||||||
import Search from '../../components/Search';
|
import Search from '../../components/Search';
|
||||||
|
|
||||||
export default class Home extends Component {
|
import classes from "./home.scss";
|
||||||
|
|
||||||
|
class Home extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.element,
|
children: PropTypes.element,
|
||||||
isUserLoggedIn: PropTypes.bool
|
isUserLoggedIn: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
showAlertDialog: false,
|
||||||
|
alertDialogContent: {
|
||||||
|
title: '',
|
||||||
|
message: ''
|
||||||
|
},
|
||||||
loading: true,
|
loading: true,
|
||||||
fistTime: true,
|
fistTime: true,
|
||||||
query: ''
|
query: ''
|
||||||
@ -24,6 +38,8 @@ export default class Home extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleSearchInput = this.handleSearchInput.bind(this);
|
this.handleSearchInput = this.handleSearchInput.bind(this);
|
||||||
|
this.handleShowAlertDialog = this.handleShowAlertDialog.bind(this);
|
||||||
|
this.handleCloseAlertDialog = this.handleCloseAlertDialog.bind(this);
|
||||||
this.searchPackage = debounce(this.searchPackage, 800);
|
this.searchPackage = debounce(this.searchPackage, 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +77,7 @@ export default class Home extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
MessageBox.msgbox({
|
this.handleShowAlertDialog({
|
||||||
type: 'error',
|
|
||||||
title: 'Warning',
|
title: 'Warning',
|
||||||
message: `Unable to load package list: ${error.message}`
|
message: `Unable to load package list: ${error.message}`
|
||||||
});
|
});
|
||||||
@ -82,14 +97,66 @@ export default class Home extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
MessageBox.msgbox({
|
this.handleShowAlertDialog({
|
||||||
type: 'error',
|
|
||||||
title: 'Warning',
|
title: 'Warning',
|
||||||
message: 'Unable to get search result, please try again later.'
|
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) {
|
handleSearchInput(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
query: e.target.value.trim()
|
query: e.target.value.trim()
|
||||||
@ -101,15 +168,16 @@ export default class Home extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {packages, loading} = this.state;
|
const { packages, loading } = this.state;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{this.renderSearchBar()}
|
{this.renderSearchBar()}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Loading text="Loading..." />
|
<CircularProgress size={50} />
|
||||||
) : (
|
) : (
|
||||||
<PackageList help={isEmpty(packages) === true} packages={packages} />
|
<PackageList help={isEmpty(packages) === true} packages={packages} />
|
||||||
)}
|
)}
|
||||||
|
{this.renderAlertDialog()}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -121,3 +189,5 @@ export default class Home extends Component {
|
|||||||
return <Search handleSearchInput={this.handleSearchInput} />;
|
return <Search handleSearchInput={this.handleSearchInput} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
$black: #000;
|
$black: #000;
|
||||||
$white: #fff;
|
$white: #fff;
|
||||||
|
$red: #d32f2f;
|
||||||
$grey: #808080;
|
$grey: #808080;
|
||||||
$grey-light: #d3d3d3;
|
$grey-light: #d3d3d3;
|
||||||
$grey-dark: #a9a9a9;
|
$grey-dark: #a9a9a9;
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-us">
|
<html lang="en-us">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title>
|
||||||
<link rel="icon" type="image/png" href="<%= htmlWebpackPlugin.options.verdaccioURL %>/-/static/favicon.ico"/>
|
<%= htmlWebpackPlugin.options.title %>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
</title>
|
||||||
|
<link rel="icon" type="image/png" href="<%= htmlWebpackPlugin.options.verdaccioURL %>/-/static/favicon.ico" />
|
||||||
|
<!-- Robot Font -->
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />
|
||||||
|
<!-- Material Icons Font -->
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<script>
|
<script>
|
||||||
window.VERDACCIO_API_URL = '<%= htmlWebpackPlugin.options.verdaccioURL %>/-/verdaccio/';
|
window.VERDACCIO_API_URL = '<%= htmlWebpackPlugin.options.verdaccioURL %>/-/verdaccio/';
|
||||||
window.VERDACCIO_SCOPE = '<%= htmlWebpackPlugin.options.scope %>';
|
window.VERDACCIO_SCOPE = '<%= htmlWebpackPlugin.options.scope %>';
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="body">
|
<body class="body">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -25,10 +25,10 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
await clickElement('header button');
|
await clickElement('header button');
|
||||||
await page.waitFor(500);
|
await page.waitFor(500);
|
||||||
// we fill the sign in form
|
// we fill the sign in form
|
||||||
const signInDialog = await page.$('.el-dialog');
|
const signInDialog = await page.$('.login-dialog');
|
||||||
const userInput = await signInDialog.$('input[type=text]');
|
const userInput = await signInDialog.$('#username');
|
||||||
expect(userInput).not.toBeNull();
|
expect(userInput).not.toBeNull();
|
||||||
const passInput = await signInDialog.$('input[type=password]');
|
const passInput = await signInDialog.$('#password');
|
||||||
expect(passInput).not.toBeNull();
|
expect(passInput).not.toBeNull();
|
||||||
await userInput.type('test', {delay: 100});
|
await userInput.type('test', {delay: 100});
|
||||||
await passInput.type('test', {delay: 100});
|
await passInput.type('test', {delay: 100});
|
||||||
@ -86,7 +86,7 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
it('should click on sign in button', async () => {
|
it('should click on sign in button', async () => {
|
||||||
const signInButton = await page.$('header button');
|
const signInButton = await page.$('header button');
|
||||||
await signInButton.click();
|
await signInButton.click();
|
||||||
const signInDialog = await page.$('.login-dialog .el-dialog__wrapper');
|
const signInDialog = await page.$('#login-dialog');
|
||||||
|
|
||||||
expect(signInDialog).not.toBeNull();
|
expect(signInDialog).not.toBeNull();
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,7 @@ describe('basic system test', () => {
|
|||||||
url: 'http://localhost:' + port + '/',
|
url: 'http://localhost:' + port + '/',
|
||||||
}, function(err, res, body) {
|
}, function(err, res, body) {
|
||||||
expect(err).toBeNull();
|
expect(err).toBeNull();
|
||||||
expect(body).toMatch(/<title>Verdaccio<\/title>/);
|
expect(body).toMatch(/Verdaccio/);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@ import storage from '../../../src/webui/utils/storage';
|
|||||||
import App from '../../../src/webui/app';
|
import App from '../../../src/webui/app';
|
||||||
import { API_ERROR } from '../../../src/lib/constants';
|
import { API_ERROR } from '../../../src/lib/constants';
|
||||||
|
|
||||||
import {generateTokenWithTimeRange} from './components/__mocks__/token';
|
import { generateTokenWithTimeRange } from './components/__mocks__/token';
|
||||||
|
|
||||||
jest.mock('../../../src/webui/utils/storage', () => {
|
jest.mock('../../../src/webui/utils/storage', () => {
|
||||||
class LocalStorageMock {
|
class LocalStorageMock {
|
||||||
@ -29,8 +29,6 @@ jest.mock('../../../src/webui/utils/storage', () => {
|
|||||||
|
|
||||||
jest.mock('element-theme-default', () => ({}));
|
jest.mock('element-theme-default', () => ({}));
|
||||||
|
|
||||||
jest.mock('element-react/src/locale/lang/en', () => ({}));
|
|
||||||
|
|
||||||
jest.mock('../../../src/webui/utils/api', () => ({
|
jest.mock('../../../src/webui/utils/api', () => ({
|
||||||
request: require('./components/__mocks__/api').default.request
|
request: require('./components/__mocks__/api').default.request
|
||||||
}));
|
}));
|
||||||
@ -58,7 +56,7 @@ describe('App', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
it('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
||||||
|
|
||||||
storage.setItem('username', 'verdaccio');
|
storage.setItem('username', 'verdaccio');
|
||||||
storage.setItem('token', generateTokenWithTimeRange(24));
|
storage.setItem('token', generateTokenWithTimeRange(24));
|
||||||
const { isUserAlreadyLoggedIn } = wrapper.instance();
|
const { isUserAlreadyLoggedIn } = wrapper.instance();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<Header /> component shallow should load header component in login state 1`] = `"<header class=\\"header\\"><div class=\\"headerWrap\\"><a href=\\"/#/\\"><img src=\\"logo.png\\" class=\\"logo\\"/></a><figure>npm set scope:registry http://localhost<br/>npm adduser --registry http://localhost</figure><div class=\\"headerRight\\"><div class=\\"user-logged\\"><span class=\\"user-logged-greetings usernameField\\">Hi, Verdaccio</span><button class=\\"el-button el-button--danger headerButton header-button-logout\\" type=\\"button\\"><span>Logout</span></button></div></div></div></header>"`;
|
exports[`<Header /> component shallow should load header component in login state 1`] = `"<header class=\\"header\\"><div class=\\"headerWrap\\"><a href=\\"/#/\\"><img src=\\"logo.png\\" class=\\"logo\\"/></a><figure>npm set scope:registry http://localhost<br/>npm adduser --registry http://localhost</figure><div class=\\"headerRight\\"><div class=\\"user-logged\\"><span class=\\"user-logged-greetings usernameField\\">Hi, Verdaccio</span><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-27 MuiButton-root-1 MuiButton-text-3 MuiButton-textPrimary-4 MuiButton-flat-6 MuiButton-flatPrimary-7 headerButton header-button-logout\\" type=\\"button\\"><span class=\\"MuiButton-label-2\\">Logout</span><span class=\\"MuiTouchRipple-root-30\\"></span></button></div></div></div></header>"`;
|
||||||
|
|
||||||
exports[`<Header /> component shallow should load header component in logout state 1`] = `"<header class=\\"header\\"><div class=\\"headerWrap\\"><a href=\\"/#/\\"><img src=\\"logo.png\\" class=\\"logo\\"/></a><figure>npm set scope:registry http://localhost<br/>npm adduser --registry http://localhost</figure><div class=\\"headerRight\\"><button class=\\"el-button el-button--default headerButton header-button-login\\" type=\\"button\\"><span>Login</span></button></div></div></header>"`;
|
exports[`<Header /> component shallow should load header component in logout state 1`] = `"<header class=\\"header\\"><div class=\\"headerWrap\\"><a href=\\"/#/\\"><img src=\\"logo.png\\" class=\\"logo\\"/></a><figure>npm set scope:registry http://localhost<br/>npm adduser --registry http://localhost</figure><div class=\\"headerRight\\"><button tabindex=\\"0\\" class=\\"MuiButtonBase-root-27 MuiButton-root-1 MuiButton-text-3 MuiButton-textPrimary-4 MuiButton-flat-6 MuiButton-flatPrimary-7 headerButton header-button-login\\" type=\\"button\\"><span class=\\"MuiButton-label-2\\">Login</span><span class=\\"MuiTouchRipple-root-30\\"></span></button></div></div></header>"`;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<LoginModal /> should load the component in default state 1`] = `"<div class=\\"login-dialog\\"><div><div style=\\"z-index: 1013;\\" class=\\"el-dialog__wrapper\\"><div style=\\"top: 15%;\\" class=\\"el-dialog el-dialog--tiny\\"><div class=\\"el-dialog__header\\"><span class=\\"el-dialog__title\\">Login</span><button type=\\"button\\" class=\\"el-dialog__headerbtn\\"><i class=\\"el-dialog__close el-icon el-icon-close\\"></i></button></div><div class=\\"el-dialog__body\\"><form class=\\"el-form el-form--label-right login-form\\"><div class=\\"el-form-item\\"><div class=\\"el-form-item__content\\"></div></div><div class=\\"el-form-item is-required\\"><div class=\\"el-form-item__content\\"><div class=\\"el-input\\"><input name=\\"username\\" placeholder=\\"Type your username\\" type=\\"text\\" class=\\"el-input__inner\\" autocomplete=\\"off\\" value=\\"\\"></div></div></div><div class=\\"el-form-item is-required\\"><div class=\\"el-form-item__content\\"><div class=\\"el-input\\"><input name=\\"password\\" placeholder=\\"Type your password\\" type=\\"password\\" class=\\"el-input__inner\\" autocomplete=\\"off\\" value=\\"\\"></div></div></div><div style=\\"float: right;\\" class=\\"el-form-item\\"><div class=\\"el-form-item__content\\"><button class=\\"el-button el-button--default cancel-login-button\\" type=\\"button\\"><span>Cancel</span></button><button class=\\"el-button el-button--default login-button\\" type=\\"submit\\"><span>Login</span></button></div></div></form></div></div></div><div class=\\"v-modal\\" style=\\"z-index: 1012;\\"></div></div></div>"`;
|
exports[`<LoginModal /> should load the component in default state 1`] = `"<div class=\\"login\\"></div>"`;
|
||||||
|
|
||||||
exports[`<LoginModal /> should load the component with props 1`] = `"<div class=\\"login-dialog\\"><div><div style=\\"z-index: 1013;\\" class=\\"el-dialog__wrapper\\"><div style=\\"top: 15%;\\" class=\\"el-dialog el-dialog--tiny\\"><div class=\\"el-dialog__header\\"><span class=\\"el-dialog__title\\">Login</span><button type=\\"button\\" class=\\"el-dialog__headerbtn\\"><i class=\\"el-dialog__close el-icon el-icon-close\\"></i></button></div><div class=\\"el-dialog__body\\"><form class=\\"el-form el-form--label-right login-form\\"><div class=\\"el-form-item\\"><div class=\\"el-form-item__content\\"><div style=\\"line-height: 10px;\\" class=\\"el-alert el-alert--error\\"><i class=\\"el-alert__icon el-icon-circle-cross is-big\\"></i><div class=\\"el-alert__content\\"><span class=\\"el-alert__title is-bold\\">Error Title</span><p class=\\"el-alert__description\\">Error Description</p><i class=\\"el-alert__closebtn el-icon-close\\" style=\\"display: none;\\"></i></div></div></div></div><div class=\\"el-form-item is-required\\"><div class=\\"el-form-item__content\\"><div class=\\"el-input\\"><input name=\\"username\\" placeholder=\\"Type your username\\" type=\\"text\\" class=\\"el-input__inner\\" autocomplete=\\"off\\" value=\\"\\"></div></div></div><div class=\\"el-form-item is-required\\"><div class=\\"el-form-item__content\\"><div class=\\"el-input\\"><input name=\\"password\\" placeholder=\\"Type your password\\" type=\\"password\\" class=\\"el-input__inner\\" autocomplete=\\"off\\" value=\\"\\"></div></div></div><div style=\\"float: right;\\" class=\\"el-form-item\\"><div class=\\"el-form-item__content\\"><button class=\\"el-button el-button--default cancel-login-button\\" type=\\"button\\"><span>Cancel</span></button><button class=\\"el-button el-button--default login-button\\" type=\\"submit\\"><span>Login</span></button></div></div></form></div></div></div><div class=\\"v-modal\\" style=\\"z-index: 1012;\\"></div></div></div>"`;
|
exports[`<LoginModal /> should load the component with props 1`] = `"<div class=\\"login\\"></div>"`;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<Package /> component should load the component 1`] = `"<section class=\\"package\\"><a href=\\"detail/verdaccio\\"><div class=\\"header\\"><div class=\\"title\\"><h1>verdaccio <span class=\\"el-tag el-tag--gray\\">v1.0.0</span></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 about 1 month ago</div><div class=\\"license\\">MIT</div></div></a></section>"`;
|
exports[`<Package /> component should load the component 1`] = `"<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-17\\">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 about 1 month ago</div><div class=\\"license\\">MIT</div></div></a></section>"`;
|
||||||
|
|
||||||
exports[`<Package /> component should load the component without author 1`] = `"<section class=\\"package\\"><a href=\\"detail/verdaccio\\"><div class=\\"header\\"><div class=\\"title\\"><h1>verdaccio <span class=\\"el-tag el-tag--gray\\">v1.0.0</span></h1></div><div role=\\"author\\" class=\\"author\\"></div></div><div class=\\"footer\\"><p class=\\"description\\">Private NPM repository</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\">Published about 1 month ago</div><div class=\\"license\\">MIT</div></div></a></section>"`;
|
exports[`<Package /> component should load the component without author 1`] = `"<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-17\\">v1.0.0</span></div></h1></div><div role=\\"author\\" class=\\"author\\"></div></div><div class=\\"footer\\"><p class=\\"description\\">Private NPM repository</p></div><div class=\\"tags\\"></div><div class=\\"details\\"><div class=\\"homepage\\">Published about 1 month ago</div><div class=\\"license\\">MIT</div></div></a></section>"`;
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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 <span class=\\"el-tag el-tag--gray\\">v1.0.0</span></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 <span class=\\"el-tag el-tag--gray\\">v1.0.1</span></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 <span class=\\"el-tag el-tag--gray\\">v1.1.0</span></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><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-17\\">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-17\\">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-17\\">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>"`;
|
||||||
|
@ -2,6 +2,22 @@ import React from 'react';
|
|||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import LoginModal from '../../../../src/webui/components/Login';
|
import LoginModal from '../../../../src/webui/components/Login';
|
||||||
|
|
||||||
|
const eventUsername = {
|
||||||
|
target: {
|
||||||
|
value: 'xyz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventPassword = {
|
||||||
|
target: {
|
||||||
|
value: '1234'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
preventDefault: jest.fn()
|
||||||
|
};
|
||||||
|
|
||||||
describe('<LoginModal />', () => {
|
describe('<LoginModal />', () => {
|
||||||
it('should load the component in default state', () => {
|
it('should load the component in default state', () => {
|
||||||
const wrapper = mount(<LoginModal />);
|
const wrapper = mount(<LoginModal />);
|
||||||
@ -16,8 +32,8 @@ describe('<LoginModal />', () => {
|
|||||||
title: 'Error Title',
|
title: 'Error Title',
|
||||||
description: 'Error Description'
|
description: 'Error Description'
|
||||||
},
|
},
|
||||||
onCancel: () => {},
|
onCancel: () => { },
|
||||||
onSubmit: () => {}
|
onSubmit: () => { }
|
||||||
};
|
};
|
||||||
const wrapper = mount(<LoginModal {...props} />);
|
const wrapper = mount(<LoginModal {...props} />);
|
||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
@ -32,12 +48,12 @@ describe('<LoginModal />', () => {
|
|||||||
description: 'Error Description'
|
description: 'Error Description'
|
||||||
},
|
},
|
||||||
onCancel: jest.fn(),
|
onCancel: jest.fn(),
|
||||||
onSubmit: () => {}
|
onSubmit: () => { }
|
||||||
};
|
};
|
||||||
const wrapper = mount(<LoginModal {...props} />);
|
const wrapper = mount(<LoginModal {...props} />);
|
||||||
wrapper.find('button.cancel-login-button').simulate('click');
|
wrapper.find('.dialog-footer > .cancel-login-button').simulate('click');
|
||||||
expect(props.onCancel).toHaveBeenCalled();
|
expect(props.onCancel).toHaveBeenCalled();
|
||||||
wrapper.find('.el-dialog__headerbtn > .el-dialog__close').simulate('click');
|
wrapper.find('.dialog-footer > .login-button').simulate('click');
|
||||||
expect(props.onCancel).toHaveBeenCalled();
|
expect(props.onCancel).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,14 +67,14 @@ describe('<LoginModal />', () => {
|
|||||||
const wrapper = mount(<LoginModal {...props} />);
|
const wrapper = mount(<LoginModal {...props} />);
|
||||||
const { setCredentials } = wrapper.instance();
|
const { setCredentials } = wrapper.instance();
|
||||||
|
|
||||||
expect(setCredentials('username', 'xyz')).toBeUndefined();
|
expect(setCredentials('username', eventUsername)).toBeUndefined();
|
||||||
expect(wrapper.state('form').username).toEqual('xyz');
|
expect(wrapper.state('form').username.value).toEqual('xyz');
|
||||||
|
|
||||||
expect(setCredentials('password', '1234')).toBeUndefined();
|
expect(setCredentials('password', eventPassword)).toBeUndefined();
|
||||||
expect(wrapper.state('form').password).toEqual('1234');
|
expect(wrapper.state('form').password.value).toEqual('1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submitCredential: should call the onSubmit', () => {
|
it('validateCredentials: should validate credentials', async () => {
|
||||||
const props = {
|
const props = {
|
||||||
visibility: true,
|
visibility: true,
|
||||||
error: {},
|
error: {},
|
||||||
@ -66,19 +82,46 @@ describe('<LoginModal />', () => {
|
|||||||
onSubmit: jest.fn()
|
onSubmit: jest.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
const event = {
|
|
||||||
preventDefault: jest.fn()
|
|
||||||
};
|
|
||||||
const wrapper = mount(<LoginModal {...props} />);
|
const wrapper = mount(<LoginModal {...props} />);
|
||||||
const { submitCredentials } = wrapper.instance();
|
const instance = wrapper.instance();
|
||||||
wrapper
|
|
||||||
.find('input[type="text"]')
|
instance.submitCredentials = jest.fn();
|
||||||
.simulate('change', { target: { value: 'sam' } });
|
const { validateCredentials, setCredentials, submitCredentials } = instance;
|
||||||
wrapper
|
|
||||||
.find('input[type="password"]')
|
expect(setCredentials('username', eventUsername)).toBeUndefined();
|
||||||
.simulate('change', { target: { value: '1234' } });
|
expect(wrapper.state('form').username.value).toEqual('xyz');
|
||||||
submitCredentials(event);
|
|
||||||
|
expect(setCredentials('password', eventPassword)).toBeUndefined();
|
||||||
|
expect(wrapper.state('form').password.value).toEqual('1234');
|
||||||
|
|
||||||
|
validateCredentials(event);
|
||||||
expect(event.preventDefault).toHaveBeenCalled();
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
expect(props.onSubmit).toHaveBeenCalledWith('sam', '1234');
|
expect(wrapper.state('form').username.pristine).toEqual(false);
|
||||||
|
expect(wrapper.state('form').password.pristine).toEqual(false);
|
||||||
|
|
||||||
|
expect(submitCredentials).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('submitCredentials: should submit credentials', async () => {
|
||||||
|
const props = {
|
||||||
|
onSubmit: jest.fn()
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = mount(<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);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import React from 'react';
|
|||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import Package from '../../../../src/webui/components/Package/index';
|
import Package from '../../../../src/webui/components/Package/index';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import Chip from '@material-ui/core/Chip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates one month back date from current time
|
* Generates one month back date from current time
|
||||||
@ -29,14 +30,17 @@ describe('<Package /> component', () => {
|
|||||||
};
|
};
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Package {...props}/>
|
<Package {...props} />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const version =
|
||||||
|
wrapper.findWhere(node => node.is(Chip) && node.prop('label') === 'v1.0.0');
|
||||||
|
|
||||||
// integration expectations
|
// integration expectations
|
||||||
expect(wrapper.find('a').prop('href')).toEqual('detail/verdaccio');
|
expect(wrapper.find('a').prop('href')).toEqual('detail/verdaccio');
|
||||||
expect(wrapper.find('h1').text()).toEqual('verdaccio v1.0.0');
|
expect(wrapper.find('h1').text()).toEqual('verdaccio v1.0.0');
|
||||||
expect(wrapper.find('.el-tag--gray').text()).toEqual('v1.0.0');
|
expect(version.exists()).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('div').filterWhere(n => n.prop('role') === 'author')
|
wrapper.find('div').filterWhere(n => n.prop('role') === 'author')
|
||||||
.text()
|
.text()
|
||||||
@ -68,4 +72,3 @@ describe('<Package /> component', () => {
|
|||||||
expect(wrapper.html()).toMatchSnapshot();
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
BIN
yarn.lock
BIN
yarn.lock
Binary file not shown.
Loading…
Reference in New Issue
Block a user