forked from sombochea/verdaccio-ui
fix: version Page - Replaces class by func. (#171)
* refactor: updated version page * refactor: rollback context * fix: added version provider
This commit is contained in:
parent
d69fc1b260
commit
f5c77ff43c
@ -1,92 +1,59 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, cleanup } from '@testing-library/react';
|
|
||||||
import { MemoryRouter } from 'react-router';
|
import { MemoryRouter } from 'react-router';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
import { waitForElement } from '@testing-library/dom';
|
import { waitForElement } from '@testing-library/dom';
|
||||||
|
|
||||||
import vueMetadata from '../../../test/fixtures/metadata/vue.json';
|
import { NOT_FOUND_TEXT } from '../../components/NotFound';
|
||||||
import ErrorBoundary from '../../App/AppError';
|
|
||||||
|
|
||||||
import Version from './Version';
|
import Version from './Version';
|
||||||
|
import { DetailContext } from './context';
|
||||||
|
import data from './__partials__/data.json';
|
||||||
|
|
||||||
// :-) we mock this otherways fails on render, some weird issue on material-ui
|
// :-) we mock this otherways fails on render, some weird issue on material-ui
|
||||||
jest.mock('../../muiComponents/Avatar');
|
jest.mock('../../muiComponents/Avatar');
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
const detailContextValue = {
|
||||||
jest.mock('../../components/NotFound', () => () => <div>{'Not found'}</div>);
|
packageName: 'foo',
|
||||||
|
packageMeta: data,
|
||||||
|
readMe: 'Read me!',
|
||||||
|
enableLoading: jest.fn(),
|
||||||
|
isLoading: false,
|
||||||
|
hasNotBeenFound: false,
|
||||||
|
version: '1.0.0',
|
||||||
|
};
|
||||||
|
|
||||||
describe('test Version page', () => {
|
describe('test Version page', () => {
|
||||||
jest.setTimeout(40000000);
|
/* eslint-disable react/jsx-max-depth */
|
||||||
beforeAll(() => {
|
|
||||||
// FIXME: a better way to mock this
|
|
||||||
// @ts-ignore
|
|
||||||
global.window.VERDACCIO_API_URL = 'http://test';
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
// @ts-ignore
|
|
||||||
fetch.resetMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render the version page', async () => {
|
test('should render the version page', async () => {
|
||||||
const readmeText = 'test';
|
|
||||||
// @ts-ignore
|
|
||||||
fetch.mockResponses(
|
|
||||||
[[JSON.stringify(vueMetadata)], { status: 200, headers: { 'content-type': 'application/json' } }],
|
|
||||||
[[`<p align="center">${readmeText}</p>`], { status: 200, headers: { 'content-type': 'text/html' } }]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getByTestId, getByText } = render(
|
const { getByTestId, getByText } = render(
|
||||||
<ErrorBoundary>
|
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Version match={{ params: { ['package']: 'vue' } }}></Version>
|
<DetailContext.Provider value={detailContextValue}>
|
||||||
|
<Version />
|
||||||
|
</DetailContext.Provider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// first it display loading
|
|
||||||
const hasLoading = getByTestId('loading');
|
|
||||||
expect(hasLoading).toBeTruthy();
|
|
||||||
|
|
||||||
// we wait fetch response (mocked above)
|
// we wait fetch response (mocked above)
|
||||||
await waitForElement(() => getByTestId('version-layout'));
|
await waitForElement(() => getByTestId('version-layout'));
|
||||||
|
|
||||||
// check whether readme was loaded
|
// check whether readme was loaded
|
||||||
const hasReadme = getByText(readmeText);
|
const hasReadme = getByText(detailContextValue.readMe);
|
||||||
|
|
||||||
expect(hasReadme).toBeTruthy();
|
expect(hasReadme).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render 404 page if the resources are not found', async () => {
|
test('should render 404 page if the resources are not found', async () => {
|
||||||
// @ts-ignore
|
const { getByText } = render(
|
||||||
fetch.mockResponses(
|
|
||||||
[[JSON.stringify({})], { status: 404, headers: { 'content-type': 'application/json' } }],
|
|
||||||
[[``], { status: 404, headers: { 'content-type': 'text/html' } }]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getByTestId, getByText } = render(
|
|
||||||
<ErrorBoundary>
|
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Version match={{ params: { ['package']: 'vue' } }}></Version>
|
<DetailContext.Provider
|
||||||
|
value={{
|
||||||
|
...detailContextValue,
|
||||||
|
hasNotBeenFound: true,
|
||||||
|
}}>
|
||||||
|
<Version />
|
||||||
|
</DetailContext.Provider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// first it display loading
|
|
||||||
const hasLoading = getByTestId('loading');
|
|
||||||
expect(hasLoading).toBeTruthy();
|
|
||||||
|
|
||||||
// we wait fetch response (mocked above)
|
// we wait fetch response (mocked above)
|
||||||
await waitForElement(() => getByText('Not found'));
|
const notFoundElement = await waitForElement(() => getByText(NOT_FOUND_TEXT));
|
||||||
|
expect(notFoundElement).toBeTruthy();
|
||||||
// check whether readme was loaded
|
|
||||||
const hasReadme = getByText('Not found');
|
|
||||||
|
|
||||||
expect(hasReadme).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wanna contribute? Here we some scenarios we need to test
|
// Wanna contribute? Here we some scenarios we need to test
|
||||||
|
@ -1,98 +1,24 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import { callDetailPage, callReadme } from '../../utils/calls';
|
|
||||||
import { buildScopePackage } from '../../utils/package';
|
|
||||||
import Loading from '../../components/Loading/Loading';
|
import Loading from '../../components/Loading/Loading';
|
||||||
import NotFound from '../../components/NotFound';
|
import NotFound from '../../components/NotFound';
|
||||||
|
|
||||||
import { Layout } from './Layout';
|
import VersionLayout from './VersionLayout';
|
||||||
import { DetailContextProvider } from './context';
|
import { DetailContext } from './context';
|
||||||
import { StateInterface } from './types';
|
|
||||||
|
|
||||||
export function getRouterPackageName(params): string {
|
const Version: React.FC = () => {
|
||||||
const packageName = params.package;
|
const detailContext = useContext(DetailContext);
|
||||||
const { scope } = params;
|
const { isLoading, hasNotBeenFound } = detailContext;
|
||||||
if (scope) {
|
|
||||||
return buildScopePackage(scope, packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return packageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fillTitle(text: string): string {
|
|
||||||
return `Verdaccio - ${text}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isVersionValid(packageMeta, packageVersion): boolean {
|
|
||||||
const hasVersion = typeof packageVersion !== 'undefined';
|
|
||||||
if (!hasVersion) {
|
|
||||||
// if is undefined, that means versions does not exist, we continue
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasMatchVersion = Object.keys(packageMeta.versions).includes(packageVersion);
|
|
||||||
return hasMatchVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Version = ({ match: { params } }) => {
|
|
||||||
const pkgName = getRouterPackageName(params);
|
|
||||||
const [readMe, setReadme] = useState();
|
|
||||||
const [packageName, setPackageName] = useState(pkgName);
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const [packageVersion, setPackageVersion] = useState(params.version);
|
|
||||||
const [packageMeta, setPackageMeta] = useState();
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [notFound, setNotFound] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const packageMeta = (await callDetailPage(packageName, packageVersion)) as Partial<StateInterface>;
|
|
||||||
const readMe = (await callReadme(packageName, packageVersion)) as Partial<StateInterface>;
|
|
||||||
if (isVersionValid(packageMeta, packageVersion)) {
|
|
||||||
setReadme(readMe);
|
|
||||||
setPackageMeta(packageMeta);
|
|
||||||
setIsLoading(false);
|
|
||||||
} else {
|
|
||||||
setIsLoading(false);
|
|
||||||
setNotFound(true);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setNotFound(true);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [packageName, packageVersion]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!packageVersion) {
|
|
||||||
document.title = fillTitle(packageName);
|
|
||||||
} else {
|
|
||||||
document.title = fillTitle(`${packageName}@${packageVersion}`);
|
|
||||||
}
|
|
||||||
}, [packageName, packageVersion]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const pkgName = getRouterPackageName(params);
|
|
||||||
|
|
||||||
setPackageName(pkgName);
|
|
||||||
setPackageVersion(params.version);
|
|
||||||
}, [params, setPackageName, setPackageVersion]);
|
|
||||||
|
|
||||||
const isNotFound = notFound || typeof packageMeta === 'undefined' || typeof packageName === 'undefined' || typeof readMe === 'undefined';
|
|
||||||
const renderContent = (): React.ReactElement<HTMLElement> => {
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
} else if (isNotFound) {
|
|
||||||
return <NotFound />;
|
|
||||||
} else {
|
|
||||||
return <Layout />;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
if (hasNotBeenFound) {
|
||||||
<DetailContextProvider value={{ packageMeta, packageVersion, readMe, packageName, enableLoading: setIsLoading }}>{renderContent()}</DetailContextProvider>
|
return <NotFound />;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return <VersionLayout />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Version;
|
export default Version;
|
||||||
|
69
src/pages/Version/VersionContextProvider.tsx
Normal file
69
src/pages/Version/VersionContextProvider.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { callDetailPage, callReadme } from '../../utils/calls';
|
||||||
|
|
||||||
|
import getRouterPackageName from './get-route-package-name';
|
||||||
|
import { DetailContext } from './context';
|
||||||
|
import isPackageVersionValid from './is-package-version-valid';
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
scope?: string;
|
||||||
|
package: string;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VersionContextProvider: React.FC = ({ children }) => {
|
||||||
|
const { version, package: pkgName, scope } = useParams<Params>();
|
||||||
|
const [packageName, setPackageName] = useState(getRouterPackageName(pkgName, scope));
|
||||||
|
const [packageVersion, setPackageVersion] = useState(version);
|
||||||
|
const [packageMeta, setPackageMeta] = useState();
|
||||||
|
const [readMe, setReadme] = useState();
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [hasNotBeenFound, setHasNotBeenFound] = useState();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updatedPackageName = getRouterPackageName(pkgName, scope);
|
||||||
|
setPackageName(updatedPackageName);
|
||||||
|
}, [pkgName, scope]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPackageVersion(version);
|
||||||
|
}, [version]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(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) {
|
||||||
|
setHasNotBeenFound(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [packageName, packageVersion]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetailContext.Provider
|
||||||
|
value={{
|
||||||
|
packageMeta,
|
||||||
|
packageVersion,
|
||||||
|
readMe,
|
||||||
|
packageName,
|
||||||
|
isLoading,
|
||||||
|
hasNotBeenFound,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</DetailContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VersionContextProvider;
|
@ -1,28 +1,20 @@
|
|||||||
import React, { FC, ReactElement } from 'react';
|
import React from 'react';
|
||||||
import Grid from '@material-ui/core/Grid';
|
import Grid from '@material-ui/core/Grid';
|
||||||
|
|
||||||
import DetailContainer from '../../components/DetailContainer';
|
import DetailContainer from '../../components/DetailContainer';
|
||||||
import DetailSidebar from '../../components/DetailSidebar';
|
import DetailSidebar from '../../components/DetailSidebar';
|
||||||
|
|
||||||
function renderDetail(): ReactElement<HTMLElement> {
|
const VersionLayout: React.FC = () => {
|
||||||
return <DetailContainer />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSidebar(): ReactElement<HTMLElement> {
|
|
||||||
return <DetailSidebar />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Layout: FC<{}> = () => {
|
|
||||||
return (
|
return (
|
||||||
<Grid className={'container content'} container={true} data-testid="version-layout" spacing={0}>
|
<Grid className={'container content'} container={true} data-testid="version-layout" spacing={0}>
|
||||||
<Grid item={true} md={8} xs={12}>
|
<Grid item={true} md={8} xs={12}>
|
||||||
{renderDetail()}
|
<DetailContainer />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item={true} md={4} xs={12}>
|
<Grid item={true} md={4} xs={12}>
|
||||||
{renderSidebar()}
|
<DetailSidebar />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Layout };
|
export default VersionLayout;
|
6406
src/pages/Version/__partials__/data.json
Normal file
6406
src/pages/Version/__partials__/data.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,26 @@
|
|||||||
import React, { Consumer, Provider } from 'react';
|
import { createContext, Consumer, Provider } from 'react';
|
||||||
|
|
||||||
import { DetailContextProps, VersionPageConsumerProps } from './types';
|
import { PackageMetaInterface } from '../../../types/packageMeta';
|
||||||
|
export interface DetailContextProps {
|
||||||
|
packageMeta: PackageMetaInterface;
|
||||||
|
packageVersion?: string;
|
||||||
|
readMe: string;
|
||||||
|
packageName: string;
|
||||||
|
enableLoading: () => void;
|
||||||
|
isLoading: boolean;
|
||||||
|
hasNotBeenFound: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const DetailContext = React.createContext<Partial<DetailContextProps>>({});
|
export interface VersionPageConsumerProps {
|
||||||
|
packageMeta: PackageMetaInterface;
|
||||||
|
readMe: string;
|
||||||
|
packageName: string;
|
||||||
|
packageVersion?: string;
|
||||||
|
// FIXME: looking for the appropiated type here
|
||||||
|
enableLoading: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DetailContext = createContext<Partial<DetailContextProps>>({});
|
||||||
|
|
||||||
export const DetailContextProvider: Provider<Partial<VersionPageConsumerProps>> = DetailContext.Provider;
|
export const DetailContextProvider: Provider<Partial<VersionPageConsumerProps>> = DetailContext.Provider;
|
||||||
export const DetailContextConsumer: Consumer<Partial<VersionPageConsumerProps>> = DetailContext.Consumer;
|
export const DetailContextConsumer: Consumer<Partial<VersionPageConsumerProps>> = DetailContext.Consumer;
|
||||||
|
9
src/pages/Version/get-route-package-name.ts
Normal file
9
src/pages/Version/get-route-package-name.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
function getRouterPackageName(packageName: string, scope?: string): string {
|
||||||
|
if (scope) {
|
||||||
|
return `@${scope}/${packageName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getRouterPackageName;
|
@ -1,3 +1,2 @@
|
|||||||
export { DetailContextProps, VersionPageConsumerProps } from './types';
|
export { DetailContext, DetailContextConsumer, DetailContextProvider, DetailContextProps, VersionPageConsumerProps } from './context';
|
||||||
export { DetailContext, DetailContextConsumer, DetailContextProvider } from './context';
|
|
||||||
export { default } from './Version';
|
export { default } from './Version';
|
||||||
|
16
src/pages/Version/is-package-version-valid.ts
Normal file
16
src/pages/Version/is-package-version-valid.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,11 +0,0 @@
|
|||||||
import styled from 'react-emotion';
|
|
||||||
|
|
||||||
import colors from '../../utils/styles/colors';
|
|
||||||
import { fontSize } from '../../utils/styles/sizes';
|
|
||||||
import DialogTitle from '../../muiComponents/DialogTitle';
|
|
||||||
|
|
||||||
export const Title = styled(DialogTitle)({
|
|
||||||
backgroundColor: colors.primary,
|
|
||||||
color: colors.white,
|
|
||||||
fontSize: fontSize.lg,
|
|
||||||
});
|
|
@ -1,30 +0,0 @@
|
|||||||
import { PackageMetaInterface } from '../../../types/packageMeta';
|
|
||||||
|
|
||||||
export interface DetailContextProps {
|
|
||||||
packageMeta: PackageMetaInterface;
|
|
||||||
packageVersion?: string;
|
|
||||||
readMe: string;
|
|
||||||
packageName: string;
|
|
||||||
enableLoading: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VersionPageConsumerProps {
|
|
||||||
packageMeta: PackageMetaInterface;
|
|
||||||
readMe: string;
|
|
||||||
packageName: string;
|
|
||||||
packageVersion?: string;
|
|
||||||
// FIXME: looking for the appropiated type here
|
|
||||||
enableLoading: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PropsInterface {
|
|
||||||
match: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StateInterface {
|
|
||||||
readMe: string;
|
|
||||||
packageName: string;
|
|
||||||
packageMeta?: PackageMetaInterface;
|
|
||||||
isLoading: boolean;
|
|
||||||
notFound: boolean;
|
|
||||||
}
|
|
@ -12,6 +12,7 @@ const history = createBrowserHistory({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const NotFound = React.lazy(() => import('./components/NotFound'));
|
const NotFound = React.lazy(() => import('./components/NotFound'));
|
||||||
|
const VersionContextProvider = React.lazy(() => import('./pages/Version/VersionContextProvider'));
|
||||||
const VersionPackage = React.lazy(() => import('./pages/Version'));
|
const VersionPackage = React.lazy(() => import('./pages/Version'));
|
||||||
const HomePage = React.lazy(() => import('./pages/home'));
|
const HomePage = React.lazy(() => import('./pages/home'));
|
||||||
|
|
||||||
@ -61,13 +62,11 @@ class RouterApp extends Component<RouterAppProps> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public renderVersionPage = (routerProps): ReactElement<HTMLDivElement> => {
|
public renderVersionPage = (): ReactElement<HTMLDivElement> => {
|
||||||
return (
|
return (
|
||||||
<AppContextConsumer>
|
<VersionContextProvider>
|
||||||
{function renderConsumerVersionPage({ isUserLoggedIn }) {
|
<VersionPackage />
|
||||||
return <VersionPackage {...routerProps} isUserLoggedIn={isUserLoggedIn} />;
|
</VersionContextProvider>
|
||||||
}}
|
|
||||||
</AppContextConsumer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user