fix: version Page - Replaces class by func. (#171)

* refactor: updated version page

* refactor: rollback context

* fix: added version provider
This commit is contained in:
Priscila Oliveira 2019-10-17 07:36:41 +02:00 committed by Juan Picado @jotadeveloper
parent d69fc1b260
commit f5c77ff43c
12 changed files with 6576 additions and 216 deletions

View File

@ -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> <DetailContext.Provider value={detailContextValue}>
<Version match={{ params: { ['package']: 'vue' } }}></Version> <Version />
</MemoryRouter> </DetailContext.Provider>
</ErrorBoundary> </MemoryRouter>
); );
// 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( <MemoryRouter>
[[JSON.stringify({})], { status: 404, headers: { 'content-type': 'application/json' } }], <DetailContext.Provider
[[``], { status: 404, headers: { 'content-type': 'text/html' } }] value={{
...detailContextValue,
hasNotBeenFound: true,
}}>
<Version />
</DetailContext.Provider>
</MemoryRouter>
); );
const { getByTestId, getByText } = render(
<ErrorBoundary>
<MemoryRouter>
<Version match={{ params: { ['package']: 'vue' } }}></Version>
</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

View File

@ -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); if (isLoading) {
return <Loading />;
} }
return packageName; if (hasNotBeenFound) {
} return <NotFound />;
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 <VersionLayout />;
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) {
return <Loading />;
} else if (isNotFound) {
return <NotFound />;
} else {
return <Layout />;
}
};
return (
<DetailContextProvider value={{ packageMeta, packageVersion, readMe, packageName, enableLoading: setIsLoading }}>{renderContent()}</DetailContextProvider>
);
}; };
export default Version; export default Version;

View 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;

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,9 @@
function getRouterPackageName(packageName: string, scope?: string): string {
if (scope) {
return `@${scope}/${packageName}`;
}
return packageName;
}
export default getRouterPackageName;

View File

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

View 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;

View File

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

View File

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

View File

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