@verdaccio/ui-components 3.0.0-next-7.4 → 3.0.0-next-7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/AppTest/AppRoute.js +34 -0
  3. package/build/AppTest/pages/Front/Home.js +9 -0
  4. package/build/AppTest/pages/Front/index.js +14 -0
  5. package/build/AppTest/pages/Version/Version.js +34 -0
  6. package/build/AppTest/pages/Version/index.js +14 -0
  7. package/build/Theme/theme.js +1 -1
  8. package/build/components/Engines/Engines.js +1 -1
  9. package/build/components/Forbidden/Forbidden.js +75 -0
  10. package/build/components/Forbidden/index.js +14 -0
  11. package/build/components/Install/InstallListItem.js +7 -7
  12. package/build/components/NotFound/NotFound.js +16 -14
  13. package/build/components/Repository/Repository.js +9 -9
  14. package/build/components/Versions/HistoryList.js +26 -22
  15. package/build/components/Versions/Versions.js +41 -10
  16. package/build/index.js +8 -0
  17. package/build/sections/Detail/Tabs.js +1 -3
  18. package/build/src/AppTest/App.stories.d.ts +8 -0
  19. package/build/src/AppTest/AppRoute.d.ts +3 -0
  20. package/build/src/AppTest/pages/Front/Home.d.ts +2 -0
  21. package/build/src/AppTest/pages/Front/index.d.ts +1 -0
  22. package/build/src/AppTest/pages/Version/Version.d.ts +3 -0
  23. package/build/src/AppTest/pages/Version/index.d.ts +1 -0
  24. package/build/src/components/Forbidden/Forbidden.d.ts +3 -0
  25. package/build/src/components/Forbidden/Forbidden.test.d.ts +1 -0
  26. package/build/src/components/Forbidden/index.d.ts +1 -0
  27. package/build/src/index.d.ts +1 -0
  28. package/build/src/sections/Detail/Detail.stories.d.ts +1 -0
  29. package/build/src/store/models/manifest.d.ts +11 -0
  30. package/build/store/api.js +12 -6
  31. package/build/store/models/manifest.js +20 -2
  32. package/package.json +3 -1
  33. package/src/AppTest/App.stories.tsx +32 -0
  34. package/src/AppTest/AppRoute.tsx +42 -0
  35. package/src/AppTest/pages/Front/Home.ts +3 -0
  36. package/src/AppTest/pages/Front/index.ts +1 -0
  37. package/src/AppTest/pages/Version/Version.tsx +28 -0
  38. package/src/AppTest/pages/Version/index.ts +1 -0
  39. package/src/Theme/theme.ts +1 -1
  40. package/src/components/Engines/Engines.tsx +1 -1
  41. package/src/components/Forbidden/Forbidden.test.tsx +38 -0
  42. package/src/components/Forbidden/Forbidden.tsx +56 -0
  43. package/src/components/Forbidden/index.ts +1 -0
  44. package/src/components/Install/InstallListItem.tsx +1 -0
  45. package/src/components/NotFound/NotFound.tsx +6 -5
  46. package/src/components/NotFound/Notfound.test.tsx +6 -3
  47. package/src/components/Repository/Repository.tsx +1 -3
  48. package/src/components/Versions/HistoryList.tsx +2 -2
  49. package/src/components/Versions/Versions.test.tsx +24 -8
  50. package/src/components/Versions/Versions.tsx +46 -18
  51. package/src/index.ts +1 -0
  52. package/src/sections/Detail/Detail.stories.tsx +10 -0
  53. package/src/sections/Detail/Tabs.tsx +1 -7
  54. package/src/store/api.test.ts +3 -2
  55. package/src/store/api.ts +13 -6
  56. package/src/store/models/manifest.ts +19 -1
@@ -1,4 +1,6 @@
1
+ /* eslint-disable verdaccio/jsx-no-style */
1
2
  import styled from '@emotion/styled';
3
+ import FolderOffIcon from '@mui/icons-material/FolderOff';
2
4
  import Box from '@mui/material/Box';
3
5
  import Button from '@mui/material/Button';
4
6
  import React, { useCallback } from 'react';
@@ -7,8 +9,6 @@ import { useHistory } from 'react-router-dom';
7
9
 
8
10
  import { Theme } from '../../';
9
11
  import Heading from '../Heading';
10
- // @ts-ignore
11
- import PackageImg from './img/package.svg';
12
12
 
13
13
  const NotFound: React.FC = () => {
14
14
  const history = useHistory();
@@ -28,7 +28,9 @@ const NotFound: React.FC = () => {
28
28
  justifyContent="center"
29
29
  p={2}
30
30
  >
31
- <EmptyPackage alt={t('error.404.page-not-found')} src={PackageImg} />
31
+ <Container>
32
+ <FolderOffIcon color="primary" style={{ fontSize: 236 }} />
33
+ </Container>
32
34
  <StyledHeading className="not-found-text" variant="h4">
33
35
  {t('error.404.sorry-we-could-not-find-it')}
34
36
  </StyledHeading>
@@ -41,8 +43,7 @@ const NotFound: React.FC = () => {
41
43
 
42
44
  export default NotFound;
43
45
 
44
- const EmptyPackage = styled('img')({
45
- width: '150px',
46
+ const Container = styled('div')({
46
47
  margin: '0 auto',
47
48
  });
48
49
 
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { MemoryRouter } from 'react-router';
3
3
 
4
- import { fireEvent, render } from '../../test/test-react-testing-library';
4
+ import { fireEvent, render, screen } from '../../test/test-react-testing-library';
5
5
  import NotFound from './NotFound';
6
6
 
7
7
  const mockHistory = jest.fn();
@@ -14,12 +14,15 @@ jest.mock('react-router-dom', () => ({
14
14
 
15
15
  describe('<NotFound /> component', () => {
16
16
  test('should load the component in default state', () => {
17
- const { container } = render(
17
+ render(
18
18
  <MemoryRouter>
19
19
  <NotFound />
20
20
  </MemoryRouter>
21
21
  );
22
- expect(container.firstChild).toMatchSnapshot();
22
+
23
+ expect(screen.getByTestId('FolderOffIcon')).toBeInTheDocument();
24
+ expect(screen.getByText('button.go-to-the-home-page')).toBeInTheDocument();
25
+ expect(screen.getByText('error.404.sorry-we-could-not-find-it')).toBeInTheDocument();
23
26
  });
24
27
 
25
28
  test('go to Home Page button click', async () => {
@@ -40,9 +40,7 @@ const RepositoryListItemText = styled(ListItemText)({
40
40
  const RepositoryAvatar = styled(Avatar)({
41
41
  borderRadius: '0px',
42
42
  padding: '0',
43
- img: {
44
- backgroundColor: 'transparent',
45
- },
43
+ backgroundColor: 'transparent',
46
44
  });
47
45
 
48
46
  const Repository: React.FC<{ packageMeta: any }> = ({ packageMeta }) => {
@@ -44,7 +44,7 @@ const VersionsHistoryList: React.FC<Props> = ({ versions, packageName, time }) =
44
44
  <Link to={`/-/web/detail/${packageName}/v/${version}`} variant="caption">
45
45
  <ListItemText disableTypography={false} primary={version}></ListItemText>
46
46
  </Link>
47
- {typeof versions[version].deprecated === 'string' ? (
47
+ {typeof versions[version]?.deprecated === 'string' ? (
48
48
  <Chip
49
49
  color="warning"
50
50
  data-testid="deprecated-badge"
@@ -55,7 +55,7 @@ const VersionsHistoryList: React.FC<Props> = ({ versions, packageName, time }) =
55
55
  />
56
56
  ) : null}
57
57
  <Spacer />
58
- <ListItemText title={utils.formatDate(time[version])}>
58
+ <ListItemText data-testid={`version-list-text`} title={utils.formatDate(time[version])}>
59
59
  {time[version]
60
60
  ? utils.formatDateDistance(time[version])
61
61
  : t('versions.not-available')}
@@ -2,24 +2,31 @@
2
2
  import React from 'react';
3
3
  import { MemoryRouter } from 'react-router-dom';
4
4
 
5
- import { cleanup, render, screen } from '../../test/test-react-testing-library';
5
+ import { fireEvent, render, screen } from '../../test/test-react-testing-library';
6
6
  import Versions, { Props } from './Versions';
7
7
  import data from './__partials__/data.json';
8
8
  import dataDeprecated from './__partials__/deprecated-versions.json';
9
9
 
10
- const ComponentToBeRendered: React.FC<Props> = (props) => (
10
+ const VersionsComponent: React.FC<Props> = (props) => (
11
11
  <MemoryRouter>
12
12
  <Versions {...props} />
13
13
  </MemoryRouter>
14
14
  );
15
15
 
16
+ jest.mock('lodash/debounce', () =>
17
+ jest.fn((fn) => {
18
+ fn.cancel = jest.fn();
19
+ return fn;
20
+ })
21
+ );
22
+
16
23
  describe('<Version /> component', () => {
17
24
  afterEach(() => {
18
- cleanup();
25
+ jest.clearAllMocks();
19
26
  });
20
27
 
21
28
  test('should render versions', () => {
22
- const { getByText } = render(<ComponentToBeRendered packageMeta={data} packageName={'foo'} />);
29
+ const { getByText } = render(<VersionsComponent packageMeta={data} packageName={'foo'} />);
23
30
 
24
31
  expect(getByText('versions.version-history')).toBeTruthy();
25
32
  expect(getByText('versions.current-tags')).toBeTruthy();
@@ -31,17 +38,26 @@ describe('<Version /> component', () => {
31
38
  expect(screen.queryByTestId('deprecated-badge')).toBeInTheDocument();
32
39
  });
33
40
 
41
+ test('should filter by version', () => {
42
+ render(<VersionsComponent packageMeta={data} packageName={'foo'} />);
43
+ expect(screen.getByText('versions.version-history')).toBeTruthy();
44
+ expect(screen.getByText('versions.current-tags')).toBeTruthy();
45
+ expect(screen.queryAllByTestId('version-list-text')).toHaveLength(65);
46
+ fireEvent.change(screen.getByRole('textbox'), { target: { value: '2.3.0' } });
47
+ expect(screen.queryAllByTestId('version-list-text')).toHaveLength(1);
48
+ });
49
+
34
50
  test('should not render versions', () => {
35
- const { queryByText } = render(<ComponentToBeRendered packageMeta={{}} packageName={'foo'} />);
51
+ render(<VersionsComponent packageMeta={{}} packageName={'foo'} />);
36
52
 
37
- expect(queryByText('versions.version-history')).toBeFalsy();
38
- expect(queryByText('versions.current-tags')).toBeFalsy();
53
+ expect(screen.queryByText('versions.version-history')).toBeFalsy();
54
+ expect(screen.queryByText('versions.current-tags')).toBeFalsy();
39
55
  });
40
56
 
41
57
  test('should render versions deprecated settings', () => {
42
58
  window.__VERDACCIO_BASENAME_UI_OPTIONS.hideDeprecatedVersions = true;
43
59
  const { getByText } = render(
44
- <ComponentToBeRendered packageMeta={dataDeprecated} packageName={'foo'} />
60
+ <VersionsComponent packageMeta={dataDeprecated} packageName={'foo'} />
45
61
  );
46
62
  expect(getByText('versions.hide-deprecated')).toBeTruthy();
47
63
 
@@ -1,8 +1,11 @@
1
1
  import Alert from '@mui/material/Alert';
2
+ import TextField from '@mui/material/TextField';
2
3
  import Typography from '@mui/material/Typography';
3
4
  import { useTheme } from '@mui/styles';
4
- import React from 'react';
5
+ import debounce from 'lodash/debounce';
6
+ import React, { useState } from 'react';
5
7
  import { useTranslation } from 'react-i18next';
8
+ import semver from 'semver';
6
9
 
7
10
  import { useConfig } from '../../providers';
8
11
  import VersionsHistoryList from './HistoryList';
@@ -14,16 +17,33 @@ const Versions: React.FC<Props> = ({ packageMeta, packageName }) => {
14
17
  const { t } = useTranslation();
15
18
  const { configOptions } = useConfig();
16
19
  const theme = useTheme();
20
+ const { versions = {}, time = {}, ['dist-tags']: distTags = {} } = packageMeta;
17
21
 
18
- if (!packageMeta) {
22
+ const [packageVersions, setPackageVersions] = useState(versions);
23
+ if (!packageMeta || Object.keys(packageMeta).length === 0) {
19
24
  return null;
20
25
  }
26
+ const hideDeprecatedVersions = configOptions.hideDeprecatedVersions;
27
+ const hasDistTags = distTags && Object.keys(distTags).length > 0 && packageName;
28
+ const hasVersionHistory =
29
+ packageVersions && Object.keys(packageVersions).length > 0 && packageName;
21
30
 
22
- const { versions = {}, time = {}, ['dist-tags']: distTags = {} } = packageMeta;
31
+ const filterVersions = (textSearch) => {
32
+ const filteredVersions = Object.keys(versions).reduce((acc, version) => {
33
+ if (textSearch !== '') {
34
+ if (typeof versions[version] !== 'undefined') {
35
+ if (semver.satisfies(version, textSearch, { includePrerelease: true, loose: true })) {
36
+ acc[version] = versions[version];
37
+ }
38
+ }
39
+ } else {
40
+ acc[version] = versions[version];
41
+ }
42
+ return acc;
43
+ }, {});
23
44
 
24
- const hasDistTags = distTags && Object.keys(distTags).length > 0 && packageName;
25
- const hasVersionHistory = versions && Object.keys(versions).length > 0 && packageName;
26
- const hideDeprecatedVersions = configOptions.hideDeprecatedVersions;
45
+ setPackageVersions(filteredVersions);
46
+ };
27
47
 
28
48
  return (
29
49
  <>
@@ -33,20 +53,28 @@ const Versions: React.FC<Props> = ({ packageMeta, packageName }) => {
33
53
  <VersionsTagList packageName={packageName} tags={distTags} time={time} />
34
54
  </>
35
55
  ) : null}
56
+ <>
57
+ <Typography variant="subtitle1">{t('versions.version-history')}</Typography>
58
+ <TextField
59
+ helperText={t('versions.search.placeholder')}
60
+ onChange={debounce((e) => {
61
+ filterVersions(e.target.value);
62
+ }, 200)}
63
+ size="small"
64
+ variant="standard"
65
+ />
66
+ </>
36
67
  {hasVersionHistory ? (
37
68
  <>
38
- <Typography variant="subtitle1">{t('versions.version-history')}</Typography>
39
- <>
40
- {hideDeprecatedVersions && (
41
- <Alert
42
- severity="info"
43
- sx={{ marginTop: theme.spacing(1), marginBottom: theme.spacing(1) }}
44
- >
45
- {t('versions.hide-deprecated')}
46
- </Alert>
47
- )}
48
- <VersionsHistoryList packageName={packageName} time={time} versions={versions} />
49
- </>
69
+ {hideDeprecatedVersions === true && (
70
+ <Alert
71
+ severity="info"
72
+ sx={{ marginTop: theme.spacing(1), marginBottom: theme.spacing(1) }}
73
+ >
74
+ {t('versions.hide-deprecated')}
75
+ </Alert>
76
+ )}
77
+ <VersionsHistoryList packageName={packageName} time={time} versions={packageVersions} />
50
78
  </>
51
79
  ) : null}
52
80
  </>
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export { default as Label } from './components/Label';
22
22
  export { default as Logo } from './components/Logo';
23
23
  export { default as MenuItem } from './components/MenuItem';
24
24
  export { default as NotFound } from './components/NotFound';
25
+ export { default as Forbidden } from './components/Forbidden';
25
26
  export { default as LoginDialog } from './components/LoginDialog';
26
27
  export { default as Search } from './components/Search';
27
28
  export { default as HeaderInfoDialog } from './components/HeaderInfoDialog';
@@ -27,3 +27,13 @@ export const DetailJquery: any = () => (
27
27
  </Route>
28
28
  </MemoryRouter>
29
29
  );
30
+
31
+ export const DetailForbidden: any = () => (
32
+ <MemoryRouter initialEntries={[`/-/web/detail/JSONStream`]}>
33
+ <Route exact={true} path="/-/web/detail/:package">
34
+ <VersionProvider>
35
+ <Detail />
36
+ </VersionProvider>
37
+ </Route>
38
+ </MemoryRouter>
39
+ );
@@ -14,13 +14,7 @@ interface Props {
14
14
  const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChange }) => {
15
15
  const { t } = useTranslation();
16
16
  return (
17
- <Tabs
18
- color={'primary'}
19
- indicatorColor={'primary'}
20
- onChange={onChange}
21
- value={tabPosition}
22
- variant={'fullWidth'}
23
- >
17
+ <Tabs onChange={onChange} value={tabPosition} variant={'fullWidth'}>
24
18
  <Tab data-testid={'readme-tab'} id={'readme-tab'} label={t('tab.readme')} />
25
19
  <Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={t('tab.dependencies')} />
26
20
  <Tab data-testid={'versions-tab'} id={'versions-tab'} label={t('tab.versions')} />
@@ -14,7 +14,8 @@ describe('api', () => {
14
14
 
15
15
  const handled = await handleResponseType(response);
16
16
 
17
- expect(handled).toEqual([ok, responseText]);
17
+ expect(handled[0]).toBeFalsy();
18
+ expect(handled[1]).toBeDefined();
18
19
  });
19
20
 
20
21
  test('should test tgz scenario', async () => {
@@ -123,7 +124,7 @@ describe('api', () => {
123
124
  })
124
125
  );
125
126
 
126
- await expect(api.request('/resource')).rejects.toThrow(new Error('something went wrong'));
127
+ await expect(api.request('/resource')).rejects.toThrow(new Error('Unknown error'));
127
128
  });
128
129
 
129
130
  test('when api returns an error 5.x.x', async () => {
package/src/store/api.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import storage from './storage';
2
2
 
3
+ class CustomError extends Error {
4
+ // @ts-ignore
5
+ code: number;
6
+ }
7
+
3
8
  /**
4
9
  * Handles response according to content type
5
10
  * @param {object} response
@@ -25,7 +30,8 @@ export function handleResponseType(response: Response): Promise<[boolean, any]>
25
30
  }
26
31
  }
27
32
 
28
- return Promise.all([response.ok, response.text()]);
33
+ // error handling
34
+ return Promise.all([response.ok, response]);
29
35
  }
30
36
 
31
37
  const AuthHeader = 'Authorization';
@@ -55,12 +61,13 @@ class API {
55
61
  })
56
62
  .then(handleResponseType)
57
63
  .then((response) => {
58
- if (response[0]) {
59
- resolve(response[1]);
64
+ const [ok, data] = response;
65
+ if (ok === true) {
66
+ resolve(data);
60
67
  } else {
61
- // eslint-disable-next-line no-console
62
- console.error(response);
63
- reject(new Error('something went wrong'));
68
+ const error = new CustomError(data?.statusText ?? 'Unknown error');
69
+ error.code = data?.status ?? 500;
70
+ reject(error);
64
71
  }
65
72
  })
66
73
  .catch((error) => {
@@ -33,6 +33,18 @@ export const manifest = createModel<RootModel>()({
33
33
  return {
34
34
  ...state,
35
35
  hasNotBeenFound: true,
36
+ forbidden: false,
37
+ manifest: undefined,
38
+ packageName: undefined,
39
+ packageVersion: undefined,
40
+ readme: undefined,
41
+ };
42
+ },
43
+ forbidden(state) {
44
+ return {
45
+ ...state,
46
+ forbidden: true,
47
+ hasNotBeenFound: false,
36
48
  manifest: undefined,
37
49
  packageName: undefined,
38
50
  packageVersion: undefined,
@@ -50,6 +62,7 @@ export const manifest = createModel<RootModel>()({
50
62
  ...state,
51
63
  isError: true,
52
64
  hasNotBeenFound: false,
65
+ forbidden: false,
53
66
  manifest: undefined,
54
67
  packageName: undefined,
55
68
  packageVersion: undefined,
@@ -64,6 +77,7 @@ export const manifest = createModel<RootModel>()({
64
77
  packageVersion,
65
78
  readme,
66
79
  hasNotBeenFound: false,
80
+ forbidden: false,
67
81
  };
68
82
  },
69
83
  },
@@ -91,7 +105,11 @@ export const manifest = createModel<RootModel>()({
91
105
  );
92
106
  dispatch.manifest.saveManifest({ packageName, packageVersion, manifest, readme });
93
107
  } catch (error: any) {
94
- dispatch.manifest.notFound();
108
+ if (error.code === 404) {
109
+ dispatch.manifest.notFound();
110
+ } else {
111
+ dispatch.manifest.forbidden();
112
+ }
95
113
  }
96
114
  },
97
115
  }),