forked from sombochea/verdaccio-ui
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a2784ba39 | ||
|
|
6d553ea607 | ||
|
|
95f173d29a | ||
|
|
531295a6d0 | ||
|
|
a38b93e127 | ||
|
|
52ed8ad67b | ||
|
|
ae0222cf65 | ||
|
|
2bc49f3ab7 | ||
|
|
ade548a7da | ||
|
|
6dfcd70025 | ||
|
|
4498aad4bf | ||
|
|
5d6ad3d783 | ||
|
|
5c06ace14a | ||
|
|
8c66dbc4d7 | ||
|
|
302f4dbd89 | ||
|
|
ff791a35f7 | ||
|
|
f5c77ff43c | ||
|
|
d69fc1b260 | ||
|
|
16b12ddc76 | ||
|
|
dd532955de |
4
.babelrc
4
.babelrc
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"presets": [
|
||||
["@verdaccio", { "typescript": true }]
|
||||
]
|
||||
"presets": [["@verdaccio"]]
|
||||
}
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -2,6 +2,19 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.3.4](https://github.com/verdaccio/ui/compare/v0.3.3...v0.3.4) (2019-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adds no uplink spec ([#213](https://github.com/verdaccio/ui/issues/213)) ([ade548a](https://github.com/verdaccio/ui/commit/ade548a))
|
||||
* api typings ([#210](https://github.com/verdaccio/ui/issues/210)) ([5d6ad3d](https://github.com/verdaccio/ui/commit/5d6ad3d))
|
||||
* PackageList component is converted to functional ([#219](https://github.com/verdaccio/ui/issues/219)) [#116](https://github.com/verdaccio/ui/issues/116) ([ae0222c](https://github.com/verdaccio/ui/commit/ae0222c))
|
||||
* routes - Replaced class by func. comp ([#159](https://github.com/verdaccio/ui/issues/159)) ([5c06ace](https://github.com/verdaccio/ui/commit/5c06ace))
|
||||
* version Page - Replaces class by func. ([#171](https://github.com/verdaccio/ui/issues/171)) ([f5c77ff](https://github.com/verdaccio/ui/commit/f5c77ff))
|
||||
* **162:** added forwardRef Card ([#216](https://github.com/verdaccio/ui/issues/216)) ([2bc49f3](https://github.com/verdaccio/ui/commit/2bc49f3))
|
||||
* **installlistitem:** changed the wrong icon ([#211](https://github.com/verdaccio/ui/issues/211)) ([4498aad](https://github.com/verdaccio/ui/commit/4498aad))
|
||||
|
||||
### [0.3.3](https://github.com/verdaccio/ui/compare/v0.3.2...v0.3.3) (2019-10-16)
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ Don't forget run the following commands before commit and push your code, it wil
|
||||
yarn lint && yarn test
|
||||
```
|
||||
|
||||
#### Commits
|
||||
|
||||
Remember we follow the [the Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0-beta.4/).
|
||||
|
||||
🤓 Feel free to participate in code reviews, let us know if you want to participate in this plugin.
|
||||
|
||||
79
package.json
79
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/ui-theme",
|
||||
"version": "0.3.3",
|
||||
"version": "0.3.4",
|
||||
"description": "Verdaccio User Interface",
|
||||
"author": {
|
||||
"name": "Verdaccio Core Team",
|
||||
@@ -15,42 +15,42 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "8.2.0",
|
||||
"@commitlint/config-conventional": "8.2.0",
|
||||
"@material-ui/core": "4.4.3",
|
||||
"@material-ui/icons": "4.4.3",
|
||||
"@octokit/rest": "16.28.7",
|
||||
"@testing-library/react": "9.2.0",
|
||||
"@material-ui/core": "4.5.1",
|
||||
"@material-ui/icons": "4.5.1",
|
||||
"@octokit/rest": "16.34.0",
|
||||
"@testing-library/react": "9.3.0",
|
||||
"@types/autosuggest-highlight": "3.1.0",
|
||||
"@types/enzyme": "3.10.3",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/jest": "24.0.20",
|
||||
"@types/js-base64": "2.3.1",
|
||||
"@types/lodash": "4.14.141",
|
||||
"@types/node": "12.7.8",
|
||||
"@types/react": "16.9.2",
|
||||
"@types/react-autosuggest": "9.3.11",
|
||||
"@types/react-dom": "16.9.0",
|
||||
"@types/lodash": "4.14.144",
|
||||
"@types/node": "12.11.7",
|
||||
"@types/react": "16.9.11",
|
||||
"@types/react-autosuggest": "9.3.13",
|
||||
"@types/react-dom": "16.9.3",
|
||||
"@types/react-router-dom": "5.1.0",
|
||||
"@types/request": "2.48.3",
|
||||
"@types/validator": "10.11.3",
|
||||
"@types/webpack-env": "1.14.0",
|
||||
"@typescript-eslint/parser": "2.3.2",
|
||||
"@verdaccio/babel-preset": "2.0.0",
|
||||
"@verdaccio/commons-api": "8.1.2",
|
||||
"@types/webpack-env": "1.14.1",
|
||||
"@typescript-eslint/parser": "2.4.0",
|
||||
"@verdaccio/babel-preset": "8.2.0",
|
||||
"@verdaccio/commons-api": "8.2.0",
|
||||
"@verdaccio/eslint-config": "2.0.0",
|
||||
"@verdaccio/types": "8.1.0",
|
||||
"autosuggest-highlight": "3.1.1",
|
||||
"babel-loader": "8.0.6",
|
||||
"bundlesize": "0.18.0",
|
||||
"codeceptjs": "2.3.2",
|
||||
"codeceptjs": "2.3.5",
|
||||
"codecov": "3.6.1",
|
||||
"concurrently": "4.1.2",
|
||||
"cross-env": "6.0.0",
|
||||
"concurrently": "5.0.0",
|
||||
"cross-env": "6.0.3",
|
||||
"css-loader": "3.2.0",
|
||||
"date-fns": "1.30.1",
|
||||
"detect-secrets": "1.0.4",
|
||||
"emotion": "9.2.12",
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.14.0",
|
||||
"enzyme-to-json": "3.4.0",
|
||||
"enzyme-adapter-react-16": "1.15.1",
|
||||
"enzyme-to-json": "3.4.3",
|
||||
"eslint": "6.5.1",
|
||||
"eslint-plugin-codeceptjs": "1.1.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
@@ -61,14 +61,14 @@
|
||||
"eslint-plugin-verdaccio": "2.0.0",
|
||||
"file-loader": "4.2.0",
|
||||
"friendly-errors-webpack-plugin": "1.7.0",
|
||||
"get-stdin": "6.0.0",
|
||||
"get-stdin": "7.0.0",
|
||||
"github-markdown-css": "3.0.1",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"husky": "3.0.7",
|
||||
"husky": "3.0.9",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"in-publish": "2.0.0",
|
||||
"jest": "24.9.0",
|
||||
"jest-emotion": "10.0.14",
|
||||
"jest-emotion": "10.0.17",
|
||||
"jest-environment-jsdom": "24.9.0",
|
||||
"jest-environment-jsdom-global": "1.2.0",
|
||||
"jest-environment-node": "24.9.0",
|
||||
@@ -77,20 +77,21 @@
|
||||
"js-yaml": "3.13.1",
|
||||
"lint-staged": "8.2.1",
|
||||
"localstorage-memory": "1.0.3",
|
||||
"lockfile-lint": "2.0.1",
|
||||
"lockfile-lint": "2.1.6",
|
||||
"lodash": "^4.17.15",
|
||||
"mini-css-extract-plugin": "0.8.0",
|
||||
"node-mocks-http": "1.8.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"ora": "3.4.0",
|
||||
"ora": "4.0.2",
|
||||
"prettier": "1.18.2",
|
||||
"prop-types": "15.7.2",
|
||||
"puppeteer": "1.8.0",
|
||||
"react": "16.10.0",
|
||||
"react": "16.11.0",
|
||||
"react-autosuggest": "9.4.3",
|
||||
"react-dom": "16.10.0",
|
||||
"react-dom": "16.11.0",
|
||||
"react-emotion": "9.2.12",
|
||||
"react-hot-loader": "4.12.11",
|
||||
"react-hot-loader": "4.12.15",
|
||||
"react-router-dom": "5.1.2",
|
||||
"request": "2.88.0",
|
||||
"resolve-url-loader": "3.1.0",
|
||||
@@ -98,26 +99,26 @@
|
||||
"source-map-loader": "0.2.4",
|
||||
"standard-version": "7.0.0",
|
||||
"style-loader": "1.0.0",
|
||||
"stylelint": "10.1.0",
|
||||
"stylelint-config-recommended": "2.2.0",
|
||||
"stylelint": "11.1.1",
|
||||
"stylelint-config-recommended": "3.0.0",
|
||||
"stylelint-config-styled-components": "0.1.1",
|
||||
"stylelint-processor-styled-components": "1.8.0",
|
||||
"stylelint-webpack-plugin": "0.10.5",
|
||||
"stylelint-webpack-plugin": "1.0.3",
|
||||
"supertest": "4.0.2",
|
||||
"typeface-roboto": "0.0.75",
|
||||
"typescript": "3.6.3",
|
||||
"typescript": "3.7.1-rc",
|
||||
"uglifyjs-webpack-plugin": "2.2.0",
|
||||
"url-loader": "2.1.0",
|
||||
"url-loader": "2.2.0",
|
||||
"validator": "11.1.0",
|
||||
"verdaccio": "4.3.3",
|
||||
"verdaccio-auth-memory": "8.1.2",
|
||||
"verdaccio-memory": "8.1.2",
|
||||
"verdaccio": "4.3.4",
|
||||
"verdaccio-auth-memory": "8.2.0",
|
||||
"verdaccio-memory": "8.2.0",
|
||||
"wait-on": "3.3.0",
|
||||
"webpack": "4.41.0",
|
||||
"webpack-bundle-analyzer": "3.5.2",
|
||||
"webpack": "4.41.2",
|
||||
"webpack-bundle-analyzer": "3.6.0",
|
||||
"webpack-bundle-size-analyzer": "3.1.0",
|
||||
"webpack-cli": "3.3.9",
|
||||
"webpack-dev-server": "3.8.1",
|
||||
"webpack-dev-server": "3.9.0",
|
||||
"webpack-merge": "4.2.2",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"xss": "1.0.6"
|
||||
|
||||
@@ -4,7 +4,8 @@ import { mount, ReactWrapper } from 'enzyme';
|
||||
import storage from '../utils/storage';
|
||||
import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__/token';
|
||||
|
||||
import App, { AppStateInterface } from './App';
|
||||
import App from './App';
|
||||
import { AppProps } from './AppContext';
|
||||
|
||||
jest.mock('../utils/storage', () => {
|
||||
class LocalStorageMock {
|
||||
@@ -33,7 +34,7 @@ jest.mock('../utils/api', () => ({
|
||||
}));
|
||||
|
||||
describe('App', () => {
|
||||
let wrapper: ReactWrapper<{}, AppStateInterface, App>;
|
||||
let wrapper: ReactWrapper<{}, AppProps, App>;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<App />);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import isNil from 'lodash/isNil';
|
||||
import 'normalize.css';
|
||||
import 'typeface-roboto/index.css';
|
||||
|
||||
import storage from '../utils/storage';
|
||||
import { makeLogin, isTokenExpire } from '../utils/login';
|
||||
@@ -7,41 +9,15 @@ import Loading from '../components/Loading';
|
||||
import LoginModal from '../components/Login';
|
||||
import Header from '../components/Header';
|
||||
import { Container, Content } from '../components/Layout';
|
||||
import RouterApp from '../router';
|
||||
import API from '../utils/api';
|
||||
import 'typeface-roboto/index.css';
|
||||
import '../utils/styles/global';
|
||||
import 'normalize.css';
|
||||
import Footer from '../components/Footer';
|
||||
import { FormError } from '../components/Login/Login';
|
||||
import { PackageInterface } from '../components/Package/Package';
|
||||
|
||||
interface AppContextData {
|
||||
logoUrl: string;
|
||||
scope: string;
|
||||
isUserLoggedIn: boolean;
|
||||
packages: PackageInterface[];
|
||||
user: {
|
||||
username?: string;
|
||||
};
|
||||
}
|
||||
export const AppContext = React.createContext<AppContextData>({
|
||||
logoUrl: window.VERDACCIO_LOGO,
|
||||
user: {},
|
||||
scope: window.VERDACCIO_SCOPE || '',
|
||||
isUserLoggedIn: false,
|
||||
packages: [],
|
||||
});
|
||||
const AppContextProvider = AppContext.Provider;
|
||||
export const AppContextConsumer = AppContext.Consumer;
|
||||
import AppRoute from './AppRoute';
|
||||
import { AppProps, AppContextProvider } from './AppContext';
|
||||
|
||||
export interface AppStateInterface extends AppContextData {
|
||||
error?: FormError;
|
||||
showLoginModal: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
export default class App extends Component<{}, AppStateInterface> {
|
||||
public state: AppStateInterface = {
|
||||
export default class App extends Component<{}, AppProps> {
|
||||
public state: AppProps = {
|
||||
logoUrl: window.VERDACCIO_LOGO,
|
||||
user: {},
|
||||
scope: window.VERDACCIO_SCOPE || '',
|
||||
@@ -57,7 +33,7 @@ export default class App extends Component<{}, AppStateInterface> {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
public componentDidUpdate(_: AppStateInterface, prevState: AppStateInterface): void {
|
||||
public componentDidUpdate(_: AppProps, prevState: AppProps): void {
|
||||
const { isUserLoggedIn } = this.state;
|
||||
if (prevState.isUserLoggedIn !== isUserLoggedIn) {
|
||||
this.loadOnHandler();
|
||||
@@ -80,7 +56,7 @@ export default class App extends Component<{}, AppStateInterface> {
|
||||
public isUserAlreadyLoggedIn = () => {
|
||||
// checks for token validity
|
||||
const token = storage.getItem('token');
|
||||
const username = storage.getItem('username');
|
||||
const username: string = storage.getItem('username') as string;
|
||||
if (isTokenExpire(token) || isNil(username)) {
|
||||
this.handleLogout();
|
||||
} else {
|
||||
@@ -94,6 +70,7 @@ export default class App extends Component<{}, AppStateInterface> {
|
||||
public loadOnHandler = async () => {
|
||||
try {
|
||||
const packages = await API.request<any[]>('packages', 'GET');
|
||||
// @ts-ignore: FIX THIS TYPE: Type 'any[]' is not assignable to type '[]'
|
||||
this.setState({
|
||||
packages,
|
||||
isLoading: false,
|
||||
@@ -176,9 +153,7 @@ export default class App extends Component<{}, AppStateInterface> {
|
||||
return (
|
||||
<>
|
||||
<Content>
|
||||
<RouterApp onLogout={this.handleLogout} onToggleLoginModal={this.handleToggleLoginModal}>
|
||||
{this.renderHeader()}
|
||||
</RouterApp>
|
||||
<AppRoute>{this.renderHeader()}</AppRoute>
|
||||
</Content>
|
||||
<Footer />
|
||||
</>
|
||||
|
||||
20
src/App/AppContext.tsx
Normal file
20
src/App/AppContext.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { FormError } from '../components/Login/Login';
|
||||
|
||||
export interface AppProps {
|
||||
error?: FormError;
|
||||
logoUrl: string;
|
||||
user: {
|
||||
username?: string;
|
||||
};
|
||||
scope: string;
|
||||
showLoginModal: boolean;
|
||||
isUserLoggedIn: boolean;
|
||||
packages: [];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const AppContext = createContext<Partial<AppProps>>({});
|
||||
export const AppContextProvider = AppContext.Provider;
|
||||
export const AppContextConsumer = AppContext.Consumer;
|
||||
68
src/App/AppRoute.tsx
Normal file
68
src/App/AppRoute.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { lazy, useContext, Suspense } from 'react';
|
||||
import { Route as ReactRouterDomRoute, Switch, Router } from 'react-router-dom';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
import Loading from '../components/Loading';
|
||||
|
||||
import { AppContext } from './AppContext';
|
||||
|
||||
const NotFound = lazy(() => import('../components/NotFound'));
|
||||
const VersionContextProvider = lazy(() => import('../pages/Version/VersionContextProvider'));
|
||||
const VersionPage = lazy(() => import('../pages/Version'));
|
||||
const HomePage = lazy(() => import('../pages/home'));
|
||||
|
||||
enum Route {
|
||||
ROOT = '/',
|
||||
SCOPE_PACKAGE = '/-/web/detail/@:scope/:package',
|
||||
SCOPE_PACKAGE_VERSION = '/-/web/detail/@:scope/:package/v/:version',
|
||||
PACKAGE = '/-/web/detail/:package',
|
||||
PACKAGE_VERSION = '/-/web/detail/:package/v/:version',
|
||||
}
|
||||
|
||||
const history = createBrowserHistory({
|
||||
basename: window.__VERDACCIO_BASENAME_UI_OPTIONS && window.__VERDACCIO_BASENAME_UI_OPTIONS.url_prefix,
|
||||
});
|
||||
|
||||
/* eslint react/jsx-max-depth: 0 */
|
||||
const AppRoute: React.FC = ({ children }) => {
|
||||
const appContext = useContext(AppContext);
|
||||
const { isUserLoggedIn, packages } = appContext;
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Suspense fallback={<Loading />}>
|
||||
{children}
|
||||
<Switch>
|
||||
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
||||
<HomePage isUserLoggedIn={!!isUserLoggedIn} packages={packages || []} />
|
||||
</ReactRouterDomRoute>
|
||||
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
||||
<VersionContextProvider>
|
||||
<VersionPage />
|
||||
</VersionContextProvider>
|
||||
</ReactRouterDomRoute>
|
||||
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
|
||||
<VersionContextProvider>
|
||||
<VersionPage />
|
||||
</VersionContextProvider>
|
||||
</ReactRouterDomRoute>
|
||||
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
|
||||
<VersionContextProvider>
|
||||
<VersionPage />
|
||||
</VersionContextProvider>
|
||||
</ReactRouterDomRoute>
|
||||
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
|
||||
<VersionContextProvider>
|
||||
<VersionPage />
|
||||
</VersionContextProvider>
|
||||
</ReactRouterDomRoute>
|
||||
<ReactRouterDomRoute>
|
||||
<NotFound />
|
||||
</ReactRouterDomRoute>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppRoute;
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import api from '../../utils/api';
|
||||
|
||||
import { ActionBar } from './ActionBar';
|
||||
|
||||
const mockPackageMeta: jest.Mock = jest.fn(() => ({
|
||||
@@ -43,6 +45,12 @@ describe('<ActionBar /> component', () => {
|
||||
expect(wrapper.html()).toEqual('');
|
||||
});
|
||||
|
||||
test('when there is no latest property in package meta', () => {
|
||||
mockPackageMeta.mockImplementation(() => ({}));
|
||||
const wrapper = mount(<ActionBar />);
|
||||
expect(wrapper.html()).toEqual('');
|
||||
});
|
||||
|
||||
test('when there is a button to download a tarball', () => {
|
||||
mockPackageMeta.mockImplementation(() => ({
|
||||
latest: {
|
||||
@@ -57,5 +65,25 @@ describe('<ActionBar /> component', () => {
|
||||
|
||||
const button = wrapper.find('button');
|
||||
expect(button).toHaveLength(1);
|
||||
|
||||
const spy = jest.spyOn(api, 'request');
|
||||
button.simulate('click');
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('when there is a button to open an issue', () => {
|
||||
mockPackageMeta.mockImplementation(() => ({
|
||||
latest: {
|
||||
bugs: {
|
||||
url: 'https://verdaccio.tld/bugs',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const wrapper = mount(<ActionBar />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
const button = wrapper.find('button');
|
||||
expect(button).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
exports[`<ActionBar /> component should render the component in default state 1`] = `""`;
|
||||
|
||||
exports[`<ActionBar /> component when there is a button to download a tarball 1`] = `"<ul class=\\"MuiList-root MuiList-padding\\"><div class=\\"MuiButtonBase-root MuiListItem-root css-1br2q5z eux6shq0 MuiListItem-gutters MuiListItem-button MuiListItem-alignItemsFlexStart\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><button class=\\"MuiButtonBase-root MuiFab-root css-96oxa0 eux6shq1 MuiFab-sizeSmall\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Download tarball\\"><span class=\\"MuiFab-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
|
||||
|
||||
exports[`<ActionBar /> component when there is a button to open an issue 1`] = `"<ul class=\\"MuiList-root MuiList-padding\\"><div class=\\"MuiButtonBase-root MuiListItem-root css-1br2q5z eux6shq0 MuiListItem-gutters MuiListItem-button MuiListItem-alignItemsFlexStart\\" tabindex=\\"0\\" role=\\"button\\" aria-disabled=\\"false\\"><a href=\\"https://verdaccio.tld/bugs\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root MuiFab-root css-96oxa0 eux6shq1 MuiFab-sizeSmall\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></a><span class=\\"MuiTouchRipple-root\\"></span></div></ul>"`;
|
||||
|
||||
@@ -117,6 +117,8 @@ const AutoComplete = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
// material-ui@4.5.1 introduce better types for TextInput, check readme
|
||||
// @ts-ignore
|
||||
startAdornment,
|
||||
disableUnderline,
|
||||
color,
|
||||
|
||||
@@ -1,38 +1,27 @@
|
||||
import React from 'react';
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
|
||||
import { copyToClipBoardUtility } from '../../utils/cli-utils';
|
||||
|
||||
import CopyToClipBoard from './CopyToClipBoard';
|
||||
import { CopyIcon } from './styles';
|
||||
|
||||
jest.mock('../../utils/cli-utils');
|
||||
|
||||
describe('<CopyToClipBoard /> component', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
const copyText = 'copy text';
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<CopyToClipBoard text={'copy text'} />);
|
||||
wrapper = mount(<CopyToClipBoard text={copyText} />);
|
||||
});
|
||||
|
||||
test('render the component', () => {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call the DOM APIs for copy to clipboard utility', () => {
|
||||
const event = {
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
|
||||
// @ts-ignore: Property 'getSelection' does not exist on type 'Global'.
|
||||
global.getSelection = jest.fn(() => ({
|
||||
removeAllRanges: () => {},
|
||||
addRange: () => {},
|
||||
}));
|
||||
|
||||
// @ts-ignore: Property 'document/getSelection' does not exist on type 'Global'.
|
||||
const { document, getSelection } = global;
|
||||
|
||||
wrapper.find(CopyIcon).simulate('click', event);
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
expect(document.createRange).toHaveBeenCalled();
|
||||
expect(getSelection).toHaveBeenCalled();
|
||||
expect(document.execCommand).toHaveBeenCalledWith('copy');
|
||||
test('should call the copyToClipBoardUtility for copy to clipboard utility', () => {
|
||||
wrapper.find(CopyIcon).simulate('click');
|
||||
expect(copyToClipBoardUtility).toHaveBeenCalledWith(copyText);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import styled from 'react-emotion';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
|
||||
import { fontWeight } from '../../utils/styles/sizes';
|
||||
import Text from '../../muiComponents/Text';
|
||||
import Card from '../../muiComponents/Card';
|
||||
|
||||
export const CardWrap = styled(Card)({
|
||||
'&&': {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
|
||||
import { ActionBar } from '../ActionBar/ActionBar';
|
||||
@@ -11,6 +10,7 @@ import Install from '../Install';
|
||||
import Repository from '../Repository/Repository';
|
||||
import { DetailContext } from '../../pages/Version';
|
||||
import List from '../../muiComponents/List';
|
||||
import Card from '../../muiComponents/Card';
|
||||
|
||||
import { TitleListItem, TitleListItemText, PackageDescription, PackageVersion } from './styles';
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Help /> component should render the component in default state 1`] = `"<div class=\\"MuiPaper-root MuiPaper-elevation1 MuiCard-root css-ryznli e1wgaou60 MuiPaper-rounded\\" id=\\"help-card\\"><div class=\\"MuiCardContent-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom\\" id=\\"help-card__title\\">No Package Published Yet.</h2><h6 class=\\"MuiTypography-root css-zg2fwz e1wgaou61 MuiTypography-h6 MuiTypography-colorTextSecondary MuiTypography-gutterBottom\\">To publish your first package just:</h6><p class=\\"MuiTypography-root MuiTypography-body1\\">1. Login</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm adduser --registry http://localhost</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><p class=\\"MuiTypography-root MuiTypography-body1\\">2. Publish</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm publish --registry http://localhost</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><p class=\\"MuiTypography-root MuiTypography-body2\\">3. Refresh this page.</p></div><div class=\\"MuiCardActions-root MuiCardActions-spacing\\"><a class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeSmall\\" tabindex=\\"0\\" aria-disabled=\\"false\\" href=\\"https://verdaccio.org/docs/en/installation\\"><span class=\\"MuiButton-label\\">Learn More</span><span class=\\"MuiTouchRipple-root\\"></span></a></div></div>"`;
|
||||
exports[`<Help /> component should render the component in default state 1`] = `"<div class=\\"MuiPaper-root MuiPaper-elevation1 MuiCard-root css-ryznli e1wgaou60 MuiPaper-rounded\\" id=\\"help-card\\"><div class=\\"MuiCardContent-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h5 MuiTypography-gutterBottom\\" id=\\"help-card__title\\">No Package Published Yet.</h2><h6 class=\\"MuiTypography-root css-zg2fwz e1wgaou61 MuiTypography-h6 MuiTypography-colorTextSecondary MuiTypography-gutterBottom\\">To publish your first package just:</h6><p class=\\"MuiTypography-root MuiTypography-body1\\">1. Login</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm adduser --registry http://localhost</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><p class=\\"MuiTypography-root MuiTypography-body1\\">2. Publish</p><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm publish --registry http://localhost</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><p class=\\"MuiTypography-root MuiTypography-body2\\">3. Refresh this page.</p></div><div class=\\"MuiCardActions-root MuiCardActions-spacing\\"><a class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-textSizeSmall MuiButton-sizeSmall\\" tabindex=\\"0\\" aria-disabled=\\"false\\" href=\\"https://verdaccio.org/docs/en/installation\\"><span class=\\"MuiButton-label\\">Learn More</span><span class=\\"MuiTouchRipple-root\\"></span></a></div></div>"`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Card from '@material-ui/core/Card';
|
||||
import styled from 'react-emotion';
|
||||
|
||||
import { default as Typography } from '../../muiComponents/Heading';
|
||||
import Card from '../../muiComponents/Card';
|
||||
|
||||
export const CardStyled = styled(Card)({
|
||||
'&&': {
|
||||
|
||||
@@ -51,14 +51,14 @@ const InstallListItem: React.FC<Interface> = ({ packageName, dependencyManager }
|
||||
case DependencyManager.YARN:
|
||||
return (
|
||||
<InstallItem button={true} data-testid={'installListItem-yarn'}>
|
||||
<PackageMangerAvatar alt="yarn" src={pnpmLogo} />
|
||||
<PackageMangerAvatar alt="yarn" src={yarnLogo} />
|
||||
<InstallListItemText primary={<CopyToClipBoard text={`yarn add ${packageName}`} />} secondary={'Install using yarn'} />
|
||||
</InstallItem>
|
||||
);
|
||||
case DependencyManager.PNPM:
|
||||
return (
|
||||
<InstallItem button={true} data-testid={'installListItem-pnpm'}>
|
||||
<PackageMangerAvatar alt={'pnpm'} src={yarnLogo} />
|
||||
<PackageMangerAvatar alt={'pnpm'} src={pnpmLogo} />
|
||||
<InstallListItemText primary={<CopyToClipBoard text={`pnpm install ${packageName}`} />} secondary={'Install using pnpm'} />
|
||||
</InstallItem>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LoginModal /> should load the component in default state 1`] = `"<div role=\\"presentation\\" class=\\"MuiDialog-root\\" id=\\"login--form-container\\" style=\\"position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;\\"><div class=\\"MuiBackdrop-root\\" aria-hidden=\\"true\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div tabindex=\\"0\\" data-test=\\"sentinelStart\\"></div><div class=\\"MuiDialog-container MuiDialog-scrollPaper\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"document\\" tabindex=\\"-1\\"><div class=\\"MuiPaper-root MuiPaper-elevation24 MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthXs MuiDialog-paperFullWidth MuiPaper-rounded\\" role=\\"dialog\\"><form novalidate=\\"\\"><div class=\\"MuiDialogTitle-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h6\\">Login</h2></div><div class=\\"MuiDialogContent-root\\"><div class=\\"MuiFormControl-root MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root css-164r41r MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root dialog-footer MuiDialogActions-spacing\\"><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit\\" tabindex=\\"0\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label\\">Cancel</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text Mui-disabled MuiButton-colorInherit Mui-disabled\\" tabindex=\\"-1\\" type=\\"submit\\" disabled=\\"\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label\\">Login</span></button></div></form></div></div><div tabindex=\\"0\\" data-test=\\"sentinelEnd\\"></div></div>"`;
|
||||
exports[`<LoginModal /> should load the component in default state 1`] = `"<div role=\\"presentation\\" class=\\"MuiDialog-root\\" id=\\"login--form-container\\" style=\\"position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;\\"><div class=\\"MuiBackdrop-root\\" aria-hidden=\\"true\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div tabindex=\\"0\\" data-test=\\"sentinelStart\\"></div><div class=\\"MuiDialog-container MuiDialog-scrollPaper\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"none presentation\\" tabindex=\\"-1\\"><div class=\\"MuiPaper-root MuiPaper-elevation24 MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthXs MuiDialog-paperFullWidth MuiPaper-rounded\\" role=\\"dialog\\"><form novalidate=\\"\\"><div class=\\"MuiDialogTitle-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h6\\">Login</h2></div><div class=\\"MuiDialogContent-root\\"><div class=\\"MuiFormControl-root MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root css-164r41r MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root dialog-footer MuiDialogActions-spacing\\"><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit\\" tabindex=\\"0\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label\\">Cancel</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit Mui-disabled Mui-disabled\\" tabindex=\\"-1\\" type=\\"submit\\" disabled=\\"\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label\\">Login</span></button></div></form></div></div><div tabindex=\\"0\\" data-test=\\"sentinelEnd\\"></div></div>"`;
|
||||
|
||||
exports[`<LoginModal /> should load the component with props 1`] = `"<div role=\\"presentation\\" class=\\"MuiDialog-root\\" id=\\"login--form-container\\" style=\\"position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;\\"><div class=\\"MuiBackdrop-root\\" aria-hidden=\\"true\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div tabindex=\\"0\\" data-test=\\"sentinelStart\\"></div><div class=\\"MuiDialog-container MuiDialog-scrollPaper\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"document\\" tabindex=\\"-1\\"><div class=\\"MuiPaper-root MuiPaper-elevation24 MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthXs MuiDialog-paperFullWidth MuiPaper-rounded\\" role=\\"dialog\\"><form novalidate=\\"\\"><div class=\\"MuiDialogTitle-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h6\\">Login</h2></div><div class=\\"MuiDialogContent-root\\"><div class=\\"MuiTypography-root MuiPaper-root MuiPaper-elevation6 MuiSnackbarContent-root css-11e09xf MuiTypography-body2\\" role=\\"alertdialog\\"><div class=\\"MuiSnackbarContent-message\\"><div class=\\"css-70qvj9\\" id=\\"client-snackbar\\"><svg class=\\"MuiSvgIcon-root css-1mbwbu9\\" 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 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\\"></path></svg><span><div><strong>Error Title</strong></div><div>Error Description</div></span></div></div></div><div class=\\"MuiFormControl-root MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root css-164r41r MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root dialog-footer MuiDialogActions-spacing\\"><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit\\" tabindex=\\"0\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label\\">Cancel</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text Mui-disabled MuiButton-colorInherit Mui-disabled\\" tabindex=\\"-1\\" type=\\"submit\\" disabled=\\"\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label\\">Login</span></button></div></form></div></div><div tabindex=\\"0\\" data-test=\\"sentinelEnd\\"></div></div>"`;
|
||||
exports[`<LoginModal /> should load the component with props 1`] = `"<div role=\\"presentation\\" class=\\"MuiDialog-root\\" id=\\"login--form-container\\" style=\\"position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;\\"><div class=\\"MuiBackdrop-root\\" aria-hidden=\\"true\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\"></div><div tabindex=\\"0\\" data-test=\\"sentinelStart\\"></div><div class=\\"MuiDialog-container MuiDialog-scrollPaper\\" style=\\"opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;\\" role=\\"none presentation\\" tabindex=\\"-1\\"><div class=\\"MuiPaper-root MuiPaper-elevation24 MuiDialog-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthXs MuiDialog-paperFullWidth MuiPaper-rounded\\" role=\\"dialog\\"><form novalidate=\\"\\"><div class=\\"MuiDialogTitle-root\\"><h2 class=\\"MuiTypography-root MuiTypography-h6\\">Login</h2></div><div class=\\"MuiDialogContent-root\\"><div class=\\"MuiTypography-root MuiPaper-root MuiPaper-elevation6 MuiSnackbarContent-root css-11e09xf MuiTypography-body2\\" role=\\"alertdialog\\"><div class=\\"MuiSnackbarContent-message\\"><div class=\\"css-70qvj9\\" id=\\"client-snackbar\\"><svg class=\\"MuiSvgIcon-root css-1mbwbu9\\" 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 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\\"></path></svg><span><div><strong>Error Title</strong></div><div>Error Description</div></span></div></div></div><div class=\\"MuiFormControl-root MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"username\\">Username<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-username\\" placeholder=\\"Your username\\" required=\\"\\" type=\\"text\\" value=\\"\\"></div></div><div class=\\"MuiFormControl-root css-164r41r MuiFormControl-fullWidth\\"><label class=\\"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated Mui-required Mui-required\\" data-shrink=\\"false\\" for=\\"password\\">Password<span class=\\"MuiFormLabel-asterisk MuiInputLabel-asterisk\\"> *</span></label><div class=\\"MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl\\"><input aria-invalid=\\"false\\" class=\\"MuiInputBase-input MuiInput-input\\" id=\\"login--form-password\\" placeholder=\\"Your strong password\\" required=\\"\\" type=\\"password\\" value=\\"\\"></div></div></div><div class=\\"MuiDialogActions-root dialog-footer MuiDialogActions-spacing\\"><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit\\" tabindex=\\"0\\" type=\\"button\\" id=\\"login--form-cancel\\"><span class=\\"MuiButton-label\\">Cancel</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-colorInherit Mui-disabled Mui-disabled\\" tabindex=\\"-1\\" type=\\"submit\\" disabled=\\"\\" id=\\"login--form-submit\\"><span class=\\"MuiButton-label\\">Login</span></button></div></form></div></div><div tabindex=\\"0\\" data-test=\\"sentinelEnd\\"></div></div>"`;
|
||||
|
||||
@@ -27,7 +27,7 @@ const StyledHeading = styled(Typography)({
|
||||
const NotFound: React.FC = () => {
|
||||
const history = useHistory();
|
||||
|
||||
const handleGomHome = useCallback(() => {
|
||||
const handleGoHome = useCallback(() => {
|
||||
history.push('/');
|
||||
}, [history]);
|
||||
|
||||
@@ -37,7 +37,7 @@ const NotFound: React.FC = () => {
|
||||
<StyledHeading className="not-found-text" variant="h4">
|
||||
{NOT_FOUND_TEXT}
|
||||
</StyledHeading>
|
||||
<Button onClick={handleGomHome} variant="contained">
|
||||
<Button onClick={handleGoHome} variant="contained">
|
||||
{GO_TO_HOME_PAGE}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
|
||||
import NotFound from './NotFound';
|
||||
import NotFound, { GO_TO_HOME_PAGE } from './NotFound';
|
||||
|
||||
describe('<NotFound /> component', () => {
|
||||
test('should load the component in default state', () => {
|
||||
@@ -13,5 +13,17 @@ describe('<NotFound /> component', () => {
|
||||
);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
test.todo('Test Button Click');
|
||||
test('go to Home Page button click', () => {
|
||||
const spy = jest.spyOn(React, 'useCallback');
|
||||
const { getByText } = render(
|
||||
<Router>
|
||||
<NotFound />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const node = getByText(GO_TO_HOME_PAGE);
|
||||
fireEvent.click(node);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { default as MuiCard } from '@material-ui/core/Card';
|
||||
import styled from 'react-emotion';
|
||||
|
||||
import { default as Typography } from '../../muiComponents/Heading';
|
||||
import List from '../../muiComponents/List';
|
||||
import { default as MuiCard } from '../../muiComponents/Card';
|
||||
|
||||
export const Wrapper = styled('div')({
|
||||
display: 'flex',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { Fragment, ReactElement } from 'react';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import * as React from 'react';
|
||||
|
||||
import Package from '../Package';
|
||||
import Help from '../Help';
|
||||
import { formatLicense } from '../../utils/package';
|
||||
import { PackageInterface } from '../Package/Package';
|
||||
import Divider from '../../muiComponents/Divider';
|
||||
|
||||
import * as classes from './styles';
|
||||
|
||||
@@ -12,36 +12,26 @@ interface Props {
|
||||
packages: PackageInterface[];
|
||||
}
|
||||
|
||||
export default class PackageList extends React.Component<Props, {}> {
|
||||
public render(): ReactElement<HTMLElement> {
|
||||
return (
|
||||
<div className={'package-list-items'}>
|
||||
<div className={classes.pkgContainer}>{this.hasPackages() ? this.renderPackages() : <Help />}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private hasPackages(): boolean {
|
||||
const { packages } = this.props;
|
||||
return packages.length > 0;
|
||||
}
|
||||
|
||||
private renderPackages = () => {
|
||||
return <>{this.renderList()}</>;
|
||||
};
|
||||
|
||||
private renderList = () => {
|
||||
const { packages } = this.props;
|
||||
return packages.map((pkg, i) => {
|
||||
export const PackageList: React.FC<Props> = props => {
|
||||
const renderPackages: () => React.ReactElement<HTMLElement>[] = () => {
|
||||
return props.packages.map((pkg, i) => {
|
||||
const { name, version, description, time, keywords, dist, homepage, bugs, author } = pkg;
|
||||
// TODO: move format license to API side.
|
||||
const license = formatLicense(pkg.license);
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<React.Fragment key={i}>
|
||||
{i !== 0 && <Divider />}
|
||||
<Package {...{ name, dist, version, author, description, license, time, keywords, homepage, bugs }} />
|
||||
</Fragment>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const hasPackages: () => boolean = () => props.packages.length > 0;
|
||||
|
||||
return (
|
||||
<div className={'package-list-items'}>
|
||||
<div className={classes.pkgContainer}>{hasPackages() ? renderPackages() : <Help />}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import Help from '../Help';
|
||||
|
||||
import PackageList from './PackageList';
|
||||
import { PackageList } from './PackageList';
|
||||
|
||||
describe('<PackageList /> component', () => {
|
||||
test('should load the component with no packages', () => {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { default } from './PackageList';
|
||||
export { PackageList } from './PackageList';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import RegistryInfoContent from './RegistryInfoContent';
|
||||
|
||||
describe('<RegistryInfoContent /> component', () => {
|
||||
test('should render the component in default state with npm tab', () => {
|
||||
const wrapper = mount(<RegistryInfoContent registryUrl="https://registry.verdaccio.org" scope="@" />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the component in default state with pnpm tab', () => {
|
||||
const wrapper = mount(<RegistryInfoContent registryUrl="https://registry.verdaccio.org" scope="@" />);
|
||||
wrapper.setState({ tabPosition: 1 });
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the component in default state with yarn tab', () => {
|
||||
const wrapper = mount(<RegistryInfoContent registryUrl="https://registry.verdaccio.org" scope="@" />);
|
||||
wrapper.setState({ tabPosition: 2 });
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RegistryInfoContent /> component should render the component in default state with npm tab 1`] = `"<div><div class=\\"MuiTabs-root\\"><div class=\\"MuiTabs-scroller MuiTabs-fixed\\" style=\\"overflow: hidden;\\"><div class=\\"MuiTabs-flexContainer\\" role=\\"tablist\\"><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"true\\"><span class=\\"MuiTab-wrapper\\">npm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">pnpm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">yarn</span><span class=\\"MuiTouchRipple-root\\"></span></button></div><span class=\\"PrivateTabIndicator-root-25 PrivateTabIndicator-colorPrimary-26 MuiTabs-indicator\\" style=\\"left: 0px; width: 0px;\\"></span></div></div><div class=\\"css-1qvg11o ehfwshd0\\"><div class=\\"MuiTypography-root css-17iubtz MuiTypography-h6\\"><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm set @registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm adduser --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm profile set password --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div></div></div></div>"`;
|
||||
|
||||
exports[`<RegistryInfoContent /> component should render the component in default state with pnpm tab 1`] = `"<div><div class=\\"MuiTabs-root\\"><div class=\\"MuiTabs-scroller MuiTabs-fixed\\" style=\\"overflow: hidden;\\"><div class=\\"MuiTabs-flexContainer\\" role=\\"tablist\\"><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">npm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"true\\"><span class=\\"MuiTab-wrapper\\">pnpm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">yarn</span><span class=\\"MuiTouchRipple-root\\"></span></button></div><span class=\\"PrivateTabIndicator-root-25 PrivateTabIndicator-colorPrimary-26 MuiTabs-indicator\\" style=\\"left: 0px; width: 0px;\\"></span></div></div><div class=\\"css-1qvg11o ehfwshd0\\"><div class=\\"MuiTypography-root css-17iubtz MuiTypography-h6\\"><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">pnpm set @registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">pnpm adduser --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">pnpm profile set password --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div></div></div></div>"`;
|
||||
|
||||
exports[`<RegistryInfoContent /> component should render the component in default state with yarn tab 1`] = `"<div><div class=\\"MuiTabs-root\\"><div class=\\"MuiTabs-scroller MuiTabs-fixed\\" style=\\"overflow: hidden;\\"><div class=\\"MuiTabs-flexContainer\\" role=\\"tablist\\"><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">npm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">pnpm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"true\\"><span class=\\"MuiTab-wrapper\\">yarn</span><span class=\\"MuiTouchRipple-root\\"></span></button></div><span class=\\"PrivateTabIndicator-root-25 PrivateTabIndicator-colorPrimary-26 MuiTabs-indicator\\" style=\\"left: 0px; width: 0px;\\"></span></div></div><div class=\\"css-1qvg11o ehfwshd0\\"><div class=\\"MuiTypography-root css-17iubtz MuiTypography-h6\\"><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">yarn config set @registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div></div></div></div>"`;
|
||||
@@ -139,7 +139,6 @@ export class Search extends Component<RouteComponentProps<{}>, State> {
|
||||
*/
|
||||
private handleFetchPackages: handleFetchPackages = async ({ value }) => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const controller = new window.AbortController();
|
||||
const signal = controller.signal;
|
||||
// Keep track of search requests.
|
||||
|
||||
@@ -2,10 +2,26 @@ import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Spinner from './Spinner';
|
||||
import { Circular, Wrapper } from './styles';
|
||||
|
||||
describe('<Spinner /> component', () => {
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = shallow(<Spinner />);
|
||||
const wrapperComponent = wrapper.find(Wrapper);
|
||||
const circular = wrapper.find(Circular);
|
||||
|
||||
expect(circular.props().size).toBe(50);
|
||||
expect(wrapperComponent.props().centered).toBe(false);
|
||||
});
|
||||
|
||||
test('should render the component in custom state', () => {
|
||||
const wrapper = shallow(<Spinner centered={true} size={10} />);
|
||||
const wrapperComponent = wrapper.find(Wrapper);
|
||||
const circular = wrapper.find(Circular);
|
||||
|
||||
expect(circular.props().size).toBe(10);
|
||||
expect(wrapperComponent.props().centered).toBe(true);
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Spinner /> component should render the component in default state 1`] = `"<div class=\\"css-vqrgi e1ag4h8b0\\"><div class=\\"MuiCircularProgress-root css-15gl0ho e1ag4h8b1 MuiCircularProgress-colorPrimary MuiCircularProgress-indeterminate\\" style=\\"width:50px;height:50px\\" role=\\"progressbar\\"><svg class=\\"MuiCircularProgress-svg\\" viewBox=\\"22 22 44 44\\"><circle class=\\"MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate\\" cx=\\"44\\" cy=\\"44\\" r=\\"20.2\\" fill=\\"none\\" stroke-width=\\"3.6\\"></circle></svg></div></div>"`;
|
||||
exports[`<Spinner /> component should render the component in custom state 1`] = `"<div class=\\"css-zo92co e1ag4h8b0\\"><div class=\\"MuiCircularProgress-root css-15gl0ho e1ag4h8b1 MuiCircularProgress-colorPrimary MuiCircularProgress-indeterminate\\" style=\\"width:10px;height:10px\\" role=\\"progressbar\\"><svg class=\\"MuiCircularProgress-svg\\" viewBox=\\"22 22 44 44\\"><circle class=\\"MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate\\" cx=\\"44\\" cy=\\"44\\" r=\\"20.2\\" fill=\\"none\\" stroke-width=\\"3.6\\"></circle></svg></div></div>"`;
|
||||
|
||||
@@ -6,8 +6,26 @@ import { DetailContext } from '../../pages/Version';
|
||||
import UpLinks from './UpLinks';
|
||||
|
||||
describe('<UpLinks /> component', () => {
|
||||
test('should render the component in default state', () => {
|
||||
test('should return null without packageMeta', () => {
|
||||
const wrapper = shallow(<UpLinks />);
|
||||
expect(wrapper.html()).toBeNull();
|
||||
});
|
||||
|
||||
test('should render the component when there is no uplink', () => {
|
||||
const packageMeta = {
|
||||
latest: {
|
||||
name: 'verdaccio',
|
||||
version: '4.0.0',
|
||||
dist: { fileCount: 0, unpackedSize: 0 },
|
||||
},
|
||||
_uplinks: {},
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<DetailContext.Provider value={{ packageMeta }}>
|
||||
<UpLinks />
|
||||
</DetailContext.Provider>
|
||||
);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UpLinks /> component should render the component in default state 1`] = `null`;
|
||||
exports[`<UpLinks /> component should render the component when there is no uplink 1`] = `"<h6 class=\\"MuiTypography-root MuiTypography-subtitle1 MuiTypography-gutterBottom\\">verdaccio has no uplinks.</h6>"`;
|
||||
|
||||
exports[`<UpLinks /> component should render the component with uplinks 1`] = `"<h6 class=\\"MuiTypography-root css-1vg6q84 e14i1sy10 MuiTypography-subtitle1\\">Uplinks</h6><ul class=\\"MuiList-root MuiList-padding\\"><li class=\\"MuiListItem-root MuiListItem-gutters\\"><div class=\\"MuiListItemText-root css-5tz9yo e14i1sy12\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">npmjs</span></div><div class=\\"css-1l1cv61 e14i1sy11\\"></div><div class=\\"MuiListItemText-root css-5tz9yo e14i1sy12\\"><span class=\\"MuiTypography-root MuiListItemText-primary MuiTypography-body1\\">over 1 year ago</span></div></li></ul>"`;
|
||||
|
||||
10
src/muiComponents/Card/Card.tsx
Normal file
10
src/muiComponents/Card/Card.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { default as MaterialUICard, CardProps } from '@material-ui/core/Card';
|
||||
|
||||
type CardRef = HTMLDivElement;
|
||||
|
||||
const Card = forwardRef<CardRef, CardProps>(function Card(props, ref) {
|
||||
return <MaterialUICard {...props} ref={ref} />;
|
||||
});
|
||||
|
||||
export default Card;
|
||||
1
src/muiComponents/Card/index.ts
Normal file
1
src/muiComponents/Card/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Card';
|
||||
10
src/muiComponents/Divider/Divider.tsx
Normal file
10
src/muiComponents/Divider/Divider.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { default as MaterialUIDivider, DividerProps } from '@material-ui/core/Divider';
|
||||
|
||||
type DividerRef = keyof HTMLElementTagNameMap;
|
||||
|
||||
const Divider = forwardRef<DividerRef, DividerProps>(function Divider(props, ref) {
|
||||
return <MaterialUIDivider {...props} innerRef={ref} />;
|
||||
});
|
||||
|
||||
export default Divider;
|
||||
1
src/muiComponents/Divider/index.ts
Normal file
1
src/muiComponents/Divider/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Divider';
|
||||
@@ -1,92 +1,59 @@
|
||||
import React from 'react';
|
||||
import { render, cleanup } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { render } from '@testing-library/react';
|
||||
import { waitForElement } from '@testing-library/dom';
|
||||
|
||||
import vueMetadata from '../../../test/fixtures/metadata/vue.json';
|
||||
import ErrorBoundary from '../../App/AppError';
|
||||
import { NOT_FOUND_TEXT } from '../../components/NotFound';
|
||||
|
||||
import Version from './Version';
|
||||
import { DetailContext } from './context';
|
||||
import data from './__partials__/data.json';
|
||||
|
||||
// :-) we mock this otherways fails on render, some weird issue on material-ui
|
||||
jest.mock('../../muiComponents/Avatar');
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
jest.mock('../../components/NotFound', () => () => <div>{'Not found'}</div>);
|
||||
const detailContextValue = {
|
||||
packageName: 'foo',
|
||||
packageMeta: data,
|
||||
readMe: 'Read me!',
|
||||
enableLoading: jest.fn(),
|
||||
isLoading: false,
|
||||
hasNotBeenFound: false,
|
||||
version: '1.0.0',
|
||||
};
|
||||
|
||||
describe('test Version page', () => {
|
||||
jest.setTimeout(40000000);
|
||||
beforeAll(() => {
|
||||
// FIXME: a better way to mock this
|
||||
// @ts-ignore
|
||||
global.window.VERDACCIO_API_URL = 'http://test';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
// @ts-ignore
|
||||
fetch.resetMocks();
|
||||
});
|
||||
|
||||
/* eslint-disable react/jsx-max-depth */
|
||||
test('should render the version page', async () => {
|
||||
const readmeText = 'test';
|
||||
// @ts-ignore
|
||||
fetch.mockResponses(
|
||||
[[JSON.stringify(vueMetadata)], { status: 200, headers: { 'content-type': 'application/json' } }],
|
||||
[[`<p align="center">${readmeText}</p>`], { status: 200, headers: { 'content-type': 'text/html' } }]
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = render(
|
||||
<ErrorBoundary>
|
||||
<MemoryRouter>
|
||||
<Version match={{ params: { ['package']: 'vue' } }}></Version>
|
||||
</MemoryRouter>
|
||||
</ErrorBoundary>
|
||||
<MemoryRouter>
|
||||
<DetailContext.Provider value={detailContextValue}>
|
||||
<Version />
|
||||
</DetailContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// first it display loading
|
||||
const hasLoading = getByTestId('loading');
|
||||
expect(hasLoading).toBeTruthy();
|
||||
|
||||
// we wait fetch response (mocked above)
|
||||
await waitForElement(() => getByTestId('version-layout'));
|
||||
|
||||
// check whether readme was loaded
|
||||
const hasReadme = getByText(readmeText);
|
||||
|
||||
const hasReadme = getByText(detailContextValue.readMe);
|
||||
expect(hasReadme).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render 404 page if the resources are not found', async () => {
|
||||
// @ts-ignore
|
||||
fetch.mockResponses(
|
||||
[[JSON.stringify({})], { status: 404, headers: { 'content-type': 'application/json' } }],
|
||||
[[``], { status: 404, headers: { 'content-type': 'text/html' } }]
|
||||
const { getByText } = render(
|
||||
<MemoryRouter>
|
||||
<DetailContext.Provider
|
||||
value={{
|
||||
...detailContextValue,
|
||||
hasNotBeenFound: true,
|
||||
}}>
|
||||
<Version />
|
||||
</DetailContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const { getByTestId, getByText } = render(
|
||||
<ErrorBoundary>
|
||||
<MemoryRouter>
|
||||
<Version match={{ params: { ['package']: 'vue' } }}></Version>
|
||||
</MemoryRouter>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
// first it display loading
|
||||
const hasLoading = getByTestId('loading');
|
||||
expect(hasLoading).toBeTruthy();
|
||||
|
||||
// we wait fetch response (mocked above)
|
||||
await waitForElement(() => getByText('Not found'));
|
||||
|
||||
// check whether readme was loaded
|
||||
const hasReadme = getByText('Not found');
|
||||
|
||||
expect(hasReadme).toBeTruthy();
|
||||
const notFoundElement = await waitForElement(() => getByText(NOT_FOUND_TEXT));
|
||||
expect(notFoundElement).toBeTruthy();
|
||||
});
|
||||
|
||||
// Wanna contribute? Here we some scenarios we need to test
|
||||
|
||||
@@ -1,98 +1,24 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { callDetailPage, callReadme } from '../../utils/calls';
|
||||
import { buildScopePackage } from '../../utils/package';
|
||||
import Loading from '../../components/Loading/Loading';
|
||||
import NotFound from '../../components/NotFound';
|
||||
|
||||
import { Layout } from './Layout';
|
||||
import { DetailContextProvider } from './context';
|
||||
import { StateInterface } from './types';
|
||||
import VersionLayout from './VersionLayout';
|
||||
import { DetailContext } from './context';
|
||||
|
||||
export function getRouterPackageName(params): string {
|
||||
const packageName = params.package;
|
||||
const { scope } = params;
|
||||
if (scope) {
|
||||
return buildScopePackage(scope, packageName);
|
||||
const Version: React.FC = () => {
|
||||
const detailContext = useContext(DetailContext);
|
||||
const { isLoading, hasNotBeenFound } = detailContext;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
function fillTitle(text: string): string {
|
||||
return `Verdaccio - ${text}`;
|
||||
}
|
||||
|
||||
function isVersionValid(packageMeta, packageVersion): boolean {
|
||||
const hasVersion = typeof packageVersion !== 'undefined';
|
||||
if (!hasVersion) {
|
||||
// if is undefined, that means versions does not exist, we continue
|
||||
return true;
|
||||
if (hasNotBeenFound) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
const hasMatchVersion = Object.keys(packageMeta.versions).includes(packageVersion);
|
||||
return hasMatchVersion;
|
||||
}
|
||||
|
||||
const Version = ({ match: { params } }) => {
|
||||
const pkgName = getRouterPackageName(params);
|
||||
const [readMe, setReadme] = useState();
|
||||
const [packageName, setPackageName] = useState(pkgName);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [packageVersion, setPackageVersion] = useState(params.version);
|
||||
const [packageMeta, setPackageMeta] = useState();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const packageMeta = (await callDetailPage(packageName, packageVersion)) as Partial<StateInterface>;
|
||||
const readMe = (await callReadme(packageName, packageVersion)) as Partial<StateInterface>;
|
||||
if (isVersionValid(packageMeta, packageVersion)) {
|
||||
setReadme(readMe);
|
||||
setPackageMeta(packageMeta);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setNotFound(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setNotFound(true);
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [packageName, packageVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!packageVersion) {
|
||||
document.title = fillTitle(packageName);
|
||||
} else {
|
||||
document.title = fillTitle(`${packageName}@${packageVersion}`);
|
||||
}
|
||||
}, [packageName, packageVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
const pkgName = getRouterPackageName(params);
|
||||
|
||||
setPackageName(pkgName);
|
||||
setPackageVersion(params.version);
|
||||
}, [params, setPackageName, setPackageVersion]);
|
||||
|
||||
const isNotFound = notFound || typeof packageMeta === 'undefined' || typeof packageName === 'undefined' || typeof readMe === 'undefined';
|
||||
const renderContent = (): React.ReactElement<HTMLElement> => {
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
} else if (isNotFound) {
|
||||
return <NotFound />;
|
||||
} else {
|
||||
return <Layout />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DetailContextProvider value={{ packageMeta, packageVersion, readMe, packageName, enableLoading: setIsLoading }}>{renderContent()}</DetailContextProvider>
|
||||
);
|
||||
return <VersionLayout />;
|
||||
};
|
||||
|
||||
export default Version;
|
||||
|
||||
69
src/pages/Version/VersionContextProvider.tsx
Normal file
69
src/pages/Version/VersionContextProvider.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { callDetailPage, callReadme } from '../../utils/calls';
|
||||
|
||||
import getRouterPackageName from './get-route-package-name';
|
||||
import { DetailContext } from './context';
|
||||
import isPackageVersionValid from './is-package-version-valid';
|
||||
|
||||
interface Params {
|
||||
scope?: string;
|
||||
package: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
const VersionContextProvider: React.FC = ({ children }) => {
|
||||
const { version, package: pkgName, scope } = useParams<Params>();
|
||||
const [packageName, setPackageName] = useState(getRouterPackageName(pkgName, scope));
|
||||
const [packageVersion, setPackageVersion] = useState(version);
|
||||
const [packageMeta, setPackageMeta] = useState();
|
||||
const [readMe, setReadme] = useState();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [hasNotBeenFound, setHasNotBeenFound] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const updatedPackageName = getRouterPackageName(pkgName, scope);
|
||||
setPackageName(updatedPackageName);
|
||||
}, [pkgName, scope]);
|
||||
|
||||
useEffect(() => {
|
||||
setPackageVersion(version);
|
||||
}, [version]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const packageMeta = await callDetailPage(packageName, packageVersion);
|
||||
const readMe = await callReadme(packageName, packageVersion);
|
||||
if (isPackageVersionValid(packageMeta, packageVersion)) {
|
||||
setReadme(readMe);
|
||||
setPackageMeta(packageMeta);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
setHasNotBeenFound(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setHasNotBeenFound(true);
|
||||
setIsLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [packageName, packageVersion]);
|
||||
|
||||
return (
|
||||
<DetailContext.Provider
|
||||
value={{
|
||||
packageMeta,
|
||||
packageVersion,
|
||||
readMe,
|
||||
packageName,
|
||||
isLoading,
|
||||
hasNotBeenFound,
|
||||
}}>
|
||||
{children}
|
||||
</DetailContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionContextProvider;
|
||||
@@ -1,28 +1,20 @@
|
||||
import React, { FC, ReactElement } from 'react';
|
||||
import React from 'react';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
|
||||
import DetailContainer from '../../components/DetailContainer';
|
||||
import DetailSidebar from '../../components/DetailSidebar';
|
||||
|
||||
function renderDetail(): ReactElement<HTMLElement> {
|
||||
return <DetailContainer />;
|
||||
}
|
||||
|
||||
function renderSidebar(): ReactElement<HTMLElement> {
|
||||
return <DetailSidebar />;
|
||||
}
|
||||
|
||||
const Layout: FC<{}> = () => {
|
||||
const VersionLayout: React.FC = () => {
|
||||
return (
|
||||
<Grid className={'container content'} container={true} data-testid="version-layout" spacing={0}>
|
||||
<Grid item={true} md={8} xs={12}>
|
||||
{renderDetail()}
|
||||
<DetailContainer />
|
||||
</Grid>
|
||||
<Grid item={true} md={4} xs={12}>
|
||||
{renderSidebar()}
|
||||
<DetailSidebar />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export { Layout };
|
||||
export default VersionLayout;
|
||||
6406
src/pages/Version/__partials__/data.json
Normal file
6406
src/pages/Version/__partials__/data.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,26 @@
|
||||
import React, { Consumer, Provider } from 'react';
|
||||
import { createContext, Consumer, Provider } from 'react';
|
||||
|
||||
import { DetailContextProps, VersionPageConsumerProps } from './types';
|
||||
import { PackageMetaInterface } from '../../../types/packageMeta';
|
||||
export interface DetailContextProps {
|
||||
packageMeta: PackageMetaInterface;
|
||||
packageVersion?: string;
|
||||
readMe: string;
|
||||
packageName: string;
|
||||
enableLoading: () => void;
|
||||
isLoading: boolean;
|
||||
hasNotBeenFound: boolean;
|
||||
}
|
||||
|
||||
export const DetailContext = React.createContext<Partial<DetailContextProps>>({});
|
||||
export interface VersionPageConsumerProps {
|
||||
packageMeta: PackageMetaInterface;
|
||||
readMe: string;
|
||||
packageName: string;
|
||||
packageVersion?: string;
|
||||
// FIXME: looking for the appropiated type here
|
||||
enableLoading: any;
|
||||
}
|
||||
|
||||
export const DetailContext = createContext<Partial<DetailContextProps>>({});
|
||||
|
||||
export const DetailContextProvider: Provider<Partial<VersionPageConsumerProps>> = DetailContext.Provider;
|
||||
export const DetailContextConsumer: Consumer<Partial<VersionPageConsumerProps>> = DetailContext.Consumer;
|
||||
|
||||
9
src/pages/Version/get-route-package-name.ts
Normal file
9
src/pages/Version/get-route-package-name.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
function getRouterPackageName(packageName: string, scope?: string): string {
|
||||
if (scope) {
|
||||
return `@${scope}/${packageName}`;
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
export default getRouterPackageName;
|
||||
@@ -1,3 +1,2 @@
|
||||
export { DetailContextProps, VersionPageConsumerProps } from './types';
|
||||
export { DetailContext, DetailContextConsumer, DetailContextProvider } from './context';
|
||||
export { DetailContext, DetailContextConsumer, DetailContextProvider, DetailContextProps, VersionPageConsumerProps } from './context';
|
||||
export { default } from './Version';
|
||||
|
||||
16
src/pages/Version/is-package-version-valid.ts
Normal file
16
src/pages/Version/is-package-version-valid.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { PackageMetaInterface } from '../../../types/packageMeta';
|
||||
|
||||
function isPackageVersionValid(packageMeta: Partial<PackageMetaInterface>, packageVersion?: string): boolean {
|
||||
if (!packageVersion || typeof packageVersion === 'undefined') {
|
||||
// if is undefined, that means versions does not exist, we continue
|
||||
return true;
|
||||
}
|
||||
|
||||
if (packageMeta.versions) {
|
||||
return Object.keys(packageMeta.versions).includes(packageVersion);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default isPackageVersionValid;
|
||||
@@ -1,11 +0,0 @@
|
||||
import styled from 'react-emotion';
|
||||
|
||||
import colors from '../../utils/styles/colors';
|
||||
import { fontSize } from '../../utils/styles/sizes';
|
||||
import DialogTitle from '../../muiComponents/DialogTitle';
|
||||
|
||||
export const Title = styled(DialogTitle)({
|
||||
backgroundColor: colors.primary,
|
||||
color: colors.white,
|
||||
fontSize: fontSize.lg,
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import { PackageMetaInterface } from '../../../types/packageMeta';
|
||||
|
||||
export interface DetailContextProps {
|
||||
packageMeta: PackageMetaInterface;
|
||||
packageVersion?: string;
|
||||
readMe: string;
|
||||
packageName: string;
|
||||
enableLoading: () => void;
|
||||
}
|
||||
|
||||
export interface VersionPageConsumerProps {
|
||||
packageMeta: PackageMetaInterface;
|
||||
readMe: string;
|
||||
packageName: string;
|
||||
packageVersion?: string;
|
||||
// FIXME: looking for the appropiated type here
|
||||
enableLoading: any;
|
||||
}
|
||||
|
||||
export interface PropsInterface {
|
||||
match: boolean;
|
||||
}
|
||||
|
||||
export interface StateInterface {
|
||||
readMe: string;
|
||||
packageName: string;
|
||||
packageMeta?: PackageMetaInterface;
|
||||
isLoading: boolean;
|
||||
notFound: boolean;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import PackageList from '../../components/PackageList';
|
||||
import { PackageList } from '../../components/PackageList';
|
||||
import { PackageInterface } from '../../components/Package/Package';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
/* eslint react/jsx-max-depth:0 */
|
||||
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
import { Router, Route, Switch } from 'react-router-dom';
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
import { AppContextConsumer } from './App/App';
|
||||
import Header from './components/Header';
|
||||
|
||||
const history = createBrowserHistory({
|
||||
basename: window.__VERDACCIO_BASENAME_UI_OPTIONS && window.__VERDACCIO_BASENAME_UI_OPTIONS.url_prefix,
|
||||
});
|
||||
|
||||
const NotFound = React.lazy(() => import('./components/NotFound'));
|
||||
const VersionPackage = React.lazy(() => import('./pages/Version'));
|
||||
const HomePage = React.lazy(() => import('./pages/home'));
|
||||
|
||||
interface RouterAppProps {
|
||||
onLogout: () => void;
|
||||
onToggleLoginModal: () => void;
|
||||
}
|
||||
|
||||
class RouterApp extends Component<RouterAppProps> {
|
||||
public render(): ReactElement<HTMLDivElement> {
|
||||
return (
|
||||
<Router history={history}>
|
||||
<React.Suspense fallback={null}>
|
||||
{this.renderHeader()}
|
||||
<Switch>
|
||||
<Route exact={true} path={'/'} render={this.renderHomePage} />
|
||||
<Route exact={true} path={'/-/web/detail/@:scope/:package'} render={this.renderVersionPage} />
|
||||
<Route exact={true} path={'/-/web/detail/:package'} render={this.renderVersionPage} />
|
||||
<Route exact={true} path={'/-/web/detail/:package/v/:version'} render={this.renderVersionPage} />
|
||||
<Route exact={true} path={'/-/web/detail/@:scope/:package/v/:version'} render={this.renderVersionPage} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</React.Suspense>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
public renderHeader = (): ReactElement<HTMLDivElement> => {
|
||||
const { onLogout, onToggleLoginModal } = this.props;
|
||||
|
||||
return (
|
||||
<AppContextConsumer>
|
||||
{function renderConsumerVersionPage({ logoUrl, scope = '', user }) {
|
||||
return <Header logo={logoUrl} onLogout={onLogout} onToggleLoginModal={onToggleLoginModal} scope={scope} username={user && user.username} />;
|
||||
}}
|
||||
</AppContextConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
public renderHomePage = (): ReactElement<HTMLDivElement> => {
|
||||
return (
|
||||
<AppContextConsumer>
|
||||
{function renderConsumerVersionPage({ isUserLoggedIn, packages }) {
|
||||
return <HomePage isUserLoggedIn={isUserLoggedIn} packages={packages} />;
|
||||
}}
|
||||
</AppContextConsumer>
|
||||
);
|
||||
};
|
||||
|
||||
public renderVersionPage = (routerProps): ReactElement<HTMLDivElement> => {
|
||||
return (
|
||||
<AppContextConsumer>
|
||||
{function renderConsumerVersionPage({ isUserLoggedIn }) {
|
||||
return <VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />;
|
||||
}}
|
||||
</AppContextConsumer>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default RouterApp;
|
||||
@@ -3,22 +3,12 @@
|
||||
import { handleResponseType } from '../../src/utils/api';
|
||||
|
||||
describe('api', () => {
|
||||
// no the best mock, but I'd look for a mock library to work with fetch in the future
|
||||
// @ts-ignore
|
||||
const headers: Headers = {
|
||||
// @ts-ignore
|
||||
get: () => [],
|
||||
};
|
||||
|
||||
describe('handleResponseType', () => {
|
||||
test('should handle missing Content-Type', async () => {
|
||||
const response: Response = {
|
||||
url: 'http://localhost:8080/-/packages',
|
||||
ok: false,
|
||||
// @ts-ignore
|
||||
headers: {
|
||||
get: () => null,
|
||||
} as Headers,
|
||||
headers: new Headers(),
|
||||
} as Response;
|
||||
|
||||
const handled = await handleResponseType(response);
|
||||
@@ -34,7 +24,7 @@ describe('api', () => {
|
||||
url: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
||||
blob: () => blobPromise,
|
||||
ok: true,
|
||||
headers,
|
||||
headers: new Headers(),
|
||||
} as Response;
|
||||
const handled = await handleResponseType(response);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import '../../types';
|
||||
* @param {object} response
|
||||
* @returns {promise}
|
||||
*/
|
||||
export function handleResponseType(response: Response): Promise<[boolean, Blob | string]> | Promise<void> {
|
||||
export function handleResponseType(response: Response): Promise<[boolean, Blob | string] | void> {
|
||||
if (response.headers) {
|
||||
const contentType = response.headers.get('Content-Type');
|
||||
if (contentType && contentType.includes('application/pdf')) {
|
||||
@@ -52,7 +52,6 @@ class API {
|
||||
credentials: 'same-origin',
|
||||
...options,
|
||||
})
|
||||
// @ts-ignore
|
||||
.then(handleResponseType)
|
||||
.then(response => {
|
||||
if (response[0]) {
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function callDetailPage(packageName: string, packageVersion?: strin
|
||||
return packageMeta;
|
||||
}
|
||||
|
||||
export function callSearch(value: string, signal: any) {
|
||||
export function callSearch(value: string, signal: AbortSignal) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility
|
||||
// FUTURE: signal is not well supported for IE and Samsung Browser
|
||||
return API.request(`search/${encodeURIComponent(value)}`, 'GET', { signal, headers: {} });
|
||||
|
||||
49
src/utils/cli-utils.test.ts
Normal file
49
src/utils/cli-utils.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { SyntheticEvent } from 'react';
|
||||
|
||||
import { copyToClipBoardUtility } from './cli-utils';
|
||||
|
||||
describe('copyToClipBoardUtility', () => {
|
||||
let originalGetSelection;
|
||||
|
||||
const mockGetSelectionResult = {
|
||||
removeAllRanges: jest.fn(),
|
||||
addRange: jest.fn(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
originalGetSelection = window.getSelection;
|
||||
|
||||
window.getSelection = jest.fn().mockReturnValue(mockGetSelectionResult);
|
||||
});
|
||||
afterEach(() => {
|
||||
window.getSelection = originalGetSelection;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('should call the DOM APIs', () => {
|
||||
// Given
|
||||
const testEvent: { preventDefault: Function } = {
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
const testCopy = 'copy text';
|
||||
const spys = {
|
||||
createElement: jest.spyOn(document, 'createElement'),
|
||||
execCommand: jest.spyOn(document, 'execCommand'),
|
||||
appendChild: jest.spyOn(document.body, 'appendChild'),
|
||||
removeChild: jest.spyOn(document.body, 'removeChild'),
|
||||
};
|
||||
const expectedDiv = document.createElement('div');
|
||||
expectedDiv.innerText = testCopy;
|
||||
|
||||
// When
|
||||
const copyFunc = copyToClipBoardUtility(testCopy);
|
||||
copyFunc(testEvent as SyntheticEvent<HTMLElement>);
|
||||
|
||||
// Then
|
||||
expect(mockGetSelectionResult.removeAllRanges).toHaveBeenCalledWith();
|
||||
expect(mockGetSelectionResult.addRange).toHaveBeenCalled();
|
||||
expect(spys.createElement).toHaveBeenCalledWith('div');
|
||||
expect(spys.appendChild).toHaveBeenCalledWith(expectedDiv);
|
||||
expect(spys.execCommand).toHaveBeenCalledWith('copy');
|
||||
expect(spys.removeChild).toHaveBeenCalledWith(expectedDiv);
|
||||
});
|
||||
});
|
||||
@@ -59,10 +59,6 @@ export function formatDateDistance(lastUpdate: Date | string | number): string {
|
||||
return distanceInWordsToNow(new Date(lastUpdate));
|
||||
}
|
||||
|
||||
export function buildScopePackage(scope: string, packageName: string): string {
|
||||
return `@${scope}/${packageName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* For <LastSync /> component
|
||||
* @param {array} uplinks
|
||||
|
||||
@@ -35,6 +35,7 @@ export function downloadFile(fileStream: Blob, fileName: string): void {
|
||||
let file: File;
|
||||
// File constructor is not supported by Edge
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/File#Browser_compatibility
|
||||
// @ts-ignore. Please see: https://github.com/microsoft/TypeScript/issues/33792
|
||||
if (navigator.msSaveBlob) {
|
||||
// Detect if Edge
|
||||
file = blobToFile(new Blob([fileStream], { type: 'application/octet-stream' }), fileName);
|
||||
|
||||
Reference in New Issue
Block a user