mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-02-21 07:29:37 +01:00
refactor: rematch as web state storage for UI (#2447)
* trying rematch refactor: rematch for store packages migrate login to rematch Update packages/plugins/ui-theme/src/store/store.ts Co-authored-by: Sergio Moreno <sergiomorenoalbert@gmail.com> hide temporary fix test for login migrate package download resource fix tests * add missing fixture * migrate detail page support * fix tests * migrate search * migrate search * clean up tests * remove tags * fix lint * add changeset * fix: search model typings * add type * types * apply suggestions Co-authored-by: Sergio Moreno <22656541+semoal@users.noreply.github.com>
This commit is contained in:
parent
9dbf73e955
commit
5fed1955a9
5
.changeset/olive-candles-speak.md
Normal file
5
.changeset/olive-candles-speak.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@verdaccio/ui-theme': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: integrate rematch for ui state storage
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -48,5 +48,5 @@ hyper-results*.json
|
|||||||
api-results*.json
|
api-results*.json
|
||||||
|
|
||||||
#docs
|
#docs
|
||||||
api/
|
./api
|
||||||
packages/core/core/docs
|
packages/core/core/docs
|
||||||
|
@ -85,7 +85,6 @@
|
|||||||
"jest-environment-jsdom": "27.1.0",
|
"jest-environment-jsdom": "27.1.0",
|
||||||
"jest-environment-jsdom-global": "3.0.0",
|
"jest-environment-jsdom-global": "3.0.0",
|
||||||
"jest-environment-node": "27.1.0",
|
"jest-environment-node": "27.1.0",
|
||||||
"jest-fetch-mock": "3.0.3",
|
|
||||||
"jest-junit": "12.2.0",
|
"jest-junit": "12.2.0",
|
||||||
"kleur": "3.0.3",
|
"kleur": "3.0.3",
|
||||||
"lint-staged": "11.1.2",
|
"lint-staged": "11.1.2",
|
||||||
@ -123,7 +122,7 @@
|
|||||||
"benchmark:submit": "pnpm ts-node ./scripts/submit-metrics.ts",
|
"benchmark:submit": "pnpm ts-node ./scripts/submit-metrics.ts",
|
||||||
"start:watch": "concurrently --kill-others \"pnpm _build:watch\" \"pnpm _start:server\" \"pnpm _debug:reload\"",
|
"start:watch": "concurrently --kill-others \"pnpm _build:watch\" \"pnpm _start:server\" \"pnpm _debug:reload\"",
|
||||||
"_build:watch": "pnpm run --parallel watch --filter ./packages",
|
"_build:watch": "pnpm run --parallel watch --filter ./packages",
|
||||||
"_start:server": "node packages/verdaccio/debug/bootstrap.js --listen 8000",
|
"_start:server": "node --inspect packages/verdaccio/debug/bootstrap.js --listen 8000",
|
||||||
"_start:web": "pnpm start --filter ...@verdaccio/ui-theme",
|
"_start:web": "pnpm start --filter ...@verdaccio/ui-theme",
|
||||||
"_debug:reload": "nodemon -d 3 packages/verdaccio/debug/bootstrap.js",
|
"_debug:reload": "nodemon -d 3 packages/verdaccio/debug/bootstrap.js",
|
||||||
"start:ts": "ts-node packages/verdaccio/src/start.ts -- --listen 8000",
|
"start:ts": "ts-node packages/verdaccio/src/start.ts -- --listen 8000",
|
||||||
@ -138,7 +137,7 @@
|
|||||||
"ts:ref": "update-ts-references --discardComments",
|
"ts:ref": "update-ts-references --discardComments",
|
||||||
"website": "pnpm build --filter ...@verdaccio/website",
|
"website": "pnpm build --filter ...@verdaccio/website",
|
||||||
"crowdin:upload": "crowdin upload sources --auto-update --config ./crowdin.yaml",
|
"crowdin:upload": "crowdin upload sources --auto-update --config ./crowdin.yaml",
|
||||||
"crowdin:download": "crowdin download --config ./crowdin.yaml",
|
"crowdin:download": "crowdin download --verbose --config ./crowdin.yaml",
|
||||||
"crowdin:sync": "pnpm crowdin:upload && pnpm crowdin:download --verbose",
|
"crowdin:sync": "pnpm crowdin:upload && pnpm crowdin:download --verbose",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
},
|
},
|
||||||
|
42
packages/plugins/ui-theme/jest/api/packages.json
Normal file
42
packages/plugins/ui-theme/jest/api/packages.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "1.0.22",
|
||||||
|
"description": "test",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "git+https://github.com/test/test.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": {
|
||||||
|
"name": "",
|
||||||
|
"email": "",
|
||||||
|
"url": "",
|
||||||
|
"avatar": "data:image/svg+xml;utf8,%3Csvg%20height%3D%22100%22%20viewBox%3D%22-27%2024%20100%20100%22%20width%3D%22100%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cdefs%3E%3Ccircle%20cx%3D%2223%22%20cy%3D%2274%22%20id%3D%22a%22%20r%3D%2250%22%2F%3E%3C%2Fdefs%3E%3Cuse%20fill%3D%22%23F5EEE5%22%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23a%22%2F%3E%3CclipPath%20id%3D%22b%22%3E%3Cuse%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23a%22%2F%3E%3C%2FclipPath%3E%3Cg%20clip-path%3D%22url(%23b)%22%3E%3Cdefs%3E%3Cpath%20d%3D%22M36%2095.9c0%204%204.7%205.2%207.1%205.8%207.6%202%2022.8%205.9%2022.8%205.9%203.2%201.1%205.7%203.5%207.1%206.6v9.8H-27v-9.8c1.3-3.1%203.9-5.5%207.1-6.6%200%200%2015.2-3.9%2022.8-5.9%202.4-.6%207.1-1.8%207.1-5.8V85h26v10.9z%22%20id%3D%22c%22%2F%3E%3C%2Fdefs%3E%3Cuse%20fill%3D%22%23E6C19C%22%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23c%22%2F%3E%3CclipPath%20id%3D%22d%22%3E%3Cuse%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23c%22%2F%3E%3C%2FclipPath%3E%3Cpath%20clip-path%3D%22url(%23d)%22%20d%3D%22M23.2%2035h.2c3.3%200%208.2.2%2011.4%202%203.3%201.9%207.3%205.6%208.5%2012.1%202.4%2013.7-2.1%2035.4-6.3%2042.4-4%206.7-9.8%209.2-13.5%209.4H23h-.1c-3.7-.2-9.5-2.7-13.5-9.4-4.2-7-8.7-28.7-6.3-42.4%201.2-6.5%205.2-10.2%208.5-12.1%203.2-1.8%208.1-2%2011.4-2h.2z%22%20fill%3D%22%23D4B08C%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M22.6%2040c19.1%200%2020.7%2013.8%2020.8%2015.1%201.1%2011.9-3%2028.1-6.8%2033.7-4%205.9-9.8%208.1-13.5%208.3h-.5c-3.8-.3-9.6-2.5-13.6-8.4-3.8-5.6-7.9-21.8-6.8-33.8C2.3%2053.7%203.5%2040%2022.6%2040z%22%20fill%3D%22%23F2CEA5%22%2F%3E%3C%2Fsvg%3E"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"readmeFilename": "README.md",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/test/test/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/test/test#readme",
|
||||||
|
"_id": "test@1.0.22",
|
||||||
|
"_nodeVersion": "14.17.4",
|
||||||
|
"_npmVersion": "7.20.5",
|
||||||
|
"dist": {
|
||||||
|
"integrity": "sha512-2IDD0lLzGUL7YJ+17Oh9VtbOwdKLqBLS+ZFATDXi5R22TL2hZ9LBFE10bzsDovNc4xtgwZAk1/K+5LHTye4ztg==",
|
||||||
|
"shasum": "c9152f57636bce762ccb5a83113c42a5831579bc",
|
||||||
|
"tarball": "http://localhost:4873/test/-/test-1.0.22.tgz"
|
||||||
|
},
|
||||||
|
"contributors": [],
|
||||||
|
"time": "2021-08-14T20:15:19.336Z",
|
||||||
|
"users": {}
|
||||||
|
}
|
||||||
|
]
|
@ -10,9 +10,9 @@ module.exports = Object.assign({}, config, {
|
|||||||
'^.+\\.(js|ts|tsx)$': 'babel-jest',
|
'^.+\\.(js|ts|tsx)$': 'babel-jest',
|
||||||
},
|
},
|
||||||
moduleFileExtensions: ['js', 'ts', 'tsx'],
|
moduleFileExtensions: ['js', 'ts', 'tsx'],
|
||||||
testURL: 'http://localhost',
|
testURL: 'http://localhost:9000/',
|
||||||
rootDir: '..',
|
rootDir: '..',
|
||||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
|
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', '<rootDir>/jest/setup-env.ts'],
|
||||||
setupFiles: ['<rootDir>/jest/setup.ts'],
|
setupFiles: ['<rootDir>/jest/setup.ts'],
|
||||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!react-syntax-highlighter)'],
|
transformIgnorePatterns: ['<rootDir>/node_modules/(?!react-syntax-highlighter)'],
|
||||||
modulePathIgnorePatterns: [
|
modulePathIgnorePatterns: [
|
||||||
|
9
packages/plugins/ui-theme/jest/server-handlers.ts
Normal file
9
packages/plugins/ui-theme/jest/server-handlers.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { rest } from 'msw';
|
||||||
|
|
||||||
|
const packagesPayload = require('./api/packages.json');
|
||||||
|
|
||||||
|
export const handlers = [
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/packages', (req, res, ctx) => {
|
||||||
|
return res(ctx.json(packagesPayload));
|
||||||
|
}),
|
||||||
|
];
|
6
packages/plugins/ui-theme/jest/server.ts
Normal file
6
packages/plugins/ui-theme/jest/server.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
|
||||||
|
import { handlers } from './server-handlers';
|
||||||
|
|
||||||
|
const server = setupServer(...handlers);
|
||||||
|
export { server };
|
15
packages/plugins/ui-theme/jest/setup-env.ts
Normal file
15
packages/plugins/ui-theme/jest/setup-env.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
|
import 'whatwg-fetch';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
import { server } from './server';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen({
|
||||||
|
onUnhandledRequest: 'warn',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(() => server.resetHandlers());
|
||||||
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
});
|
@ -2,29 +2,24 @@
|
|||||||
* Setup configuration for Jest
|
* Setup configuration for Jest
|
||||||
* This file includes global settings for the JEST environment.
|
* This file includes global settings for the JEST environment.
|
||||||
*/
|
*/
|
||||||
import { GlobalWithFetchMock } from 'jest-fetch-mock';
|
|
||||||
import 'mutationobserver-shim';
|
import 'mutationobserver-shim';
|
||||||
|
|
||||||
// @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'.
|
// @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'.
|
||||||
global.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
global.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||||
base: 'http://localhost',
|
base: 'http://localhost:9000/',
|
||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
primaryColor: '#4b5e40',
|
primaryColor: '#4b5e40',
|
||||||
url_prefix: '',
|
url_prefix: '',
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
uri: 'http://localhost:4873',
|
uri: 'http://localhost:9000/',
|
||||||
pkgManagers: ['pnpm', 'yarn', 'npm'],
|
pkgManagers: ['pnpm', 'yarn', 'npm'],
|
||||||
title: 'Verdaccio Dev UI',
|
title: 'Verdaccio Dev UI',
|
||||||
scope: '',
|
scope: '',
|
||||||
version: 'v1.0.0',
|
version: 'v1.0.0',
|
||||||
};
|
};
|
||||||
|
|
||||||
const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
|
|
||||||
customGlobal.fetch = require('jest-fetch-mock');
|
|
||||||
customGlobal.fetchMock = customGlobal.fetch;
|
|
||||||
|
|
||||||
// mocking few DOM methods
|
// mocking few DOM methods
|
||||||
// @ts-ignore : Property 'document' does not exist on type 'Global'.
|
// @ts-ignore : Property 'document' does not exist on type 'Global'.
|
||||||
if (global.document) {
|
if (global.document) {
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"@types/react-autosuggest": "10.1.5",
|
"@types/react-autosuggest": "10.1.5",
|
||||||
"@types/react-dom": "17.0.9",
|
"@types/react-dom": "17.0.9",
|
||||||
"@types/react-helmet": "6.1.2",
|
"@types/react-helmet": "6.1.2",
|
||||||
|
"@types/redux": "3.6.0",
|
||||||
"@types/react-router-dom": "5.1.8",
|
"@types/react-router-dom": "5.1.8",
|
||||||
"@types/react-virtualized": "9.21.13",
|
"@types/react-virtualized": "9.21.13",
|
||||||
"@emotion/core": "10.1.1",
|
"@emotion/core": "10.1.1",
|
||||||
@ -30,9 +31,11 @@
|
|||||||
"@material-ui/core": "4.11.4",
|
"@material-ui/core": "4.11.4",
|
||||||
"@material-ui/icons": "4.11.2",
|
"@material-ui/icons": "4.11.2",
|
||||||
"@material-ui/styles": "4.11.4",
|
"@material-ui/styles": "4.11.4",
|
||||||
"@testing-library/dom": "8.2.0",
|
"@rematch/core": "2.1.0",
|
||||||
|
"@rematch/loading": "2.1.0",
|
||||||
|
"@testing-library/dom": "8.5.0",
|
||||||
"@testing-library/jest-dom": "5.14.1",
|
"@testing-library/jest-dom": "5.14.1",
|
||||||
"@testing-library/react": "12.0.0",
|
"@testing-library/react": "12.1.0",
|
||||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.20",
|
"@verdaccio/node-api": "workspace:6.0.0-6-next.20",
|
||||||
"autosuggest-highlight": "3.1.1",
|
"autosuggest-highlight": "3.1.1",
|
||||||
"babel-loader": "8.2.2",
|
"babel-loader": "8.2.2",
|
||||||
@ -65,13 +68,16 @@
|
|||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-autosuggest": "10.1.0",
|
"react-autosuggest": "10.1.0",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-hook-form": "7.14.2",
|
"react-hook-form": "7.15.3",
|
||||||
"react-hot-loader": "4.13.0",
|
"react-hot-loader": "4.13.0",
|
||||||
"react-i18next": "11.12.0",
|
"react-i18next": "11.12.0",
|
||||||
"react-router": "5.2.1",
|
"react-router": "5.2.1",
|
||||||
"react-router-dom": "5.3.0",
|
"react-router-dom": "5.3.0",
|
||||||
"react-virtualized": "9.22.3",
|
"react-virtualized": "9.22.3",
|
||||||
|
"react-redux": "7.2.1",
|
||||||
|
"redux": "4.1.1",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
|
"msw": "0.35.0",
|
||||||
"standard-version": "9.3.1",
|
"standard-version": "9.3.1",
|
||||||
"style-loader": "3.2.1",
|
"style-loader": "3.2.1",
|
||||||
"stylelint": "13.13.1",
|
"stylelint": "13.13.1",
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import storage from 'verdaccio-ui/utils/storage';
|
import {
|
||||||
import { render, waitFor, fireEvent } from 'verdaccio-ui/utils/test-react-testing-library';
|
renderWithStore,
|
||||||
|
act,
|
||||||
|
waitFor,
|
||||||
|
fireEvent,
|
||||||
|
screen,
|
||||||
|
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
// eslint-disable-next-line jest/no-mocks-import
|
// eslint-disable-next-line jest/no-mocks-import
|
||||||
import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__/token';
|
import { generateTokenWithTimeRange } from '../../jest/unit/components/__mocks__/token';
|
||||||
|
import { store } from '../store';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
@ -30,38 +36,30 @@ jest.mock('verdaccio-ui/utils/storage', () => {
|
|||||||
return new LocalStorageMock();
|
return new LocalStorageMock();
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('verdaccio-ui/providers/API/api', () => ({
|
// force the windows to expand to display items
|
||||||
// eslint-disable-next-line jest/no-mocks-import
|
// https://github.com/bvaughn/react-virtualized/issues/493#issuecomment-640084107
|
||||||
request: require('../../jest/unit/components/__mocks__/api').default.request,
|
jest.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(600);
|
||||||
}));
|
jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(600);
|
||||||
|
|
||||||
/* eslint-disable react/jsx-no-bind*/
|
/* eslint-disable react/jsx-no-bind*/
|
||||||
describe('<App />', () => {
|
describe('<App />', () => {
|
||||||
// test('should display the Header component ', async () => {
|
describe('login - log out', () => {
|
||||||
// const { queryByTestId } = render(<App />);
|
|
||||||
//
|
|
||||||
// expect(queryByTestId('loading')).toBeTruthy();
|
|
||||||
//
|
|
||||||
// // wait for the Header component appearance and return the element
|
|
||||||
// const headerElement = await waitFor(() => queryByTestId('header'));
|
|
||||||
// expect(headerElement).toBeTruthy();
|
|
||||||
// });
|
|
||||||
|
|
||||||
test('handleLogout - logouts the user and clear localstorage', async () => {
|
test('handleLogout - logouts the user and clear localstorage', async () => {
|
||||||
storage.setItem('username', 'verdaccio');
|
const { queryByTestId } = renderWithStore(<App />, store);
|
||||||
storage.setItem('token', generateTokenWithTimeRange(24));
|
store.dispatch.login.logInUser({
|
||||||
|
username: 'verdaccio',
|
||||||
const { queryByTestId } = render(<App />);
|
token: generateTokenWithTimeRange(24),
|
||||||
|
});
|
||||||
|
|
||||||
// wait for the Account's circle element component appearance and return the element
|
// wait for the Account's circle element component appearance and return the element
|
||||||
const accountCircleElement = await waitFor(() => queryByTestId('header--menu-accountcircle'));
|
const accountCircleElement = await waitFor(() => queryByTestId('logInDialogIcon'));
|
||||||
expect(accountCircleElement).toBeTruthy();
|
expect(accountCircleElement).toBeTruthy();
|
||||||
|
|
||||||
if (accountCircleElement) {
|
if (accountCircleElement) {
|
||||||
fireEvent.click(accountCircleElement);
|
fireEvent.click(accountCircleElement);
|
||||||
|
|
||||||
// wait for the Button's logout element component appearance and return the element
|
// wait for the Button's logout element component appearance and return the element
|
||||||
const buttonLogoutElement = await waitFor(() => queryByTestId('header--button-logout'));
|
const buttonLogoutElement = await waitFor(() => queryByTestId('logOutDialogIcon'));
|
||||||
expect(buttonLogoutElement).toBeTruthy();
|
expect(buttonLogoutElement).toBeTruthy();
|
||||||
|
|
||||||
if (buttonLogoutElement) {
|
if (buttonLogoutElement) {
|
||||||
@ -73,13 +71,14 @@ describe('<App />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
test('isUserAlreadyLoggedIn: token already available in storage', async () => {
|
||||||
storage.setItem('username', 'verdaccio');
|
const { queryByTestId, queryAllByText } = renderWithStore(<App />, store);
|
||||||
storage.setItem('token', generateTokenWithTimeRange(24));
|
store.dispatch.login.logInUser({
|
||||||
|
username: 'verdaccio',
|
||||||
const { queryByTestId, queryAllByText } = render(<App />);
|
token: generateTokenWithTimeRange(24),
|
||||||
|
});
|
||||||
|
|
||||||
// wait for the Account's circle element component appearance and return the element
|
// wait for the Account's circle element component appearance and return the element
|
||||||
const accountCircleElement = await waitFor(() => queryByTestId('header--menu-accountcircle'));
|
const accountCircleElement = await waitFor(() => queryByTestId('logInDialogIcon'));
|
||||||
expect(accountCircleElement).toBeTruthy();
|
expect(accountCircleElement).toBeTruthy();
|
||||||
|
|
||||||
if (accountCircleElement) {
|
if (accountCircleElement) {
|
||||||
@ -95,3 +94,30 @@ describe('<App />', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('list packages', () => {
|
||||||
|
test('should display the Header component', async () => {
|
||||||
|
renderWithStore(<App />, store);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByTestId('loading')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for the Header component appearance and return the element
|
||||||
|
const headerElement = await waitFor(() => screen.queryByTestId('header'));
|
||||||
|
expect(headerElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display package lists', async () => {
|
||||||
|
act(() => {
|
||||||
|
renderWithStore(<App />, store);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('package-item-list')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(store.getState().packages.response).toHaveLength(1);
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable react/jsx-max-depth */
|
/* eslint-disable react/jsx-max-depth */
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import isNil from 'lodash/isNil';
|
import React, { useEffect, Suspense } from 'react';
|
||||||
import React, { useState, useEffect, Suspense } from 'react';
|
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
|
|
||||||
import Box from 'verdaccio-ui/components/Box';
|
import Box from 'verdaccio-ui/components/Box';
|
||||||
@ -9,10 +8,7 @@ import Loading from 'verdaccio-ui/components/Loading';
|
|||||||
import loadDayJSLocale from 'verdaccio-ui/design-tokens/load-dayjs-locale';
|
import loadDayJSLocale from 'verdaccio-ui/design-tokens/load-dayjs-locale';
|
||||||
import StyleBaseline from 'verdaccio-ui/design-tokens/StyleBaseline';
|
import StyleBaseline from 'verdaccio-ui/design-tokens/StyleBaseline';
|
||||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||||
import { isTokenExpire } from 'verdaccio-ui/utils/login';
|
|
||||||
import storage from 'verdaccio-ui/utils/storage';
|
|
||||||
|
|
||||||
import AppContextProvider from './AppContextProvider';
|
|
||||||
import AppRoute, { history } from './AppRoute';
|
import AppRoute, { history } from './AppRoute';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
@ -32,35 +28,8 @@ const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/* eslint-disable react/jsx-no-bind */
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [user, setUser] = useState<undefined | { username: string | null }>();
|
|
||||||
/**
|
|
||||||
* Logout user
|
|
||||||
* Required by: <Header />
|
|
||||||
*/
|
|
||||||
const logout = () => {
|
|
||||||
storage.removeItem('username');
|
|
||||||
storage.removeItem('token');
|
|
||||||
setUser(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkUserAlreadyLoggedIn = () => {
|
|
||||||
// checks for token validity
|
|
||||||
const token = storage.getItem('token');
|
|
||||||
const username = storage.getItem('username');
|
|
||||||
|
|
||||||
if (isTokenExpire(token) || isNil(username)) {
|
|
||||||
logout();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUser({ username });
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkUserAlreadyLoggedIn();
|
|
||||||
loadDayJSLocale();
|
loadDayJSLocale();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -70,12 +39,10 @@ const App: React.FC = () => {
|
|||||||
<StyledBox display="flex" flexDirection="column" height="100%">
|
<StyledBox display="flex" flexDirection="column" height="100%">
|
||||||
<>
|
<>
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<AppContextProvider user={user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
<StyledBoxContent flexGrow={1}>
|
<StyledBoxContent flexGrow={1}>
|
||||||
<AppRoute />
|
<AppRoute />
|
||||||
</StyledBoxContent>
|
</StyledBoxContent>
|
||||||
</AppContextProvider>
|
|
||||||
</Router>
|
</Router>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export interface AppProps {
|
|
||||||
user?: User;
|
|
||||||
scope: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface User {
|
|
||||||
username: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppContextProps extends AppProps {
|
|
||||||
setUser: (user?: User) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppContext = createContext<undefined | AppContextProps>(undefined);
|
|
||||||
|
|
||||||
export default AppContext;
|
|
@ -1,44 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
|
||||||
|
|
||||||
import AppContext, { AppProps, User } from './AppContext';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
user?: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
const AppContextProvider: React.FC<Props> = ({ children, user }) => {
|
|
||||||
const { configOptions } = useConfig();
|
|
||||||
const [state, setState] = useState<AppProps>({
|
|
||||||
scope: configOptions.scope ?? '',
|
|
||||||
user,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
user,
|
|
||||||
});
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
const setUser = (user?: User) => {
|
|
||||||
setState({
|
|
||||||
...state,
|
|
||||||
user,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppContext.Provider
|
|
||||||
value={{
|
|
||||||
...state,
|
|
||||||
setUser,
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppContextProvider;
|
|
@ -1,9 +1,7 @@
|
|||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Route as ReactRouterDomRoute, Switch, Router } from 'react-router-dom';
|
import { Route as ReactRouterDomRoute, Switch, Router } from 'react-router-dom';
|
||||||
|
|
||||||
import AppContext from './AppContext';
|
|
||||||
import loadable from './utils/loadable';
|
import loadable from './utils/loadable';
|
||||||
|
|
||||||
const NotFound = loadable(
|
const NotFound = loadable(
|
||||||
@ -28,22 +26,11 @@ export const history = createBrowserHistory({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const AppRoute: React.FC = () => {
|
const AppRoute: React.FC = () => {
|
||||||
const appContext = useContext(AppContext);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
if (!appContext) {
|
|
||||||
throw Error(t('app-context-not-correct-used'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { user } = appContext;
|
|
||||||
|
|
||||||
const isUserLoggedIn = user?.username;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
||||||
<HomePage isUserLoggedIn={!!isUserLoggedIn} />
|
<HomePage />
|
||||||
</ReactRouterDomRoute>
|
</ReactRouterDomRoute>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
||||||
<VersionContextProvider>
|
<VersionContextProvider>
|
||||||
|
@ -2,79 +2,78 @@ import React from 'react';
|
|||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
render,
|
renderWithStore,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
waitFor,
|
waitFor,
|
||||||
|
cleanup,
|
||||||
screen,
|
screen,
|
||||||
waitForElementToBeRemoved,
|
waitForElementToBeRemoved,
|
||||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
import { AppContextProvider } from '../../App';
|
|
||||||
import translationEN from '../../i18n/crowdin/ui.json';
|
import translationEN from '../../i18n/crowdin/ui.json';
|
||||||
|
import { store } from '../../store';
|
||||||
|
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
|
||||||
const props = {
|
|
||||||
user: {
|
|
||||||
username: 'verddacio-user',
|
|
||||||
},
|
|
||||||
packages: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
/* eslint-disable react/jsx-no-bind*/
|
/* eslint-disable react/jsx-no-bind*/
|
||||||
describe('<Header /> component with logged in state', () => {
|
describe('<Header /> component with logged in state', () => {
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
test('should load the component in logged out state', () => {
|
test('should load the component in logged out state', () => {
|
||||||
render(
|
renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByTestId('header--menu-accountcircle')).toBeNull();
|
expect(screen.queryByTestId('logInDialogIcon')).toBeNull();
|
||||||
expect(screen.getByText('Login')).toBeTruthy();
|
expect(screen.getByText('Login')).toBeTruthy();
|
||||||
expect(screen.queryByTestId('header--button-login')).toBeInTheDocument();
|
expect(screen.queryByTestId('header--button-login')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should load the component in logged in state', () => {
|
test('should load the component in logged in state', async () => {
|
||||||
const { getByTestId, queryByText } = render(
|
renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider user={props.user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
store.dispatch.login.logInUser({ username: 'store', token: '12345' });
|
||||||
|
|
||||||
expect(getByTestId('header--menu-accountcircle')).toBeTruthy();
|
await waitFor(() => {
|
||||||
expect(queryByText('Login')).toBeNull();
|
expect(screen.getByTestId('logInDialogIcon')).toBeTruthy();
|
||||||
|
expect(screen.queryByText('Login')).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should open login dialog', async () => {
|
test('should open login dialog', async () => {
|
||||||
const { getByTestId } = render(
|
renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginBtn = getByTestId('header--button-login');
|
store.dispatch.login.logOutUser();
|
||||||
|
|
||||||
|
const loginBtn = screen.getByTestId('header--button-login');
|
||||||
fireEvent.click(loginBtn);
|
fireEvent.click(loginBtn);
|
||||||
const loginDialog = await waitFor(() => getByTestId('login--dialog'));
|
const loginDialog = await waitFor(() => screen.getByTestId('login--dialog'));
|
||||||
expect(loginDialog).toBeTruthy();
|
expect(loginDialog).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should logout the user', async () => {
|
test('should logout the user', async () => {
|
||||||
const { getByText, getByTestId } = render(
|
const { getByText, getByTestId } = renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider user={props.user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
const headerMenuAccountCircle = getByTestId('header--menu-accountcircle');
|
store.dispatch.login.logInUser({ username: 'store', token: '12345' });
|
||||||
|
|
||||||
|
const headerMenuAccountCircle = getByTestId('logInDialogIcon');
|
||||||
fireEvent.click(headerMenuAccountCircle);
|
fireEvent.click(headerMenuAccountCircle);
|
||||||
|
|
||||||
// wait for button Logout's appearance and return the element
|
// wait for button Logout's appearance and return the element
|
||||||
@ -84,12 +83,11 @@ describe('<Header /> component with logged in state', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("The question icon should open a new tab of verdaccio's website - installation doc", () => {
|
test("The question icon should open a new tab of verdaccio's website - installation doc", () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider user={props.user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
const documentationBtn = getByTestId('header--tooltip-documentation');
|
const documentationBtn = getByTestId('header--tooltip-documentation');
|
||||||
@ -99,12 +97,11 @@ describe('<Header /> component with logged in state', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should open the registrationInfo modal when clicking on the info icon', async () => {
|
test('should open the registrationInfo modal when clicking on the info icon', async () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider user={props.user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
const infoBtn = getByTestId('header--tooltip-info');
|
const infoBtn = getByTestId('header--tooltip-info');
|
||||||
@ -116,12 +113,11 @@ describe('<Header /> component with logged in state', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should close the registrationInfo modal when clicking on the button close', async () => {
|
test('should close the registrationInfo modal when clicking on the button close', async () => {
|
||||||
const { getByTestId, getByText, queryByTestId } = render(
|
const { getByTestId, getByText, queryByTestId } = renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider user={props.user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
const infoBtn = getByTestId('header--tooltip-info');
|
const infoBtn = getByTestId('header--tooltip-info');
|
||||||
@ -138,17 +134,15 @@ describe('<Header /> component with logged in state', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should hide login if is disabled', () => {
|
test('should hide login if is disabled', () => {
|
||||||
// @ts-expect-error
|
|
||||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||||
base: 'foo',
|
base: 'foo',
|
||||||
login: false,
|
login: false,
|
||||||
};
|
};
|
||||||
render(
|
renderWithStore(
|
||||||
<Router>
|
<Router>
|
||||||
<AppContextProvider user={props.user}>
|
|
||||||
<Header />
|
<Header />
|
||||||
</AppContextProvider>
|
</Router>,
|
||||||
</Router>
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByTestId('header--button-login')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('header--button-login')).not.toBeInTheDocument();
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useState, useContext } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Button from 'verdaccio-ui/components/Button';
|
import Button from 'verdaccio-ui/components/Button';
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
import { useConfig } from 'verdaccio-ui/providers/config';
|
||||||
import storage from 'verdaccio-ui/utils/storage';
|
|
||||||
|
|
||||||
import AppContext from '../../App/AppContext';
|
import { Dispatch, RootState } from '../../store/store';
|
||||||
|
|
||||||
import HeaderInfoDialog from './HeaderInfoDialog';
|
import HeaderInfoDialog from './HeaderInfoDialog';
|
||||||
import HeaderLeft from './HeaderLeft';
|
import HeaderLeft from './HeaderLeft';
|
||||||
@ -21,26 +21,15 @@ interface Props {
|
|||||||
/* eslint-disable react/jsx-no-bind*/
|
/* eslint-disable react/jsx-no-bind*/
|
||||||
const Header: React.FC<Props> = ({ withoutSearch }) => {
|
const Header: React.FC<Props> = ({ withoutSearch }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const appContext = useContext(AppContext);
|
|
||||||
const [isInfoDialogOpen, setOpenInfoDialog] = useState<boolean>(false);
|
const [isInfoDialogOpen, setOpenInfoDialog] = useState<boolean>(false);
|
||||||
const [showMobileNavBar, setShowMobileNavBar] = useState<boolean>(false);
|
const [showMobileNavBar, setShowMobileNavBar] = useState<boolean>(false);
|
||||||
const [showLoginModal, setShowLoginModal] = useState<boolean>(false);
|
const [showLoginModal, setShowLoginModal] = useState<boolean>(false);
|
||||||
|
const loginStore = useSelector((state: RootState) => state.login);
|
||||||
if (!appContext) {
|
const configStore = useSelector((state: RootState) => state.configuration);
|
||||||
throw Error(t('app-context-not-correct-used'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { user, scope, setUser } = appContext;
|
|
||||||
const { configOptions } = useConfig();
|
const { configOptions } = useConfig();
|
||||||
|
const dispatch = useDispatch<Dispatch>();
|
||||||
/**
|
|
||||||
* Logouts user
|
|
||||||
* Required by: <Header />
|
|
||||||
*/
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
storage.removeItem('username');
|
dispatch.login.logOutUser();
|
||||||
storage.removeItem('token');
|
|
||||||
setUser(undefined);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -54,7 +43,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
|||||||
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
|
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
|
||||||
onToggleLogin={() => setShowLoginModal(!showLoginModal)}
|
onToggleLogin={() => setShowLoginModal(!showLoginModal)}
|
||||||
onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)}
|
onToggleMobileNav={() => setShowMobileNavBar(!showMobileNavBar)}
|
||||||
username={user?.username}
|
username={loginStore?.username}
|
||||||
withoutSearch={withoutSearch}
|
withoutSearch={withoutSearch}
|
||||||
/>
|
/>
|
||||||
</InnerNavBar>
|
</InnerNavBar>
|
||||||
@ -62,7 +51,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
|||||||
isOpen={isInfoDialogOpen}
|
isOpen={isInfoDialogOpen}
|
||||||
onCloseDialog={() => setOpenInfoDialog(false)}
|
onCloseDialog={() => setOpenInfoDialog(false)}
|
||||||
registryUrl={configOptions.base}
|
registryUrl={configOptions.base}
|
||||||
scope={scope}
|
scope={configStore.scope}
|
||||||
/>
|
/>
|
||||||
</NavBar>
|
</NavBar>
|
||||||
{showMobileNavBar && !withoutSearch && (
|
{showMobileNavBar && !withoutSearch && (
|
||||||
@ -75,7 +64,9 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</MobileNavBar>
|
</MobileNavBar>
|
||||||
)}
|
)}
|
||||||
{!user && <LoginDialog onClose={() => setShowLoginModal(false)} open={showLoginModal} />}
|
{!loginStore.user && (
|
||||||
|
<LoginDialog onClose={() => setShowLoginModal(false)} open={showLoginModal} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@ const HeaderMenu: React.FC<Props> = ({
|
|||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
color="inherit"
|
color="inherit"
|
||||||
data-testid="header--menu-accountcircle"
|
data-testid="logInDialogIcon"
|
||||||
id="header--button-account"
|
id="header--button-account"
|
||||||
onClick={onLoggedInMenu}>
|
onClick={onLoggedInMenu}>
|
||||||
<AccountCircle />
|
<AccountCircle />
|
||||||
@ -52,8 +52,8 @@ const HeaderMenu: React.FC<Props> = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
button={true}
|
button={true}
|
||||||
data-testid="header--button-logout"
|
data-testid="logOutDialogIcon"
|
||||||
id="header--button-logout"
|
id="logOutDialogIcon"
|
||||||
onClick={onLogout}>
|
onClick={onLogout}>
|
||||||
{t('button.logout')}
|
{t('button.logout')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import api from 'verdaccio-ui/providers/API/api';
|
import api from 'verdaccio-ui/providers/API/api';
|
||||||
import {
|
import {
|
||||||
render,
|
renderWithStore,
|
||||||
waitFor,
|
waitFor,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
cleanup,
|
cleanup,
|
||||||
@ -10,15 +10,10 @@ import {
|
|||||||
act,
|
act,
|
||||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
} from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
import AppContext, { AppContextProps } from '../../AppContext';
|
import { store } from '../../../store';
|
||||||
|
|
||||||
import LoginDialog from './LoginDialog';
|
import LoginDialog from './LoginDialog';
|
||||||
|
|
||||||
const appContextValue: AppContextProps = {
|
|
||||||
scope: '',
|
|
||||||
setUser: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('<LoginDialog /> component', () => {
|
describe('<LoginDialog /> component', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
@ -30,11 +25,7 @@ describe('<LoginDialog /> component', () => {
|
|||||||
const props = {
|
const props = {
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
};
|
};
|
||||||
const { container } = render(
|
const { container } = renderWithStore(<LoginDialog onClose={props.onClose} />, store);
|
||||||
<AppContext.Provider value={appContextValue}>
|
|
||||||
<LoginDialog onClose={props.onClose} />
|
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,10 +35,9 @@ describe('<LoginDialog /> component', () => {
|
|||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = renderWithStore(
|
||||||
<AppContext.Provider value={appContextValue}>
|
<LoginDialog onClose={props.onClose} open={props.open} />,
|
||||||
<LoginDialog onClose={props.onClose} open={props.open} />
|
store
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginDialogHeading = await waitFor(() => getByTestId('login-dialog-form-login-button'));
|
const loginDialogHeading = await waitFor(() => getByTestId('login-dialog-form-login-button'));
|
||||||
@ -60,18 +50,18 @@ describe('<LoginDialog /> component', () => {
|
|||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = renderWithStore(
|
||||||
<AppContext.Provider value={appContextValue}>
|
<LoginDialog onClose={props.onClose} open={props.open} />,
|
||||||
<LoginDialog onClose={props.onClose} open={props.open} />
|
store
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const loginDialogButton = await waitFor(() => getByTestId('close-login-dialog-button'));
|
const loginDialogButton = await waitFor(() => getByTestId('close-login-dialog-button'));
|
||||||
expect(loginDialogButton).toBeTruthy();
|
expect(loginDialogButton).toBeTruthy();
|
||||||
|
|
||||||
act(() => {
|
await act(() => {
|
||||||
fireEvent.click(loginDialogButton, { open: false });
|
fireEvent.click(loginDialogButton, { open: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(props.onClose).toHaveBeenCalled();
|
expect(props.onClose).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,11 +78,9 @@ describe('<LoginDialog /> component', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
render(
|
await act(async () => {
|
||||||
<AppContext.Provider value={appContextValue}>
|
renderWithStore(<LoginDialog onClose={props.onClose} open={props.open} />, store);
|
||||||
<LoginDialog onClose={props.onClose} open={props.open} />
|
});
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
const userNameInput = screen.getByPlaceholderText('Your username');
|
const userNameInput = screen.getByPlaceholderText('Your username');
|
||||||
expect(userNameInput).toBeInTheDocument();
|
expect(userNameInput).toBeInTheDocument();
|
||||||
@ -104,13 +92,18 @@ describe('<LoginDialog /> component', () => {
|
|||||||
const passwordInput = screen.getByPlaceholderText('Your strong password');
|
const passwordInput = screen.getByPlaceholderText('Your strong password');
|
||||||
expect(userNameInput).toBeInTheDocument();
|
expect(userNameInput).toBeInTheDocument();
|
||||||
fireEvent.focus(passwordInput);
|
fireEvent.focus(passwordInput);
|
||||||
fireEvent.change(passwordInput, { target: { value: '1234' } });
|
|
||||||
|
|
||||||
act(async () => {
|
await act(async () => {
|
||||||
const signInButton = await screen.getByTestId('login-dialog-form-login-button');
|
fireEvent.change(passwordInput, { target: { value: '1234' } });
|
||||||
|
});
|
||||||
|
const signInButton = screen.getByTestId('login-dialog-form-login-button');
|
||||||
expect(signInButton).not.toBeDisabled();
|
expect(signInButton).not.toBeDisabled();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
fireEvent.click(signInButton);
|
fireEvent.click(signInButton);
|
||||||
});
|
});
|
||||||
|
expect(props.onClose).toHaveBeenCalledTimes(1);
|
||||||
|
// screen.debug();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.todo('validateCredentials: should validate credentials');
|
test.todo('validateCredentials: should validate credentials');
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import React, { useState, useContext, useCallback } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Dialog from 'verdaccio-ui/components/Dialog';
|
import Dialog from 'verdaccio-ui/components/Dialog';
|
||||||
import DialogContent from 'verdaccio-ui/components/DialogContent';
|
import DialogContent from 'verdaccio-ui/components/DialogContent';
|
||||||
import { useAPI, LoginBody } from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
import { LoginError } from 'verdaccio-ui/utils/login';
|
|
||||||
import storage from 'verdaccio-ui/utils/storage';
|
|
||||||
|
|
||||||
import AppContext from '../../../App/AppContext';
|
import { LoginBody } from '../../../store/models/login';
|
||||||
|
import { Dispatch, RootState } from '../../../store/store';
|
||||||
|
|
||||||
import LoginDialogCloseButton from './LoginDialogCloseButton';
|
import LoginDialogCloseButton from './LoginDialogCloseButton';
|
||||||
import LoginDialogForm, { FormValues } from './LoginDialogForm';
|
import LoginDialogForm, { FormValues } from './LoginDialogForm';
|
||||||
@ -21,61 +19,47 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
|
const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
|
||||||
const { t } = useTranslation();
|
const loginStore = useSelector((state: RootState) => state.login);
|
||||||
const appContext = useContext(AppContext);
|
const dispatch = useDispatch<Dispatch>();
|
||||||
const { doLogin } = useAPI();
|
|
||||||
|
|
||||||
const makeLogin = useCallback(
|
const makeLogin = useCallback(
|
||||||
async (username?: string, password?: string): Promise<LoginBody> => {
|
async (username?: string, password?: string): Promise<LoginBody | void> => {
|
||||||
// checks isEmpty
|
// checks isEmpty
|
||||||
if (isEmpty(username) || isEmpty(password)) {
|
if (isEmpty(username) || isEmpty(password)) {
|
||||||
const error = {
|
dispatch.login.addError({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
description: i18next.t('form-validation.username-or-password-cant-be-empty'),
|
description: i18next.t('form-validation.username-or-password-cant-be-empty'),
|
||||||
};
|
});
|
||||||
return { error };
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response: LoginBody = await doLogin(username as string, password as string);
|
dispatch.login.getUser({ username, password });
|
||||||
|
// const response: LoginBody = await doLogin(username as string, password as string);
|
||||||
return response;
|
dispatch.login.clearError();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// eslint-disable-next-line no-console
|
dispatch.login.addError({
|
||||||
console.error('login error', e.message);
|
|
||||||
const error = {
|
|
||||||
type: 'error',
|
type: 'error',
|
||||||
description: i18next.t('form-validation.unable-to-sign-in'),
|
description: i18next.t('form-validation.unable-to-sign-in'),
|
||||||
};
|
});
|
||||||
return { error };
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('login error', e.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[doLogin]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!appContext) {
|
|
||||||
throw Error(t('app-context-not-correct-used'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const [error, setError] = useState<LoginError>();
|
|
||||||
|
|
||||||
const handleDoLogin = useCallback(
|
const handleDoLogin = useCallback(
|
||||||
async (data: FormValues) => {
|
async (data: FormValues) => {
|
||||||
const { username, token, error } = await makeLogin(data.username, data.password);
|
await makeLogin(data.username, data.password);
|
||||||
|
},
|
||||||
|
[makeLogin]
|
||||||
|
);
|
||||||
|
|
||||||
if (error) {
|
useEffect(() => {
|
||||||
setError(error);
|
if (loginStore.token && typeof loginStore.error === 'undefined') {
|
||||||
}
|
|
||||||
|
|
||||||
if (username && token) {
|
|
||||||
storage.setItem('username', username);
|
|
||||||
storage.setItem('token', token);
|
|
||||||
appContext.setUser({ username });
|
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
},
|
}, [loginStore, onClose]);
|
||||||
[appContext, onClose, makeLogin]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -88,7 +72,7 @@ const LoginDialog: React.FC<Props> = ({ onClose, open = false }) => {
|
|||||||
<LoginDialogCloseButton onClose={onClose} />
|
<LoginDialogCloseButton onClose={onClose} />
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<LoginDialogHeader />
|
<LoginDialogHeader />
|
||||||
<LoginDialogForm error={error} onSubmit={handleDoLogin} />
|
<LoginDialogForm error={loginStore.error} onSubmit={handleDoLogin} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,6 @@ describe('<RegistryInfoContent /> component', () => {
|
|||||||
const props = { registryUrl: 'http://localhost:4872', scope: '@' };
|
const props = { registryUrl: 'http://localhost:4872', scope: '@' };
|
||||||
render(<RegistryInfoContent registryUrl={props.registryUrl} scope={props.scope} />);
|
render(<RegistryInfoContent registryUrl={props.registryUrl} scope={props.scope} />);
|
||||||
|
|
||||||
screen.debug();
|
|
||||||
expect(screen.getByText('pnpm set @:registry http://localhost:4872')).toBeInTheDocument();
|
expect(screen.getByText('pnpm set @:registry http://localhost:4872')).toBeInTheDocument();
|
||||||
expect(screen.getByText('pnpm adduser --registry http://localhost:4872')).toBeInTheDocument();
|
expect(screen.getByText('pnpm adduser --registry http://localhost:4872')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
|
@ -2,7 +2,9 @@ import React from 'react';
|
|||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
|
||||||
import api from 'verdaccio-ui/providers/API/api';
|
import api from 'verdaccio-ui/providers/API/api';
|
||||||
import { render, fireEvent, waitFor } from 'verdaccio-ui/utils/test-react-testing-library';
|
import { renderWithStore, fireEvent, waitFor } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
|
import { store } from '../../../store/store';
|
||||||
|
|
||||||
jest.mock('lodash/debounce', () =>
|
jest.mock('lodash/debounce', () =>
|
||||||
jest.fn((fn) => {
|
jest.fn((fn) => {
|
||||||
@ -41,12 +43,15 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should load the component in default state', () => {
|
test('should load the component in default state', () => {
|
||||||
const { container } = render(<ComponentToBeRendered />);
|
const { container } = renderWithStore(<ComponentToBeRendered />, store);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handleSearch: when user type package name in search component, show suggestions', async () => {
|
test('handleSearch: when user type package name in search component, show suggestions', async () => {
|
||||||
const { getByPlaceholderText, getAllByText } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getAllByText } = renderWithStore(
|
||||||
|
<ComponentToBeRendered />,
|
||||||
|
store
|
||||||
|
);
|
||||||
|
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
|
|
||||||
@ -62,7 +67,10 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('onBlur: should cancel all search requests', async () => {
|
test('onBlur: should cancel all search requests', async () => {
|
||||||
const { getByPlaceholderText, getByRole, getAllByText } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getByRole, getAllByText } = renderWithStore(
|
||||||
|
<ComponentToBeRendered />,
|
||||||
|
store
|
||||||
|
);
|
||||||
|
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
|
|
||||||
@ -80,7 +88,7 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSearch: cancel all search requests when there is no value in search component with type method', async () => {
|
test('handleSearch: cancel all search requests when there is no value in search component with type method', async () => {
|
||||||
const { getByPlaceholderText, getByRole } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getByRole } = renderWithStore(<ComponentToBeRendered />, store);
|
||||||
|
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
fireEvent.focus(autoCompleteInput);
|
fireEvent.focus(autoCompleteInput);
|
||||||
@ -92,7 +100,7 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSearch: when method is not type method', async () => {
|
test('handleSearch: when method is not type method', async () => {
|
||||||
const { getByPlaceholderText, getByRole } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getByRole } = renderWithStore(<ComponentToBeRendered />, store);
|
||||||
|
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
|
|
||||||
@ -105,7 +113,7 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSearch: loading is been displayed', async () => {
|
test('handleSearch: loading is been displayed', async () => {
|
||||||
const { getByPlaceholderText, getByText } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getByText } = renderWithStore(<ComponentToBeRendered />, store);
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
|
|
||||||
fireEvent.focus(autoCompleteInput);
|
fireEvent.focus(autoCompleteInput);
|
||||||
@ -117,7 +125,10 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handlePackagesClearRequested: should clear suggestions', async () => {
|
test('handlePackagesClearRequested: should clear suggestions', async () => {
|
||||||
const { getByPlaceholderText, getAllByText, getByRole } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getAllByText, getByRole } = renderWithStore(
|
||||||
|
<ComponentToBeRendered />,
|
||||||
|
store
|
||||||
|
);
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
|
|
||||||
fireEvent.focus(autoCompleteInput);
|
fireEvent.focus(autoCompleteInput);
|
||||||
@ -135,7 +146,10 @@ describe('<Search /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleClickSearch: should change the window location on click or return key', async () => {
|
test('handleClickSearch: should change the window location on click or return key', async () => {
|
||||||
const { getByPlaceholderText, getAllByText, getByRole } = render(<ComponentToBeRendered />);
|
const { getByPlaceholderText, getAllByText, getByRole } = renderWithStore(
|
||||||
|
<ComponentToBeRendered />,
|
||||||
|
store
|
||||||
|
);
|
||||||
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
const autoCompleteInput = getByPlaceholderText('Search Packages');
|
||||||
|
|
||||||
fireEvent.focus(autoCompleteInput);
|
fireEvent.focus(autoCompleteInput);
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import React, { useState, FormEvent, useCallback, useRef, useEffect } from 'react';
|
import React, { useState, FormEvent, useCallback } from 'react';
|
||||||
import { SuggestionSelectedEventData } from 'react-autosuggest';
|
import { SuggestionSelectedEventData } from 'react-autosuggest';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { RouteComponentProps, withRouter } from 'react-router';
|
import { RouteComponentProps, withRouter } from 'react-router';
|
||||||
|
|
||||||
import AutoComplete from 'verdaccio-ui/components/AutoComplete';
|
import AutoComplete from 'verdaccio-ui/components/AutoComplete';
|
||||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
|
import { Dispatch, RootState } from '../../../store/store';
|
||||||
|
|
||||||
import SearchAdornment from './SearchAdornment';
|
import SearchAdornment from './SearchAdornment';
|
||||||
|
|
||||||
@ -16,22 +18,18 @@ const CONSTANTS = {
|
|||||||
|
|
||||||
const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [suggestions, setSuggestions] = useState([]);
|
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [error, setError] = useState(false);
|
// const mountedRef = useRef(true);
|
||||||
const [loading, setLoading] = useState(false);
|
const { isError, suggestions } = useSelector((state: RootState) => state.search);
|
||||||
const mountedRef = useRef(true);
|
const isLoading = useSelector((state: RootState) => state?.loading?.models.search);
|
||||||
const [requestList, setRequestList] = useState<{ abort: () => void }[]>([]);
|
const dispatch = useDispatch<Dispatch>();
|
||||||
const { callSearch } = useAPI();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel all the requests which are in pending state.
|
* Cancel all the requests which are in pending state.
|
||||||
*/
|
*/
|
||||||
const cancelAllSearchRequests = useCallback(() => {
|
const cancelAllSearchRequests = useCallback(() => {
|
||||||
requestList.forEach((request) => request.abort());
|
dispatch.search.clearRequestQueue();
|
||||||
setRequestList([]);
|
}, [dispatch]);
|
||||||
}, [requestList, setRequestList]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As user focuses out from input, we cancel all the request from requestList
|
* As user focuses out from input, we cancel all the request from requestList
|
||||||
@ -41,12 +39,9 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
|||||||
(event: FormEvent<HTMLInputElement>) => {
|
(event: FormEvent<HTMLInputElement>) => {
|
||||||
// stops event bubbling
|
// stops event bubbling
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setLoaded(false);
|
|
||||||
setLoading(false);
|
|
||||||
setError(false);
|
|
||||||
cancelAllSearchRequests();
|
cancelAllSearchRequests();
|
||||||
},
|
},
|
||||||
[setLoaded, setLoading, cancelAllSearchRequests, setError]
|
[cancelAllSearchRequests]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,9 +53,6 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (method === 'type') {
|
if (method === 'type') {
|
||||||
const value = newValue.trim();
|
const value = newValue.trim();
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
setSearch(value);
|
setSearch(value);
|
||||||
setLoaded(false);
|
setLoaded(false);
|
||||||
/**
|
/**
|
||||||
@ -79,8 +71,8 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
|||||||
* Cancel all the request from list and make request list empty.
|
* Cancel all the request from list and make request list empty.
|
||||||
*/
|
*/
|
||||||
const handlePackagesClearRequested = useCallback(() => {
|
const handlePackagesClearRequested = useCallback(() => {
|
||||||
setSuggestions([]);
|
dispatch.search.saveSearch({ suggestions: [] });
|
||||||
}, [setSuggestions]);
|
}, [dispatch]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When an user select any package by clicking or pressing return key.
|
* When an user select any package by clicking or pressing return key.
|
||||||
@ -108,46 +100,12 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
|||||||
* For AbortController see: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
* For AbortController see: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
||||||
*/
|
*/
|
||||||
const handleFetchPackages = useCallback(
|
const handleFetchPackages = useCallback(
|
||||||
async ({ value }: { value: string }) => {
|
({ value }: { value: string }) => {
|
||||||
try {
|
dispatch.search.getSuggestions({ value });
|
||||||
const controller = new window.AbortController();
|
|
||||||
const signal = controller.signal;
|
|
||||||
if (!mountedRef.current) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Keep track of search requests.
|
|
||||||
setRequestList([...requestList, controller]);
|
|
||||||
const suggestions = await callSearch(value, signal);
|
|
||||||
// FIXME: Argument of type 'unknown' is not assignable to parameter of type 'SetStateAction<never[]>'
|
|
||||||
setSuggestions(suggestions as any);
|
|
||||||
setLoaded(true);
|
|
||||||
} catch (error: any) {
|
|
||||||
/**
|
|
||||||
* AbortError is not the API error.
|
|
||||||
* It means browser has cancelled the API request.
|
|
||||||
*/
|
|
||||||
if (error.name === CONSTANTS.ABORT_ERROR) {
|
|
||||||
setError(false);
|
|
||||||
setLoaded(false);
|
|
||||||
} else {
|
|
||||||
setError(true);
|
|
||||||
setLoaded(false);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (mountedRef.current) {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[requestList, setRequestList, setSuggestions, setLoaded, setError, setLoading, callSearch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
mountedRef.current = false;
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
onBlur={handleOnBlur}
|
onBlur={handleOnBlur}
|
||||||
@ -158,9 +116,9 @@ const Search: React.FC<RouteComponentProps> = ({ history }) => {
|
|||||||
placeholder={t('search.packages')}
|
placeholder={t('search.packages')}
|
||||||
startAdornment={<SearchAdornment />}
|
startAdornment={<SearchAdornment />}
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
suggestionsError={error}
|
suggestionsError={isError}
|
||||||
suggestionsLoaded={loaded}
|
suggestionsLoaded={loaded}
|
||||||
suggestionsLoading={loading}
|
suggestionsLoading={isLoading}
|
||||||
value={search}
|
value={search}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export { default } from './App';
|
export { default } from './App';
|
||||||
export { default as AppContextProvider } from './AppContextProvider';
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { render, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
import { renderWithStore, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
import { DetailContext, DetailContextProps } from '../../pages/Version';
|
import { DetailContext, DetailContextProps } from '../../pages/Version';
|
||||||
|
import { store } from '../../store/store';
|
||||||
|
|
||||||
import ActionBar from './ActionBar';
|
import ActionBar from './ActionBar';
|
||||||
|
|
||||||
@ -44,7 +45,10 @@ describe('<ActionBar /> component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render the component in default state', () => {
|
test('should render the component in default state', () => {
|
||||||
const { container } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
const { container } = renderWithStore(
|
||||||
|
<ComponentToBeRendered contextValue={detailContextValue} />,
|
||||||
|
store
|
||||||
|
);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,22 +66,25 @@ describe('<ActionBar /> component', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { container } = render(
|
const { container } = renderWithStore(
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />
|
<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />,
|
||||||
|
store
|
||||||
);
|
);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when there is a button to download a tarball', () => {
|
test('when there is a button to download a tarball', () => {
|
||||||
const { getByTitle } = render(
|
const { getByTitle } = renderWithStore(
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />
|
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />,
|
||||||
|
store
|
||||||
);
|
);
|
||||||
expect(getByTitle('Download tarball')).toBeTruthy();
|
expect(getByTitle('Download tarball')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when there is a button to open an issue', () => {
|
test('when there is a button to open an issue', () => {
|
||||||
const { getByTitle } = render(
|
const { getByTitle } = renderWithStore(
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />
|
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />,
|
||||||
|
store
|
||||||
);
|
);
|
||||||
expect(getByTitle('Open an issue')).toBeTruthy();
|
expect(getByTitle('Open an issue')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -4,11 +4,11 @@ import DownloadIcon from '@material-ui/icons/CloudDownload';
|
|||||||
import HomeIcon from '@material-ui/icons/Home';
|
import HomeIcon from '@material-ui/icons/Home';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
import { extractFileName, downloadFile } from 'verdaccio-ui/utils/url';
|
|
||||||
|
|
||||||
|
import { Dispatch } from '../../store/store';
|
||||||
import FloatingActionButton from '../FloatingActionButton';
|
import FloatingActionButton from '../FloatingActionButton';
|
||||||
import Link from '../Link';
|
import Link from '../Link';
|
||||||
import Tooltip from '../Tooltip';
|
import Tooltip from '../Tooltip';
|
||||||
@ -34,13 +34,11 @@ export interface ActionBarActionProps {
|
|||||||
/* eslint-disable react/jsx-no-bind */
|
/* eslint-disable react/jsx-no-bind */
|
||||||
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
const ActionBarAction: React.FC<ActionBarActionProps> = ({ type, link }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getResource } = useAPI();
|
const dispatch = useDispatch<Dispatch>();
|
||||||
|
|
||||||
const handleDownload = useCallback(async () => {
|
const handleDownload = useCallback(async () => {
|
||||||
const fileStream = await getResource(link);
|
dispatch.download.getTarball({ link });
|
||||||
const fileName = extractFileName(link);
|
}, [dispatch, link]);
|
||||||
downloadFile(fileStream, fileName);
|
|
||||||
}, [getResource, link]);
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'VISIT_HOMEPAGE':
|
case 'VISIT_HOMEPAGE':
|
||||||
|
@ -51,7 +51,6 @@ describe('<Author /> component', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = render(withAuthorComponent(packageMeta));
|
const wrapper = render(withAuthorComponent(packageMeta));
|
||||||
wrapper.debug();
|
|
||||||
expect(wrapper.queryAllByText('verdaccio')).toHaveLength(0);
|
expect(wrapper.queryAllByText('verdaccio')).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ function loadTranslationFile(lng) {
|
|||||||
return require(`./download_translations/${lng}/ui.json`);
|
return require(`./download_translations/${lng}/ui.json`);
|
||||||
} catch {
|
} catch {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(`language ${lng} file not found, fallback to en-US`);
|
console.warn(`language ${lng} file not found, fallback to en-US`);
|
||||||
// in case the file is not there, fallback to en-US
|
// in case the file is not there, fallback to en-US
|
||||||
return require(`./crowdin/ui.json`);
|
return require(`./crowdin/ui.json`);
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { AppContainer } from 'react-hot-loader';
|
import { AppContainer } from 'react-hot-loader';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import APIProvider from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
import AppConfigurationContext from 'verdaccio-ui/providers/config';
|
import AppConfigurationContext from 'verdaccio-ui/providers/config';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import StyleBaseline from './design-tokens/StyleBaseline';
|
import StyleBaseline from './design-tokens/StyleBaseline';
|
||||||
import ThemeProvider from './design-tokens/ThemeProvider';
|
import ThemeProvider from './design-tokens/ThemeProvider';
|
||||||
|
import { store } from './store';
|
||||||
|
|
||||||
const rootNode = document.getElementById('root');
|
const rootNode = document.getElementById('root');
|
||||||
const renderApp = (Component: React.ElementType): void => {
|
const renderApp = (Component: React.ElementType): void => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<AppConfigurationContext>
|
<AppConfigurationContext>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<StyleBaseline />
|
<StyleBaseline />
|
||||||
<APIProvider>
|
|
||||||
<Component />
|
<Component />
|
||||||
</APIProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AppConfigurationContext>
|
</AppConfigurationContext>
|
||||||
</AppContainer>,
|
</AppContainer>
|
||||||
|
</Provider>,
|
||||||
rootNode
|
rootNode
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -9,12 +9,6 @@ import UpLinks from './UpLinks';
|
|||||||
describe('<UpLinks /> component', () => {
|
describe('<UpLinks /> component', () => {
|
||||||
beforeEach(cleanup);
|
beforeEach(cleanup);
|
||||||
|
|
||||||
test('should return null without packageMeta', () => {
|
|
||||||
const wrapper = render(<UpLinks />);
|
|
||||||
wrapper.debug();
|
|
||||||
// expect(wrapper).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render the component when there is no uplink', () => {
|
test('should render the component when there is no uplink', () => {
|
||||||
const packageMeta = {
|
const packageMeta = {
|
||||||
latest: {
|
latest: {
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import ActionBar from 'verdaccio-ui/components/ActionBar';
|
|
||||||
import { render, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { DetailContext } from '../../context';
|
|
||||||
import { DetailContextProps } from '../../version-config';
|
|
||||||
|
|
||||||
const detailContextValue: DetailContextProps = {
|
|
||||||
packageName: 'foo',
|
|
||||||
readMe: 'test',
|
|
||||||
enableLoading: () => {},
|
|
||||||
isLoading: false,
|
|
||||||
hasNotBeenFound: false,
|
|
||||||
packageMeta: {
|
|
||||||
_uplinks: {},
|
|
||||||
latest: {
|
|
||||||
name: 'verdaccio-ui/local-storage',
|
|
||||||
version: '8.0.1-next.1',
|
|
||||||
dist: {
|
|
||||||
fileCount: 0,
|
|
||||||
unpackedSize: 0,
|
|
||||||
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
|
||||||
},
|
|
||||||
homepage: 'https://verdaccio.org',
|
|
||||||
bugs: {
|
|
||||||
url: 'https://github.com/verdaccio/monorepo/issues',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({
|
|
||||||
contextValue,
|
|
||||||
}) => (
|
|
||||||
<DetailContext.Provider value={contextValue}>
|
|
||||||
<ActionBar />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<ActionBar /> component', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render the component in default state', () => {
|
|
||||||
const { container } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is no action bar data', () => {
|
|
||||||
const packageMeta = {
|
|
||||||
...detailContextValue.packageMeta,
|
|
||||||
latest: {
|
|
||||||
...detailContextValue.packageMeta.latest,
|
|
||||||
homepage: undefined,
|
|
||||||
bugs: undefined,
|
|
||||||
dist: {
|
|
||||||
...detailContextValue.packageMeta.latest.dist,
|
|
||||||
tarball: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { container } = render(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />
|
|
||||||
);
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is a button to download a tarball', () => {
|
|
||||||
const { getByTitle } = render(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />
|
|
||||||
);
|
|
||||||
expect(getByTitle('Download tarball')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is a button to open an issue', () => {
|
|
||||||
const { getByTitle } = render(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} />
|
|
||||||
);
|
|
||||||
expect(getByTitle('Open an issue')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,122 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`<ActionBar /> component should render the component in default state 1`] = `
|
|
||||||
.emotion-0 {
|
|
||||||
background-color: #4b5e40;
|
|
||||||
color: #fff;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-0:hover {
|
|
||||||
color: #4b5e40;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="MuiBox-root MuiBox-root-1"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="https://verdaccio.org"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Visit homepage"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiTypography-subtitle1"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="MuiButtonBase-root MuiFab-root emotion-0 emotion-1 MuiFab-sizeSmall"
|
|
||||||
data-testid="fab"
|
|
||||||
tabindex="0"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiFab-label"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="https://github.com/verdaccio/monorepo/issues"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Open an issue"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiTypography-subtitle1"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="MuiButtonBase-root MuiFab-root emotion-0 emotion-1 MuiFab-sizeSmall"
|
|
||||||
data-testid="fab"
|
|
||||||
tabindex="0"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiFab-label"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
class="MuiButtonBase-root MuiFab-root emotion-0 emotion-1 MuiFab-sizeSmall"
|
|
||||||
data-testid="fab"
|
|
||||||
tabindex="0"
|
|
||||||
title="Download tarball"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiFab-label"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<ActionBar /> component when there is no action bar data 1`] = `
|
|
||||||
<div
|
|
||||||
class="MuiBox-root MuiBox-root-2"
|
|
||||||
/>
|
|
||||||
`;
|
|
@ -1,20 +1,37 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Loading from 'verdaccio-ui/components/Loading';
|
import Loading from 'verdaccio-ui/components/Loading';
|
||||||
import NotFound from 'verdaccio-ui/components/NotFound';
|
import NotFound from 'verdaccio-ui/components/NotFound';
|
||||||
|
|
||||||
import { DetailContext } from './context';
|
import { Dispatch, RootState } from '../../store/store';
|
||||||
|
|
||||||
|
import getRouterPackageName from './get-route-package-name';
|
||||||
import VersionLayout from './VersionLayout';
|
import VersionLayout from './VersionLayout';
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
scope?: string;
|
||||||
|
package: string;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const Version: React.FC = () => {
|
const Version: React.FC = () => {
|
||||||
const detailContext = useContext(DetailContext);
|
const { version: packageVersion, package: pkgName, scope } = useParams<Params>();
|
||||||
const { isLoading, hasNotBeenFound } = detailContext;
|
const manifestStore = useSelector((state: RootState) => state.manifest);
|
||||||
|
const isLoading = useSelector((state: RootState) => state?.loading?.models.manifest);
|
||||||
|
const dispatch = useDispatch<Dispatch>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const packageName = getRouterPackageName(pkgName, scope);
|
||||||
|
dispatch.manifest.getManifest({ packageName, packageVersion });
|
||||||
|
}, [dispatch, pkgName, scope, packageVersion]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasNotBeenFound) {
|
if (manifestStore.hasNotBeenFound) {
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { PackageMetaInterface } from 'types/packageMeta';
|
|
||||||
|
|
||||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
import { Dispatch, RootState } from '../../store/store';
|
||||||
|
|
||||||
import { DetailContext } from './context';
|
import { DetailContext } from './context';
|
||||||
import getRouterPackageName from './get-route-package-name';
|
import getRouterPackageName from './get-route-package-name';
|
||||||
import isPackageVersionValid from './is-package-version-valid';
|
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
scope?: string;
|
scope?: string;
|
||||||
@ -15,52 +14,23 @@ interface Params {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VersionContextProvider: React.FC = ({ children }) => {
|
const VersionContextProvider: React.FC = ({ children }) => {
|
||||||
const { version, package: pkgName, scope } = useParams<Params>();
|
const { version: packageVersion, package: pkgName, scope } = useParams<Params>();
|
||||||
const [packageName, setPackageName] = useState(getRouterPackageName(pkgName, scope));
|
const { manifest, readme, packageName, hasNotBeenFound } = useSelector(
|
||||||
const [packageVersion, setPackageVersion] = useState(version);
|
(state: RootState) => state.manifest
|
||||||
const [packageMeta, setPackageMeta] = useState<PackageMetaInterface>();
|
);
|
||||||
const [readMe, setReadme] = useState<string>();
|
const isLoading = useSelector((state: RootState) => state?.loading?.models.manifest);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const dispatch = useDispatch<Dispatch>();
|
||||||
const [hasNotBeenFound, setHasNotBeenFound] = useState<boolean>();
|
|
||||||
const { callDetailPage, callReadme } = useAPI();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updatedPackageName = getRouterPackageName(pkgName, scope);
|
const packageName = getRouterPackageName(pkgName, scope);
|
||||||
setPackageName(updatedPackageName);
|
dispatch.manifest.getManifest({ packageName, packageVersion });
|
||||||
}, [pkgName, scope]);
|
}, [dispatch, packageVersion, pkgName, scope]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPackageVersion(version);
|
|
||||||
}, [version]);
|
|
||||||
|
|
||||||
const doCalls = useCallback(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: any) {
|
|
||||||
setHasNotBeenFound(true);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [packageName, packageVersion, callDetailPage, callReadme]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
doCalls();
|
|
||||||
}, [doCalls]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DetailContext.Provider
|
<DetailContext.Provider
|
||||||
value={{
|
value={{
|
||||||
packageMeta,
|
packageMeta: manifest,
|
||||||
packageVersion,
|
packageVersion,
|
||||||
readMe,
|
readMe: readme,
|
||||||
packageName,
|
packageName,
|
||||||
isLoading,
|
isLoading,
|
||||||
hasNotBeenFound,
|
hasNotBeenFound,
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
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,36 +1,19 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Loading from 'verdaccio-ui/components/Loading';
|
import Loading from 'verdaccio-ui/components/Loading';
|
||||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
|
import { Dispatch, RootState } from '../../store/store';
|
||||||
|
|
||||||
import { PackageList } from './PackageList';
|
import { PackageList } from './PackageList';
|
||||||
|
|
||||||
interface Props {
|
const Home: React.FC = () => {
|
||||||
isUserLoggedIn: boolean;
|
const packages = useSelector((state: RootState) => state.packages.response);
|
||||||
}
|
const isLoading = useSelector((state: RootState) => state?.loading?.models.packages);
|
||||||
|
const dispatch = useDispatch<Dispatch>();
|
||||||
const Home: React.FC<Props> = ({ isUserLoggedIn }) => {
|
|
||||||
const [packages, setPackages] = useState([]);
|
|
||||||
const { getPackages } = useAPI();
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const loadPackages = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const packages = await getPackages();
|
|
||||||
// FIXME add correct type for package
|
|
||||||
setPackages(packages as never[]);
|
|
||||||
} catch (error: any) {
|
|
||||||
// FIXME: add dialog
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error({
|
|
||||||
title: 'Warning',
|
|
||||||
message: `Unable to load package list: ${error.message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
}, [getPackages]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPackages().then();
|
dispatch.packages.getPackages();
|
||||||
}, [isUserLoggedIn, loadPackages]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container content" data-testid="home-page-container">
|
<div className="container content" data-testid="home-page-container">
|
||||||
|
@ -63,7 +63,7 @@ exports[`<Help /> component should load the component in default state 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="emotion-6 emotion-7"
|
class="emotion-6 emotion-7"
|
||||||
>
|
>
|
||||||
npm adduser --registry http://localhost
|
npm adduser --registry http://localhost:9000/
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="MuiButtonBase-root MuiIconButton-root"
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
@ -102,7 +102,7 @@ exports[`<Help /> component should load the component in default state 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="emotion-6 emotion-7"
|
class="emotion-6 emotion-7"
|
||||||
>
|
>
|
||||||
npm publish --registry http://localhost
|
npm publish --registry http://localhost:9000/
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
class="MuiButtonBase-root MuiIconButton-root"
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MemoryRouter } from 'react-router';
|
import { MemoryRouter } from 'react-router';
|
||||||
|
|
||||||
import { render, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
import { renderWithStore, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
|
import { store } from '../../../../store';
|
||||||
|
|
||||||
import Package from './Package';
|
import Package from './Package';
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ describe('<Package /> component', () => {
|
|||||||
keywords: ['verdaccio'],
|
keywords: ['verdaccio'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = render(
|
const wrapper = renderWithStore(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Package
|
<Package
|
||||||
author={props.author}
|
author={props.author}
|
||||||
@ -39,7 +41,8 @@ describe('<Package /> component', () => {
|
|||||||
time={props.time}
|
time={props.time}
|
||||||
version={props.version}
|
version={props.version}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>,
|
||||||
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
// FUTURE: improve this expectectations
|
// FUTURE: improve this expectectations
|
||||||
|
@ -5,6 +5,7 @@ import DownloadIcon from '@material-ui/icons/CloudDownload';
|
|||||||
import HomeIcon from '@material-ui/icons/Home';
|
import HomeIcon from '@material-ui/icons/Home';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Grid from 'verdaccio-ui/components/Grid';
|
import Grid from 'verdaccio-ui/components/Grid';
|
||||||
import { Version, FileBinary, Time, Law } from 'verdaccio-ui/components/Icons';
|
import { Version, FileBinary, Time, Law } from 'verdaccio-ui/components/Icons';
|
||||||
@ -12,14 +13,12 @@ import Link from 'verdaccio-ui/components/Link';
|
|||||||
import ListItem from 'verdaccio-ui/components/ListItem';
|
import ListItem from 'verdaccio-ui/components/ListItem';
|
||||||
import Tooltip from 'verdaccio-ui/components/Tooltip';
|
import Tooltip from 'verdaccio-ui/components/Tooltip';
|
||||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
||||||
import { useAPI } from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
|
||||||
import fileSizeSI from 'verdaccio-ui/utils/file-size';
|
import fileSizeSI from 'verdaccio-ui/utils/file-size';
|
||||||
import { formatDate, formatDateDistance, getAuthorName } from 'verdaccio-ui/utils/package';
|
import { formatDate, formatDateDistance, getAuthorName } from 'verdaccio-ui/utils/package';
|
||||||
import { extractFileName, downloadFile } from 'verdaccio-ui/utils/url';
|
|
||||||
import { isURL } from 'verdaccio-ui/utils/url';
|
import { isURL } from 'verdaccio-ui/utils/url';
|
||||||
|
|
||||||
import { PackageMetaInterface, Author as PackageAuthor } from '../../../../../types/packageMeta';
|
import { PackageMetaInterface, Author as PackageAuthor } from '../../../../../types/packageMeta';
|
||||||
|
import { Dispatch, RootState } from '../../../../store/store';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Author,
|
Author,
|
||||||
@ -72,19 +71,17 @@ const Package: React.FC<PackageInterface> = ({
|
|||||||
time,
|
time,
|
||||||
version,
|
version,
|
||||||
}) => {
|
}) => {
|
||||||
|
const config = useSelector((state: RootState) => state.configuration);
|
||||||
|
const dispatch = useDispatch<Dispatch>();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getResource } = useAPI();
|
|
||||||
const { configOptions } = useConfig();
|
|
||||||
|
|
||||||
const handleDownload = useCallback(
|
const handleDownload = useCallback(
|
||||||
async (tarballDist: string) => {
|
async (tarballDist: string) => {
|
||||||
// FIXME: check, the dist might be different thant npmjs
|
// FIXME: check, the dist might be different thant npmjs
|
||||||
const link = tarballDist.replace(`https://registry.npmjs.org/`, configOptions.base as string);
|
const link = tarballDist.replace(`https://registry.npmjs.org/`, config.base);
|
||||||
const fileStream = await getResource(link);
|
dispatch.download.getTarball({ link });
|
||||||
const fileName = extractFileName(link);
|
|
||||||
downloadFile(fileStream, fileName);
|
|
||||||
},
|
},
|
||||||
[getResource, configOptions]
|
[dispatch, config]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderVersionInfo = (): React.ReactNode =>
|
const renderVersionInfo = (): React.ReactNode =>
|
||||||
|
@ -26,7 +26,6 @@ const PackageList: React.FC<Props> = ({ packages }) => {
|
|||||||
packages[index];
|
packages[index];
|
||||||
// TODO: move format license to API side.
|
// TODO: move format license to API side.
|
||||||
const formattedLicense = formatLicense(license);
|
const formattedLicense = formatLicense(license);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellMeasurer cache={cache} columnIndex={0} key={key} parent={parent} rowIndex={index}>
|
<CellMeasurer cache={cache} columnIndex={0} key={key} parent={parent} rowIndex={index}>
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
@ -56,7 +55,8 @@ const PackageList: React.FC<Props> = ({ packages }) => {
|
|||||||
<WindowScroller>
|
<WindowScroller>
|
||||||
{({ height, isScrolling, scrollTop, onChildScroll }) => (
|
{({ height, isScrolling, scrollTop, onChildScroll }) => (
|
||||||
<AutoSizer disableHeight={true}>
|
<AutoSizer disableHeight={true}>
|
||||||
{({ width }) => (
|
{({ width }) => {
|
||||||
|
return (
|
||||||
<List
|
<List
|
||||||
autoHeight={true}
|
autoHeight={true}
|
||||||
deferredMeasurementCache={cache}
|
deferredMeasurementCache={cache}
|
||||||
@ -70,7 +70,8 @@ const PackageList: React.FC<Props> = ({ packages }) => {
|
|||||||
scrollTop={scrollTop}
|
scrollTop={scrollTop}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
)}
|
);
|
||||||
|
}}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
)}
|
)}
|
||||||
</WindowScroller>
|
</WindowScroller>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import { render, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
import { renderWithStore, cleanup } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
|
import { store } from '../../../store';
|
||||||
|
|
||||||
import { PackageList } from './PackageList';
|
import { PackageList } from './PackageList';
|
||||||
|
|
||||||
@ -12,7 +14,7 @@ describe('<PackageList /> component', () => {
|
|||||||
const props = {
|
const props = {
|
||||||
packages: [],
|
packages: [],
|
||||||
};
|
};
|
||||||
const wrapper = render(<PackageList packages={props.packages} />);
|
const wrapper = renderWithStore(<PackageList packages={props.packages} />, store);
|
||||||
expect(wrapper.getByText('No Package Published Yet.')).toBeInTheDocument();
|
expect(wrapper.getByText('No Package Published Yet.')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,10 +48,11 @@ describe('<PackageList /> component', () => {
|
|||||||
help: false,
|
help: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = render(
|
const wrapper = renderWithStore(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<PackageList packages={props.packages} />
|
<PackageList packages={props.packages} />
|
||||||
</BrowserRouter>
|
</BrowserRouter>,
|
||||||
|
store
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(wrapper.queryAllByTestId('package-item-list')).toHaveLength(3);
|
expect(wrapper.queryAllByTestId('package-item-list')).toHaveLength(3);
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { Package } from '@verdaccio/types';
|
|
||||||
import React, { createContext, FunctionComponent, useContext, useMemo } from 'react';
|
|
||||||
|
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
|
||||||
|
|
||||||
import { HEADERS } from '../../../lib/constants';
|
|
||||||
import { PackageMetaInterface } from '../../../types/packageMeta';
|
|
||||||
|
|
||||||
import API from './api';
|
|
||||||
|
|
||||||
type ConfigProviderProps = {
|
|
||||||
callReadme: (packageName: string, packageVersion?: string) => Promise<string>;
|
|
||||||
callDetailPage: (packageName: string, packageVersion?: string) => Promise<PackageMetaInterface>;
|
|
||||||
callSearch: (value: string, signal: AbortSignal) => Promise<string>;
|
|
||||||
getPackages: () => Promise<Package[]>;
|
|
||||||
doLogin: (username: string, password: string) => Promise<LoginBody>;
|
|
||||||
getResource: (link: string) => Promise<Blob>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface LoginError {
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginBody {
|
|
||||||
username?: string;
|
|
||||||
token?: string;
|
|
||||||
error?: LoginError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const AppAPIContext = createContext<ConfigProviderProps>({});
|
|
||||||
|
|
||||||
const APIProvider: FunctionComponent = ({ children }) => {
|
|
||||||
const { configOptions } = useConfig();
|
|
||||||
|
|
||||||
const buildURL = (basePath: string) => {
|
|
||||||
return `${configOptions?.base}-/verdaccio/${basePath}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const callReadme = async (packageName: string, packageVersion?: string): Promise<string> => {
|
|
||||||
return await API.request<string>(
|
|
||||||
buildURL(`package/readme/${packageName}${packageVersion ? `?v=${packageVersion}` : ''}`),
|
|
||||||
'GET'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const callDetailPage = async (
|
|
||||||
packageName: string,
|
|
||||||
packageVersion?: string
|
|
||||||
): Promise<PackageMetaInterface> => {
|
|
||||||
return await API.request<PackageMetaInterface>(
|
|
||||||
buildURL(`sidebar/${packageName}${packageVersion ? `?v=${packageVersion}` : ''}`),
|
|
||||||
'GET'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const callSearch = async (value: string, signal: AbortSignal): Promise<string> => {
|
|
||||||
// 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(buildURL(`search/${encodeURIComponent(value)}`), 'GET', {
|
|
||||||
signal,
|
|
||||||
headers: {},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPackages = async (): Promise<Package[]> => {
|
|
||||||
return await API.request(buildURL('packages'), 'GET');
|
|
||||||
};
|
|
||||||
|
|
||||||
const doLogin = async (username: string, password: string): Promise<LoginBody> => {
|
|
||||||
return await API.request(buildURL('login'), 'POST', {
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
headers: {
|
|
||||||
Accept: HEADERS.JSON,
|
|
||||||
'Content-Type': HEADERS.JSON,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getResource = async (link: string): Promise<Blob> => {
|
|
||||||
return await API.request(link, 'GET', {
|
|
||||||
headers: {
|
|
||||||
['accept']:
|
|
||||||
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = useMemo(
|
|
||||||
() => ({
|
|
||||||
callReadme,
|
|
||||||
callDetailPage,
|
|
||||||
callSearch,
|
|
||||||
getPackages,
|
|
||||||
doLogin,
|
|
||||||
getResource,
|
|
||||||
}),
|
|
||||||
[callReadme, getResource, callDetailPage, callSearch, doLogin]
|
|
||||||
);
|
|
||||||
|
|
||||||
return <AppAPIContext.Provider value={value}>{children}</AppAPIContext.Provider>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default APIProvider;
|
|
||||||
|
|
||||||
export const useAPI = () => useContext(AppAPIContext);
|
|
@ -85,6 +85,7 @@ describe('api', () => {
|
|||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
Authorization: 'Bearer token-xx-xx-xx',
|
Authorization: 'Bearer token-xx-xx-xx',
|
||||||
|
'x-client': 'verdaccio-ui',
|
||||||
}),
|
}),
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
@ -29,6 +29,8 @@ export function handleResponseType(response: Response): Promise<[boolean, any]>
|
|||||||
return Promise.all([response.ok, response.text()]);
|
return Promise.all([response.ok, response.text()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AuthHeader = 'Authorization';
|
||||||
|
|
||||||
class API {
|
class API {
|
||||||
public request<T>(
|
public request<T>(
|
||||||
url: string,
|
url: string,
|
||||||
@ -38,11 +40,13 @@ class API {
|
|||||||
const token = storage.getItem('token');
|
const token = storage.getItem('token');
|
||||||
const headers = new Headers(options.headers);
|
const headers = new Headers(options.headers);
|
||||||
|
|
||||||
if (token && headers.has('Authorization') === false) {
|
if (token && headers.has(AuthHeader) === false) {
|
||||||
headers.set('Authorization', `Bearer ${token}`);
|
headers.set(AuthHeader, `Bearer ${token}`);
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers.set('x-client', 'verdaccio-ui');
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method,
|
method,
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export { default, useAPI } from './APIProvider';
|
|
1
packages/plugins/ui-theme/src/store/index.ts
Normal file
1
packages/plugins/ui-theme/src/store/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { store } from './store';
|
42
packages/plugins/ui-theme/src/store/models/configuration.ts
Normal file
42
packages/plugins/ui-theme/src/store/models/configuration.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { createModel } from '@rematch/core';
|
||||||
|
import { Package, TemplateUIOptions } from '@verdaccio/types';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import isNil from 'lodash/isNil';
|
||||||
|
|
||||||
|
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
|
||||||
|
|
||||||
|
import API from '../../providers/API/api';
|
||||||
|
|
||||||
|
import type { RootModel } from '.';
|
||||||
|
|
||||||
|
const defaultValues: TemplateUIOptions = {
|
||||||
|
primaryColor: PRIMARY_COLOR,
|
||||||
|
darkMode: false,
|
||||||
|
pkgManagers: ['yarn', 'pnpm', 'npm'],
|
||||||
|
scope: '',
|
||||||
|
base: '',
|
||||||
|
login: true,
|
||||||
|
url_prefix: '',
|
||||||
|
title: 'Verdaccio',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getConfiguration() {
|
||||||
|
const uiConfiguration = window?.__VERDACCIO_BASENAME_UI_OPTIONS ?? defaultValues;
|
||||||
|
if (isNil(uiConfiguration.primaryColor) || isEmpty(uiConfiguration.primaryColor)) {
|
||||||
|
uiConfiguration.primaryColor = PRIMARY_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uiConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const configuration = createModel<RootModel>()({
|
||||||
|
state: {
|
||||||
|
config: getConfiguration(),
|
||||||
|
},
|
||||||
|
effects: (dispatch) => ({
|
||||||
|
async getPackages() {
|
||||||
|
const payload: Package[] = await API.request(`/-/verdaccio/packages`, 'GET');
|
||||||
|
dispatch.packages.savePackages(payload);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
31
packages/plugins/ui-theme/src/store/models/download.ts
Normal file
31
packages/plugins/ui-theme/src/store/models/download.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { createModel } from '@rematch/core';
|
||||||
|
|
||||||
|
import API from '../../providers/API/api';
|
||||||
|
import { downloadFile, extractFileName } from '../../utils/url';
|
||||||
|
|
||||||
|
import type { RootModel } from '.';
|
||||||
|
|
||||||
|
export const download = createModel<RootModel>()({
|
||||||
|
state: {},
|
||||||
|
reducers: {},
|
||||||
|
effects: () => ({
|
||||||
|
async getTarball({ link }) {
|
||||||
|
// const basePath = state.configuration.config.base;
|
||||||
|
try {
|
||||||
|
const fileStream: Blob = await API.request(link, 'GET', {
|
||||||
|
headers: {
|
||||||
|
['accept']:
|
||||||
|
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
const fileName = extractFileName(link);
|
||||||
|
downloadFile(fileStream, fileName);
|
||||||
|
} catch (error: any) {
|
||||||
|
// TODO: handle better error
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('error on download', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
17
packages/plugins/ui-theme/src/store/models/index.ts
Normal file
17
packages/plugins/ui-theme/src/store/models/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Models } from '@rematch/core';
|
||||||
|
|
||||||
|
import { configuration } from './configuration';
|
||||||
|
import { download } from './download';
|
||||||
|
import { login } from './login';
|
||||||
|
import { manifest } from './manifest';
|
||||||
|
import { packages } from './packages';
|
||||||
|
import { search } from './search';
|
||||||
|
export interface RootModel extends Models<RootModel> {
|
||||||
|
packages: typeof packages;
|
||||||
|
manifest: typeof manifest;
|
||||||
|
configuration: typeof configuration;
|
||||||
|
download: typeof download;
|
||||||
|
login: typeof login;
|
||||||
|
search: typeof search;
|
||||||
|
}
|
||||||
|
export const models: RootModel = { packages, configuration, search, download, login, manifest };
|
90
packages/plugins/ui-theme/src/store/models/login.ts
Normal file
90
packages/plugins/ui-theme/src/store/models/login.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { createModel } from '@rematch/core';
|
||||||
|
import i18next from 'i18next';
|
||||||
|
|
||||||
|
import { HEADERS } from '../../lib/constants';
|
||||||
|
import API from '../../providers/API/api';
|
||||||
|
import storage from '../../utils/storage';
|
||||||
|
|
||||||
|
import type { RootModel } from '.';
|
||||||
|
|
||||||
|
export type LoginError = {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginResponse = {
|
||||||
|
username: string | null;
|
||||||
|
token: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginBody = {
|
||||||
|
error?: LoginError;
|
||||||
|
} & LoginResponse;
|
||||||
|
|
||||||
|
const token = storage.getItem('token');
|
||||||
|
const username = storage.getItem('username');
|
||||||
|
const defaultUserState: LoginBody = {
|
||||||
|
token,
|
||||||
|
username,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const login = createModel<RootModel>()({
|
||||||
|
state: {
|
||||||
|
username: defaultUserState.username,
|
||||||
|
token: defaultUserState.token,
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
logOutUser(state) {
|
||||||
|
storage.removeItem('username');
|
||||||
|
storage.removeItem('token');
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
username: null,
|
||||||
|
token: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
addError(state, error: LoginError) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
clearError(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
logInUser(state, response: LoginResponse) {
|
||||||
|
// we might persist this in another way with
|
||||||
|
storage.setItem('username', response.username as string);
|
||||||
|
storage.setItem('token', response.token as string);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
token: response.token as string,
|
||||||
|
username: response.username as string,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
effects: (dispatch) => ({
|
||||||
|
async getUser({ username, password }, state) {
|
||||||
|
const basePath = state.configuration.config.base;
|
||||||
|
try {
|
||||||
|
const payload: LoginResponse = await API.request(`${basePath}-/verdaccio/login`, 'POST', {
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
headers: {
|
||||||
|
Accept: HEADERS.JSON,
|
||||||
|
'Content-Type': HEADERS.JSON,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch.login.logInUser(payload);
|
||||||
|
dispatch.packages.getPackages();
|
||||||
|
} catch (error: any) {
|
||||||
|
dispatch.login.addError({
|
||||||
|
type: 'error',
|
||||||
|
description: i18next.t('form-validation.unable-to-sign-in'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
91
packages/plugins/ui-theme/src/store/models/manifest.ts
Normal file
91
packages/plugins/ui-theme/src/store/models/manifest.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { createModel } from '@rematch/core';
|
||||||
|
import { Package } from '@verdaccio/types';
|
||||||
|
import { PackageMetaInterface } from 'types/packageMeta';
|
||||||
|
|
||||||
|
import API from '../../providers/API/api';
|
||||||
|
|
||||||
|
import type { RootModel } from '.';
|
||||||
|
|
||||||
|
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 const manifest = createModel<RootModel>()({
|
||||||
|
state: {},
|
||||||
|
reducers: {
|
||||||
|
notFound(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
hasNotBeenFound: true,
|
||||||
|
manifest: undefined,
|
||||||
|
packageName: undefined,
|
||||||
|
packageVersion: undefined,
|
||||||
|
readme: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
clearError(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isError: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
isError(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isError: true,
|
||||||
|
hasNotBeenFound: false,
|
||||||
|
manifest: undefined,
|
||||||
|
packageName: undefined,
|
||||||
|
packageVersion: undefined,
|
||||||
|
readme: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
saveManifest(state, { packageName, packageVersion, manifest, readme }) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
manifest,
|
||||||
|
packageName,
|
||||||
|
packageVersion,
|
||||||
|
readme,
|
||||||
|
hasNotBeenFound: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
effects: (dispatch) => ({
|
||||||
|
async getManifest({ packageName, packageVersion }, state) {
|
||||||
|
const basePath = state.configuration.config.base;
|
||||||
|
try {
|
||||||
|
if (!isPackageVersionValid(packageName, packageVersion)) {
|
||||||
|
throw new Error('not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest: Package = await API.request(
|
||||||
|
`${basePath}-/verdaccio/sidebar/${packageName}${
|
||||||
|
packageVersion ? `?v=${packageVersion}` : ''
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
const readme: string = await API.request<string>(
|
||||||
|
`${basePath}-/verdaccio/package/readme/${packageName}${
|
||||||
|
packageVersion ? `?v=${packageVersion}` : ''
|
||||||
|
}`,
|
||||||
|
'GET'
|
||||||
|
);
|
||||||
|
dispatch.manifest.saveManifest({ packageName, packageVersion, manifest, readme });
|
||||||
|
} catch (error: any) {
|
||||||
|
dispatch.manifest.notFound();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
36
packages/plugins/ui-theme/src/store/models/packages.ts
Normal file
36
packages/plugins/ui-theme/src/store/models/packages.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { createModel } from '@rematch/core';
|
||||||
|
import { Package } from '@verdaccio/types';
|
||||||
|
|
||||||
|
import API from '../../providers/API/api';
|
||||||
|
|
||||||
|
import type { RootModel } from '.';
|
||||||
|
|
||||||
|
export const packages = createModel<RootModel>()({
|
||||||
|
state: {
|
||||||
|
response: [] as Package[],
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
savePackages(state, response: Package[]) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
response,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
effects: (dispatch) => ({
|
||||||
|
async getPackages(_payload, state) {
|
||||||
|
const basePath = state.configuration.config.base;
|
||||||
|
try {
|
||||||
|
const payload: Package[] = await API.request(`${basePath}-/verdaccio/packages`, 'GET');
|
||||||
|
dispatch.packages.savePackages(payload);
|
||||||
|
} catch (error: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error({
|
||||||
|
title: 'Warning',
|
||||||
|
message: `Unable to load package list: ${error.message}`,
|
||||||
|
});
|
||||||
|
// TODO: handle error, display something retry or something
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
81
packages/plugins/ui-theme/src/store/models/search.ts
Normal file
81
packages/plugins/ui-theme/src/store/models/search.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { createModel } from '@rematch/core';
|
||||||
|
import { Package } from '@verdaccio/types';
|
||||||
|
|
||||||
|
import API from '../../providers/API/api';
|
||||||
|
|
||||||
|
import type { RootModel } from '.';
|
||||||
|
|
||||||
|
const CONSTANTS = {
|
||||||
|
API_DELAY: 300,
|
||||||
|
ABORT_ERROR: 'AbortError',
|
||||||
|
};
|
||||||
|
|
||||||
|
type SearchState = {
|
||||||
|
suggestions: Partial<Package>[];
|
||||||
|
controller: AbortController[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const search = createModel<RootModel>()({
|
||||||
|
state: {
|
||||||
|
suggestions: [],
|
||||||
|
controller: [],
|
||||||
|
} as SearchState,
|
||||||
|
reducers: {
|
||||||
|
clearRequestQueue(state) {
|
||||||
|
const controllers = state.controller;
|
||||||
|
controllers.forEach((request) => request.abort());
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
controller: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
addControllerToQueue(state, { controller }: { controller: AbortController }) {
|
||||||
|
const currentControllers = state.controller;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
controller: [...currentControllers, controller],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setError(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
saveSearch(state, { suggestions }: { suggestions: Partial<Package>[] }) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
suggestions,
|
||||||
|
isError: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
effects: (dispatch) => ({
|
||||||
|
async getSuggestions({ value }, state) {
|
||||||
|
const basePath = state.configuration.config.base;
|
||||||
|
try {
|
||||||
|
const controller = new window.AbortController();
|
||||||
|
dispatch.search.addControllerToQueue({ controller });
|
||||||
|
const signal = controller.signal;
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility
|
||||||
|
// FUTURE: signal is not well supported for IE and Samsung Browser
|
||||||
|
const suggestions: Partial<Package>[] = await API.request(
|
||||||
|
`${basePath}-/verdaccio/search/${encodeURIComponent(value)}`,
|
||||||
|
'GET',
|
||||||
|
{
|
||||||
|
signal,
|
||||||
|
headers: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch.search.saveSearch({ suggestions });
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name === CONSTANTS.ABORT_ERROR) {
|
||||||
|
dispatch.search.saveSearch({ suggestions: [] });
|
||||||
|
} else {
|
||||||
|
dispatch.search.setError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
15
packages/plugins/ui-theme/src/store/store.ts
Normal file
15
packages/plugins/ui-theme/src/store/store.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { init, RematchDispatch, RematchRootState } from '@rematch/core';
|
||||||
|
import loadingPlugin, { ExtraModelsFromLoading } from '@rematch/loading';
|
||||||
|
|
||||||
|
import { models, RootModel } from './models';
|
||||||
|
|
||||||
|
type FullModel = ExtraModelsFromLoading<RootModel>;
|
||||||
|
|
||||||
|
export const store = init<RootModel, FullModel>({
|
||||||
|
models,
|
||||||
|
plugins: [loadingPlugin()],
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Store = typeof store;
|
||||||
|
export type Dispatch = RematchDispatch<RootModel>;
|
||||||
|
export type RootState = RematchRootState<RootModel, FullModel>;
|
@ -1,25 +1,38 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import ThemeProvider from 'verdaccio-ui/design-tokens/ThemeProvider';
|
import ThemeProvider from 'verdaccio-ui/design-tokens/ThemeProvider';
|
||||||
import APIProvider from 'verdaccio-ui/providers/API/APIProvider';
|
|
||||||
import AppConfigurationProvider from 'verdaccio-ui/providers/config';
|
import AppConfigurationProvider from 'verdaccio-ui/providers/config';
|
||||||
|
|
||||||
import i18nConfig from '../i18n/config';
|
import i18nConfig from '../i18n/config';
|
||||||
|
|
||||||
|
const renderWithStore = (ui, store) =>
|
||||||
|
render(ui, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<AppConfigurationProvider>
|
||||||
|
<ThemeProvider>
|
||||||
|
<I18nextProvider i18n={i18nConfig}>{children}</I18nextProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</AppConfigurationProvider>
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const customRender = (node: React.ReactElement, ...options: any) => {
|
const customRender = (node: React.ReactElement, ...options: any) => {
|
||||||
return render(
|
return render(
|
||||||
<AppConfigurationProvider>
|
<AppConfigurationProvider>
|
||||||
<APIProvider>
|
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<I18nextProvider i18n={i18nConfig}>{node}</I18nextProvider>
|
<I18nextProvider i18n={i18nConfig}>{node}</I18nextProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</APIProvider>
|
|
||||||
</AppConfigurationProvider>,
|
</AppConfigurationProvider>,
|
||||||
...options
|
...options
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from '@testing-library/react';
|
export * from '@testing-library/react';
|
||||||
|
// FIXME: rename all references with customRemder
|
||||||
export { customRender as render };
|
export { customRender as render };
|
||||||
|
export { customRender, renderWithStore };
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"verdaccio-ui/utils/*": ["src/utils/*"]
|
"verdaccio-ui/utils/*": ["src/utils/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "types/*.d.ts", "src/i18n/**/*.json"],
|
"include": ["src", "types/*.d.ts", "src/i18n/**/*.json", "jest/unit/components"],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../../node-api"
|
"path": "../../node-api"
|
||||||
|
@ -1,26 +1,8 @@
|
|||||||
// FIXME: this should comes from @verdaccio/types
|
import { TemplateUIOptions } from '@verdaccio/types';
|
||||||
|
|
||||||
type PackageManagers = 'pnpm' | 'yarn' | 'npm';
|
|
||||||
export interface VerdaccioOptions {
|
|
||||||
url_prefix: string;
|
|
||||||
base: string;
|
|
||||||
scope: string;
|
|
||||||
title: string;
|
|
||||||
primaryColor: string;
|
|
||||||
darkMode: boolean;
|
|
||||||
uri?: string;
|
|
||||||
login?: boolean;
|
|
||||||
language?: string;
|
|
||||||
version?: string;
|
|
||||||
protocol?: string;
|
|
||||||
host?: string;
|
|
||||||
logo?: string;
|
|
||||||
pkgManagers?: PackageManagers[];
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__VERDACCIO_BASENAME_UI_OPTIONS: VerdaccioOptions;
|
__VERDACCIO_BASENAME_UI_OPTIONS: TemplateUIOptions;
|
||||||
|
// FIXME: remove all these variables
|
||||||
VERDACCIO_PRIMARY_COLOR: string;
|
VERDACCIO_PRIMARY_COLOR: string;
|
||||||
VERDACCIO_LOGO: string;
|
VERDACCIO_LOGO: string;
|
||||||
VERDACCIO_SCOPE: string;
|
VERDACCIO_SCOPE: string;
|
||||||
|
@ -25,6 +25,7 @@ const debug = buildDebug('verdaccio:web:api:package');
|
|||||||
|
|
||||||
function addPackageWebApi(route: Router, storage: Storage, auth: IAuth, config: Config): void {
|
function addPackageWebApi(route: Router, storage: Storage, auth: IAuth, config: Config): void {
|
||||||
const isLoginEnabled = config?.web?.login === true ?? true;
|
const isLoginEnabled = config?.web?.login === true ?? true;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const anonymousRemoteUser: RemoteUser = {
|
const anonymousRemoteUser: RemoteUser = {
|
||||||
name: undefined,
|
name: undefined,
|
||||||
real_groups: [],
|
real_groups: [],
|
||||||
@ -35,9 +36,10 @@ function addPackageWebApi(route: Router, storage: Storage, auth: IAuth, config:
|
|||||||
const checkAllow = (name: string, remoteUser: RemoteUser): Promise<boolean> =>
|
const checkAllow = (name: string, remoteUser: RemoteUser): Promise<boolean> =>
|
||||||
new Promise((resolve, reject): void => {
|
new Promise((resolve, reject): void => {
|
||||||
debug('is login disabled %o', isLoginEnabled);
|
debug('is login disabled %o', isLoginEnabled);
|
||||||
const remoteUserAccess = !isLoginEnabled ? anonymousRemoteUser : remoteUser;
|
// FIXME: this logic does not work, review
|
||||||
|
// const remoteUserAccess = !isLoginEnabled ? anonymousRemoteUser : remoteUser;
|
||||||
try {
|
try {
|
||||||
auth.allow_access({ packageName: name }, remoteUserAccess, (err, allowed): void => {
|
auth.allow_access({ packageName: name }, remoteUser, (err, allowed): void => {
|
||||||
if (err) {
|
if (err) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
|
273
pnpm-lock.yaml
generated
273
pnpm-lock.yaml
generated
@ -74,7 +74,6 @@ importers:
|
|||||||
jest-environment-jsdom: 27.1.0
|
jest-environment-jsdom: 27.1.0
|
||||||
jest-environment-jsdom-global: 3.0.0
|
jest-environment-jsdom-global: 3.0.0
|
||||||
jest-environment-node: 27.1.0
|
jest-environment-node: 27.1.0
|
||||||
jest-fetch-mock: 3.0.3
|
|
||||||
jest-junit: 12.2.0
|
jest-junit: 12.2.0
|
||||||
kleur: 3.0.3
|
kleur: 3.0.3
|
||||||
lint-staged: 11.1.2
|
lint-staged: 11.1.2
|
||||||
@ -165,7 +164,6 @@ importers:
|
|||||||
jest-environment-jsdom: 27.1.0
|
jest-environment-jsdom: 27.1.0
|
||||||
jest-environment-jsdom-global: 3.0.0_jest-environment-jsdom@27.1.0
|
jest-environment-jsdom-global: 3.0.0_jest-environment-jsdom@27.1.0
|
||||||
jest-environment-node: 27.1.0
|
jest-environment-node: 27.1.0
|
||||||
jest-fetch-mock: 3.0.3
|
|
||||||
jest-junit: 12.2.0
|
jest-junit: 12.2.0
|
||||||
kleur: 3.0.3
|
kleur: 3.0.3
|
||||||
lint-staged: 11.1.2
|
lint-staged: 11.1.2
|
||||||
@ -750,15 +748,18 @@ importers:
|
|||||||
'@material-ui/core': 4.11.4
|
'@material-ui/core': 4.11.4
|
||||||
'@material-ui/icons': 4.11.2
|
'@material-ui/icons': 4.11.2
|
||||||
'@material-ui/styles': 4.11.4
|
'@material-ui/styles': 4.11.4
|
||||||
'@testing-library/dom': 8.2.0
|
'@rematch/core': 2.1.0
|
||||||
|
'@rematch/loading': 2.1.0
|
||||||
|
'@testing-library/dom': 8.5.0
|
||||||
'@testing-library/jest-dom': 5.14.1
|
'@testing-library/jest-dom': 5.14.1
|
||||||
'@testing-library/react': 12.0.0
|
'@testing-library/react': 12.1.0
|
||||||
'@types/react': 17.0.19
|
'@types/react': 17.0.19
|
||||||
'@types/react-autosuggest': 10.1.5
|
'@types/react-autosuggest': 10.1.5
|
||||||
'@types/react-dom': 17.0.9
|
'@types/react-dom': 17.0.9
|
||||||
'@types/react-helmet': 6.1.2
|
'@types/react-helmet': 6.1.2
|
||||||
'@types/react-router-dom': 5.1.8
|
'@types/react-router-dom': 5.1.8
|
||||||
'@types/react-virtualized': 9.21.13
|
'@types/react-virtualized': 9.21.13
|
||||||
|
'@types/redux': 3.6.0
|
||||||
'@verdaccio/node-api': workspace:6.0.0-6-next.20
|
'@verdaccio/node-api': workspace:6.0.0-6-next.20
|
||||||
autosuggest-highlight: 3.1.1
|
autosuggest-highlight: 3.1.1
|
||||||
babel-loader: 8.2.2
|
babel-loader: 8.2.2
|
||||||
@ -782,6 +783,7 @@ importers:
|
|||||||
localstorage-memory: 1.0.3
|
localstorage-memory: 1.0.3
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
mini-css-extract-plugin: 2.2.2
|
mini-css-extract-plugin: 2.2.2
|
||||||
|
msw: 0.35.0
|
||||||
mutationobserver-shim: 0.3.7
|
mutationobserver-shim: 0.3.7
|
||||||
node-mocks-http: 1.10.1
|
node-mocks-http: 1.10.1
|
||||||
normalize.css: 8.0.1
|
normalize.css: 8.0.1
|
||||||
@ -791,12 +793,14 @@ importers:
|
|||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-autosuggest: 10.1.0
|
react-autosuggest: 10.1.0
|
||||||
react-dom: 17.0.2
|
react-dom: 17.0.2
|
||||||
react-hook-form: 7.14.2
|
react-hook-form: 7.15.3
|
||||||
react-hot-loader: 4.13.0
|
react-hot-loader: 4.13.0
|
||||||
react-i18next: 11.12.0
|
react-i18next: 11.12.0
|
||||||
|
react-redux: 7.2.1
|
||||||
react-router: 5.2.1
|
react-router: 5.2.1
|
||||||
react-router-dom: 5.3.0
|
react-router-dom: 5.3.0
|
||||||
react-virtualized: 9.22.3
|
react-virtualized: 9.22.3
|
||||||
|
redux: 4.1.1
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
standard-version: 9.3.1
|
standard-version: 9.3.1
|
||||||
style-loader: 3.2.1
|
style-loader: 3.2.1
|
||||||
@ -826,15 +830,18 @@ importers:
|
|||||||
'@material-ui/core': 4.11.4_9c6a8df88c2691f81f37725d5b4de033
|
'@material-ui/core': 4.11.4_9c6a8df88c2691f81f37725d5b4de033
|
||||||
'@material-ui/icons': 4.11.2_842d6fd0a208aabbcab28b4283e0161f
|
'@material-ui/icons': 4.11.2_842d6fd0a208aabbcab28b4283e0161f
|
||||||
'@material-ui/styles': 4.11.4_9c6a8df88c2691f81f37725d5b4de033
|
'@material-ui/styles': 4.11.4_9c6a8df88c2691f81f37725d5b4de033
|
||||||
'@testing-library/dom': 8.2.0
|
'@rematch/core': 2.1.0_redux@4.1.1
|
||||||
|
'@rematch/loading': 2.1.0_@rematch+core@2.1.0
|
||||||
|
'@testing-library/dom': 8.5.0
|
||||||
'@testing-library/jest-dom': 5.14.1
|
'@testing-library/jest-dom': 5.14.1
|
||||||
'@testing-library/react': 12.0.0_react-dom@17.0.2+react@17.0.2
|
'@testing-library/react': 12.1.0_react-dom@17.0.2+react@17.0.2
|
||||||
'@types/react': 17.0.19
|
'@types/react': 17.0.19
|
||||||
'@types/react-autosuggest': 10.1.5
|
'@types/react-autosuggest': 10.1.5
|
||||||
'@types/react-dom': 17.0.9
|
'@types/react-dom': 17.0.9
|
||||||
'@types/react-helmet': 6.1.2
|
'@types/react-helmet': 6.1.2
|
||||||
'@types/react-router-dom': 5.1.8
|
'@types/react-router-dom': 5.1.8
|
||||||
'@types/react-virtualized': 9.21.13
|
'@types/react-virtualized': 9.21.13
|
||||||
|
'@types/redux': 3.6.0
|
||||||
'@verdaccio/node-api': link:../../node-api
|
'@verdaccio/node-api': link:../../node-api
|
||||||
autosuggest-highlight: 3.1.1
|
autosuggest-highlight: 3.1.1
|
||||||
babel-loader: 8.2.2_02ab79faf18a98050fd2cd956ffa58f7
|
babel-loader: 8.2.2_02ab79faf18a98050fd2cd956ffa58f7
|
||||||
@ -858,6 +865,7 @@ importers:
|
|||||||
localstorage-memory: 1.0.3
|
localstorage-memory: 1.0.3
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
mini-css-extract-plugin: 2.2.2_webpack@5.52.0
|
mini-css-extract-plugin: 2.2.2_webpack@5.52.0
|
||||||
|
msw: 0.35.0
|
||||||
mutationobserver-shim: 0.3.7
|
mutationobserver-shim: 0.3.7
|
||||||
node-mocks-http: 1.10.1
|
node-mocks-http: 1.10.1
|
||||||
normalize.css: 8.0.1
|
normalize.css: 8.0.1
|
||||||
@ -867,12 +875,14 @@ importers:
|
|||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-autosuggest: 10.1.0_react@17.0.2
|
react-autosuggest: 10.1.0_react@17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
react-hook-form: 7.14.2_react@17.0.2
|
react-hook-form: 7.15.3_react@17.0.2
|
||||||
react-hot-loader: 4.13.0_9c6a8df88c2691f81f37725d5b4de033
|
react-hot-loader: 4.13.0_9c6a8df88c2691f81f37725d5b4de033
|
||||||
react-i18next: 11.12.0_i18next@20.6.1+react@17.0.2
|
react-i18next: 11.12.0_i18next@20.6.1+react@17.0.2
|
||||||
|
react-redux: 7.2.1_e084f946920a04d85390aa355643c4d5
|
||||||
react-router: 5.2.1_react@17.0.2
|
react-router: 5.2.1_react@17.0.2
|
||||||
react-router-dom: 5.3.0_react@17.0.2
|
react-router-dom: 5.3.0_react@17.0.2
|
||||||
react-virtualized: 9.22.3_react-dom@17.0.2+react@17.0.2
|
react-virtualized: 9.22.3_react-dom@17.0.2+react@17.0.2
|
||||||
|
redux: 4.1.1
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
standard-version: 9.3.1
|
standard-version: 9.3.1
|
||||||
style-loader: 3.2.1_webpack@5.52.0
|
style-loader: 3.2.1_webpack@5.52.0
|
||||||
@ -5713,6 +5723,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==}
|
resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@mswjs/cookies/0.1.6:
|
||||||
|
resolution: {integrity: sha512-A53XD5TOfwhpqAmwKdPtg1dva5wrng2gH5xMvklzbd9WLTSVU953eCRa8rtrrm6G7Cy60BOGsBRN89YQK0mlKA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/set-cookie-parser': 2.4.1
|
||||||
|
set-cookie-parser: 2.4.8
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@mswjs/interceptors/0.12.7:
|
||||||
|
resolution: {integrity: sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg==}
|
||||||
|
dependencies:
|
||||||
|
'@open-draft/until': 1.0.3
|
||||||
|
'@xmldom/xmldom': 0.7.5
|
||||||
|
debug: 4.3.2
|
||||||
|
headers-utils: 3.0.2
|
||||||
|
outvariant: 1.2.1
|
||||||
|
strict-event-emitter: 0.2.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.2:
|
/@nicolo-ribaudo/chokidar-2/2.1.8-no-fsevents.2:
|
||||||
resolution: {integrity: sha512-Fb8WxUFOBQVl+CX4MWet5o7eCc6Pj04rXIwVKZ6h1NnqTo45eOQW6aWyhG25NIODvWFwTDMwBsYxrQ3imxpetg==}
|
resolution: {integrity: sha512-Fb8WxUFOBQVl+CX4MWet5o7eCc6Pj04rXIwVKZ6h1NnqTo45eOQW6aWyhG25NIODvWFwTDMwBsYxrQ3imxpetg==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
@ -5749,6 +5779,10 @@ packages:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.11.0
|
fastq: 1.11.0
|
||||||
|
|
||||||
|
/@open-draft/until/1.0.3:
|
||||||
|
resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@polka/url/1.0.0-next.15:
|
/@polka/url/1.0.0-next.15:
|
||||||
resolution: {integrity: sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA==}
|
resolution: {integrity: sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA==}
|
||||||
|
|
||||||
@ -5795,6 +5829,23 @@ packages:
|
|||||||
resolution: {integrity: sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=}
|
resolution: {integrity: sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@rematch/core/2.1.0_redux@4.1.1:
|
||||||
|
resolution: {integrity: sha512-izr4LlXsHp1gfK8v05FNvqSthX64cCj59/x3tu+3qNXSuSM7d1/YXAlUtBJIoX9RApr+d1mqA7CCAteUjZrdLg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
redux: '>=4'
|
||||||
|
dependencies:
|
||||||
|
redux: 4.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@rematch/loading/2.1.0_@rematch+core@2.1.0:
|
||||||
|
resolution: {integrity: sha512-4sLWHiuTW50zjdbhUVHb9HB9Ors6+ktM9tANBOinYu4c43TSArZLkaC7lb8fZYLb6eCsHHAtrrWYsz8lBJ6UOw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@rematch/core': '>=2'
|
||||||
|
dependencies:
|
||||||
|
'@rematch/core': 2.1.0_redux@4.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@sideway/address/4.1.0:
|
/@sideway/address/4.1.0:
|
||||||
resolution: {integrity: sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA==}
|
resolution: {integrity: sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5974,8 +6025,8 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
defer-to-connect: 1.1.3
|
defer-to-connect: 1.1.3
|
||||||
|
|
||||||
/@testing-library/dom/8.2.0:
|
/@testing-library/dom/8.5.0:
|
||||||
resolution: {integrity: sha512-U8cTWENQPHO3QHvxBdfltJ+wC78ytMdg69ASvIdkGdQ/XRg4M9H2vvM3mHddxl+w/fM6NNqzGMwpQoh82v9VIA==}
|
resolution: {integrity: sha512-O0fmHFaPlqaYCpa/cBL0cvroMridb9vZsMLacgIqrlxj+fd+bGF8UfAgwsLCHRF84KLBafWlm9CuOvxeNTlodw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.14.5
|
'@babel/code-frame': 7.14.5
|
||||||
@ -5985,7 +6036,7 @@ packages:
|
|||||||
chalk: 4.1.1
|
chalk: 4.1.1
|
||||||
dom-accessibility-api: 0.5.6
|
dom-accessibility-api: 0.5.6
|
||||||
lz-string: 1.4.4
|
lz-string: 1.4.4
|
||||||
pretty-format: 27.0.2
|
pretty-format: 27.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@testing-library/jest-dom/5.14.1:
|
/@testing-library/jest-dom/5.14.1:
|
||||||
@ -6003,15 +6054,15 @@ packages:
|
|||||||
redent: 3.0.0
|
redent: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@testing-library/react/12.0.0_react-dom@17.0.2+react@17.0.2:
|
/@testing-library/react/12.1.0_react-dom@17.0.2+react@17.0.2:
|
||||||
resolution: {integrity: sha512-sh3jhFgEshFyJ/0IxGltRhwZv2kFKfJ3fN1vTZ6hhMXzz9ZbbcTgmDYM4e+zJv+oiVKKEWZPyqPAh4MQBI65gA==}
|
resolution: {integrity: sha512-Ge3Ht3qXE82Yv9lyPpQ7ZWgzo/HgOcHu569Y4ZGWcZME38iOFiOg87qnu6hTEa8jTJVL7zYovnvD3GE2nsNIoQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.15.4
|
'@babel/runtime': 7.15.4
|
||||||
'@testing-library/dom': 8.2.0
|
'@testing-library/dom': 8.5.0
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
dev: true
|
dev: true
|
||||||
@ -6118,6 +6169,10 @@ packages:
|
|||||||
'@types/node': 16.9.1
|
'@types/node': 16.9.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/cookie/0.4.1:
|
||||||
|
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/cookiejar/2.1.2:
|
/@types/cookiejar/2.1.2:
|
||||||
resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==}
|
resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -6190,6 +6245,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==}
|
resolution: {integrity: sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/inquirer/7.3.3:
|
||||||
|
resolution: {integrity: sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==}
|
||||||
|
dependencies:
|
||||||
|
'@types/through': 0.0.30
|
||||||
|
rxjs: 6.6.7
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/istanbul-lib-coverage/2.0.3:
|
/@types/istanbul-lib-coverage/2.0.3:
|
||||||
resolution: {integrity: sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==}
|
resolution: {integrity: sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -6226,6 +6288,10 @@ packages:
|
|||||||
pretty-format: 27.0.2
|
pretty-format: 27.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/js-levenshtein/1.1.0:
|
||||||
|
resolution: {integrity: sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/json-schema/7.0.6:
|
/@types/json-schema/7.0.6:
|
||||||
resolution: {integrity: sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==}
|
resolution: {integrity: sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==}
|
||||||
|
|
||||||
@ -6432,6 +6498,13 @@ packages:
|
|||||||
csstype: 3.0.8
|
csstype: 3.0.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/redux/3.6.0:
|
||||||
|
resolution: {integrity: sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=}
|
||||||
|
deprecated: This is a stub types definition for Redux (https://github.com/reactjs/redux). Redux provides its own type definitions, so you don't need @types/redux installed!
|
||||||
|
dependencies:
|
||||||
|
redux: 4.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/request/2.48.7:
|
/@types/request/2.48.7:
|
||||||
resolution: {integrity: sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==}
|
resolution: {integrity: sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6466,6 +6539,12 @@ packages:
|
|||||||
'@types/node': 16.9.1
|
'@types/node': 16.9.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/set-cookie-parser/2.4.1:
|
||||||
|
resolution: {integrity: sha512-N0IWe4vT1w5IOYdN9c9PNpQniHS+qe25W4tj4vfhJDJ9OkvA/YA55YUhaC+HNmMMeLlOSnBW9UMno0qlt5xu3Q==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 16.9.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/sonic-boom/2.1.1:
|
/@types/sonic-boom/2.1.1:
|
||||||
resolution: {integrity: sha512-CiKn+8CDgtBspfAVPwC8PXCMPhqeL7pFS4aWuj+WJnHLZlu4OGPctdZ6Mob43jRe0kkd7Ztb2Hcu9kzB+b7ZFw==}
|
resolution: {integrity: sha512-CiKn+8CDgtBspfAVPwC8PXCMPhqeL7pFS4aWuj+WJnHLZlu4OGPctdZ6Mob43jRe0kkd7Ztb2Hcu9kzB+b7ZFw==}
|
||||||
deprecated: This is a stub types definition. sonic-boom provides its own type definitions, so you do not need this installed.
|
deprecated: This is a stub types definition. sonic-boom provides its own type definitions, so you do not need this installed.
|
||||||
@ -6509,6 +6588,12 @@ packages:
|
|||||||
'@types/jest': 26.0.19
|
'@types/jest': 26.0.19
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/through/0.0.30:
|
||||||
|
resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 16.9.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/tough-cookie/4.0.0:
|
/@types/tough-cookie/4.0.0:
|
||||||
resolution: {integrity: sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==}
|
resolution: {integrity: sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -7032,6 +7117,11 @@ packages:
|
|||||||
webpack-cli: 4.8.0_3691794a826a95bb278217ad314163a5
|
webpack-cli: 4.8.0_3691794a826a95bb278217ad314163a5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@xmldom/xmldom/0.7.5:
|
||||||
|
resolution: {integrity: sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@xtuc/ieee754/1.2.0:
|
/@xtuc/ieee754/1.2.0:
|
||||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||||
|
|
||||||
@ -8547,6 +8637,11 @@ packages:
|
|||||||
string-width: 4.2.2
|
string-width: 4.2.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/cli-width/3.0.0:
|
||||||
|
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/clipanion/3.0.1:
|
/clipanion/3.0.1:
|
||||||
resolution: {integrity: sha512-/ujK3YJ1MGjGr18w99Gl9XZjy4xcC/5bZRJXsgvYG6GbUTO4CTKriC+oUxDbo8G+G/dxDqSJhm8QIDnK6iH6Ig==}
|
resolution: {integrity: sha512-/ujK3YJ1MGjGr18w99Gl9XZjy4xcC/5bZRJXsgvYG6GbUTO4CTKriC+oUxDbo8G+G/dxDqSJhm8QIDnK6iH6Ig==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -9018,6 +9113,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==}
|
resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
/cookie/0.4.1:
|
||||||
|
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/cookiejar/2.1.2:
|
/cookiejar/2.1.2:
|
||||||
resolution: {integrity: sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==}
|
resolution: {integrity: sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==}
|
||||||
|
|
||||||
@ -9169,6 +9269,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==}
|
resolution: {integrity: sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
node-fetch: 2.6.1
|
node-fetch: 2.6.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cross-spawn/5.1.0:
|
/cross-spawn/5.1.0:
|
||||||
resolution: {integrity: sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=}
|
resolution: {integrity: sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=}
|
||||||
@ -10732,6 +10833,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==}
|
resolution: {integrity: sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==}
|
||||||
engines: {node: '>=0.8.x'}
|
engines: {node: '>=0.8.x'}
|
||||||
|
|
||||||
|
/events/3.3.0:
|
||||||
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
|
engines: {node: '>=0.8.x'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/eventsource/1.0.7:
|
/eventsource/1.0.7:
|
||||||
resolution: {integrity: sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==}
|
resolution: {integrity: sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==}
|
||||||
engines: {node: '>=0.12.0'}
|
engines: {node: '>=0.12.0'}
|
||||||
@ -11874,6 +11980,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/graphql/15.5.3:
|
||||||
|
resolution: {integrity: sha512-sM+jXaO5KinTui6lbK/7b7H/Knj9BpjGxZ+Ki35v7YbUJxxdBCUqNM0h3CRVU1ZF9t5lNiBzvBCSYPvIwxPOQA==}
|
||||||
|
engines: {node: '>= 10.x'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/gray-matter/4.0.3:
|
/gray-matter/4.0.3:
|
||||||
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
|
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@ -12144,6 +12255,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
/headers-utils/3.0.2:
|
||||||
|
resolution: {integrity: sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/hex-color-regex/1.1.0:
|
/hex-color-regex/1.1.0:
|
||||||
resolution: {integrity: sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==}
|
resolution: {integrity: sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==}
|
||||||
|
|
||||||
@ -12594,6 +12709,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
|
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/inquirer/8.1.5:
|
||||||
|
resolution: {integrity: sha512-G6/9xUqmt/r+UvufSyrPpt84NYwhKZ9jLsgMbQzlx804XErNupor8WQdBnBRrXmBfTPpuwf1sV+ss2ovjgdXIg==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
dependencies:
|
||||||
|
ansi-escapes: 4.3.2
|
||||||
|
chalk: 4.1.1
|
||||||
|
cli-cursor: 3.1.0
|
||||||
|
cli-width: 3.0.0
|
||||||
|
external-editor: 3.1.0
|
||||||
|
figures: 3.2.0
|
||||||
|
lodash: 4.17.21
|
||||||
|
mute-stream: 0.0.8
|
||||||
|
ora: 5.4.1
|
||||||
|
run-async: 2.4.1
|
||||||
|
rxjs: 7.3.0
|
||||||
|
string-width: 4.2.2
|
||||||
|
strip-ansi: 6.0.0
|
||||||
|
through: 2.3.8
|
||||||
|
dev: true
|
||||||
|
|
||||||
/internal-ip/4.3.0:
|
/internal-ip/4.3.0:
|
||||||
resolution: {integrity: sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==}
|
resolution: {integrity: sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -12864,6 +12999,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==}
|
resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
/is-node-process/1.0.1:
|
||||||
|
resolution: {integrity: sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/is-npm/4.0.0:
|
/is-npm/4.0.0:
|
||||||
resolution: {integrity: sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==}
|
resolution: {integrity: sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -13300,13 +13439,6 @@ packages:
|
|||||||
jest-util: 27.1.0
|
jest-util: 27.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/jest-fetch-mock/3.0.3:
|
|
||||||
resolution: {integrity: sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==}
|
|
||||||
dependencies:
|
|
||||||
cross-fetch: 3.0.6
|
|
||||||
promise-polyfill: 8.2.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/jest-get-type/26.3.0:
|
/jest-get-type/26.3.0:
|
||||||
resolution: {integrity: sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==}
|
resolution: {integrity: sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==}
|
||||||
engines: {node: '>= 10.14.2'}
|
engines: {node: '>= 10.14.2'}
|
||||||
@ -13677,6 +13809,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==}
|
resolution: {integrity: sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/js-levenshtein/1.1.6:
|
||||||
|
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/js-tokens/4.0.0:
|
/js-tokens/4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
@ -15028,6 +15165,35 @@ packages:
|
|||||||
/ms/2.1.3:
|
/ms/2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
/msw/0.35.0:
|
||||||
|
resolution: {integrity: sha512-V7A6PqaS31F1k//fPS0OnO7vllfaqBUFsMEu3IpYixyWpiUInfyglodnbXhhtDyytkQikpkPZv8TZi/CvZzv/w==}
|
||||||
|
hasBin: true
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
'@mswjs/cookies': 0.1.6
|
||||||
|
'@mswjs/interceptors': 0.12.7
|
||||||
|
'@open-draft/until': 1.0.3
|
||||||
|
'@types/cookie': 0.4.1
|
||||||
|
'@types/inquirer': 7.3.3
|
||||||
|
'@types/js-levenshtein': 1.1.0
|
||||||
|
chalk: 4.1.1
|
||||||
|
chokidar: 3.5.1
|
||||||
|
cookie: 0.4.1
|
||||||
|
graphql: 15.5.3
|
||||||
|
headers-utils: 3.0.2
|
||||||
|
inquirer: 8.1.5
|
||||||
|
is-node-process: 1.0.1
|
||||||
|
js-levenshtein: 1.1.6
|
||||||
|
node-fetch: 2.6.1
|
||||||
|
node-match-path: 0.6.3
|
||||||
|
statuses: 2.0.1
|
||||||
|
strict-event-emitter: 0.2.0
|
||||||
|
type-fest: 1.4.0
|
||||||
|
yargs: 17.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/multicast-dns-service-types/1.1.0:
|
/multicast-dns-service-types/1.1.0:
|
||||||
resolution: {integrity: sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=}
|
resolution: {integrity: sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=}
|
||||||
|
|
||||||
@ -15042,6 +15208,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ==}
|
resolution: {integrity: sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/mute-stream/0.0.8:
|
||||||
|
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/mv/2.1.1:
|
/mv/2.1.1:
|
||||||
resolution: {integrity: sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=}
|
resolution: {integrity: sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
@ -15195,6 +15365,10 @@ packages:
|
|||||||
resolution: {integrity: sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=}
|
resolution: {integrity: sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/node-match-path/0.6.3:
|
||||||
|
resolution: {integrity: sha512-fB1reOHKLRZCJMAka28hIxCwQLxGmd7WewOCBDYKpyA1KXi68A7vaGgdZAPhY2E6SXoYt3KqYCCvXLJ+O0Fu/Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/node-mocks-http/1.10.1:
|
/node-mocks-http/1.10.1:
|
||||||
resolution: {integrity: sha512-/Nz83kiJ3z+vGqxmlDyv8+L1CJno+gH23DzG3oPH9dBSfMYa5IFVwPgZpXCB2kdiiIu/HoDpZ2BuLqQs7qjFLQ==}
|
resolution: {integrity: sha512-/Nz83kiJ3z+vGqxmlDyv8+L1CJno+gH23DzG3oPH9dBSfMYa5IFVwPgZpXCB2kdiiIu/HoDpZ2BuLqQs7qjFLQ==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@ -15679,6 +15853,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
|
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/outvariant/1.2.1:
|
||||||
|
resolution: {integrity: sha512-bcILvFkvpMXh66+Ubax/inxbKRyWTUiiFIW2DWkiS79wakrLGn3Ydy+GvukadiyfZjaL6C7YhIem4EZSM282wA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/p-cancelable/1.1.0:
|
/p-cancelable/1.1.0:
|
||||||
resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==}
|
resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -16824,10 +17002,6 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/promise-polyfill/8.2.0:
|
|
||||||
resolution: {integrity: sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/promise/7.3.1:
|
/promise/7.3.1:
|
||||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -17134,8 +17308,8 @@ packages:
|
|||||||
react-side-effect: 2.1.1_react@17.0.2
|
react-side-effect: 2.1.1_react@17.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-hook-form/7.14.2_react@17.0.2:
|
/react-hook-form/7.15.3_react@17.0.2:
|
||||||
resolution: {integrity: sha512-32uvgKkaE/0vOncfnJdwQhfahhocPpcb5c7F4j9Eq7dOnqS2Hg8h70Bmt6KXb6veLSWJultc1+ik9QSfqXFmLA==}
|
resolution: {integrity: sha512-z30aZoEHkWE8oZvad4OcYSBI0kQua/T5sFGH9tB2HfeykFnP/pGXNap8lDio4/U1yPj2ffpbvRIvqKd/6jjBVA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17
|
react: ^16.8.0 || ^17
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -17241,6 +17415,29 @@ packages:
|
|||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-redux/7.2.1_e084f946920a04d85390aa355643c4d5:
|
||||||
|
resolution: {integrity: sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.3
|
||||||
|
react-dom: '*'
|
||||||
|
react-native: '*'
|
||||||
|
redux: ^2.0.0 || ^3.0.0 || ^4.0.0-0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
react-native:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.15.4
|
||||||
|
hoist-non-react-statics: 3.3.2
|
||||||
|
loose-envify: 1.4.0
|
||||||
|
prop-types: 15.7.2
|
||||||
|
react: 17.0.2
|
||||||
|
react-dom: 17.0.2_react@17.0.2
|
||||||
|
react-is: 16.13.1
|
||||||
|
redux: 4.1.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/react-router-config/5.1.1_react-router@5.2.0+react@17.0.2:
|
/react-router-config/5.1.1_react-router@5.2.0+react@17.0.2:
|
||||||
resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==}
|
resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -17514,6 +17711,12 @@ packages:
|
|||||||
strip-indent: 3.0.0
|
strip-indent: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/redux/4.1.1:
|
||||||
|
resolution: {integrity: sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.15.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/regenerate-unicode-properties/8.2.0:
|
/regenerate-unicode-properties/8.2.0:
|
||||||
resolution: {integrity: sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==}
|
resolution: {integrity: sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -17986,6 +18189,11 @@ packages:
|
|||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/run-async/2.4.1:
|
||||||
|
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||||
|
engines: {node: '>=0.12.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/run-parallel/1.2.0:
|
/run-parallel/1.2.0:
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -18699,6 +18907,11 @@ packages:
|
|||||||
resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=}
|
resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
/statuses/2.0.1:
|
||||||
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/std-env/2.3.0:
|
/std-env/2.3.0:
|
||||||
resolution: {integrity: sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==}
|
resolution: {integrity: sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -18730,6 +18943,12 @@ packages:
|
|||||||
mixme: 0.4.0
|
mixme: 0.4.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/strict-event-emitter/0.2.0:
|
||||||
|
resolution: {integrity: sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w==}
|
||||||
|
dependencies:
|
||||||
|
events: 3.3.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/string-argv/0.3.1:
|
/string-argv/0.3.1:
|
||||||
resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==}
|
resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==}
|
||||||
engines: {node: '>=0.6.19'}
|
engines: {node: '>=0.6.19'}
|
||||||
|
@ -103,7 +103,7 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
const accountButton = await page.$('#header--button-account');
|
const accountButton = await page.$('#header--button-account');
|
||||||
expect(accountButton).toBeDefined();
|
expect(accountButton).toBeDefined();
|
||||||
// check whether user is logged
|
// check whether user is logged
|
||||||
const buttonLogout = await page.$('#header--button-logout');
|
const buttonLogout = await page.$('#logOutDialogIcon');
|
||||||
expect(buttonLogout).toBeDefined();
|
expect(buttonLogout).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ describe('/ (Verdaccio Page)', () => {
|
|||||||
// we assume the user is logged already
|
// we assume the user is logged already
|
||||||
await clickElement('#header--button-account', { delay: 500 });
|
await clickElement('#header--button-account', { delay: 500 });
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
await clickElement('#header--button-logout > span', { delay: 500 });
|
await clickElement('#logOutDialogIcon > span', { delay: 500 });
|
||||||
await page.waitForTimeout(1000);
|
await page.waitForTimeout(1000);
|
||||||
await evaluateSignIn();
|
await evaluateSignIn();
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user