Skip to content

Commit

Permalink
Prequel to upgrading to react-router 6 (#6453)
Browse files Browse the repository at this point in the history
### Description of the change

Updates our routing with some preparation for the react-router upgrade.
This involved:

- Re-writing the class-based PrivateRoute component to use
function/hooks,
- Refactoring the use of `Route` out of the PrivateRoute component and
into the routes directly, as these need to be top-level for react-router
6
- Renaming PrivateRoute to RequireAuthentication (as it no longer
generates a route).
- Updating to simplify tests using react testing library rather than
enzyme, including the renderWithProviders util (as per recommended docs)

See https://reactrouter.com/en/main/upgrading/v5

### Benefits

One step closer to updating to react-router 6.
Starts transition from enzyme to react testinglibrary.

### Applicable issues

<!-- Enter any applicable Issues here (You can reference an issue using
#) -->

- ref #6187

---------

Signed-off-by: Michael Nelson <minelson@vmware.com>
  • Loading branch information
absoludity authored Jul 17, 2023
1 parent b5720a2 commit 8c98273
Show file tree
Hide file tree
Showing 18 changed files with 334 additions and 290 deletions.
2 changes: 2 additions & 0 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@paciolan/remote-component": "^2.13.0",
"@tanstack/match-sorter-utils": "^8.7.6",
"@tanstack/react-table": "^8.9.3",
"@testing-library/jest-dom": "^5.16.5",
"@types/react": "^17.0.39",
"ajv": "^8.12.0",
"axios": "^1.3.5",
Expand Down Expand Up @@ -81,6 +82,7 @@
"@bufbuild/protoc-gen-es": "^1.3.0",
"@craco/craco": "^7.0.0",
"@formatjs/cli": "^6.0.4",
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/react": "^12.1.5",
"@types/enzyme": "^3.10.12",
"@types/jest": "^29.5.3",
Expand Down
78 changes: 15 additions & 63 deletions dashboard/src/components/LoginForm/LoginForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0

import LoadingWrapper from "components/LoadingWrapper";
import { Location } from "history";
import { act } from "react-dom/test-utils";
import { MemoryRouter, Redirect } from "react-router-dom";
import { IConfigState } from "reducers/config";
Expand All @@ -14,27 +13,8 @@ import TokenLogin from "./TokenLogin";
import actions from "actions";
import * as ReactRedux from "react-redux";

const emptyLocation: Location = {
hash: "",
pathname: "",
search: "",
state: "",
key: "",
};

const defaultCluster = "default-cluster";

const defaultProps = {
location: emptyLocation,
checkCookieAuthentication: jest.fn().mockReturnValue({
then: jest.fn(f => f()),
catch: jest.fn(f => f()),
}),
oauthLoginURI: "",
appVersion: "devel",
authProxySkipLoginPage: false,
};

let spyOnUseDispatch: jest.SpyInstance;
beforeEach(() => {
const mockDispatch = jest.fn(res => res);
Expand All @@ -55,7 +35,7 @@ describe("while authenticating", () => {
authenticating: true,
},
};
const wrapper = mountWrapper(getStore(state), <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(getStore(state), <LoginForm />);
expect(wrapper.find(LoadingWrapper)).toExist();
expect(wrapper.find(TokenLogin)).not.toExist();
expect(wrapper.find(OAuthLogin)).not.toExist();
Expand All @@ -64,7 +44,7 @@ describe("while authenticating", () => {

describe("token login form", () => {
it("renders a token login form", () => {
const wrapper = mountWrapper(defaultStore, <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(defaultStore, <LoginForm />);
expect(wrapper.find(TokenLogin)).toExist();
expect(wrapper.find(OAuthLogin)).not.toExist();
});
Expand All @@ -76,15 +56,15 @@ describe("token login form", () => {
appVersion: "devel",
},
};
const wrapper = mountWrapper(getStore(state), <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(getStore(state), <LoginForm />);
expect(wrapper.find("a").props()).toMatchObject({
href: "https://github.com/vmware-tanzu/kubeapps/blob/devel/site/content/docs/latest/howto/access-control.md",
target: "_blank",
});
});

it("updates the token in the state when the input is changed", () => {
const wrapper = mountWrapper(defaultStore, <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(defaultStore, <LoginForm />);
let input = wrapper.find("input#token");
act(() => {
input.simulate("change", {
Expand All @@ -105,27 +85,10 @@ describe("token login form", () => {
authenticated: true,
},
};
const wrapper = mountWrapper(getStore(state), <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(getStore(state), <LoginForm />);
const redirect = wrapper.find(Redirect);
expect(redirect.props()).toEqual({ to: { pathname: "/" } });
});

it("redirects to previous location", () => {
const location = Object.assign({}, emptyLocation);
location.state = { from: "/test" };
const state = {
...defaultStore,
auth: {
authenticated: true,
},
};
const wrapper = mountWrapper(
getStore(state),
<LoginForm {...defaultProps} location={location} />,
);
const redirect = wrapper.find(Redirect);
expect(redirect.props()).toEqual({ to: "/test" });
});
});

it("calls the authenticate handler when the form is submitted", () => {
Expand All @@ -134,7 +97,7 @@ describe("token login form", () => {
catch: jest.fn(f => f()),
});
actions.auth.authenticate = authenticate;
const wrapper = mountWrapper(defaultStore, <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(defaultStore, <LoginForm />);
act(() => {
wrapper.find("input#token").simulate("change", { target: { value: "f00b4r" } });
});
Expand All @@ -153,7 +116,7 @@ describe("token login form", () => {
mountWrapper(
defaultStore,
<MemoryRouter initialEntries={["/login?token=f00b4r"]}>
<LoginForm {...defaultProps} />
<LoginForm />
</MemoryRouter>,
);
expect(authenticate).toBeCalledWith(defaultCluster, "f00b4r", false);
Expand All @@ -168,7 +131,7 @@ describe("token login form", () => {
mountWrapper(
defaultStore,
<MemoryRouter initialEntries={["/login?token=bad-token"]}>
<LoginForm {...defaultProps} />
<LoginForm />
</MemoryRouter>,
);
expect(authenticate).toBeCalledWith(defaultCluster, "bad-token", false);
Expand All @@ -180,7 +143,7 @@ describe("token login form", () => {
mountWrapper(
defaultStore,
<MemoryRouter initialEntries={["/login?token=f00b4r"]}>
<LoginForm {...defaultProps} />
<LoginForm />
</MemoryRouter>,
);
expect(authenticate).not.toBeCalled();
Expand All @@ -193,18 +156,13 @@ describe("token login form", () => {
authenticationError,
},
};
const wrapper = mountWrapper(getStore(state), <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(getStore(state), <LoginForm />);

expect(wrapper.find(".error").exists()).toBe(true);
});

it("does not display the oauth login if oauthLoginURI provided", () => {
const props = {
...defaultProps,
oauthLoginURI: "",
};

const wrapper = mountWrapper(defaultStore, <LoginForm {...props} />);
const wrapper = mountWrapper(defaultStore, <LoginForm />);

expect(wrapper.find("a.button").exists()).toBe(false);
});
Expand All @@ -218,7 +176,7 @@ describe("oauth login form", () => {
oauthLoginURI: "/sign/in",
} as IConfigState,
};
const wrapper = mountWrapper(getStore(state), <LoginForm {...defaultProps} />);
const wrapper = mountWrapper(getStore(state), <LoginForm />);

expect(wrapper.find("input#token").exists()).toBe(false);
});
Expand All @@ -237,10 +195,7 @@ describe("oauth login form", () => {
oauthLoginURI: "/sign/in",
} as IConfigState,
};
const wrapper = mountWrapper(
getStore({ ...state } as Partial<IStoreState>),
<LoginForm {...defaultProps} />,
);
const wrapper = mountWrapper(getStore({ ...state } as Partial<IStoreState>), <LoginForm />);
expect(checkCookieAuthentication).toHaveBeenCalled();
expect(wrapper.find(OAuthLogin)).toExist();
expect(wrapper.find("a").findWhere(a => a.prop("href") === "/sign/in")).toExist();
Expand All @@ -259,10 +214,7 @@ describe("oauth login form", () => {
});
actions.auth.checkCookieAuthentication = checkCookieAuthentication;

const wrapper = mountWrapper(
getStore({ ...state } as Partial<IStoreState>),
<LoginForm {...defaultProps} />,
);
const wrapper = mountWrapper(getStore({ ...state } as Partial<IStoreState>), <LoginForm />);
expect(wrapper.find(LoadingWrapper)).toExist();
expect(wrapper.find(OAuthLogin)).not.toExist();
});
Expand All @@ -282,7 +234,7 @@ describe("oauth login form", () => {
oauthLoginURI: "/sign/in",
},
};
mountWrapper(getStore(state), <LoginForm {...defaultProps} />);
mountWrapper(getStore(state), <LoginForm />);
expect(window.location.replace).toHaveBeenCalledWith("/sign/in");
});
});
9 changes: 2 additions & 7 deletions dashboard/src/components/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0

import { CdsIcon } from "@cds/react/icon";
import { Location } from "history";
import qs from "qs";
import { useEffect, useState } from "react";
import { useIntl } from "react-intl";
Expand All @@ -17,11 +16,7 @@ import actions from "actions";
import { ThunkDispatch } from "redux-thunk";
import { Action } from "typesafe-actions";

export interface ILoginFormProps {
location: Location;
}

function LoginForm(props: ILoginFormProps) {
function LoginForm() {
const intl = useIntl();
const [token, setToken] = useState("");
const [cookieChecked, setCookieChecked] = useState(false);
Expand Down Expand Up @@ -69,7 +64,7 @@ function LoginForm(props: ILoginFormProps) {
// TODO(minelson): I don't think this redirect has been working for a while. Nothing
// populates this location prop with the from attribute (from the history package) other
// than a test.
const { from } = (props.location.state as any) || { from: { pathname: "/" } };
const { from } = (location.state as any) || { from: { pathname: "/" } };
return <ReactRouter.Redirect to={from} />;
}

Expand Down
78 changes: 0 additions & 78 deletions dashboard/src/components/PrivateRoute/PrivateRoute.test.tsx

This file was deleted.

57 changes: 0 additions & 57 deletions dashboard/src/components/PrivateRoute/PrivateRoute.tsx

This file was deleted.

6 changes: 0 additions & 6 deletions dashboard/src/components/PrivateRoute/index.tsx

This file was deleted.

Loading

0 comments on commit 8c98273

Please sign in to comment.