1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-08 23:25:51 +01:00

Merge branch 'feat-new-detail-page' of github.com:verdaccio/verdaccio into feat-new-detail-page

This commit is contained in:
Ayush Sharma 2019-02-03 22:33:31 +01:00
commit 495ec89da4
43 changed files with 587 additions and 327 deletions

@ -1,68 +1,3 @@
{
"env": {
"ui": {
"presets": [
"@babel/react",
"@babel/flow",
["@babel/env",{
"targets": {
"browsers": [
"last 5 versions",
"FireFox >= 44",
"Safari >= 7",
"Explorer 11",
"last 4 Edge versions"
]
}
}]
],
"plugins": [
"react-hot-loader/babel",
"@babel/transform-runtime",
"@babel/proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/syntax-dynamic-import",
"emotion"
]
},
"test": {
"presets": [["@babel/env", {
"targets": {
"node": "6.10"
}
}], "@babel/flow",
"@babel/react"],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"babel-plugin-dynamic-import-node",
"emotion"
]
},
"registry": {
"presets": [
["@babel/env", {
"targets": {
"node": "6.10"
}
}], "@babel/flow"],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/proposal-class-properties"
]
},
"registry-docker": {
"presets": [
["@babel/env", {
"targets": {
"node": "10"
}
}],
"@babel/flow"],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/proposal-class-properties"
]
}
}
"presets": [["@verdaccio", {"flow": true}]]
}

@ -14,3 +14,4 @@ Dockerfile
*.scss
*.png
*.jpg
test/unit/partials/

@ -66,6 +66,42 @@ All notable changes to this project will be documented in this file. See [standa
* replaced lunr by lunr-mutable ([#915](https://github.com/verdaccio/verdaccio/issues/915)) ([1602840](https://github.com/verdaccio/verdaccio/commit/1602840))
* verdaccio update notification on CLI ([#988](https://github.com/verdaccio/verdaccio/issues/988)) ([#998](https://github.com/verdaccio/verdaccio/issues/998)) ([bc04703](https://github.com/verdaccio/verdaccio/commit/bc04703))
<a name="3.11.1"></a>
## [3.11.1](https://github.com/verdaccio/verdaccio/compare/v3.11.0...v3.11.1) (2019-01-31)
### Bug Fixes
* dont make change if `time` fields match ([#1167](https://github.com/verdaccio/verdaccio/issues/1167)) ([e62ef8d](https://github.com/verdaccio/verdaccio/commit/e62ef8d))
* dont packages that have no uplinks after reading ([#1204](https://github.com/verdaccio/verdaccio/issues/1204)) ([95686be](https://github.com/verdaccio/verdaccio/commit/95686be))
<a name="3.11.0"></a>
# [3.11.0](https://github.com/verdaccio/verdaccio/compare/v3.10.2...v3.11.0) (2019-01-27)
### Features
* introduce server keepAliveTimeout into config files ([a359055](https://github.com/verdaccio/verdaccio/commit/a359055))
<a name="3.10.2"></a>
## [3.10.2](https://github.com/verdaccio/verdaccio/compare/v3.10.1...v3.10.2) (2019-01-22)
### Bug Fixes
* add logic to catch clause ([#1183](https://github.com/verdaccio/verdaccio/issues/1183)) ([056d396](https://github.com/verdaccio/verdaccio/commit/056d396))
* adds _id to normalise metadata ([#1194](https://github.com/verdaccio/verdaccio/issues/1194)) ([e2fa581](https://github.com/verdaccio/verdaccio/commit/e2fa581))
* remove some unneeded checks ([#1182](https://github.com/verdaccio/verdaccio/issues/1182)) ([ab56d75](https://github.com/verdaccio/verdaccio/commit/ab56d75))
* remove unused object ([#1185](https://github.com/verdaccio/verdaccio/issues/1185)) ([e9b3907](https://github.com/verdaccio/verdaccio/commit/e9b3907))
* remove unused parameters from processBody method invocation ([#1184](https://github.com/verdaccio/verdaccio/issues/1184)) ([064f7cf](https://github.com/verdaccio/verdaccio/commit/064f7cf))
* remove use of comma separator ([#1186](https://github.com/verdaccio/verdaccio/issues/1186)) ([f20fefa](https://github.com/verdaccio/verdaccio/commit/f20fefa))
* remove useless assignment to local variable emailCopy ([#1181](https://github.com/verdaccio/verdaccio/issues/1181)) ([13b8347](https://github.com/verdaccio/verdaccio/commit/13b8347))
<a name="3.10.1"></a>
## [3.10.1](https://github.com/verdaccio/verdaccio/compare/v3.10.0...v3.10.1) (2018-12-20)

@ -61,6 +61,12 @@ packages:
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
# You can specify HTTP/1.1 server keep alive timeout in seconds for incomming connections.
# A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
# WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enought.
server:
keepAliveTimeout: 60
# To use `npm audit` uncomment the following section
middlewares:
audit:

@ -16,77 +16,49 @@
},
"dependencies": {
"@verdaccio/file-locking": "0.0.7",
"@verdaccio/local-storage": "1.2.0",
"@verdaccio/streams": "1.0.0",
"JSONStream": "1.3.4",
"@verdaccio/local-storage": "2.0.0-beta.1",
"@verdaccio/streams": "2.0.0-beta.0",
"JSONStream": "1.3.5",
"async": "2.6.1",
"body-parser": "1.18.3",
"bunyan": "1.8.12",
"chalk": "2.4.1",
"commander": "2.18.0",
"chalk": "2.4.2",
"commander": "2.19.0",
"compression": "1.7.3",
"cookies": "0.7.2",
"cors": "2.8.4",
"date-fns": "1.29.0",
"express": "4.16.3",
"cookies": "0.7.3",
"cors": "2.8.5",
"date-fns": "1.30.1",
"express": "4.16.4",
"global": "4.3.2",
"handlebars": "4.0.12",
"http-errors": "1.7.1",
"js-base64": "2.4.9",
"js-base64": "2.5.1",
"js-string-escape": "1.0.1",
"js-yaml": "3.12.0",
"jsonwebtoken": "8.3.0",
"js-yaml": "3.12.1",
"jsonwebtoken": "8.4.0",
"lockfile": "1.0.4",
"lodash": "4.17.11",
"lunr-mutable-indexes": "2.3.1",
"marked": "0.5.1",
"mime": "2.3.1",
"lunr-mutable-indexes": "2.3.2",
"marked": "0.6.0",
"mime": "2.4.0",
"minimatch": "3.0.4",
"mkdirp": "0.5.1",
"mv": "2.1.1",
"pkginfo": "0.4.1",
"request": "2.88.0",
"semver": "5.5.1",
"semver": "5.6.0",
"verdaccio-audit": "1.1.0",
"verdaccio-htpasswd": "1.0.1",
"verdaccio-htpasswd": "2.0.0-beta.0",
"verror": "1.10.0"
},
"devDependencies": {
"@babel/cli": "7.2.3",
"@babel/core": "7.2.2",
"@babel/node": "7.2.2",
"@babel/plugin-proposal-class-properties": "7.2.3",
"@babel/plugin-proposal-decorators": "7.2.3",
"@babel/plugin-proposal-export-namespace-from": "7.2.0",
"@babel/plugin-proposal-function-sent": "7.2.0",
"@babel/plugin-proposal-json-strings": "7.2.0",
"@babel/plugin-proposal-numeric-separator": "7.2.0",
"@babel/plugin-proposal-object-rest-spread": "7.2.0",
"@babel/plugin-proposal-throw-expressions": "7.2.0",
"@babel/plugin-syntax-dynamic-import": "7.2.0",
"@babel/plugin-syntax-import-meta": "7.2.0",
"@babel/plugin-transform-async-to-generator": "7.2.0",
"@babel/plugin-transform-classes": "7.2.2",
"@babel/plugin-transform-runtime": "7.2.0",
"@babel/polyfill": "7.2.3",
"@babel/preset-env": "7.2.3",
"@babel/preset-flow": "7.0.0",
"@babel/preset-react": "7.0.0",
"@babel/register": "7.0.0",
"@babel/runtime": "^7.2.0",
"@commitlint/cli": "7.2.1",
"@commitlint/config-conventional": "7.1.2",
"@material-ui/core": "3.9.0",
"@material-ui/icons": "3.0.2",
"@verdaccio/babel-preset": "0.0.4",
"@verdaccio/types": "4.1.4",
"autosuggest-highlight": "3.1.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"babel-jest": "23.6.0",
"babel-loader": "8.0.4",
"babel-plugin-dynamic-import-node": "2.2.0",
"babel-plugin-emotion": "9.2.10",
"babel-plugin-flow-runtime": "0.17.0",
"bundlesize": "0.17.0",
"codecov": "3.1.0",
"cross-env": "5.2.0",
@ -154,7 +126,7 @@
"typeface-roboto": "0.0.54",
"url-loader": "1.1.1",
"verdaccio-auth-memory": "0.0.4",
"verdaccio-memory": "1.0.3",
"verdaccio-memory": "2.0.0-beta.0",
"webpack": "4.20.2",
"webpack-bundle-analyzer": "3.0.2",
"webpack-cli": "3.1.1",

@ -12,6 +12,7 @@ import express from 'express';
import { combineBaseUrl, getWebProtocol } from '../../lib/utils';
import Search from '../../lib/search';
import { HEADERS, HTTP_STATUS, WEB_TITLE } from '../../lib/constants';
import { spliceURL } from '../../utils/string';
const { securityIframe } = require('../middleware');
/* eslint new-cap:off */
@ -52,9 +53,8 @@ module.exports = function(config, auth, storage) {
});
});
router.get('/', function(req, res) {
const installPath = _.get(config, 'url_prefix', '');
const base = combineBaseUrl(getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol), req.get('host'), installPath);
function renderHTML(req, res) {
const base = combineBaseUrl(getWebProtocol(req.get(HEADERS.FORWARDED_PROTO), req.protocol), req.get('host'), config.url_prefix);
const webPage = template
.replace(/ToReplaceByVerdaccio/g, base)
.replace(/ToReplaceByTitle/g, _.get(config, 'web.title') ? config.web.title : WEB_TITLE)
@ -64,6 +64,20 @@ module.exports = function(config, auth, storage) {
res.setHeader('Content-Type', 'text/html');
res.send(webPage);
}
router.get('/-/web/:pkg', function(req, res) {
renderHTML(req, res);
});
router.get('/-/verdaccio/logo', function(req, res) {
const installPath = _.get(config, 'url_prefix', '');
res.send(_.get(config, 'web.logo') || spliceURL(installPath, '/-/static/logo.png'));
});
router.get('/', function(req, res) {
renderHTML(req, res);
});
return router;

@ -48,7 +48,10 @@ function startVerdaccio(config: any, cliListen: string, configPath: string, pkgV
// http
webServer = http.createServer(app);
}
if (config.server && config.server.keepAliveTimeout) {
// $FlowFixMe library definition for node is not up to date (doesn't contain recent 8.0 changes)
webServer.keepAliveTimeout = config.server.keepAliveTimeout;
}
unlinkAddressPath(addr);
callback(webServer, addr, pkgName, pkgVersion);

@ -102,8 +102,10 @@ export const API_ERROR = {
CONFIG_BAD_FORMAT: 'config file must be an object',
BAD_USERNAME_PASSWORD: 'bad username/password, access denied',
NO_PACKAGE: 'no such package available',
PACKAGE_CANNOT_BE_ADDED: 'this package cannot be added',
BAD_DATA: 'bad data',
NOT_ALLOWED: 'not allowed to access package',
NOT_ALLOWED_PUBLISH: 'not allowed to publish package',
INTERNAL_SERVER_ERROR: 'internal server error',
UNKNOWN_ERROR: 'unknown error',
NOT_PACKAGE_UPLINK: 'package does not exist on uplink',

@ -169,7 +169,7 @@ class LocalStorage implements IStorage {
}
}
if ('time' in packageInfo) {
if ('time' in packageInfo && !_.isEqual(packageLocalJson.time, packageInfo.time)) {
packageLocalJson.time = packageInfo.time;
change = true;
}
@ -631,29 +631,6 @@ class LocalStorage implements IStorage {
});
}
_getCustomPackageLocalStorages() {
const storages = {};
// add custom storage if exist
if (this.config.storage) {
storages[this.config.storage] = true;
}
const { packages } = this.config;
if (packages) {
const listPackagesConf = Object.keys(packages);
listPackagesConf.map(pkg => {
if (packages[pkg].storage) {
storages[packages[pkg].storage] = true;
}
});
}
return storages;
}
/**
* Walks through each package and calls `on_package` on them.
* @param {*} onPackage

@ -45,6 +45,10 @@ export function normalizePackage(pkg: Package) {
pkg._rev = STORAGE.DEFAULT_REVISION;
}
if (_.isString(pkg._id) === false) {
pkg._id = pkg.name;
}
// normalize dist-tags
normalizeDistTags(pkg);
@ -93,7 +97,7 @@ export function normalizeContributors(contributors: Array<Author>): Array<Author
return contributors;
}
export const WHITELIST = ['_rev', 'name', 'versions', 'dist-tags', 'readme', 'time'];
export const WHITELIST = ['_rev', 'name', 'versions', 'dist-tags', 'readme', 'time', '_id'];
export function cleanUpLinksRef(keepUpLinkData: boolean, result: Package): Package {
const propertyToKeep = [...WHITELIST];

@ -497,6 +497,10 @@ class Storage implements IStorageHandler {
return callback(ErrorCode.getNotFound(API_ERROR.NO_PACKAGE), null, upLinksErrors);
}
if (upLinks.length === 0) {
return callback(null, packageInfo);
}
self.localStorage.updateVersions(name, packageInfo, function(err, packageJsonLocal: Package) {
if (err) {
return callback(err);

@ -4,16 +4,16 @@ import isNil from 'lodash/isNil';
import storage from './utils/storage';
import { makeLogin, isTokenExpire } from './utils/login';
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 RouterApp from './router';
import API from './utils/api';
import './styles/typeface-roboto.scss';
import './styles/main.scss';
import 'normalize.css';
import Footer from './components/Footer';
export const AppContext = React.createContext();
@ -67,7 +67,8 @@ export default class App extends Component {
isLoading: false,
});
} catch (error) {
this.handleShowAlertDialog({
// FIXME: add dialog
console.error({
title: 'Warning',
message: `Unable to load package list: ${error.message}`,
});
@ -140,15 +141,14 @@ export default class App extends Component {
}
render() {
const { isLoading, isUserLoggedIn, packages } = this.state;
const { isLoading, isUserLoggedIn, packages, logoUrl, user, scope } = this.state;
return (
<Container isLoading={isLoading}>
{isLoading ? (
<Loading />
) : (
<Fragment>
<AppContextProvider value={{isUserLoggedIn, packages}}>
{this.renderHeader()}
<AppContextProvider value={{isUserLoggedIn, packages, logoUrl, user, scope}}>
{this.renderContent()}
</AppContextProvider>
</Fragment>
@ -175,7 +175,11 @@ export default class App extends Component {
return (
<Fragment>
<Content>
<Route></Route>
<RouterApp
onLogout={this.handleLogout}
onToggleLoginModal={this.handleToggleLoginModal}>
{this.renderHeader()}
</RouterApp>
</Content>
<Footer />
</Fragment>

@ -34,7 +34,7 @@ class DepDetail extends Component<any, any> {
const { onLoading, history } = this.props;
onLoading();
history.push(`/version/${name}`);
history.push(`/-/web/version/${name}`);
};
}

@ -5,6 +5,7 @@
import React, { Component } from 'react';
import type { Node } from 'react';
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button/index';
import IconButton from '@material-ui/core/IconButton/index';
@ -17,7 +18,7 @@ import AccountCircle from '@material-ui/icons/AccountCircle';
import { default as IconSearch } from '@material-ui/icons/Search';
import { getRegistryURL } from '../../utils/url';
import Link from '../Link';
import ExternalLink from '../Link';
import Logo from '../Logo';
import RegistryInfoDialog from '../RegistryInfoDialog';
import Label from '../Label';
@ -138,7 +139,7 @@ class Header extends Component<IProps, IState> {
switch (type) {
case 'help':
content = (
<IconButton blank={true} color={'inherit'} component={Link} to={'https://verdaccio.org/docs/en/installation'}>
<IconButton blank={true} color={'inherit'} component={ExternalLink} to={'https://verdaccio.org/docs/en/installation'}>
<Help />
</IconButton>
);

@ -9,7 +9,9 @@ export const Content = styled.div`
&& {
background-color: #ffffff;
flex: 1;
display: flex;
position: relative;
flex-direction: column;
}
`;

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495.2 495.2"><path fill="#d38c0d" d="M325.6 224.4L495.2 126 325.6 28.4l-168.8 99.2z"/><g fill="#efbb67"><path d="M170.4 224.4l168-97.6-168-98.4L0 126.8z"/><path d="M416 368.4l-168 98.4-168-98.4v-196L248 74l168 98.4z"/></g><path fill="#d38c0d" d="M248 74l168 98.4v196l-168 98.4"/><path fill="#efbb67" d="M326.4 314.8L495.2 218l-169.6-98.4L156 218z"/><path fill="#d38c0d" d="M170.4 316.4l168.8-99.2-168.8-97.6L0 218z"/><path fill="#704a0e" d="M248.8 270.8L416 172.4 248.8 74 78.4 172.4z"/><path fill="#513307" d="M248.8 270.8L416 172.4 248.8 74"/><path fill="#2d1c05" d="M248.8 270.8l36-21.6-36-20.8-36 20.8z"/><g fill="#0dd396"><path d="M368 379.6l40-23.2v-12.8l-40 23.2zM368 356.4l40-23.2v-12.8l-40 23.2zM368 333.2l40-23.2v-13.6l-40 24z"/></g></svg>

After

Width:  |  Height:  |  Size: 802 B

@ -1,21 +1,53 @@
/**
* @prettier
* @flow
*/
import React from 'react';
import { Wrapper } from './styles';
import { IProps } from './types';
import { withRouter } from 'react-router-dom';
import withWidth, { isWidthUp } from '@material-ui/core/withWidth/index';
import ListItem from '@material-ui/core/ListItem/index';
import Typography from '@material-ui/core/Typography/index';
import { Wrapper, Inner, EmptyPackage, Heading, Card, List } from './styles';
import PackageImg from './img/package.svg';
const NotFound = ({ pkg }: IProps) => (
<Wrapper>
<h1>
{'Error 404 - '}
{pkg}
</h1>
<hr />
<p>{'Oops, The package you are trying to access does not exist.'}</p>
</Wrapper>
);
// eslint-disable-next-line react/prop-types
const NotFound = ({ history, width }) => {
const handleGoTo = to => () => {
history.push(to);
};
export default NotFound;
const handleGoBack = () => () => {
history.goBack();
};
const renderList = () => (
<List>
<ListItem button={true} divider={true} onClick={handleGoTo('/')}>
{'Home'}
</ListItem>
<ListItem button={true} divider={true} onClick={handleGoBack()}>
{'Back'}
</ListItem>
</List>
);
const renderSubTitle = () => (
<Typography variant={'subtitle1'}>
<div>{"The page you're looking for doesn't exist."}</div>
<div>{'Perhaps these links will help find what you are looking for:'}</div>
</Typography>
);
return (
<Wrapper>
<Inner>
<EmptyPackage alt={'404 - Page not found'} src={PackageImg} />
<Heading variant={isWidthUp('sm', width) ? 'h2' : 'h4'}>{"Sorry, we couldn't find it..."}</Heading>
{renderSubTitle()}
<Card>{renderList()}</Card>
</Inner>
</Wrapper>
);
};
export default withRouter(withWidth()(NotFound));

@ -4,16 +4,43 @@
*/
import styled from 'react-emotion';
import Typography from '@material-ui/core/Typography/index';
import { default as MuiList } from '@material-ui/core/List/index';
import { default as MuiCard } from '@material-ui/core/Card/index';
import { fontSize, lineHeight } from '../../utils/styles/sizes';
export const Wrapper = styled('div')`
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
flex: 1;
padding: 16px;
`;
export const Wrapper = styled.div`
export const Inner = styled('div')`
max-width: 650px;
display: flex;
flex-direction: column;
`;
export const EmptyPackage = styled('img')`
width: 150px;
margin: 0 auto;
`;
export const Heading = styled(Typography)`
&& {
font-size: ${fontSize.md};
line-height: ${lineHeight.xl};
border: none;
outline: none;
flex-direction: column;
align-items: center;
color: #4b5e40;
}
`;
export const List = styled(MuiList)`
&& {
padding: 0;
color: #4b5e40;
}
`;
export const Card = styled(MuiCard)`
margin-top: 24px;
`;

@ -1,8 +0,0 @@
/**
* @prettier
* @flow
*/
export interface IProps {
pkg: string;
}

@ -80,7 +80,7 @@ const Package = ({ name: label, version, time, author: { name, avatar }, descrip
);
return (
<WrapperLink className={'package'} to={`version/${label}`}>
<WrapperLink className={'package'} to={`/-/web/version/${label}`}>
<Header>
{renderMainInfo()}
<Overview>

@ -5,6 +5,7 @@
import React, { Component } from 'react';
import type { Node } from 'react';
import { withRouter } from 'react-router-dom';
import { default as IconSearch } from '@material-ui/icons/Search';
import InputAdornment from '@material-ui/core/InputAdornment';
@ -13,7 +14,6 @@ import debounce from 'lodash/debounce';
import API from '../../utils/api';
import AutoComplete from '../AutoComplete';
import colors from '../../utils/styles/colors';
import { getDetailPageURL } from '../../utils/url';
import { IProps, IState } from './types';
import type { cancelAllSearchRequests, handlePackagesClearRequested, handleSearch, handleClickSearch, handleFetchPackages, onBlur } from './types';
@ -24,7 +24,7 @@ const CONSTANTS = {
ABORT_ERROR: 'AbortError',
};
class Search extends Component<IProps, IState> {
export class Search extends Component<IProps, IState> {
requestList: Array<any>;
constructor(props: IProps) {
@ -92,13 +92,15 @@ class Search extends Component<IProps, IState> {
* When an user select any package by clicking or pressing return key.
*/
handleClickSearch: handleClickSearch = (event, { suggestionValue, method }) => {
const { history } = this.props;
// stops event bubbling
event.stopPropagation();
switch (method) {
case 'click':
case 'enter':
this.setState({ search: '' });
window.location.assign(getDetailPageURL(suggestionValue));
// $FlowFixMe
history.push(`/-/web/version/${suggestionValue}`);
break;
}
};
@ -181,4 +183,4 @@ class Search extends Component<IProps, IState> {
}
}
export default Search;
export default withRouter(Search);

@ -3,7 +3,9 @@
* @flow
*/
export interface IProps {}
export interface IProps {
history?: any;
}
export interface IState {
search: string;

@ -2,9 +2,9 @@
* @prettier
*/
import { createHashHistory } from 'history';
import { createBrowserHistory } from 'history';
const history = createHashHistory();
const history = createBrowserHistory();
// Listen for changes to the current location.
history.listen((location, action) => {

@ -9,6 +9,7 @@ import Loading from '../../components/Loading';
import DetailContainer from '../../components/DetailContainer';
import DetailSidebar from '../../components/DetailSidebar';
import { callDetailPage } from '../../utils/calls';
import { getRouterPackageName } from '../../utils/package';
export const DetailContext = React.createContext();
@ -21,7 +22,7 @@ class VersionPage extends Component<any, any> {
this.state = {
readMe: '',
packageName: this.getPackageName(props),
packageName: getRouterPackageName(props.match),
packageMeta: null,
isLoading: true,
notFound: false,
@ -32,13 +33,6 @@ class VersionPage extends Component<any, any> {
await this.loadPackageInfo();
}
getPackageName(props: any = this.props): string {
const { match } = props;
const packageName = match.params.package;
return packageName;
}
/* eslint no-unused-vars: 0 */
async componentDidUpdate(nextProps: any, prevState: any) {
const { packageName } = this.state;
@ -56,7 +50,7 @@ class VersionPage extends Component<any, any> {
static getDerivedStateFromProps(nextProps: any, prevState: any) {
const { match } = nextProps;
const packageName = match.params.package;
const packageName = getRouterPackageName(match);
if (packageName !== prevState.packageName) {
try {

@ -3,39 +3,52 @@
* @flow
*/
import React, { Component } from 'react';
/* eslint react/jsx-max-depth:0 */
import React, { Component, Fragment } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { AppContextConsumer } from './app';
import { asyncComponent } from './utils/asyncComponent';
import history from './history';
import Header from './components/Header';
const NotFound = asyncComponent(() => import('./components/NotFound'));
const DetailPackage = asyncComponent(() => import('./pages/detail'));
const VersionPackage = asyncComponent(() => import('./pages/version'));
const HomePage = asyncComponent(() => import('./pages/home'));
interface IProps {
isUserLoggedIn: boolean;
packages: Array<Object>;
}
interface IState {}
class RouterApp extends Component<IProps, IState> {
class RouterApp extends Component<any, any> {
render() {
return (
<Router history={history}>
<Switch>
<Route exact={true} path={'/'} render={this.renderHomePage} />
<Route exact={true} path={'/detail/@:scope/:package'} render={this.renderDetailPage} />
<Route exact={true} path={'/detail/:package'} render={this.renderDetailPage} />
<Route exact={true} path={'/version/@:scope/:package'} render={this.renderVersionPage} />
<Route exact={true} path={'/version/:package'} render={this.renderVersionPage} />
</Switch>
<Fragment>
{this.renderHeader()}
<Switch>
<Route exact={true} path={'/'} render={this.renderHomePage} />
<Route exact={true} path={'/-/web/detail/@:scope/:package'} render={this.renderDetailPage} />
<Route exact={true} path={'/-/web/detail/:package'} render={this.renderDetailPage} />
<Route exact={true} path={'/-/web/version/@:scope/:package'} render={this.renderVersionPage} />
<Route exact={true} path={'/-/web/version/:package'} render={this.renderVersionPage} />
<Route component={NotFound} />
</Switch>
</Fragment>
</Router>
);
}
renderHeader = () => {
const { onLogout, onToggleLoginModal } = this.props;
return (
<AppContextConsumer>
{function renderConsumerVersionPage({ logoUrl, scope, user }) {
return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user.username} />;
}}
</AppContextConsumer>
);
};
renderHomePage = () => {
return (
<AppContextConsumer>

@ -106,3 +106,13 @@ export function formatDate(lastUpdate) {
export function formatDateDistance(lastUpdate) {
return distanceInWordsToNow(new Date(lastUpdate));
}
export function getRouterPackageName(match) {
const packageName = match.params.package;
const scope = match.params.scope;
if (scope) {
return `@${scope}/${packageName}`;
}
return packageName;
}

@ -8,5 +8,5 @@ export function getRegistryURL() {
* @param {string} packageName
*/
export function getDetailPageURL(packageName) {
return `${getRegistryURL()}/#/detail/${packageName}`;
return `${getRegistryURL()}/-/web/version/${packageName}`;
}

@ -5,7 +5,7 @@
const scopedPackageMetadata = require('./partials/pkg-scoped');
const protectedPackageMetadata = require('./partials/pkg-protected');
describe('/ (Verdaccio Page)', () => {
describe.skip('/ (Verdaccio Page)', () => {
let page;
// this might be increased based on the delays included in all test
jest.setTimeout(200000);
@ -139,7 +139,7 @@ describe('/ (Verdaccio Page)', () => {
});
test('should contains last sync information', async () => {
const versionList = await page.$$('.sidebar-info .last-sync-item');
const versionList = await page.$$('.sidebar-info .detail-info');
expect(versionList).toHaveLength(3);
});

@ -13,17 +13,17 @@ export default function(server) {
* Check whether the user is allowed to fetch packages
* @param auth {object} disable auth
* @param pkg {string} package name
* @param ok {boolean}
* @param status {boolean}
*/
function checkAccess(auth, pkg, ok) {
function checkAccess(auth, pkg, status) {
test(
`${(ok ? 'allows' : 'forbids')} access ${auth} to ${pkg}`, () => {
`${(status ? 'allows' : 'forbids')} access ${auth} to ${pkg}`, () => {
server.authstr = auth ? buildAccesToken(auth) : undefined;
const req = server.getPackage(pkg);
if (ok) {
if (status === HTTP_STATUS.NOT_FOUND) {
return req.status(HTTP_STATUS.NOT_FOUND).body_error(API_ERROR.NO_PACKAGE);
} else {
} else if (status === HTTP_STATUS.FORBIDDEN) {
return req.status(HTTP_STATUS.FORBIDDEN).body_error(API_ERROR.NOT_ALLOWED);
}
}
@ -34,16 +34,20 @@ export default function(server) {
* Check whether the user is allowed to publish packages
* @param auth {object} disable auth
* @param pkg {string} package name
* @param ok {boolean}
* @param status {boolean}
*/
function checkPublish(auth, pkg, ok) {
test(`${(ok ? 'allows' : 'forbids')} publish ${auth} to ${pkg}`, () => {
function checkPublish(auth, pkg, status) {
test(`${(status ? 'allows' : 'forbids')} publish ${auth} to ${pkg}`, () => {
server.authstr = auth ? buildAccesToken(auth) : undefined;
const req = server.putPackage(pkg, require('../fixtures/package')(pkg));
if (ok) {
return req.status(HTTP_STATUS.NOT_FOUND).body_error(/this package cannot be added/);
} else {
return req.status(HTTP_STATUS.FORBIDDEN).body_error(/not allowed to publish package/);
if (status === HTTP_STATUS.NOT_FOUND) {
return req.status(HTTP_STATUS.NOT_FOUND).body_error(API_ERROR.PACKAGE_CANNOT_BE_ADDED);
} else if (status === HTTP_STATUS.FORBIDDEN) {
return req.status(HTTP_STATUS.FORBIDDEN).body_error(API_ERROR.NOT_ALLOWED_PUBLISH);
} else if (status === HTTP_STATUS.CREATED) {
return req.status(HTTP_STATUS.CREATED);
} else if (status === HTTP_STATUS.CONFLICT) {
return req.status(HTTP_STATUS.CONFLICT);
}
});
}
@ -60,39 +64,39 @@ export default function(server) {
const testOnlyAuth = 'test-only-auth';
describe('all are allowed to access', () => {
checkAccess(validCredentials, testAccessOnly, true);
checkAccess(undefined, testAccessOnly, true);
checkAccess(badCredentials, testAccessOnly, true);
checkPublish(validCredentials, testAccessOnly, false);
checkPublish(undefined, testAccessOnly, false);
checkPublish(badCredentials, testAccessOnly, false);
checkAccess(validCredentials, testAccessOnly, HTTP_STATUS.NOT_FOUND);
checkAccess(undefined, testAccessOnly, HTTP_STATUS.NOT_FOUND);
checkAccess(badCredentials, testAccessOnly, HTTP_STATUS.NOT_FOUND);
checkPublish(validCredentials, testAccessOnly, HTTP_STATUS.FORBIDDEN);
checkPublish(undefined, testAccessOnly, HTTP_STATUS.FORBIDDEN);
checkPublish(badCredentials, testAccessOnly, HTTP_STATUS.FORBIDDEN);
});
describe('all are allowed to publish', () => {
checkAccess(validCredentials, testPublishOnly, false);
checkAccess(undefined, testPublishOnly, false);
checkAccess(badCredentials, testPublishOnly, false);
checkPublish(validCredentials, testPublishOnly, true);
checkPublish(undefined, testPublishOnly, true);
checkPublish(badCredentials, testPublishOnly, true);
checkAccess(validCredentials, testPublishOnly, HTTP_STATUS.FORBIDDEN);
checkAccess(undefined, testPublishOnly, HTTP_STATUS.FORBIDDEN);
checkAccess(badCredentials, testPublishOnly, HTTP_STATUS.FORBIDDEN);
checkPublish(validCredentials, testPublishOnly, HTTP_STATUS.CREATED);
checkPublish(undefined, testPublishOnly, HTTP_STATUS.CONFLICT);
checkPublish(badCredentials, testPublishOnly, HTTP_STATUS.CONFLICT);
});
describe('only user "test" is allowed to publish and access', () => {
checkAccess(validCredentials, testOnlyTest, true);
checkAccess(undefined, testOnlyTest, false);
checkAccess(badCredentials, testOnlyTest, false);
checkPublish(validCredentials, testOnlyTest, true);
checkPublish(undefined, testOnlyTest, false);
checkPublish(badCredentials, testOnlyTest, false);
checkAccess(validCredentials, testOnlyTest, HTTP_STATUS.NOT_FOUND);
checkAccess(undefined, testOnlyTest, HTTP_STATUS.FORBIDDEN);
checkAccess(badCredentials, testOnlyTest, HTTP_STATUS.FORBIDDEN);
checkPublish(validCredentials, testOnlyTest, HTTP_STATUS.CREATED);
checkPublish(undefined, testOnlyTest, HTTP_STATUS.FORBIDDEN);
checkPublish(badCredentials, testOnlyTest, HTTP_STATUS.FORBIDDEN);
});
describe('only authenticated users are allowed', () => {
checkAccess(validCredentials, testOnlyAuth, true);
checkAccess(undefined, testOnlyAuth, false);
checkAccess(badCredentials, testOnlyAuth, false);
checkPublish(validCredentials, testOnlyAuth, true);
checkPublish(undefined, testOnlyAuth, false);
checkPublish(badCredentials, testOnlyAuth, false);
checkAccess(validCredentials, testOnlyAuth, HTTP_STATUS.NOT_FOUND);
checkAccess(undefined, testOnlyAuth, HTTP_STATUS.FORBIDDEN);
checkAccess(badCredentials, testOnlyAuth, HTTP_STATUS.FORBIDDEN);
checkPublish(validCredentials, testOnlyAuth, HTTP_STATUS.CREATED);
checkPublish(undefined, testOnlyAuth, HTTP_STATUS.FORBIDDEN);
checkPublish(badCredentials, testOnlyAuth, HTTP_STATUS.FORBIDDEN);
});
});
}

@ -13,6 +13,7 @@ export default function (server, server2) {
const PKG_NAME = 'test-nullstorage2';
const PKG_VERSION = '0.0.1';
// const TARBALL = `${PKG_NAME}-file.name`;
describe('should test a scenario when tarball is being fetch from uplink', () => {
@ -38,8 +39,7 @@ export default function (server, server2) {
describe(`should succesfully publish ${PKG_NAME} package on server2`, () => {
beforeAll(function() {
return server2.putTarball(PKG_NAME, TARBALL, getBinary())
.status(HTTP_STATUS.CREATED).body_ok(/.*/);
return server2.putTarball(PKG_NAME, TARBALL, getBinary()).status(HTTP_STATUS.CREATED).body_ok(/.*/);
});
beforeAll(function() {

@ -19,6 +19,7 @@ uplinks:
timeout: 100ms
server2:
url: http://localhost:55552/
maxage: 0
server3:
url: http://localhost:55553/
baduplink:
@ -88,7 +89,7 @@ packages:
access: $all
publish: $all
proxy: server2
storage: false
storage: sub_storage
'baduplink':
access: $all
@ -98,22 +99,22 @@ packages:
'test-access-only':
access: $all
publish: nobody
storage: false
storage: sub_storage
'test-publish-only':
access: nobody
publish: $all
storage: false
storage: sub_storage
'test-only-test':
access: test
publish: test
storage: false
storage: sub_storage
'test-only-auth':
access: $authenticated
publish: $authenticated
storage: false
storage: sub_storage
'*':
access: test $anonymous

@ -25,6 +25,13 @@ describe('LocalStorage', () => {
const pkgNameScoped = `@scope/${pkgName}-scope`;
const tarballName: string = `${pkgName}-add-tarball-1.0.4.tgz`;
const tarballName2: string = `${pkgName}-add-tarball-1.0.5.tgz`;
const getStorage = (LocalStorageClass = LocalStorage) => {
const config: Config = new AppConfig(configExample);
config.self_path = path.join('../partials/store');
return new LocalStorageClass(config, Logger.logger);
}
const getPackageMetadataFromStore = (pkgName: string) => {
return new Promise((resolve) => {
storage.getPackageMetadata(pkgName, (err, data ) => {
@ -76,10 +83,7 @@ describe('LocalStorage', () => {
};
beforeAll(() => {
const config: Config = new AppConfig(configExample);
config.self_path = path.join('../partials/store');
storage = new LocalStorage(config, Logger.logger);
storage = getStorage();
});
test('should be defined', () => {
@ -249,16 +253,27 @@ describe('LocalStorage', () => {
});
describe('LocalStorage::updateVersions', () => {
test('should update versions from external source', async (done) => {
const metadata = JSON.parse(readMetadata('metadata-update-versions-tags'));
const pkgName = 'add-update-versions-test-1';
const version = '1.0.2';
await addPackageToStore(pkgName, generatePackageTemplate(pkgName));
await addNewVersion(pkgName, '1.0.1');
await addNewVersion(pkgName, version);
const metadata = JSON.parse(readMetadata('metadata-update-versions-tags'));
const pkgName = 'add-update-versions-test-1';
const version = '1.0.2';
let _storage;
beforeEach(done => {
class MockLocalStorage extends LocalStorage {}
// $FlowFixMe
MockLocalStorage.prototype._writePackage = jest.fn(LocalStorage.prototype._writePackage)
_storage = getStorage(MockLocalStorage);
rimRaf(path.join(configExample.storage, pkgName), async () => {
await addPackageToStore(pkgName, generatePackageTemplate(pkgName));
await addNewVersion(pkgName, '1.0.1');
await addNewVersion(pkgName, version);
done();
})
})
storage.updateVersions(pkgName, metadata, (err, data) => {
test('should update versions from external source', async (done) => {
_storage.updateVersions(pkgName, metadata, (err, data) => {
expect(err).toBeNull();
expect(_storage._writePackage).toHaveBeenCalledTimes(1);
expect(data.versions['1.0.1']).toBeDefined();
expect(data.versions[version]).toBeDefined();
expect(data.versions['1.0.4']).toBeDefined();
@ -273,6 +288,17 @@ describe('LocalStorage', () => {
done();
});
});
test('should not update if the metadata match', done => {
_storage.updateVersions(pkgName, metadata, e => {
expect(e).toBeNull()
_storage.updateVersions(pkgName, metadata, err => {
expect(err).toBeNull()
expect(_storage._writePackage).toHaveBeenCalledTimes(1);
done()
})
})
})
});
describe('LocalStorage::changePackage', () => {

@ -2,6 +2,8 @@
import _ from 'lodash';
import path from 'path';
import fs from 'fs';
import rimraf from 'rimraf';
// $FlowFixMe
import configExample from '../partials/config/index';
import AppConfig from '../../../src/lib/config';
@ -16,12 +18,12 @@ import {DOMAIN_SERVERS} from '../../functional/config.functional';
setup(configExample.logs);
const storagePath = path.join(__dirname, '../partials/store/test-storage-store.spec');
const mockServerPort: number = 55548;
const generateStorage = async function(port = mockServerPort, configDefault = configExample) {
const storageConfig = _.clone(configDefault);
const storage = path.join(__dirname, '../partials/store/test-storage-store.spec');
storageConfig.self_path = __dirname;
storageConfig.storage = storage;
storageConfig.storage = storagePath;
storageConfig.uplinks = {
npmjs: {
url: `http://${DOMAIN_SERVERS}:${port}`
@ -37,8 +39,11 @@ const generateStorage = async function(port = mockServerPort, configDefault = co
describe('StorageTest', () => {
let mockRegistry;
beforeAll(async () => {
mockRegistry = await mockServer(mockServerPort).init();
beforeAll(done => {
rimraf(storagePath, async () => {
mockRegistry = await mockServer(mockServerPort).init();
done()
})
});
afterAll(function(done) {
@ -90,5 +95,21 @@ describe('StorageTest', () => {
done();
});
});
test('should not touch if the package exists and has no uplinks', async (done) => {
const storage: IStorageHandler = await generateStorage();
const metadataSource = path.join(__dirname, '../partials/metadata');
const metadataPath = path.join(storagePath, 'npm_test/package.json');
fs.mkdirSync(path.join(storagePath, 'npm_test'));
fs.writeFileSync(metadataPath, fs.readFileSync(metadataSource));
const metadata = JSON.parse(fs.readFileSync(metadataPath).toString());
// $FlowFixMe
storage.localStorage.updateVersions = jest.fn(storage.localStorage.updateVersions);
storage._syncUplinksMetadata('npm_test', metadata, {}, (err) => {
expect(err).toBeNull();
expect(storage.localStorage.updateVersions).not.toHaveBeenCalled();
done();
});
});
});
});

@ -82,7 +82,13 @@
"beta": "1.0.2",
"next": "1.0.4"
},
"time": {},
"time": {
"modified": "2019-01-29T03:20:04.000Z",
"created": "2019-01-29T03:20:00.000Z",
"1.0.1": "2019-01-29T03:20:01.000Z",
"1.0.2": "2019-01-29T03:20:02.000Z",
"1.0.4": "2019-01-29T03:20:04.000Z"
},
"_distfiles": {},
"_attachments": {},
"_uplinks": {},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-1jzwe6w e1ctrp128\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1ctrp123\\"><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 e1ctrp122\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span><span class=\\"MuiTouchRipple-root-85\\"></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span><span class=\\"MuiTouchRipple-root-85\\"></span></button><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-account\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z\\"></path><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path></svg></span><span class=\\"MuiTouchRipple-root-85\\"></span></button></div></div></header></div>"`;
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-1jzwe6w e1ctrp128\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1ctrp123\\"><a style=\\"margin-right:1em\\" href=\\"/\\"><div class=\\"css-12nq0oo e18wxr160\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1ctrp122\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span></button><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-account\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z\\"></path><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path></svg></span></button></div></div></header></div>"`;
exports[`<Header /> component with logged out state should load the component in logged out state 1`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-1jzwe6w e1ctrp128\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1ctrp123\\"><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 e1ctrp122\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span><span class=\\"MuiTouchRipple-root-85\\"></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span><span class=\\"MuiTouchRipple-root-85\\"></span></button><button class=\\"MuiButtonBase-root-55 MuiButton-root-113 MuiButton-text-115 MuiButton-flat-118 MuiButton-colorInherit-134\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-login\\"><span class=\\"MuiButton-label-114\\">Login</span><span class=\\"MuiTouchRipple-root-85\\"></span></button></div></div></header></div>"`;
exports[`<Header /> component with logged out state should load the component in logged out state 1`] = `"<div><header class=\\"MuiPaper-root-10 MuiPaper-elevation4-16 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-8 css-1jzwe6w e1ctrp128\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1pwdmmq e1ctrp120\\"><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-1vacr9s e1ctrp123\\"><a style=\\"margin-right:1em\\" href=\\"/\\"><div class=\\"css-12nq0oo e18wxr160\\"></div></a></div><div class=\\"MuiToolbar-root-37 MuiToolbar-regular-39 MuiToolbar-gutters-38 css-m61s5i e1ctrp122\\"><a href=\\"https://verdaccio.org/docs/en/installation\\" target=\\"_blank\\" class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" role=\\"button\\" title=\\"Documentation\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z\\"></path></svg></span></a><button class=\\"MuiButtonBase-root-55 MuiIconButton-root-49 MuiIconButton-colorInherit-50\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-registryInfo\\" title=\\"Registry Information\\"><span class=\\"MuiIconButton-label-54\\"><svg class=\\"MuiSvgIcon-root-58\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\\"></path></svg></span></button><button class=\\"MuiButtonBase-root-55 MuiButton-root-85 MuiButton-text-87 MuiButton-flat-90 MuiButton-colorInherit-106\\" tabindex=\\"0\\" type=\\"button\\" id=\\"header--button-login\\"><span class=\\"MuiButton-label-86\\">Login</span></button></div></div></header></div>"`;

@ -1,5 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NotFound /> component should load the component in default state 1`] = `"<div class=\\"css-24p7uw e1gvymku0\\"><h1>Error 404 - test</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=\\"css-24p7uw e1gvymku0\\"><h1>Error 404 - verdaccio</h1><hr/><p>Oops, The package you are trying to access does not exist.</p></div>"`;
exports[`<NotFound /> component should load the component in default state 1`] = `
ShallowWrapper {
Symbol(enzyme.__root__): ShallowWrapper {
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <BrowserRouter>
<withRouter(WithTheme(WithWidth(NotFound))) />
</BrowserRouter>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateError": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <withRouter(WithTheme(WithWidth(NotFound))) />,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"children": <withRouter(WithTheme(WithWidth(NotFound))) />,
"history": Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {},
"ref": null,
"rendered": null,
"type": [Function],
},
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
"lifecycles": Object {
"componentDidUpdate": Object {
"onSetState": true,
},
"getDerivedStateFromProps": true,
"getSnapshotBeforeUpdate": true,
"setState": Object {
"skipsComponentDidUpdateOnNullish": true,
},
},
},
},
"attachTo": undefined,
"hydrateIn": undefined,
},
},
Symbol(enzyme.__unrendered__): null,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateError": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {},
"ref": null,
"rendered": null,
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "function",
"props": Object {},
"ref": null,
"rendered": null,
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
"lifecycles": Object {
"componentDidUpdate": Object {
"onSetState": true,
},
"getDerivedStateFromProps": true,
"getSnapshotBeforeUpdate": true,
"setState": Object {
"skipsComponentDidUpdateOnNullish": true,
},
},
},
},
"attachTo": undefined,
"hydrateIn": undefined,
},
}
`;

@ -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\\"><a class=\\"package css-zrrjf6 e11fsc2k16\\" href=\\"version/verdaccio\\"><div class=\\"css-esn5nr e11fsc2k0\\"><span class=\\"css-1e6w198 e11fsc2k2\\"><span class=\\"css-bxt2bt e11fsc2k1\\">verdaccio</span><span class=\\"css-17xn9wj e11fsc2k5\\">v1.0.0</span></span><span class=\\"css-1dq57rh e11fsc2k4\\"><span class=\\"css-19brcdm e11fsc2k3\\"><svg class=\\"e11fsc2k6 css-y8pkl4 ej4jd2o0\\"><title>Time</title><use xlink:href=\\"[object Object]#time\\"></use></svg><span class=\\"css-1qw5qv3 e11fsc2k7\\">Published on 21.07.2018, 22:11:12 •</span>6 months ago</span></span></div><div class=\\"css-tywa7u e11fsc2k9\\"><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-15496ft e11fsc2k12\\"><div class=\\"MuiAvatar-root-128 MuiAvatar-colorDefault-129 css-1to0t9u e11fsc2k13\\">S</div><span class=\\"css-1xj37ub e11fsc2k11\\"><div class=\\"e11fsc2k10 css-1xe0n7g e1pneb170\\">Sam</div></span></div></div><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-1b14dls e11fsc2k14\\">Private NPM repository</div></div></div></a><a class=\\"package css-zrrjf6 e11fsc2k16\\" href=\\"version/abc\\"><div class=\\"css-esn5nr e11fsc2k0\\"><span class=\\"css-1e6w198 e11fsc2k2\\"><span class=\\"css-bxt2bt e11fsc2k1\\">abc</span><span class=\\"css-17xn9wj e11fsc2k5\\">v1.0.1</span></span><span class=\\"css-1dq57rh e11fsc2k4\\"><span class=\\"css-19brcdm e11fsc2k3\\"><svg class=\\"e11fsc2k6 css-y8pkl4 ej4jd2o0\\"><title>Time</title><use xlink:href=\\"[object Object]#time\\"></use></svg><span class=\\"css-1qw5qv3 e11fsc2k7\\">Published on 21.07.2018, 22:11:12 •</span>6 months ago</span></span></div><div class=\\"css-tywa7u e11fsc2k9\\"><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-15496ft e11fsc2k12\\"><div class=\\"MuiAvatar-root-128 MuiAvatar-colorDefault-129 css-1to0t9u e11fsc2k13\\">R</div><span class=\\"css-1xj37ub e11fsc2k11\\"><div class=\\"e11fsc2k10 css-1xe0n7g e1pneb170\\">Rose</div></span></div></div><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-1b14dls e11fsc2k14\\">abc description</div></div></div></a><a class=\\"package css-zrrjf6 e11fsc2k16\\" href=\\"version/xyz\\"><div class=\\"css-esn5nr e11fsc2k0\\"><span class=\\"css-1e6w198 e11fsc2k2\\"><span class=\\"css-bxt2bt e11fsc2k1\\">xyz</span><span class=\\"css-17xn9wj e11fsc2k5\\">v1.1.0</span></span><span class=\\"css-1dq57rh e11fsc2k4\\"><span class=\\"css-19brcdm e11fsc2k3\\"><svg class=\\"e11fsc2k6 css-y8pkl4 ej4jd2o0\\"><title>Time</title><use xlink:href=\\"[object Object]#time\\"></use></svg><span class=\\"css-1qw5qv3 e11fsc2k7\\">Published on Invalid Date •</span>almost NaN years ago</span></span></div><div class=\\"css-tywa7u e11fsc2k9\\"><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-15496ft e11fsc2k12\\"><div class=\\"MuiAvatar-root-128 MuiAvatar-colorDefault-129 css-1to0t9u e11fsc2k13\\">M</div><span class=\\"css-1xj37ub e11fsc2k11\\"><div class=\\"e11fsc2k10 css-1xe0n7g e1pneb170\\">Martin</div></span></div></div><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-1b14dls e11fsc2k14\\">xyz description</div></div></div></a></div></div>"`;
exports[`<PackageList /> component should load the component with packages 1`] = `"<div class=\\"package-list-items\\"><div class=\\"pkgContainer\\"><a class=\\"package css-zrrjf6 e11fsc2k16\\" href=\\"/-/web/version/verdaccio\\"><div class=\\"css-esn5nr e11fsc2k0\\"><span class=\\"css-1e6w198 e11fsc2k2\\"><span class=\\"css-bxt2bt e11fsc2k1\\">verdaccio</span><span class=\\"css-17xn9wj e11fsc2k5\\">v1.0.0</span></span><span class=\\"css-1dq57rh e11fsc2k4\\"><span class=\\"css-19brcdm e11fsc2k3\\"><svg class=\\"e11fsc2k6 css-y8pkl4 ej4jd2o0\\"><title>Time</title><use xlink:href=\\"[object Object]#time\\"></use></svg><span class=\\"css-1qw5qv3 e11fsc2k7\\">Published on 21.07.2018, 22:11:12 •</span>7 months ago</span></span></div><div class=\\"css-tywa7u e11fsc2k9\\"><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-15496ft e11fsc2k12\\"><div class=\\"MuiAvatar-root-128 MuiAvatar-colorDefault-129 css-1to0t9u e11fsc2k13\\">S</div><span class=\\"css-1xj37ub e11fsc2k11\\"><div class=\\"e11fsc2k10 css-1xe0n7g e1pneb170\\">Sam</div></span></div></div><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-1b14dls e11fsc2k14\\">Private NPM repository</div></div></div></a><a class=\\"package css-zrrjf6 e11fsc2k16\\" href=\\"/-/web/version/abc\\"><div class=\\"css-esn5nr e11fsc2k0\\"><span class=\\"css-1e6w198 e11fsc2k2\\"><span class=\\"css-bxt2bt e11fsc2k1\\">abc</span><span class=\\"css-17xn9wj e11fsc2k5\\">v1.0.1</span></span><span class=\\"css-1dq57rh e11fsc2k4\\"><span class=\\"css-19brcdm e11fsc2k3\\"><svg class=\\"e11fsc2k6 css-y8pkl4 ej4jd2o0\\"><title>Time</title><use xlink:href=\\"[object Object]#time\\"></use></svg><span class=\\"css-1qw5qv3 e11fsc2k7\\">Published on 21.07.2018, 22:11:12 •</span>7 months ago</span></span></div><div class=\\"css-tywa7u e11fsc2k9\\"><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-15496ft e11fsc2k12\\"><div class=\\"MuiAvatar-root-128 MuiAvatar-colorDefault-129 css-1to0t9u e11fsc2k13\\">R</div><span class=\\"css-1xj37ub e11fsc2k11\\"><div class=\\"e11fsc2k10 css-1xe0n7g e1pneb170\\">Rose</div></span></div></div><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-1b14dls e11fsc2k14\\">abc description</div></div></div></a><a class=\\"package css-zrrjf6 e11fsc2k16\\" href=\\"/-/web/version/xyz\\"><div class=\\"css-esn5nr e11fsc2k0\\"><span class=\\"css-1e6w198 e11fsc2k2\\"><span class=\\"css-bxt2bt e11fsc2k1\\">xyz</span><span class=\\"css-17xn9wj e11fsc2k5\\">v1.1.0</span></span><span class=\\"css-1dq57rh e11fsc2k4\\"><span class=\\"css-19brcdm e11fsc2k3\\"><svg class=\\"e11fsc2k6 css-y8pkl4 ej4jd2o0\\"><title>Time</title><use xlink:href=\\"[object Object]#time\\"></use></svg><span class=\\"css-1qw5qv3 e11fsc2k7\\">Published on Invalid Date •</span>almost NaN years ago</span></span></div><div class=\\"css-tywa7u e11fsc2k9\\"><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-15496ft e11fsc2k12\\"><div class=\\"MuiAvatar-root-128 MuiAvatar-colorDefault-129 css-1to0t9u e11fsc2k13\\">M</div><span class=\\"css-1xj37ub e11fsc2k11\\"><div class=\\"e11fsc2k10 css-1xe0n7g e1pneb170\\">Martin</div></span></div></div><div class=\\"css-hnjjgz e11fsc2k8\\"><div class=\\"css-1b14dls e11fsc2k14\\">xyz description</div></div></div></a></div></div>"`;

@ -4,11 +4,14 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { BrowserRouter as Router } from 'react-router-dom';
import { shallow } from 'enzyme';
import Header from '../../../../src/webui/components/Header';
describe('<Header /> component with logged in state', () => {
let wrapper;
let routerWrapper;
let instance;
let props;
beforeEach(() => {
@ -20,7 +23,13 @@ describe('<Header /> component with logged in state', () => {
scope: 'test scope',
withoutSearch: true,
};
wrapper = mount(<Header {...props} />);
routerWrapper = shallow(
<Router>
<Header {...props} />
</Router>
);
wrapper = routerWrapper.find(Header).dive();
instance = wrapper.instance();
});
test('should load the component in logged in state', () => {
@ -30,13 +39,12 @@ describe('<Header /> component with logged in state', () => {
registryUrl: 'http://localhost',
showMobileNavBar: false,
};
expect(wrapper.state()).toEqual(state);
expect(wrapper.html()).toMatchSnapshot();
expect(routerWrapper.html()).toMatchSnapshot();
});
test('handleLoggedInMenu: set anchorEl to html element value in state', () => {
const { handleLoggedInMenu } = wrapper.instance();
// creates a sample menu
const div = document.createElement('div');
const text = document.createTextNode('sample menu');
@ -46,13 +54,15 @@ describe('<Header /> component with logged in state', () => {
currentTarget: div,
};
handleLoggedInMenu(event);
instance.handleLoggedInMenu(event);
expect(wrapper.state('anchorEl')).toEqual(div);
});
});
describe('<Header /> component with logged out state', () => {
let wrapper;
let routerWrapper;
let instance;
let props;
beforeEach(() => {
@ -63,7 +73,13 @@ describe('<Header /> component with logged out state', () => {
logo: '',
withoutSearch: true,
};
wrapper = mount(<Header {...props} />);
routerWrapper = shallow(
<Router>
<Header {...props} />
</Router>
);
wrapper = routerWrapper.find(Header).dive();
instance = wrapper.instance();
});
test('should load the component in logged out state', () => {
@ -74,30 +90,26 @@ describe('<Header /> component with logged out state', () => {
showMobileNavBar: false,
};
expect(wrapper.state()).toEqual(state);
expect(wrapper.html()).toMatchSnapshot();
expect(routerWrapper.html()).toMatchSnapshot();
});
test('handleLoggedInMenuClose: set anchorEl value to null in state', () => {
const { handleLoggedInMenuClose } = wrapper.instance();
handleLoggedInMenuClose();
instance.handleLoggedInMenuClose();
expect(wrapper.state('anchorEl')).toBeNull();
});
test('handleOpenRegistryInfoDialog: set openInfoDialog to be truthy in state', () => {
const { handleOpenRegistryInfoDialog } = wrapper.instance();
handleOpenRegistryInfoDialog();
instance.handleOpenRegistryInfoDialog();
expect(wrapper.state('openInfoDialog')).toBeTruthy();
});
test('handleCloseRegistryInfoDialog: set openInfoDialog to be falsy in state', () => {
const { handleCloseRegistryInfoDialog } = wrapper.instance();
handleCloseRegistryInfoDialog();
instance.handleCloseRegistryInfoDialog();
expect(wrapper.state('openInfoDialog')).toBeFalsy();
});
test('handleToggleLogin: close/open popover menu', () => {
const { handleToggleLogin } = wrapper.instance();
handleToggleLogin();
instance.handleToggleLogin();
expect(wrapper.state('anchorEl')).toBeNull();
expect(props.onToggleLoginModal).toHaveBeenCalled();
});

@ -3,26 +3,25 @@
*/
import React from 'react';
import { shallow, mount } from 'enzyme';
import { BrowserRouter as Router } from 'react-router-dom';
import { shallow } from 'enzyme';
import NotFound from '../../../../src/webui/components/NotFound/index';
console.error = jest.fn();
describe('<NotFound /> component', () => {
test('should load the component in default state', () => {
const wrapper = mount(<NotFound pkg={"test"} />);
expect(wrapper.html()).toMatchSnapshot();
let routerWrapper;
beforeEach(() => {
routerWrapper = shallow(
<Router>
<NotFound />
</Router>
);
});
test('should set html from props', () => {
const props = {
pkg: 'verdaccio'
};
const wrapper = shallow(<NotFound {...props} />);
expect(wrapper.find('h1').text()).toEqual('Error 404 - verdaccio');
expect(wrapper.find('p').text()).toEqual(
'Oops, The package you are trying to access does not exist.'
);
expect(wrapper.html()).toMatchSnapshot();
test('should load the component in default state', () => {
expect(routerWrapper.find(NotFound)).toMatchSnapshot();
});
});

@ -6,7 +6,7 @@
import React from 'react';
import { mount } from 'enzyme';
import Search from '../../../../src/webui/components/Search/index';
import { Search } from '../../../../src/webui/components/Search/index';
const SEARCH_FILE_PATH = '../../../../src/webui/components/Search/index';
const API_FILE_PATH = '../../../../src/webui/utils/api';
@ -121,7 +121,7 @@ describe('<Search /> component: mocks specific tests ', () => {
},
}));
const Search = require(SEARCH_FILE_PATH).default;
const Search = require(SEARCH_FILE_PATH).Search;
const component = mount(<Search />);
component.setState({ search: 'verdaccio' });
const { handleFetchPackages } = component.instance();
@ -138,7 +138,7 @@ describe('<Search /> component: mocks specific tests ', () => {
jest.doMock(API_FILE_PATH, () => ({ request: jest.fn(() => Promise.reject(apiResponse)) }));
const Search = require(SEARCH_FILE_PATH).default;
const Search = require(SEARCH_FILE_PATH).Search;
const component = mount(<Search />);
component.setState({ search: 'verdaccio' });
const { handleFetchPackages } = component.instance();
@ -159,7 +159,7 @@ describe('<Search /> component: mocks specific tests ', () => {
},
}));
const Search = require(SEARCH_FILE_PATH).default;
const Search = require(SEARCH_FILE_PATH).Search;
const component = mount(<Search />);
component.setState({ search: 'verdaccio' });
const { handleFetchPackages } = component.instance();
@ -175,20 +175,19 @@ describe('<Search /> component: mocks specific tests ', () => {
jest.doMock(URL_FILE_PATH, () => ({ getDetailPageURL }));
const suggestionValue = [];
const Search = require(SEARCH_FILE_PATH).default;
const component = mount(<Search />);
const Search = require(SEARCH_FILE_PATH).Search;
const pushHandler = jest.fn();
const component = mount(<Search history={{ push: pushHandler }} />);
const { handleClickSearch } = component.instance();
// click
handleClickSearch(event, { suggestionValue, method: 'click' });
expect(event.stopPropagation).toHaveBeenCalled();
expect(getDetailPageURL).toHaveBeenCalledWith(suggestionValue);
expect(window.location.assign).toHaveBeenCalledWith('detail/page/url');
expect(pushHandler).toHaveBeenCalledTimes(1);
// return key
handleClickSearch(event, { suggestionValue, method: 'enter' });
expect(event.stopPropagation).toHaveBeenCalled();
expect(getDetailPageURL).toHaveBeenCalledWith(suggestionValue);
expect(window.location.assign).toHaveBeenCalledWith('detail/page/url');
expect(pushHandler).toHaveBeenCalledTimes(2);
});
});

BIN
yarn.lock

Binary file not shown.