mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
fix: undefined field on missing count (#3743)
* fix: undefined field on missing count * types
This commit is contained in:
parent
dbd58cd275
commit
0dafa98263
16 changed files with 189 additions and 1463 deletions
6
.changeset/tough-days-search.md
Normal file
6
.changeset/tough-days-search.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@verdaccio/ui-theme': patch
|
||||
'@verdaccio/ui-components': patch
|
||||
---
|
||||
|
||||
fix: undefined field on missing count
|
|
@ -89,6 +89,7 @@ const App: React.FC = () => {
|
|||
useEffect(() => {
|
||||
loadDayJSLocale();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StrictMode>
|
||||
<TranslatorProvider i18n={i18n} listLanguages={listLanguages} onMount={loadDayJSLocale}>
|
||||
|
|
|
@ -29,7 +29,6 @@ i18n
|
|||
whitelist: [...listLanguagesAsString],
|
||||
load: 'currentOnly',
|
||||
react: {
|
||||
wait: true,
|
||||
useSuspense: false,
|
||||
},
|
||||
resources: languages,
|
||||
|
|
|
@ -10,11 +10,17 @@ module.exports = Object.assign({}, config, {
|
|||
url: 'http://localhost:9000/',
|
||||
},
|
||||
rootDir: '..',
|
||||
collectCoverage: true,
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', '<rootDir>/jest/setup-env.ts'],
|
||||
setupFiles: ['<rootDir>/jest/setup.ts'],
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!react-syntax-highlighter)'],
|
||||
snapshotSerializers: ['@emotion/jest/serializer'],
|
||||
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/node_modules/**'],
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
'!**/node_modules/**',
|
||||
'!src/**/*.stories.{ts,tsx}',
|
||||
'!src/types/**',
|
||||
],
|
||||
modulePathIgnorePatterns: ['<rootDir>/build/'],
|
||||
moduleNameMapper: {
|
||||
'\\.(s?css)$': '<rootDir>/jest/identity.js',
|
||||
|
@ -26,7 +32,7 @@ module.exports = Object.assign({}, config, {
|
|||
'react-markdown': '<rootDir>/src/__mocks__/react-markdown.tsx',
|
||||
'remark-*': '<rootDir>/src/__mocks__/remark-plugin.ts',
|
||||
},
|
||||
coverageReporters: [['text', { skipFull: true }]],
|
||||
coverageReporters: ['text', 'html'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 68,
|
||||
|
|
|
@ -6,7 +6,7 @@ export default {
|
|||
title: 'Dist',
|
||||
};
|
||||
|
||||
export const DistFileAll: any = () => (
|
||||
export const AllProperties: any = () => (
|
||||
<Dist
|
||||
packageMeta={{
|
||||
latest: {
|
||||
|
@ -16,7 +16,50 @@ export const DistFileAll: any = () => (
|
|||
fileCount: 7,
|
||||
unpackedSize: 10,
|
||||
},
|
||||
license: '',
|
||||
license: 'MIT',
|
||||
},
|
||||
_uplinks: {},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const NoFileSize: any = () => (
|
||||
<Dist
|
||||
packageMeta={{
|
||||
latest: {
|
||||
name: 'verdaccio1',
|
||||
version: '4.0.0',
|
||||
dist: {
|
||||
// @ts-ignore
|
||||
fileCount: undefined,
|
||||
unpackedSize: 10,
|
||||
},
|
||||
license: 'MIT',
|
||||
},
|
||||
_uplinks: {},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const OnlyLicense: any = () => (
|
||||
<Dist
|
||||
packageMeta={{
|
||||
latest: {
|
||||
name: 'verdaccio1',
|
||||
version: '4.0.0',
|
||||
license: { type: 'MIT' },
|
||||
},
|
||||
_uplinks: {},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const NoRender: any = () => (
|
||||
<Dist
|
||||
packageMeta={{
|
||||
latest: {
|
||||
name: 'verdaccio1',
|
||||
version: '4.0.0',
|
||||
},
|
||||
_uplinks: {},
|
||||
}}
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('<Dist /> component', () => {
|
|||
});
|
||||
|
||||
test('should render the component in default state', () => {
|
||||
const wrapper = render(
|
||||
const { getByText } = render(
|
||||
withDistComponent({
|
||||
latest: {
|
||||
name: 'verdaccio1',
|
||||
|
@ -28,11 +28,16 @@ describe('<Dist /> component', () => {
|
|||
_uplinks: {},
|
||||
})
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(getByText('sidebar.distribution.title')).toBeInTheDocument();
|
||||
expect(getByText('sidebar.distribution.file-count')).toBeInTheDocument();
|
||||
expect(getByText('sidebar.distribution.size')).toBeInTheDocument();
|
||||
expect(getByText('sidebar.distribution.size')).toBeInTheDocument();
|
||||
expect(getByText('7', { exact: false })).toBeInTheDocument();
|
||||
expect(getByText('10.00 Bytes', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the component with license as string', () => {
|
||||
const wrapper = render(
|
||||
const { getByText } = render(
|
||||
withDistComponent({
|
||||
latest: {
|
||||
name: 'verdaccio2',
|
||||
|
@ -46,11 +51,11 @@ describe('<Dist /> component', () => {
|
|||
_uplinks: {},
|
||||
})
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(getByText('MIT', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render the component with license as object', () => {
|
||||
const wrapper = render(
|
||||
const { getByText } = render(
|
||||
withDistComponent({
|
||||
latest: {
|
||||
name: 'verdaccio3',
|
||||
|
@ -67,6 +72,28 @@ describe('<Dist /> component', () => {
|
|||
_uplinks: {},
|
||||
})
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(getByText('MIT', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render if latest is missing', () => {
|
||||
const { queryAllByText } = render(
|
||||
withDistComponent({
|
||||
// @ts-ignore
|
||||
latest: undefined,
|
||||
_uplinks: {},
|
||||
})
|
||||
);
|
||||
expect(queryAllByText('sidebar.distribution.title')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should not render if latest content is missing', () => {
|
||||
const { queryAllByText } = render(
|
||||
withDistComponent({
|
||||
// @ts-expect-error
|
||||
latest: { dist: undefined, license: undefined },
|
||||
_uplinks: {},
|
||||
})
|
||||
);
|
||||
expect(queryAllByText('sidebar.distribution.title')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,23 +25,31 @@ const DistChip: FC<{ name: string; children?: React.ReactElement | string }> = (
|
|||
const Dist: FC<{ packageMeta: PackageMetaInterface }> = ({ packageMeta }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!packageMeta) {
|
||||
if (!packageMeta?.latest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { dist, license } = packageMeta && packageMeta.latest;
|
||||
const { dist, license } = packageMeta.latest;
|
||||
|
||||
if (!dist && !license) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
subheader={<StyledText variant="subtitle1">{t('sidebar.distribution.title')}</StyledText>}
|
||||
>
|
||||
<DistListItem>
|
||||
<DistChip name={t('sidebar.distribution.file-count')}>{`${dist.fileCount}`}</DistChip>
|
||||
{dist.unpackedSize ? (
|
||||
{dist?.fileCount && (
|
||||
<DistChip name={t('sidebar.distribution.file-count')}>{`${dist.fileCount}`}</DistChip>
|
||||
)}
|
||||
{dist?.unpackedSize ? (
|
||||
<DistChip name={t('sidebar.distribution.size')}>{fileSizeSI(dist.unpackedSize)}</DistChip>
|
||||
) : null}
|
||||
|
||||
<DistChip name={t('sidebar.distribution.license')}>{formatLicense(license)}</DistChip>
|
||||
<DistChip name={t('sidebar.distribution.license')}>
|
||||
{formatLicense(license as string)}
|
||||
</DistChip>
|
||||
</DistListItem>
|
||||
</List>
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,11 @@
|
|||
import { LicenseInterface } from '../../types/packageMeta';
|
||||
|
||||
/**
|
||||
* Formats license field for webui.
|
||||
* @see https://docs.npmjs.com/files/package.json#license
|
||||
*/
|
||||
// License should use type License defined above, but conflicts with the unit test that provide array or empty object
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function formatLicense(license: any): string | undefined {
|
||||
export function formatLicense(license: string | LicenseInterface): string | undefined {
|
||||
if (typeof license === 'string') {
|
||||
return license;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { fileSizeSI, formatLicense } from './utils';
|
||||
|
||||
test('formatLicense as string', () => {
|
||||
expect(formatLicense('MIT')).toEqual('MIT');
|
||||
});
|
||||
|
||||
test('formatLicense as format object', () => {
|
||||
expect(formatLicense({ type: 'MIT' })).toEqual('MIT');
|
||||
});
|
||||
|
||||
test('fileSizeSI as number 1000', () => {
|
||||
expect(fileSizeSI(1000)).toEqual('1.00 kB');
|
||||
});
|
||||
|
||||
test('fileSizeSI as number 0', () => {
|
||||
expect(fileSizeSI(0)).toEqual('0.00 Bytes');
|
||||
});
|
|
@ -122,7 +122,7 @@ const Package: React.FC<PackageInterface> = ({
|
|||
license && (
|
||||
<OverviewItem>
|
||||
<StyledLaw />
|
||||
{license}
|
||||
{license as string}
|
||||
</OverviewItem>
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import i18n from 'i18next';
|
||||
import React from 'react';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import TranslatorProvider, { useLanguage } from './TranslatorProvider';
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: 'en-US',
|
||||
fallbackLng: 'en-US',
|
||||
whitelist: ['en-US'],
|
||||
load: 'currentOnly',
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
resources: {
|
||||
'en-US': {
|
||||
translation: {
|
||||
'copy-to-clipboard': 'Copy to clipboard',
|
||||
'author-anonymous': 'Anonymous',
|
||||
'author-unknown': 'Unknown',
|
||||
},
|
||||
},
|
||||
},
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
const RandomComponent = () => {
|
||||
const { language } = useLanguage();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div>{language}</div>
|
||||
<div>{t('copy-to-clipboard')}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
test('should provide translation', () => {
|
||||
const mount = jest.fn();
|
||||
const { getByText } = render(
|
||||
<TranslatorProvider i18n={i18n} listLanguages={[]} onMount={mount}>
|
||||
<RandomComponent />
|
||||
</TranslatorProvider>
|
||||
);
|
||||
expect(getByText('en-US')).toBeInTheDocument();
|
||||
expect(getByText('Copy to clipboard')).toBeInTheDocument();
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
import { i18n } from 'i18next';
|
||||
import React, { FunctionComponent, createContext, useCallback, useContext, useEffect } from 'react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
|
||||
|
@ -24,7 +25,7 @@ const I18nTranslatorContext = createContext<TranslatorProviderProps>({
|
|||
*/
|
||||
const TranslatorProvider: FunctionComponent<{
|
||||
children: React.ReactElement<any>;
|
||||
i18n: any;
|
||||
i18n: i18n;
|
||||
listLanguages: any;
|
||||
onMount: () => {};
|
||||
}> = ({ children, onMount, i18n, listLanguages }) => {
|
||||
|
|
|
@ -16,7 +16,7 @@ function CustomComponent() {
|
|||
const { packageMeta, packageName, packageVersion } = useVersion();
|
||||
return (
|
||||
<div>
|
||||
<div>{packageMeta?.latest?.license}</div>
|
||||
<div>{packageMeta?.latest?.license as string}</div>
|
||||
<div>{packageName}</div>
|
||||
<div>{packageVersion}</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ export type Latest = {
|
|||
author?: Author;
|
||||
deprecated?: string;
|
||||
name: string;
|
||||
dist: {
|
||||
dist?: {
|
||||
fileCount: number;
|
||||
unpackedSize: number;
|
||||
tarball?: string;
|
||||
|
@ -15,7 +15,7 @@ export type Latest = {
|
|||
pnpm?: string;
|
||||
yarn?: string;
|
||||
};
|
||||
license?: Partial<LicenseInterface> | string;
|
||||
license?: undefined | LicenseInterface | string;
|
||||
version: string;
|
||||
homepage?: string;
|
||||
bugs?: {
|
||||
|
@ -50,12 +50,12 @@ export interface Developer {
|
|||
|
||||
interface Funding {
|
||||
type?: string;
|
||||
url: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
interface LicenseInterface {
|
||||
type: string;
|
||||
url: string;
|
||||
export interface LicenseInterface {
|
||||
type?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface DistTags {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"jsx": "react"
|
||||
},
|
||||
"include": ["src/**/*", "./src/types/index.d.ts"],
|
||||
"types": ["node", "jest", "@testing-library/jest-dom"],
|
||||
"typedocOptions": {
|
||||
"categoryOrder": ["Model", "Component", "Provider", "*"],
|
||||
"defaultCategory": "Component",
|
||||
|
|
Loading…
Reference in a new issue