0
Fork 0
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:
Juan Picado 2023-04-18 00:52:45 +02:00 committed by GitHub
parent dbd58cd275
commit 0dafa98263
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 189 additions and 1463 deletions

View file

@ -0,0 +1,6 @@
---
'@verdaccio/ui-theme': patch
'@verdaccio/ui-components': patch
---
fix: undefined field on missing count

View file

@ -89,6 +89,7 @@ const App: React.FC = () => {
useEffect(() => {
loadDayJSLocale();
}, []);
return (
<StrictMode>
<TranslatorProvider i18n={i18n} listLanguages={listLanguages} onMount={loadDayJSLocale}>

View file

@ -29,7 +29,6 @@ i18n
whitelist: [...listLanguagesAsString],
load: 'currentOnly',
react: {
wait: true,
useSuspense: false,
},
resources: languages,

View file

@ -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,

View file

@ -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: {},
}}

View file

@ -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);
});
});

View file

@ -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>
);

View file

@ -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;
}

View file

@ -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');
});

View file

@ -122,7 +122,7 @@ const Package: React.FC<PackageInterface> = ({
license && (
<OverviewItem>
<StyledLaw />
{license}
{license as string}
</OverviewItem>
);

View file

@ -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();
});

View file

@ -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 }) => {

View file

@ -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>

View file

@ -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 {

View file

@ -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",