0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00

feat: versions filter by semver range (#4555)

* fix: improve dark mode styles

* fix background color

* feat: ui improvements

* add tests
This commit is contained in:
Juan Picado 2024-03-23 20:41:19 +01:00 committed by GitHub
parent a99a4bb1b3
commit ba53d1edc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 93 additions and 76 deletions

View file

@ -0,0 +1,6 @@
---
'@verdaccio/ui-theme': patch
'@verdaccio/ui-components': patch
---
feat: versions filter by semver range

View file

@ -55,6 +55,7 @@
"version-history": "Version History",
"not-available": "Not available",
"deprecated": "Deprecated",
"search.placeholder": "Search for version by semver range, e.g. ^1.0.0",
"hide-deprecated": "All deprecated versions are hidden by global configuration"
},
"package": {

View file

@ -7,4 +7,4 @@
"filename": "index.html",
"base": "http://localhost:9000/"
}
</script>
</script>

View file

@ -35,7 +35,7 @@ module.exports = Object.assign({}, config, {
coverageThreshold: {
global: {
branches: 70,
functions: 76,
functions: 75,
lines: 80,
statements: 81,
},

View file

@ -53,6 +53,7 @@
"react-redux": "8.1.3",
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
"semver": "7.6.0",
"react-virtualized": "9.22.5",
"redux": "4.2.1",
"validator": "13.11.0"

View file

@ -36,7 +36,7 @@ const themeModes = {
},
dark: {
...colors,
primary: '#24394e',
primary: '#ffffff',
secondary: '#424242',
background: '#1A202C',
},

View file

@ -23,7 +23,7 @@ const EngineItem: FC<EngineItemProps> = ({ title, element, engineText }) => (
<Grid item={true} xs={6}>
<List subheader={<StyledText variant={'subtitle1'}>{title}</StyledText>}>
<EngineListItem>
<Avatar sx={{ bgcolor: '#FFF' }}>{element}</Avatar>
<Avatar sx={{ bgcolor: 'transparent' }}>{element}</Avatar>
<Typography variant="subtitle2">{engineText}</Typography>
</EngineListItem>
</List>

View file

@ -23,6 +23,7 @@ const InstallListItemText = styled(ListItemText)({
const PackageMangerAvatar = styled(Avatar)({
borderRadius: '0px',
backgroundColor: 'transparent',
padding: 0,
});

View file

@ -40,9 +40,7 @@ const RepositoryListItemText = styled(ListItemText)({
const RepositoryAvatar = styled(Avatar)({
borderRadius: '0px',
padding: '0',
img: {
backgroundColor: 'transparent',
},
backgroundColor: 'transparent',
});
const Repository: React.FC<{ packageMeta: any }> = ({ packageMeta }) => {

View file

@ -44,7 +44,7 @@ const VersionsHistoryList: React.FC<Props> = ({ versions, packageName, time }) =
<Link to={`/-/web/detail/${packageName}/v/${version}`} variant="caption">
<ListItemText disableTypography={false} primary={version}></ListItemText>
</Link>
{typeof versions[version].deprecated === 'string' ? (
{typeof versions[version]?.deprecated === 'string' ? (
<Chip
color="warning"
data-testid="deprecated-badge"
@ -55,7 +55,7 @@ const VersionsHistoryList: React.FC<Props> = ({ versions, packageName, time }) =
/>
) : null}
<Spacer />
<ListItemText title={utils.formatDate(time[version])}>
<ListItemText data-testid={`version-list-text`} title={utils.formatDate(time[version])}>
{time[version]
? utils.formatDateDistance(time[version])
: t('versions.not-available')}

View file

@ -2,24 +2,31 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { cleanup, render, screen } from '../../test/test-react-testing-library';
import { fireEvent, render, screen } from '../../test/test-react-testing-library';
import Versions, { Props } from './Versions';
import data from './__partials__/data.json';
import dataDeprecated from './__partials__/deprecated-versions.json';
const ComponentToBeRendered: React.FC<Props> = (props) => (
const VersionsComponent: React.FC<Props> = (props) => (
<MemoryRouter>
<Versions {...props} />
</MemoryRouter>
);
jest.mock('lodash/debounce', () =>
jest.fn((fn) => {
fn.cancel = jest.fn();
return fn;
})
);
describe('<Version /> component', () => {
afterEach(() => {
cleanup();
jest.clearAllMocks();
});
test('should render versions', () => {
const { getByText } = render(<ComponentToBeRendered packageMeta={data} packageName={'foo'} />);
const { getByText } = render(<VersionsComponent packageMeta={data} packageName={'foo'} />);
expect(getByText('versions.version-history')).toBeTruthy();
expect(getByText('versions.current-tags')).toBeTruthy();
@ -31,17 +38,26 @@ describe('<Version /> component', () => {
expect(screen.queryByTestId('deprecated-badge')).toBeInTheDocument();
});
test('should not render versions', () => {
const { queryByText } = render(<ComponentToBeRendered packageMeta={{}} packageName={'foo'} />);
test('should filter by version', () => {
render(<VersionsComponent packageMeta={data} packageName={'foo'} />);
expect(screen.getByText('versions.version-history')).toBeTruthy();
expect(screen.getByText('versions.current-tags')).toBeTruthy();
expect(screen.queryAllByTestId('version-list-text')).toHaveLength(65);
fireEvent.change(screen.getByRole('textbox'), { target: { value: '2.3.0' } });
expect(screen.queryAllByTestId('version-list-text')).toHaveLength(1);
});
expect(queryByText('versions.version-history')).toBeFalsy();
expect(queryByText('versions.current-tags')).toBeFalsy();
test('should not render versions', () => {
render(<VersionsComponent packageMeta={{}} packageName={'foo'} />);
expect(screen.queryByText('versions.version-history')).toBeFalsy();
expect(screen.queryByText('versions.current-tags')).toBeFalsy();
});
test('should render versions deprecated settings', () => {
window.__VERDACCIO_BASENAME_UI_OPTIONS.hideDeprecatedVersions = true;
const { getByText } = render(
<ComponentToBeRendered packageMeta={dataDeprecated} packageName={'foo'} />
<VersionsComponent packageMeta={dataDeprecated} packageName={'foo'} />
);
expect(getByText('versions.hide-deprecated')).toBeTruthy();

View file

@ -1,8 +1,11 @@
import Alert from '@mui/material/Alert';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { useTheme } from '@mui/styles';
import React from 'react';
import debounce from 'lodash/debounce';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import semver from 'semver';
import { useConfig } from '../../providers';
import VersionsHistoryList from './HistoryList';
@ -14,16 +17,33 @@ const Versions: React.FC<Props> = ({ packageMeta, packageName }) => {
const { t } = useTranslation();
const { configOptions } = useConfig();
const theme = useTheme();
if (!packageMeta) {
return null;
}
const { versions = {}, time = {}, ['dist-tags']: distTags = {} } = packageMeta;
const hasDistTags = distTags && Object.keys(distTags).length > 0 && packageName;
const hasVersionHistory = versions && Object.keys(versions).length > 0 && packageName;
const [packageVersions, setPackageVersions] = useState(versions);
if (!packageMeta || Object.keys(packageMeta).length === 0) {
return null;
}
const hideDeprecatedVersions = configOptions.hideDeprecatedVersions;
const hasDistTags = distTags && Object.keys(distTags).length > 0 && packageName;
const hasVersionHistory =
packageVersions && Object.keys(packageVersions).length > 0 && packageName;
const filterVersions = (textSearch) => {
const filteredVersions = Object.keys(versions).reduce((acc, version) => {
if (textSearch !== '') {
if (typeof versions[version] !== 'undefined') {
if (semver.satisfies(version, textSearch, { includePrerelease: true, loose: true })) {
acc[version] = versions[version];
}
}
} else {
acc[version] = versions[version];
}
return acc;
}, {});
setPackageVersions(filteredVersions);
};
return (
<>
@ -33,20 +53,28 @@ const Versions: React.FC<Props> = ({ packageMeta, packageName }) => {
<VersionsTagList packageName={packageName} tags={distTags} time={time} />
</>
) : null}
<>
<Typography variant="subtitle1">{t('versions.version-history')}</Typography>
<TextField
helperText={t('versions.search.placeholder')}
onChange={debounce((e) => {
filterVersions(e.target.value);
}, 200)}
size="small"
variant="standard"
/>
</>
{hasVersionHistory ? (
<>
<Typography variant="subtitle1">{t('versions.version-history')}</Typography>
<>
{hideDeprecatedVersions && (
<Alert
severity="info"
sx={{ marginTop: theme.spacing(1), marginBottom: theme.spacing(1) }}
>
{t('versions.hide-deprecated')}
</Alert>
)}
<VersionsHistoryList packageName={packageName} time={time} versions={versions} />
</>
{hideDeprecatedVersions === true && (
<Alert
severity="info"
sx={{ marginTop: theme.spacing(1), marginBottom: theme.spacing(1) }}
>
{t('versions.hide-deprecated')}
</Alert>
)}
<VersionsHistoryList packageName={packageName} time={time} versions={packageVersions} />
</>
) : null}
</>

View file

@ -14,13 +14,7 @@ interface Props {
const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChange }) => {
const { t } = useTranslation();
return (
<Tabs
color={'primary'}
indicatorColor={'primary'}
onChange={onChange}
value={tabPosition}
variant={'fullWidth'}
>
<Tabs onChange={onChange} value={tabPosition} variant={'fullWidth'}>
<Tab data-testid={'readme-tab'} id={'readme-tab'} label={t('tab.readme')} />
<Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={t('tab.dependencies')} />
<Tab data-testid={'versions-tab'} id={'versions-tab'} label={t('tab.versions')} />

View file

@ -160,7 +160,6 @@ exports[`DetailContainer renders correctly 1`] = `
>
<div
class="MuiTabs-root emotion-1 emotion-2"
color="primary"
>
<div
class="MuiTabs-scroller MuiTabs-fixed emotion-3"

View file

@ -426,24 +426,6 @@ importers:
specifier: 9.9.2
version: 9.9.2
e2e/cli/e2e-pnpm6:
dependencies:
'@verdaccio/test-cli-commons':
specifier: workspace:1.1.0
version: link:../cli-commons
pnpm:
specifier: ^6.35.1
version: 6.35.1
e2e/cli/e2e-pnpm7:
dependencies:
'@verdaccio/test-cli-commons':
specifier: workspace:1.1.0
version: link:../cli-commons
pnpm:
specifier: ^7.27.1
version: 7.32.0
e2e/cli/e2e-pnpm8:
dependencies:
'@verdaccio/test-cli-commons':
@ -1950,6 +1932,9 @@ importers:
redux:
specifier: 4.2.1
version: 4.2.1
semver:
specifier: 7.6.0
version: 7.6.0
validator:
specifier: 13.11.0
version: 13.11.0
@ -24003,18 +23988,6 @@ packages:
- typescript
dev: true
/pnpm@6.35.1:
resolution: {integrity: sha512-Pt5JEjV2JSORAqw/XtjfF4k34PBQFpqV3kUMFYSlyrWJsio5Hr3lqy2oJkpprMrVbOyBJsxXfZDQOlvmytIrgA==}
engines: {node: '>=12.17'}
hasBin: true
dev: false
/pnpm@7.32.0:
resolution: {integrity: sha512-XkLEMinrF4046cWGvvam7dsCKeRdJ9i2SeDiKNodoZEPmJp1KrzQe1qYC5Vs/v9qBXJqyI0vLzjoMHjXgphP6g==}
engines: {node: '>=14.6'}
hasBin: true
dev: false
/pnpm@8.15.5:
resolution: {integrity: sha512-sFGjLH5pWDO4SSbTspuMylclS1ifBknYmcbp0O22cLkex+KkNFm65zdZu1zmGcMmbxFr+THOItHvF1mn5Fqpbw==}
engines: {node: '>=16.14'}