Unverified Commit 39b2110f authored by Andrey Azov's avatar Andrey Azov Committed by GitHub

Add 404 screen (#344)

parent ce763e09
Pipeline #98252 passed with stages
in 7 minutes and 19 seconds
......@@ -15,18 +15,17 @@
*/
import React, { useEffect, lazy, Suspense } from 'react';
import { Route, Switch, useLocation } from 'react-router-dom';
import { Route, Switch, Redirect, useLocation } from 'react-router-dom';
import { connect } from 'react-redux';
import { changeCurrentApp } from 'src/header/headerActions';
import ErrorBoundary from 'src/shared/components/error-boundary/ErrorBoundary';
import { NewTechError } from 'src/shared/components/error-screen';
import Header from 'src/header/Header';
const HomePage = lazy(() => import('../home/Home'));
const GlobalSearch = lazy(() => import('./global-search/GlobalSearch'));
const SpeciesSelector = lazy(() =>
import('./species-selector/SpeciesSelector')
const SpeciesSelector = lazy(
() => import('./species-selector/SpeciesSelector')
);
const SpeciesPage = lazy(() => import('./species/SpeciesPage'));
const CustomDownload = lazy(() => import('./custom-download/CustomDownload'));
......@@ -59,22 +58,26 @@ const AppInner = (props: AppProps) => {
}, [location.pathname]);
return (
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path={`/`} component={HomePage} exact />
<Route path={`/global-search`} component={GlobalSearch} />
<Route path={`/species-selector`} component={SpeciesSelector} />
<Route path={`/species/:genomeId`} component={SpeciesPage} />
<Route path={`/custom-download`} component={CustomDownload} />
<Route
path={`/entity-viewer/:genomeId?/:entityId?`}
component={EntityViewer}
/>
<ErrorBoundary fallbackComponent={NewTechError}>
<>
<Header />
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path={`/`} component={HomePage} exact />
<Route path={`/global-search`} component={GlobalSearch} />
<Route path={`/species-selector`} component={SpeciesSelector} />
<Route path={`/species/:genomeId`} component={SpeciesPage} />
<Route path={`/custom-download`} component={CustomDownload} />
<Route
path={`/entity-viewer/:genomeId?/:entityId?`}
component={EntityViewer}
/>
<Route path={`/genome-browser/:genomeId?`} component={Browser} />
</ErrorBoundary>
</Switch>
</Suspense>
<Route>
<Redirect to={{ ...location, state: { is404: true } }} />
</Route>
</Switch>
</Suspense>
</>
);
};
......
......@@ -58,13 +58,13 @@ import TrackPanelTabs from './track-panel/track-panel-tabs/TrackPanelTabs';
import BrowserAppBar from './browser-app-bar/BrowserAppBar';
import Drawer from './drawer/Drawer';
import { StandardAppLayout } from 'src/shared/components/layout';
import ErrorBoundary from 'src/shared/components/error-boundary/ErrorBoundary';
import { NewTechError } from 'src/shared/components/error-screen';
import { RootState } from 'src/store';
import { ChrLocation } from './browserState';
import { EnsObject } from 'src/shared/state/ens-object/ensObjectTypes';
import 'ensembl-genome-browser';
import styles from './Browser.scss';
export type BrowserProps = {
......@@ -201,4 +201,30 @@ const mapDispatchToProps = {
toggleTrackPanel
};
export default connect(mapStateToProps, mapDispatchToProps)(Browser);
const ReduxConnectedBrowser = connect(
mapStateToProps,
mapDispatchToProps
)(Browser);
const WasmLoadingBrowserContainer = () => {
useEffect(() => {
/* eslint-disable */
// @ts-ignore ensembl-genome-browser does not have typescript definitions
import('ensembl-genome-browser');
/* eslint-enable */
});
return <ReduxConnectedBrowser />;
};
const ErrorWrappedBrowser = () => {
// if an error happens during loading of the browser,
// we will be able to show custom error string
return (
<ErrorBoundary fallbackComponent={NewTechError}>
<WasmLoadingBrowserContainer />
</ErrorBoundary>
);
};
export default ErrorWrappedBrowser;
......@@ -16,16 +16,15 @@
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter, Redirect, useLocation } from 'react-router-dom';
import { Root } from './Root';
import Header from '../header/Header';
import App from '../content/app/App';
import privacyBannerService from '../shared/components/privacy-banner/privacy-banner-service';
import windowService from 'src/services/window-service';
import { mockMatchMedia } from 'tests/mocks/mockWindowService';
jest.mock('../header/Header', () => () => 'Header');
jest.mock('../content/app/App', () => () => 'App');
jest.mock('../shared/components/privacy-banner/PrivacyBanner', () => () => (
<div className="privacyBanner">PrivacyBanner</div>
......@@ -40,7 +39,11 @@ describe('<Root />', () => {
breakpointWidth: 0,
updateBreakpointWidth: updateBreakpointWidth
};
const getRenderedRoot = (props: any) => <Root {...props} />;
const getRenderedRoot = (props: any) => (
<MemoryRouter>
<Root {...props} />
</MemoryRouter>
);
beforeEach(() => {
jest
......@@ -53,10 +56,6 @@ describe('<Root />', () => {
jest.resetAllMocks();
});
it('contains Header', () => {
expect(wrapper.contains(<Header />)).toBe(true);
});
it('contains App', () => {
expect(wrapper.contains(<App />)).toBe(true);
});
......@@ -82,4 +81,22 @@ describe('<Root />', () => {
expect(wrapper.find('.privacyBanner').length).toBe(0);
(privacyBannerService.shouldShowBanner as any).mockRestore();
});
it('displays 404 screen if no route was matched', () => {
const Redirect404 = () => {
const location = useLocation();
return <Redirect to={{ ...location, state: { is404: true } }} />;
};
const wrapper = mount(
<MemoryRouter>
<Root {...defaultProps} />
<Redirect404 />
</MemoryRouter>
);
expect(wrapper.contains(<App />)).toBe(false);
expect(wrapper.text()).toContain('page not found');
});
});
......@@ -16,17 +16,20 @@
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { globalMediaQueries, BreakpointWidth } from '../global/globalConfig';
import { updateBreakpointWidth } from '../global/globalActions';
import { observeMediaQueries } from 'src/global/windowSizeHelpers';
import Header from '../header/Header';
import App from '../content/app/App';
import PrivacyBanner from '../shared/components/privacy-banner/PrivacyBanner';
import privacyBannerService from '../shared/components/privacy-banner/privacy-banner-service';
import ErrorBoundary from 'src/shared/components/error-boundary/ErrorBoundary';
import { GeneralErrorScreen } from 'src/shared/components/error-screen';
import {
GeneralErrorScreen,
NotFoundErrorScreen
} from 'src/shared/components/error-screen';
import styles from './Root.scss';
......@@ -36,6 +39,7 @@ type Props = {
export const Root = (props: Props) => {
const [showPrivacyBanner, setShowPrivacyBanner] = useState(false);
const location = useLocation<{ is404: boolean } | undefined>();
useEffect(() => {
const subscription = observeMediaQueries(globalMediaQueries, (match) => {
......@@ -55,10 +59,13 @@ export const Root = (props: Props) => {
setShowPrivacyBanner(false);
};
if (location.state?.is404) {
return <NotFoundErrorScreen />;
}
return (
<div className={styles.root}>
<ErrorBoundary fallbackComponent={GeneralErrorScreen}>
<Header />
<App />
{showPrivacyBanner && <PrivacyBanner closeBanner={closeBanner} />}
</ErrorBoundary>
......
/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
const NotFoundErrorScreen = () => {
const containerStyles = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh'
} as const;
const firstLineStyles = {
fontSize: '72px',
margin: 0
};
const secondLineStyles = {
fontSize: '32px'
};
return (
<div style={containerStyles}>
<p style={firstLineStyles}>404</p>
<p style={secondLineStyles}>page not found</p>
</div>
);
};
export default NotFoundErrorScreen;
......@@ -16,3 +16,4 @@
export { default as NewTechError } from './NewTechError';
export { default as GeneralErrorScreen } from './GeneralErrorScreen';
export { default as NotFoundErrorScreen } from './NotFoundError';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment