Merge pull request #1171 from spotify/blam/react-router
Migrate to React Router V6
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
@@ -35,7 +35,6 @@
|
||||
"@types/jest": "^25.2.2",
|
||||
"@types/jquery": "^3.3.34",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"cypress": "^4.2.0",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"plugin-welcome": "0.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -21,7 +21,6 @@
|
||||
"@testing-library/user-event": "^10.2.4",
|
||||
"@types/jest": "^25.2.2",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"@types/testing-library__jest-dom": "^5.0.4",
|
||||
"cross-env": "^7.0.0",
|
||||
"cypress": "^4.2.0",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-use": "^14.2.0",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router-dom": "6.0.0-alpha.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^{{version}}",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@types/react": "^16.9",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.12.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
|
||||
@@ -13,9 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentType, FC, useMemo } from 'react';
|
||||
import { Route, Switch, Redirect } from 'react-router-dom';
|
||||
import { Route, Routes, Navigate } from 'react-router-dom';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { BackstageApp, AppComponents, AppConfigLoader, Apis } from './types';
|
||||
import { BackstagePlugin } from '../plugin';
|
||||
@@ -90,50 +89,31 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
for (const output of plugin.output()) {
|
||||
switch (output.type) {
|
||||
case 'legacy-route': {
|
||||
const { path, component, options = {} } = output;
|
||||
const { exact = true } = options;
|
||||
const { path, component: Component } = output;
|
||||
routes.push(
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
component={component}
|
||||
exact={exact}
|
||||
/>,
|
||||
<Route key={path} path={path} element={<Component />} />,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'route': {
|
||||
const { target, component, options = {} } = output;
|
||||
const { exact = true } = options;
|
||||
const { target, component: Component } = output;
|
||||
routes.push(
|
||||
<Route
|
||||
key={`${plugin.getId()}-${target.path}`}
|
||||
path={target.path}
|
||||
component={component}
|
||||
exact={exact}
|
||||
element={<Component />}
|
||||
/>,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'legacy-redirect-route': {
|
||||
const { path, target, options = {} } = output;
|
||||
const { exact = true } = options;
|
||||
routes.push(
|
||||
<Redirect key={path} path={path} to={target} exact={exact} />,
|
||||
);
|
||||
const { path, target } = output;
|
||||
routes.push(<Navigate key={path} to={target} />);
|
||||
break;
|
||||
}
|
||||
case 'redirect-route': {
|
||||
const { from, to, options = {} } = output;
|
||||
const { exact = true } = options;
|
||||
routes.push(
|
||||
<Redirect
|
||||
key={from.path}
|
||||
path={from.path}
|
||||
to={to.path}
|
||||
exact={exact}
|
||||
/>,
|
||||
);
|
||||
const { from, to } = output;
|
||||
routes.push(<Navigate key={from.path} to={to.path} />);
|
||||
break;
|
||||
}
|
||||
case 'feature-flag': {
|
||||
@@ -155,10 +135,10 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
}
|
||||
|
||||
const rendered = (
|
||||
<Switch>
|
||||
<Routes>
|
||||
{routes}
|
||||
<Route component={NotFoundErrorPage} />
|
||||
</Switch>
|
||||
<Route element={<NotFoundErrorPage />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
return () => rendered;
|
||||
@@ -225,7 +205,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
<ApiProvider apis={apis}>
|
||||
<AppContextProvider app={this}>
|
||||
<AppThemeProvider>
|
||||
<Router basename={pathname}>{children}</Router>
|
||||
<Router>{children}</Router>
|
||||
</AppThemeProvider>
|
||||
</AppContextProvider>
|
||||
</ApiProvider>
|
||||
|
||||
@@ -25,12 +25,11 @@ export type BootErrorPageProps = {
|
||||
step: 'load-config';
|
||||
error: Error;
|
||||
};
|
||||
|
||||
export type AppComponents = {
|
||||
NotFoundErrorPage: ComponentType<{}>;
|
||||
BootErrorPage: ComponentType<BootErrorPageProps>;
|
||||
Progress: ComponentType<{}>;
|
||||
Router: ComponentType<{ basename?: string }>;
|
||||
Router: ComponentType<{}>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@types/react": "^16.9",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/react-sparklines": "^1.7.0",
|
||||
"classnames": "^2.2.6",
|
||||
"clsx": "^1.1.0",
|
||||
@@ -48,8 +47,8 @@
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-helmet": "6.0.0",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router": "6.0.0-alpha.5",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-sparklines": "^1.7.0",
|
||||
"react-syntax-highlighter": "^12.2.1",
|
||||
"react-use": "^14.2.0"
|
||||
|
||||
@@ -15,12 +15,7 @@
|
||||
*/
|
||||
import React, { FunctionComponentFactory } from 'react';
|
||||
import { Button } from './Button';
|
||||
import {
|
||||
MemoryRouter,
|
||||
Route,
|
||||
useLocation,
|
||||
Link as RouterLink,
|
||||
} from 'react-router-dom';
|
||||
import { MemoryRouter, Route, useLocation } from 'react-router-dom';
|
||||
import { createRouteRef } from '@backstage/core-api';
|
||||
|
||||
const Location = () => {
|
||||
@@ -70,14 +65,7 @@ export const PassProps = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
to={routeRef.path}
|
||||
/** react-router-dom related prop */
|
||||
component={RouterLink}
|
||||
/** material-ui related prop */
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
>
|
||||
<Button to={routeRef.path} color="secondary" variant="outlined">
|
||||
This link
|
||||
</Button>
|
||||
has props for both material-ui's component as well as for
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { render, fireEvent, act } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { Button } from './Button';
|
||||
import { MemoryRouter, Route } from 'react-router';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Route, Routes } from 'react-router';
|
||||
|
||||
describe('<Button />', () => {
|
||||
it('navigates using react-router', async () => {
|
||||
@@ -27,12 +26,13 @@ describe('<Button />', () => {
|
||||
const buttonLabel = 'Navigate!';
|
||||
const { getByText } = render(
|
||||
wrapInTestApp(
|
||||
<MemoryRouter>
|
||||
<Routes>
|
||||
<Route path="/test" element={<p>{testString}</p>} />
|
||||
<Button to="/test">{buttonLabel}</Button>
|
||||
<Route path="/test">{testString}</Route>{' '}
|
||||
</MemoryRouter>,
|
||||
</Routes>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(() => getByText(testString)).toThrow();
|
||||
await act(async () => fireEvent.click(getByText(buttonLabel)));
|
||||
expect(getByText(testString)).toBeInTheDocument();
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { Link } from './Link';
|
||||
import { MemoryRouter, Route } from 'react-router';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
describe('<Link />', () => {
|
||||
@@ -27,10 +27,10 @@ describe('<Link />', () => {
|
||||
const linkText = 'Navigate!';
|
||||
const { getByText } = render(
|
||||
wrapInTestApp(
|
||||
<MemoryRouter>
|
||||
<Routes>
|
||||
<Link to="/test">{linkText}</Link>
|
||||
<Route path="/test">{testString}</Route>
|
||||
</MemoryRouter>,
|
||||
<Route path="/test" element={<p>{testString}</p>} />
|
||||
</Routes>,
|
||||
),
|
||||
);
|
||||
expect(() => getByText(testString)).toThrow();
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Link as MaterialLink } from '@material-ui/core';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
|
||||
type Props = ComponentProps<typeof MaterialLink> &
|
||||
ComponentProps<typeof RouterLink>;
|
||||
ComponentProps<typeof RouterLink> & { component?: React.FC<any> };
|
||||
|
||||
/**
|
||||
* Thin wrapper on top of material-ui's Link component
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Typography, Link, Grid } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { MicDrop } from './MicDrop';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
interface IErrorPageProps {
|
||||
status: string;
|
||||
@@ -40,7 +40,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
|
||||
export const ErrorPage = ({ status, statusMessage }: IErrorPageProps) => {
|
||||
const classes = useStyles();
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Grid container className={classes.container}>
|
||||
@@ -53,7 +53,7 @@ export const ErrorPage = ({ status, statusMessage }: IErrorPageProps) => {
|
||||
Looks like someone dropped the mic!
|
||||
</Typography>
|
||||
<Typography variant="h6">
|
||||
<Link data-testid="go-back-link" onClick={history.goBack}>
|
||||
<Link data-testid="go-back-link" onClick={() => navigate(-1)}>
|
||||
Go back
|
||||
</Link>
|
||||
... or if you think this is a bug, please file an{' '}
|
||||
|
||||
@@ -121,7 +121,8 @@ export const SidebarItem: FC<SidebarItemProps> = ({
|
||||
icon: Icon,
|
||||
text,
|
||||
to = '#',
|
||||
disableSelected = false,
|
||||
// TODO: isActive is not in v6
|
||||
// disableSelected = false,
|
||||
hasNotifications = false,
|
||||
onClick,
|
||||
children,
|
||||
@@ -148,8 +149,7 @@ export const SidebarItem: FC<SidebarItemProps> = ({
|
||||
<NavLink
|
||||
className={clsx(classes.root, classes.closed)}
|
||||
activeClassName={classes.selected}
|
||||
isActive={match => Boolean(match && !disableSelected)}
|
||||
exact
|
||||
end
|
||||
to={to}
|
||||
onClick={onClick}
|
||||
>
|
||||
@@ -161,8 +161,7 @@ export const SidebarItem: FC<SidebarItemProps> = ({
|
||||
<NavLink
|
||||
className={clsx(classes.root, classes.open)}
|
||||
activeClassName={classes.selected}
|
||||
isActive={match => Boolean(match && !disableSelected)}
|
||||
exact
|
||||
end
|
||||
to={to}
|
||||
onClick={onClick}
|
||||
>
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router": "^6.0.0-alpha.5",
|
||||
"react-router-dom": "^6.0.0-alpha.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^25.2.2",
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
"@types/react": "^16.9",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router": "^6.0.0-alpha.5",
|
||||
"react-router-dom": "^6.0.0-alpha.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^25.2.2",
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp, renderInTestApp } from './appWrappers';
|
||||
import { Route } from 'react-router';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { withLogCollector } from '@backstage/test-utils-core';
|
||||
import {
|
||||
useApi,
|
||||
@@ -32,15 +32,15 @@ describe('wrapInTestApp', () => {
|
||||
const { error } = await withLogCollector(['error'], async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<>
|
||||
<Route path="/route1">Route 1</Route>
|
||||
<Route path="/route2">Route 2</Route>
|
||||
</>,
|
||||
<Routes>
|
||||
<Route path="/route1" element={<p>Route 1</p>} />
|
||||
<Route path="/route2" element={<p>Route 2</p>} />
|
||||
</Routes>,
|
||||
{ routeEntries: ['/route2'] },
|
||||
),
|
||||
);
|
||||
expect(rendered.getByText('Route 2')).toBeInTheDocument();
|
||||
|
||||
expect(rendered.getByText('Route 2')).toBeInTheDocument();
|
||||
// Wait for async actions to trigger the act() warnings that we assert below
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
@@ -92,7 +92,9 @@ export function wrapInTestApp(
|
||||
|
||||
return (
|
||||
<AppProvider>
|
||||
<Route component={Wrapper} />
|
||||
{/* The path of * here is needed to be set as a catch all, so it will render the wrapper element
|
||||
* and work with nested routes if they exist too */}
|
||||
<Route path="*" element={<Wrapper />} />
|
||||
</AppProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router": "^6.0.0-alpha.5",
|
||||
"react-router-dom": "^6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0",
|
||||
"swr": "^0.2.2"
|
||||
},
|
||||
|
||||
@@ -14,32 +14,38 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
const mockNavigate = jest.fn();
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: jest.fn(() => mockNavigate),
|
||||
useParams: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render, wait } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { CatalogApi, catalogApiRef } from '../../api/types';
|
||||
import { EntityPage } from './EntityPage';
|
||||
|
||||
const getTestProps = (name: string) => {
|
||||
return {
|
||||
match: {
|
||||
params: {
|
||||
optionalNamespaceAndName: name,
|
||||
kind: 'Component',
|
||||
},
|
||||
},
|
||||
history: {
|
||||
push: jest.fn(),
|
||||
},
|
||||
};
|
||||
};
|
||||
const {
|
||||
useParams,
|
||||
useNavigate,
|
||||
}: { useParams: jest.Mock; useNavigate: () => jest.Mock } = jest.requireMock(
|
||||
'react-router-dom',
|
||||
);
|
||||
|
||||
const errorApi = { post: () => {} };
|
||||
|
||||
describe('EntityPage', () => {
|
||||
it('should redirect to catalog page when name is not provided', async () => {
|
||||
const props = getTestProps('');
|
||||
useParams.mockReturnValue({
|
||||
kind: 'Component',
|
||||
optionalNamespaceAndName: '',
|
||||
});
|
||||
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider
|
||||
@@ -53,13 +59,11 @@ describe('EntityPage', () => {
|
||||
],
|
||||
])}
|
||||
>
|
||||
<EntityPage {...props} />
|
||||
<EntityPage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
await wait(() =>
|
||||
expect(props.history.push).toHaveBeenCalledWith('/catalog'),
|
||||
);
|
||||
await wait(() => expect(useNavigate()).toHaveBeenCalledWith('/catalog'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,21 +34,9 @@ import { catalogApiRef } from '../..';
|
||||
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
|
||||
import { EntityMetadataCard } from '../EntityMetadataCard/EntityMetadataCard';
|
||||
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
const REDIRECT_DELAY = 1000;
|
||||
|
||||
type Props = {
|
||||
match: {
|
||||
params: {
|
||||
optionalNamespaceAndName: string;
|
||||
kind: string;
|
||||
};
|
||||
};
|
||||
history: {
|
||||
push: (url: string) => void;
|
||||
};
|
||||
};
|
||||
|
||||
function headerProps(
|
||||
kind: string,
|
||||
namespace: string | undefined,
|
||||
@@ -68,8 +56,12 @@ function headerProps(
|
||||
};
|
||||
}
|
||||
|
||||
export const EntityPage: FC<Props> = ({ match, history }) => {
|
||||
const { optionalNamespaceAndName, kind } = match.params;
|
||||
export const EntityPage: FC<{}> = () => {
|
||||
const { optionalNamespaceAndName, kind } = useParams() as {
|
||||
optionalNamespaceAndName: string;
|
||||
kind: string;
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
const [name, namespace] = optionalNamespaceAndName.split(':').reverse();
|
||||
|
||||
const errorApi = useApi(errorApiRef);
|
||||
@@ -85,19 +77,19 @@ export const EntityPage: FC<Props> = ({ match, history }) => {
|
||||
if (!error && !loading && !entity) {
|
||||
errorApi.post(new Error('Entity not found!'));
|
||||
setTimeout(() => {
|
||||
history.push('/');
|
||||
navigate('/');
|
||||
}, REDIRECT_DELAY);
|
||||
}
|
||||
}, [errorApi, history, error, loading, entity]);
|
||||
}, [errorApi, navigate, error, loading, entity]);
|
||||
|
||||
if (!name) {
|
||||
history.push('/catalog');
|
||||
navigate('/catalog');
|
||||
return null;
|
||||
}
|
||||
|
||||
const cleanUpAfterRemoval = async () => {
|
||||
setConfirmationDialogOpen(false);
|
||||
history.push('/');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
const showRemovalDialog = () => setConfirmationDialogOpen(true);
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-lazylog": "^4.5.2",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router": "^6.0.0-alpha.5",
|
||||
"react-router-dom": "^6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Switch, Route, MemoryRouter } from 'react-router';
|
||||
import { Route, MemoryRouter, Routes } from 'react-router';
|
||||
import { BuildsPage, Builds } from '../pages/BuildsPage';
|
||||
import { DetailedViewPage, BuildWithSteps } from '../pages/BuildWithStepsPage';
|
||||
import { AppStateProvider } from '../state';
|
||||
@@ -24,14 +24,13 @@ export const App = () => {
|
||||
return (
|
||||
<AppStateProvider>
|
||||
<>
|
||||
<Switch>
|
||||
<Route path="/circleci" exact component={BuildsPage} />
|
||||
<Routes>
|
||||
<Route path="/circleci" element={<BuildsPage />} />
|
||||
<Route
|
||||
path="/circleci/build/:buildId"
|
||||
exact
|
||||
component={DetailedViewPage}
|
||||
element={<DetailedViewPage />}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
<Settings />
|
||||
</>
|
||||
</AppStateProvider>
|
||||
@@ -45,14 +44,10 @@ export const CircleCIWidget = () => (
|
||||
<MemoryRouter initialEntries={['/circleci']}>
|
||||
<AppStateProvider>
|
||||
<>
|
||||
<Switch>
|
||||
<Route path="/circleci" exact component={Builds} />
|
||||
<Route
|
||||
path="/circleci/build/:buildId"
|
||||
exact
|
||||
component={BuildWithSteps}
|
||||
/>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route path="/circleci" element={<Builds />} />
|
||||
<Route path="/circleci/build/:buildId" element={<BuildWithSteps />} />
|
||||
</Routes>
|
||||
<Settings />
|
||||
</>
|
||||
</AppStateProvider>
|
||||
|
||||
@@ -33,8 +33,7 @@ import { gitOpsApiRef, Status } from '../../api';
|
||||
import { transformRunStatus } from '../ProfileCatalog';
|
||||
|
||||
const ClusterPage: FC<{}> = () => {
|
||||
const params = useParams<{ owner: string; repo: string }>();
|
||||
|
||||
const params = useParams() as { owner: string; repo: string };
|
||||
const [loginInfo] = useLocalStorage<{
|
||||
token: string;
|
||||
username: string;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/testing-library__jest-dom": "^5.0.4",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router-dom": "6.0.0-alpha.5"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/testing-library__jest-dom": "^5.0.4",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router-dom": "6.0.0-alpha.5"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.{js,d.ts}"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
const mocks = {
|
||||
replace: jest.fn(),
|
||||
push: jest.fn(),
|
||||
};
|
||||
const mockNavigation = jest.fn();
|
||||
return {
|
||||
...actual,
|
||||
useHistory: jest.fn(() => mocks),
|
||||
useNavigate: jest.fn(() => mockNavigation),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -41,7 +38,7 @@ import AuditList from '.';
|
||||
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
|
||||
const { useHistory } = jest.requireMock('react-router-dom');
|
||||
const { useNavigate } = jest.requireMock('react-router-dom');
|
||||
const websiteListResponse = data as WebsiteListResponse;
|
||||
|
||||
describe('AuditList', () => {
|
||||
@@ -145,7 +142,8 @@ describe('AuditList', () => {
|
||||
);
|
||||
const element = await rendered.findByLabelText(/Go to page 1/);
|
||||
fireEvent.click(element);
|
||||
expect(useHistory().replace).toHaveBeenCalledWith(`/lighthouse?page=1`);
|
||||
|
||||
expect(useNavigate()).toHaveBeenCalledWith(`/lighthouse?page=1`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React, { useState, useMemo, FC, ReactNode } from 'react';
|
||||
import { useLocalStorage, useAsync } from 'react-use';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Grid, Button } from '@material-ui/core';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import Pagination from '@material-ui/lab/Pagination';
|
||||
@@ -65,7 +65,7 @@ const AuditList: FC<{}> = () => {
|
||||
return 0;
|
||||
}, [value?.total, value?.limit]);
|
||||
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
let content: ReactNode = null;
|
||||
if (value) {
|
||||
@@ -77,7 +77,7 @@ const AuditList: FC<{}> = () => {
|
||||
page={page}
|
||||
count={pageCount}
|
||||
onChange={(_event: Event, newPage: number) => {
|
||||
history.replace(`/lighthouse?page=${newPage}`);
|
||||
navigate(`/lighthouse?page=${newPage}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -18,10 +18,10 @@ import { Link, useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import {
|
||||
makeStyles,
|
||||
Button,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
Button,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from '@material-ui/core';
|
||||
@@ -68,7 +68,7 @@ const AuditLinkList: FC<AuditLinkListProps> = ({
|
||||
component="nav"
|
||||
aria-label="lighthouse audit history"
|
||||
>
|
||||
{audits.map((audit) => (
|
||||
{audits.map(audit => (
|
||||
<ListItem
|
||||
key={audit.id}
|
||||
selected={audit.id === selectedId}
|
||||
@@ -88,7 +88,7 @@ const AuditLinkList: FC<AuditLinkListProps> = ({
|
||||
|
||||
const AuditView: FC<{ audit?: Audit }> = ({ audit }: { audit?: Audit }) => {
|
||||
const classes = useStyles();
|
||||
const params = useParams<{ id: string }>();
|
||||
const params = useParams() as { id: string };
|
||||
const { url: lighthouseUrl } = useApi(lighthouseApiRef);
|
||||
|
||||
if (audit?.status === 'RUNNING') return <Progress />;
|
||||
@@ -114,7 +114,7 @@ const AuditView: FC<{ audit?: Audit }> = ({ audit }: { audit?: Audit }) => {
|
||||
|
||||
const ConnectedAuditView: FC<{}> = () => {
|
||||
const lighthouseApi = useApi(lighthouseApiRef);
|
||||
const params = useParams<{ id: string }>();
|
||||
const params = useParams() as { id: string };
|
||||
const classes = useStyles();
|
||||
|
||||
const { loading, error, value: nextValue } = useAsync<Website>(
|
||||
@@ -136,7 +136,7 @@ const ConnectedAuditView: FC<{}> = () => {
|
||||
<AuditLinkList audits={value?.audits} selectedId={params.id} />
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<AuditView audit={value?.audits.find((a) => a.id === params.id)} />
|
||||
<AuditView audit={value?.audits.find(a => a.id === params.id)} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -16,13 +16,10 @@
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
const mocks = {
|
||||
replace: jest.fn(),
|
||||
push: jest.fn(),
|
||||
};
|
||||
const mockNavigate = jest.fn();
|
||||
return {
|
||||
...actual,
|
||||
useHistory: jest.fn(() => mocks),
|
||||
useNavigate: jest.fn(() => mockNavigate),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -41,7 +38,7 @@ import { lighthouseApiRef, LighthouseRestApi, Audit } from '../../api';
|
||||
import CreateAudit from '.';
|
||||
import * as data from '../../__fixtures__/create-audit-response.json';
|
||||
|
||||
const { useHistory }: { useHistory: jest.Mock } = jest.requireMock(
|
||||
const { useNavigate }: { useNavigate: jest.Mock } = jest.requireMock(
|
||||
'react-router-dom',
|
||||
);
|
||||
const createAuditResponse = data as Audit;
|
||||
@@ -115,7 +112,7 @@ describe('CreateAudit', () => {
|
||||
|
||||
describe('when the audit is successfully created', () => {
|
||||
it('triggers a location change to the table', async () => {
|
||||
useHistory().push.mockClear();
|
||||
useNavigate.mockClear();
|
||||
mockFetch.mockResponseOnce(JSON.stringify(createAuditResponse));
|
||||
|
||||
const rendered = render(
|
||||
@@ -140,7 +137,7 @@ describe('CreateAudit', () => {
|
||||
|
||||
await wait(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled());
|
||||
|
||||
expect(useHistory().push).toHaveBeenCalledWith('/lighthouse');
|
||||
expect(useNavigate()).toHaveBeenCalledWith('/lighthouse');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useState, useCallback, FC } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
makeStyles,
|
||||
Grid,
|
||||
@@ -40,7 +40,7 @@ import { lighthouseApiRef } from '../../api';
|
||||
import { useQuery } from '../../utils';
|
||||
import LighthouseSupportButton from '../SupportButton';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
input: {
|
||||
minWidth: 300,
|
||||
},
|
||||
@@ -58,7 +58,7 @@ const CreateAudit: FC<{}> = () => {
|
||||
const lighthouseApi = useApi(lighthouseApiRef);
|
||||
const classes = useStyles();
|
||||
const query = useQuery();
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [url, setUrl] = useState<string>(query.get('url') || '');
|
||||
const [emulatedFormFactor, setEmulatedFormFactor] = useState('mobile');
|
||||
@@ -78,7 +78,7 @@ const CreateAudit: FC<{}> = () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
history.push('/lighthouse');
|
||||
navigate('/lighthouse');
|
||||
} catch (err) {
|
||||
errorApi.post(err);
|
||||
} finally {
|
||||
@@ -90,7 +90,7 @@ const CreateAudit: FC<{}> = () => {
|
||||
lighthouseApi,
|
||||
setSubmitting,
|
||||
errorApi,
|
||||
history,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -113,7 +113,7 @@ const CreateAudit: FC<{}> = () => {
|
||||
<Grid item xs={12} sm={6}>
|
||||
<InfoCard>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
onSubmit={ev => {
|
||||
ev.preventDefault();
|
||||
triggerAudit();
|
||||
}}
|
||||
@@ -128,7 +128,7 @@ const CreateAudit: FC<{}> = () => {
|
||||
helperText="The target URL for Lighthouse to use."
|
||||
required
|
||||
disabled={submitting}
|
||||
onChange={(ev) => setUrl(ev.target.value)}
|
||||
onChange={ev => setUrl(ev.target.value)}
|
||||
value={url}
|
||||
inputProps={{ 'aria-label': 'URL' }}
|
||||
/>
|
||||
@@ -142,7 +142,7 @@ const CreateAudit: FC<{}> = () => {
|
||||
select
|
||||
required
|
||||
disabled={submitting}
|
||||
onChange={(ev) => setEmulatedFormFactor(ev.target.value)}
|
||||
onChange={ev => setEmulatedFormFactor(ev.target.value)}
|
||||
value={emulatedFormFactor}
|
||||
inputProps={{ 'aria-label': 'Emulated form factor' }}
|
||||
>
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^5.7.2",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router": "^6.0.0-alpha.5",
|
||||
"react-router-dom": "^6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3894,23 +3894,6 @@
|
||||
"@types/react" "*"
|
||||
immutable ">=3.8.2"
|
||||
|
||||
"@types/react-router-dom@^5.1.3", "@types/react-router-dom@^5.1.5":
|
||||
version "5.1.5"
|
||||
resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
|
||||
integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.4"
|
||||
resolved "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6"
|
||||
integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-sparklines@^1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.npmjs.org/@types/react-sparklines/-/react-sparklines-1.7.0.tgz#f956d0f7b0e746ad445ce1cd250fe81f8a384684"
|
||||
@@ -9971,6 +9954,13 @@ highlight.js@~9.15.0, highlight.js@~9.15.1:
|
||||
resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
|
||||
integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==
|
||||
|
||||
history@5.0.0-beta.9:
|
||||
version "5.0.0-beta.9"
|
||||
resolved "https://registry.npmjs.org/history/-/history-5.0.0-beta.9.tgz#fe230706c18c5f7f132001e55215e71b4aaab6d6"
|
||||
integrity sha512-iLpu0fzu3iM041KDMNsawyB6YZjPLB+Bn+Pvq2lMnY7xxpxDIYvEz7r4et3Na8FthWzbYeukjl74ZKGWXcLhIA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.6"
|
||||
|
||||
history@^4.9.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||
@@ -15567,7 +15557,15 @@ react-redux@^7.0.3:
|
||||
prop-types "^15.7.2"
|
||||
react-is "^16.9.0"
|
||||
|
||||
react-router-dom@^5.1.2, react-router-dom@^5.2.0:
|
||||
react-router-dom@6.0.0-alpha.5, react-router-dom@^6.0.0-alpha.5:
|
||||
version "6.0.0-alpha.5"
|
||||
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.0.0-alpha.5.tgz#3c3e22226ee610eb91042a351741ce3f53596323"
|
||||
integrity sha512-xo3VM55aE563uyZBPoUplfCPOYKJmTP2oA8wamm0k4K07e/6T4x4DDunS5Gu2VIy+m2+5mZp8n0rT6S+tYCb6Q==
|
||||
dependencies:
|
||||
history "5.0.0-beta.9"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-router-dom@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
||||
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
|
||||
@@ -15580,7 +15578,7 @@ react-router-dom@^5.1.2, react-router-dom@^5.2.0:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.2.0, react-router@^5.1.2, react-router@^5.2.0:
|
||||
react-router@5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
|
||||
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
|
||||
@@ -15596,6 +15594,14 @@ react-router@5.2.0, react-router@^5.1.2, react-router@^5.2.0:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@6.0.0-alpha.5, react-router@^6.0.0-alpha.5:
|
||||
version "6.0.0-alpha.5"
|
||||
resolved "https://registry.npmjs.org/react-router/-/react-router-6.0.0-alpha.5.tgz#c98805e50dc0e64787aa8aa4fa6753b435f2496b"
|
||||
integrity sha512-cDj70bTUAgcfx6b5Fx1+wVlBSDVZGo8N+GUDk/yNFDCyGLfAsFlRpS3BhQqx8c49w2cCW+OrXxFhB4cbLZxWJw==
|
||||
dependencies:
|
||||
history "5.0.0-beta.9"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-side-effect@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3"
|
||||
|
||||
Reference in New Issue
Block a user