Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 92 additions & 3 deletions src/components/controls/User.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,63 @@ const meta: Meta<typeof User> = {
title: "Components/Controls/User",
component: User,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component: "A control to login/logout with, and to show user info.",
},
},
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const LoggedOut: Story = {
args: { user: null },
parameters: {
docs: {
description: {
story: "Default display when not yet logged in.",
},
},
},
};

export const LoggedIn: Story = {
args: { user: { name: "Name Surname", fedid: "FedID" }, onLogout: () => {} },
parameters: {
docs: {
description: {
story: "Default display when logged in.",
},
},
},
};

export const LoggedInNoName: Story = {
args: { user: { fedid: "FedID" }, onLogout: () => {} },
export const LoggedInNoFedId: Story = {
args: { user: { name: "User's Name" }, onLogout: () => {} },
parameters: {
docs: {
description: {
story: "Logged in, but no Fed ID.",
},
},
},
};

export const LoggedInLongName: Story = {
args: {
user: { name: "Jonathan Edwards Longname", fedid: "abc12345" },
onLogout: () => {},
},
parameters: {
docs: {
description: {
story: "Logged in with a long name.",
},
},
},
};

export const LoggedInChangeColour: Story = {
Expand All @@ -37,14 +72,28 @@ export const LoggedInChangeColour: Story = {
user: { name: "Name Surname", fedid: "abc12345" },
onLogout: () => {},
},
parameters: {
docs: {
description: {
story: "You can change the colour used to display it.",
},
},
},
};

export const LoggedInReplaceAvatar: Story = {
args: {
user: { name: "Name Surname", fedid: "abc12345" },
avatar: <Avatar sx={{ bgcolor: "red" }}>JL</Avatar>,
avatar: <Avatar sx={{ bgcolor: "red" }}>SRU</Avatar>,
onLogout: () => {},
},
parameters: {
docs: {
description: {
story: "You can change the avatar image. Perhaps use a photo.",
},
},
},
};

export const AdditionalMenuItems: Story = {
Expand All @@ -63,4 +112,44 @@ export const AdditionalMenuItems: Story = {
],
onLogout: () => {},
},
parameters: {
docs: {
description: {
story: "You can add additional menu items.",
},
},
},
};

export const UsingAuth: Story = {
args: {
auth: {
authenticated: false,
initialised: false,
getProfileUrl: () => "",
getToken: () => "",
login() {},
logout() {},
_keycloak: null,
user: {
name: "User Name ",
givenName: "",
familyName: "",
fedId: "",
email: "",
},
},
},
parameters: {
docs: {
description: {
story:
"If you are using SciReactUI's auth mechanism, you can simply pass the useAuth counterpart in." +
"<br/><br/>" +
"<pre>const auth = useAuth();</pre>" +
"<br/>" +
"<pre>&lt;User auth={auth}/&gt;</pre>",
},
},
},
};
77 changes: 73 additions & 4 deletions src/components/controls/User.test.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { fireEvent, screen } from "@testing-library/react";
import { Avatar, MenuItem } from "@mui/material";
import { User } from "./User";
import { renderWithProviders } from "../../__test-utils__/helpers";
import { Auth } from "../systems/auth";

import { User } from "./User";

describe("User", () => {
it("should render", () => {
renderWithProviders(
<User onLogin={() => {}} onLogout={() => {}} user={null} />,
<User onLogin={() => 0} onLogout={() => 0} user={null} />,
);
renderWithProviders(<User onLogout={() => {}} user={null} />);
renderWithProviders(<User onLogin={() => {}} user={null} />);
renderWithProviders(<User onLogout={() => 0} user={null} />);
renderWithProviders(<User onLogin={() => 0} user={null} />);
renderWithProviders(<User user={null} />);
renderWithProviders(<User />);
});

it("should display login button when not authenticated", () => {
Expand Down Expand Up @@ -127,3 +130,69 @@ describe("User", () => {
expect(screen.getByText("Logout")).toBeInTheDocument();
});
});

describe("User with Auth", () => {
const authDummy: Auth = {
authenticated: false,
initialised: false,
getProfileUrl: () => "",
getToken: () => "",
login() {},
logout() {},
_keycloak: null,
};
const authDummyUser = {
name: "",
givenName: "",
familyName: "",
fedId: "",
email: "",
};

it("should render", () => {
renderWithProviders(<User auth={authDummy} />);
});

it("should use auth name when passed in", () => {
const auth: Auth = {
...authDummy,
user: {
...authDummyUser,
name: "test name",
},
};
const { queryByText } = renderWithProviders(<User auth={auth} />);
// @ts-expect-error It is not null, it will never be null.
expect(queryByText(auth.user.name)).toBeInTheDocument();
});

it("should fire auth login callback when button is clicked", () => {
const loginCallback = vi.fn();
const auth = {
...authDummy,
login: loginCallback,
};
const { getByText } = renderWithProviders(<User auth={auth} />);

const loginButton = getByText("Login");
fireEvent.click(loginButton);

expect(loginCallback).toHaveBeenCalledTimes(1);
});

it("should display additional menu item when auth", () => {
const auth: Auth = {
...authDummy,
user: {
...authDummyUser,
name: "test name",
},
};
const { getByRole } = renderWithProviders(<User auth={auth} />);

const userMenu = getByRole("button");
fireEvent.click(userMenu);

expect(screen.getByText("Profile")).toBeInTheDocument();
});
});
29 changes: 24 additions & 5 deletions src/components/controls/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@ import {
Typography,
useTheme,
} from "@mui/material";

import { ReactElement, ReactNode, useState } from "react";

import { MdLogin } from "react-icons/md";

import { Auth } from "../systems/auth";

interface AuthState {
fedid: string;
fedid?: string;
name?: string;
}

interface UserProps {
user: AuthState | null;
user?: AuthState | null;
onLogin?: () => void;
onLogout?: () => void;
avatar?: ReactNode;
colour?: string;
menuItems?: ReactElement<typeof MenuItem> | ReactElement<typeof MenuItem>[];
auth?: Auth;
}

const User = ({
Expand All @@ -36,6 +37,7 @@ const User = ({
avatar,
colour,
menuItems,
auth,
}: UserProps) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
Expand All @@ -48,15 +50,21 @@ const User = ({
};

const handleLogin = () => {
if (auth) auth.login();
if (onLogin) onLogin();
};
const handleLogout = () => {
handleClose();
if (auth) auth.logout();
if (onLogout) onLogout();
};

const theme = useTheme();

if (!user && auth && auth.user) {
user = { name: auth.user.name };
}

return (
<>
<Box flexGrow={1} />
Expand Down Expand Up @@ -117,14 +125,25 @@ const User = ({
</Box>
</Stack>
</Button>
{(onLogout || menuItems) && (

{(onLogout || menuItems || auth) && (
<Menu
id="menu-list"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{menuItems}
{auth && (
<MenuItem>
<Link
href={auth.getProfileUrl()}
sx={{ textDecoration: "none" }}
>
Profile
</Link>
</MenuItem>
)}
<MenuItem onClick={handleLogout} aria-label="Logout">
<Link sx={{ textDecoration: "none" }}>Logout</Link>
</MenuItem>
Expand Down
8 changes: 2 additions & 6 deletions src/storybook/helpers/Auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,9 @@ import {useAuth} from "../../components/systems/auth";
return <User onLogin={auth.login}/>
}

// Return page content for authenticated users
return (<>
<User onLogout={auth.logout}
user={{
name: auth.user.name,
fedid: auth.user.fedId
}}
/>
<User auth={auth}/>
<Box>I'm authenticating</Box>
</>)
}
Expand Down