parent
741874d07d
commit
9944f8a38b
|
@ -4,8 +4,7 @@ import Main from "./components/Main";
|
|||
import { Redirect, Route, RouteProps, Switch } from "react-router-dom";
|
||||
import SignIn from "./components/SignIn";
|
||||
import SignUp from "./components/SignUp";
|
||||
|
||||
export const isLoggedIn = (): boolean => !!localStorage.getItem("token");
|
||||
import { getJsonWebToken, isLoggedIn } from "./jwt/jwt";
|
||||
|
||||
function PrivateRoute({ children, ...rest }: RouteProps) {
|
||||
return (
|
||||
|
@ -47,10 +46,12 @@ function NotLoggedInOnlyRoute({ children, ...rest }: RouteProps) {
|
|||
}
|
||||
|
||||
function App(): React.ReactElement {
|
||||
const jwt = getJsonWebToken();
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<PrivateRoute exact path={"/"}>
|
||||
<Main />
|
||||
{jwt && <Main userRole={jwt.role} userRowId={jwt.person_row_id} />}
|
||||
</PrivateRoute>
|
||||
<NotLoggedInOnlyRoute path={"/login"}>
|
||||
<SignIn />
|
||||
|
|
|
@ -1,82 +1,71 @@
|
|||
import React from "react";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import { IconButton, MenuItem, Toolbar, Typography } from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import { ProfileMenu } from "./ProfileMenu";
|
||||
import { mainMenuOpen, mainMenuWidth } from "./MainMenu";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
import Menu from "@material-ui/core/Menu";
|
||||
import AccountCircle from "@material-ui/icons/AccountCircle";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { client } from "../backend/helper";
|
||||
import { useReactiveVar } from "@apollo/client";
|
||||
import clsx from "clsx";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
menuButton: {
|
||||
marginRight: 16,
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
});
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
appBar: {
|
||||
transition: theme.transitions.create(["margin", "width"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
appBarShift: {
|
||||
width: `calc(100% - ${mainMenuWidth}px)`,
|
||||
marginLeft: mainMenuWidth,
|
||||
transition: theme.transitions.create(["margin", "width"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
hide: {
|
||||
display: "none",
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: theme.spacing(2),
|
||||
},
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
function CustomAppBar(): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const history = useHistory();
|
||||
const open = useReactiveVar(mainMenuOpen);
|
||||
|
||||
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
await client.resetStore();
|
||||
localStorage.removeItem("token");
|
||||
history.push("/login");
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
return (
|
||||
<AppBar>
|
||||
<AppBar
|
||||
position="fixed"
|
||||
className={clsx(classes.appBar, {
|
||||
[classes.appBarShift]: open,
|
||||
})}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
className={classes.menuButton}
|
||||
className={clsx(classes.menuButton, open && classes.hide)}
|
||||
color="inherit"
|
||||
aria-label="menu"
|
||||
onClick={() => mainMenuOpen(true)}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
Candymat
|
||||
</Typography>
|
||||
<IconButton
|
||||
aria-label="account of current user"
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
onClick={handleMenu}
|
||||
color="inherit"
|
||||
>
|
||||
<AccountCircle />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<MenuItem onClick={handleClose}>Profil</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>Logout</MenuItem>
|
||||
</Menu>
|
||||
<ProfileMenu />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
|
|
|
@ -11,14 +11,15 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
interface MainPageCandidateProps {
|
||||
interface HomePageCandidateProps {
|
||||
personRowId: number;
|
||||
}
|
||||
|
||||
export function MainPageCandidate(
|
||||
props: MainPageCandidateProps
|
||||
export function HomePageCandidate(
|
||||
props: HomePageCandidateProps
|
||||
): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<QuestionAnswersList personRowId={props.personRowId} />
|
|
@ -13,7 +13,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export function MainPageEditor(): React.ReactElement {
|
||||
export function HomePageEditor(): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
|
@ -10,7 +10,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export function MainPageUser(): React.ReactElement {
|
||||
export function HomePageUser(): React.ReactElement {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
|
@ -4,38 +4,52 @@ import { MockedProvider } from "@apollo/client/testing";
|
|||
import { MemoryRouter } from "react-router-dom";
|
||||
import Main from "./Main";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import { JwtPayload } from "../jwt/jwt";
|
||||
|
||||
function renderMainPage() {
|
||||
function renderMainPage(jwt: JwtPayload) {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<SnackbarProvider>
|
||||
<Main />
|
||||
<Main userRole={jwt.role} userRowId={jwt.person_row_id} />
|
||||
</SnackbarProvider>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const baseJwt: JwtPayload = {
|
||||
aud: "postgraphile",
|
||||
exp: 0,
|
||||
iat: 0,
|
||||
iss: "postgraphile",
|
||||
role: "candymat_person",
|
||||
person_row_id: 3,
|
||||
};
|
||||
|
||||
describe("The main page", () => {
|
||||
test("displays the editors page if an editor is logged in", () => {
|
||||
const editorToken =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfZWRpdG9yIiwicGVyc29uX3Jvd19pZCI6MSwiZXhwIjoxNjA5NDEyMTY4LCJpYXQiOjE2MDkyMzkzNjgsImF1ZCI6InBvc3RncmFwaGlsZSIsImlzcyI6InBvc3RncmFwaGlsZSJ9.kxdxmDrQw0vzD4tiXPj2fu-Cr8n7aWMikxntZ1ObF6c";
|
||||
localStorage.setItem("token", editorToken);
|
||||
renderMainPage();
|
||||
const jwt: JwtPayload = {
|
||||
...baseJwt,
|
||||
role: "candymat_editor",
|
||||
person_row_id: 1,
|
||||
};
|
||||
renderMainPage(jwt);
|
||||
|
||||
// it renders question and category lists
|
||||
const questionListHeadline = screen.queryByText(/Fragen/);
|
||||
const questionListHeadline = screen.queryAllByText(/Fragen/);
|
||||
const categoryListHeadline = screen.queryByText(/Kategorien/);
|
||||
expect(questionListHeadline).not.toBeNull();
|
||||
expect(questionListHeadline.length).toBeGreaterThan(0);
|
||||
expect(categoryListHeadline).not.toBeNull();
|
||||
});
|
||||
|
||||
test("displays the candidates page if a candidate is logged in", () => {
|
||||
const candidateToken =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfY2FuZGlkYXRlIiwicGVyc29uX3Jvd19pZCI6MiwiZXhwIjoxNjA5NDEyMTY4LCJpYXQiOjE2MDkyMzkzNjgsImF1ZCI6InBvc3RncmFwaGlsZSIsImlzcyI6InBvc3RncmFwaGlsZSJ9.i66MDTPVWwfAvOawY25WE9OPb5CQ9hidoUruP91ngcg";
|
||||
localStorage.setItem("token", candidateToken);
|
||||
renderMainPage();
|
||||
const jwt: JwtPayload = {
|
||||
...baseJwt,
|
||||
role: "candymat_candidate",
|
||||
person_row_id: 2,
|
||||
};
|
||||
renderMainPage(jwt);
|
||||
|
||||
const questionListHeadline = screen.queryByText(/Fragen/);
|
||||
const categoryListHeadline = screen.queryByText(/Kategorien/);
|
||||
|
@ -44,22 +58,14 @@ describe("The main page", () => {
|
|||
});
|
||||
|
||||
test("displays the user page if an normal user is logged in", () => {
|
||||
const userToken =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfcGVyc29uIiwicGVyc29uX3Jvd19pZCI6MywiZXhwIjoxNjA5NDEyMTY4LCJpYXQiOjE2MDkyMzkzNjgsImF1ZCI6InBvc3RncmFwaGlsZSIsImlzcyI6InBvc3RncmFwaGlsZSJ9.RWo5USCmyn-OYjgYixq0y6qlObU9Rb0KdsxxvrtlW1o";
|
||||
localStorage.setItem("token", userToken);
|
||||
renderMainPage();
|
||||
const jwt: JwtPayload = {
|
||||
...baseJwt,
|
||||
role: "candymat_person",
|
||||
person_row_id: 3,
|
||||
};
|
||||
renderMainPage(jwt);
|
||||
|
||||
const placeholder = screen.queryByText(/nichts zu sehen/);
|
||||
expect(placeholder).not.toBeNull();
|
||||
});
|
||||
|
||||
test("displays a link to the loggin page if something is wrong with the token", () => {
|
||||
const invalidToken =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHOYjgYixq0y6qlObU9Rb0KdsxxvrtlW1o";
|
||||
localStorage.setItem("token", invalidToken);
|
||||
renderMainPage();
|
||||
|
||||
const placeholder = screen.queryByRole("link", { name: /Login Seite/ });
|
||||
expect(placeholder).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,57 +1,96 @@
|
|||
import CustomAppBar from "./CustomAppBar";
|
||||
import React from "react";
|
||||
import React, { ReactElement } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { MainPageEditor } from "./MainPageEditor";
|
||||
import { getJsonWebToken } from "../jwt/jwt";
|
||||
import { MainPageCandidate } from "./MainPageCandidate";
|
||||
import { MainPageUser } from "./MainPageUser";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Container } from "@material-ui/core";
|
||||
import { HomePageEditor } from "./HomePageEditor";
|
||||
import { UserRole } from "../jwt/jwt";
|
||||
import { HomePageCandidate } from "./HomePageCandidate";
|
||||
import { HomePageUser } from "./HomePageUser";
|
||||
import { mainMenuWidth, MainMenu, mainMenuOpen, MenuOption } from "./MainMenu";
|
||||
import clsx from "clsx";
|
||||
import QuestionAnswerIcon from "@material-ui/icons/QuestionAnswer";
|
||||
import PeopleIcon from "@material-ui/icons/People";
|
||||
import { useReactiveVar } from "@apollo/client";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
appBarSpacer: theme.mixins.toolbar,
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
height: "100vh",
|
||||
overflow: "auto",
|
||||
padding: theme.spacing(3),
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
marginLeft: -mainMenuWidth,
|
||||
},
|
||||
contentShift: {
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginLeft: 0,
|
||||
},
|
||||
invalidTokenContainer: {
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4),
|
||||
},
|
||||
root: {
|
||||
display: "flex",
|
||||
},
|
||||
}));
|
||||
|
||||
function Main(): React.ReactElement {
|
||||
interface MainProps {
|
||||
userRole: UserRole;
|
||||
userRowId: number;
|
||||
}
|
||||
|
||||
function Main(props: MainProps): ReactElement {
|
||||
const classes = useStyles();
|
||||
const getMainPage = () => {
|
||||
const jwt = getJsonWebToken();
|
||||
if (jwt) {
|
||||
switch (jwt.role) {
|
||||
case "candymat_editor":
|
||||
return <MainPageEditor />;
|
||||
case "candymat_candidate":
|
||||
return <MainPageCandidate personRowId={jwt.person_row_id} />;
|
||||
case "candymat_person":
|
||||
return <MainPageUser />;
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem("token");
|
||||
return (
|
||||
<Container className={classes.invalidTokenContainer}>
|
||||
Du bist nicht eingelogged oder dein Token ist ungültig. Logge dich
|
||||
erneut ein.
|
||||
<br />
|
||||
Zur <Link to={"/login"}>Login Seite</Link>
|
||||
</Container>
|
||||
);
|
||||
const open = useReactiveVar(mainMenuOpen);
|
||||
|
||||
const getHomePage = (): ReactElement => {
|
||||
switch (props.userRole) {
|
||||
case "candymat_editor":
|
||||
return <HomePageEditor />;
|
||||
case "candymat_candidate":
|
||||
return <HomePageCandidate personRowId={props.userRowId} />;
|
||||
case "candymat_person":
|
||||
return <HomePageUser />;
|
||||
}
|
||||
};
|
||||
|
||||
const getMenuOptions = (): Array<MenuOption> => {
|
||||
switch (props.userRole) {
|
||||
case "candymat_editor":
|
||||
return [
|
||||
{
|
||||
title: "Fragen bearbeiten",
|
||||
path: "/fragen",
|
||||
icon: <QuestionAnswerIcon />,
|
||||
},
|
||||
{
|
||||
title: "Benutzer verwalten",
|
||||
path: "/benutzer",
|
||||
icon: <PeopleIcon />,
|
||||
},
|
||||
];
|
||||
case "candymat_candidate":
|
||||
return [];
|
||||
case "candymat_person":
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.root}>
|
||||
<CustomAppBar />
|
||||
<main className={classes.content}>
|
||||
<MainMenu options={getMenuOptions()} />
|
||||
<main
|
||||
className={clsx(classes.content, {
|
||||
[classes.contentShift]: open,
|
||||
})}
|
||||
>
|
||||
<div className={classes.appBarSpacer} />
|
||||
{getMainPage()}
|
||||
{getHomePage()}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import React, { ReactElement, useState } from "react";
|
||||
import { createStyles, IconButton } from "@material-ui/core";
|
||||
import { makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import List from "@material-ui/core/List";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import { makeVar, useReactiveVar } from "@apollo/client";
|
||||
|
||||
export const mainMenuWidth = 240;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
drawer: {
|
||||
width: mainMenuWidth,
|
||||
flexShrink: 0,
|
||||
},
|
||||
drawerPaper: {
|
||||
width: mainMenuWidth,
|
||||
},
|
||||
drawerHeader: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing(0, 1),
|
||||
// necessary for content to be below app bar
|
||||
...theme.mixins.toolbar,
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export const mainMenuOpen = makeVar<boolean>(false);
|
||||
|
||||
export interface MenuOption {
|
||||
title: string;
|
||||
path: string;
|
||||
icon: JSX.Element;
|
||||
}
|
||||
|
||||
interface MainMenuProps {
|
||||
options: Array<MenuOption>;
|
||||
}
|
||||
|
||||
export function MainMenu(props: MainMenuProps): ReactElement {
|
||||
const classes = useStyles();
|
||||
const open = useReactiveVar(mainMenuOpen);
|
||||
|
||||
const handleClose = () => {
|
||||
mainMenuOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Drawer
|
||||
className={classes.drawer}
|
||||
variant="persistent"
|
||||
anchor="left"
|
||||
open={open}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
>
|
||||
<div className={classes.drawerHeader}>
|
||||
<IconButton onClick={handleClose}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Divider />
|
||||
<List>
|
||||
{props.options.map((option) => (
|
||||
<ListItem button key={option.title}>
|
||||
<ListItemIcon>{option.icon}</ListItemIcon>
|
||||
<ListItemText primary={option.title} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Drawer>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import React from "react";
|
||||
import { IconButton, MenuItem } from "@material-ui/core";
|
||||
import AccountCircle from "@material-ui/icons/AccountCircle";
|
||||
import Menu from "@material-ui/core/Menu";
|
||||
import { logoutUser } from "../jwt/jwt";
|
||||
|
||||
export function ProfileMenu(): React.ReactElement {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<IconButton
|
||||
aria-label="account of current user"
|
||||
aria-controls="menu-appbar"
|
||||
aria-haspopup="true"
|
||||
onClick={handleMenu}
|
||||
color="inherit"
|
||||
>
|
||||
<AccountCircle />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<MenuItem onClick={handleClose}>Profil</MenuItem>
|
||||
<MenuItem onClick={logoutUser}>Logout</MenuItem>
|
||||
</Menu>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
|
@ -57,7 +57,7 @@ export default function SignIn(): React.ReactElement {
|
|||
onCompleted(data) {
|
||||
if (data.authenticate.jwtToken) {
|
||||
localStorage.setItem("token", data.authenticate.jwtToken);
|
||||
history.replace("/");
|
||||
window.location.reload();
|
||||
} else {
|
||||
setError("Wrong username or password.");
|
||||
}
|
||||
|
@ -83,10 +83,9 @@ export default function SignIn(): React.ReactElement {
|
|||
noValidate
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
// fixme: logging?????
|
||||
login({
|
||||
variables: { email: email, password: password },
|
||||
}).catch((error) => console.log(error));
|
||||
}).catch((error) => setError(error));
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
|
|
|
@ -20,7 +20,10 @@ beforeEach(() => localStorage.clear());
|
|||
|
||||
describe("The root path /", () => {
|
||||
test("renders user's home page if they are logged in", () => {
|
||||
localStorage.setItem("token", "asdfasdfasdf");
|
||||
localStorage.setItem(
|
||||
"token",
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfZWRpdG9yIiwicGVyc29uX3Jvd19pZCI6MSwiZXhwIjoxNjEyMjEyODQyLCJpYXQiOjE2MTIwNDAwNDIsImF1ZCI6InBvc3RncmFwaGlsZSIsImlzcyI6InBvc3RncmFwaGlsZSJ9.8Z8iCKq-WHOCyKz4rdrjwcVy7sR5_9dHQZR-lJDLEg4"
|
||||
);
|
||||
renderAppAtUrl("/");
|
||||
|
||||
expect(() => screen.getByLabelText(/current user/)).not.toThrow();
|
||||
|
@ -47,7 +50,10 @@ describe("The /login path", () => {
|
|||
});
|
||||
|
||||
test("redirects to root / and the user's home page if the user is logged in", () => {
|
||||
localStorage.setItem("token", "asdfasdfasdf");
|
||||
localStorage.setItem(
|
||||
"token",
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfZWRpdG9yIiwicGVyc29uX3Jvd19pZCI6MSwiZXhwIjoxNjEyMjEyODQyLCJpYXQiOjE2MTIwNDAwNDIsImF1ZCI6InBvc3RncmFwaGlsZSIsImlzcyI6InBvc3RncmFwaGlsZSJ9.8Z8iCKq-WHOCyKz4rdrjwcVy7sR5_9dHQZR-lJDLEg4"
|
||||
);
|
||||
renderAppAtUrl("/login");
|
||||
|
||||
expect(() => screen.getByLabelText(/current user/)).not.toThrow();
|
||||
|
@ -67,7 +73,10 @@ describe("The /signup path", () => {
|
|||
});
|
||||
|
||||
test("redirects to root / and the user's home page if the user is logged in", () => {
|
||||
localStorage.setItem("token", "asdfasdfasdf");
|
||||
localStorage.setItem(
|
||||
"token",
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY2FuZHltYXRfZWRpdG9yIiwicGVyc29uX3Jvd19pZCI6MSwiZXhwIjoxNjEyMjEyODQyLCJpYXQiOjE2MTIwNDAwNDIsImF1ZCI6InBvc3RncmFwaGlsZSIsImlzcyI6InBvc3RncmFwaGlsZSJ9.8Z8iCKq-WHOCyKz4rdrjwcVy7sR5_9dHQZR-lJDLEg4"
|
||||
);
|
||||
renderAppAtUrl("/signup");
|
||||
|
||||
expect(() => screen.getByLabelText(/current user/)).not.toThrow();
|
||||
|
|
|
@ -5,17 +5,17 @@ import { MockedProvider } from "@apollo/client/testing";
|
|||
import { MemoryRouter } from "react-router-dom";
|
||||
import { loginMock } from "../backend/mutations/login.mock";
|
||||
|
||||
const mockHistoryReplace = jest.fn();
|
||||
|
||||
jest.mock("react-router-dom", () => ({
|
||||
...jest.requireActual("react-router-dom"),
|
||||
useHistory: () => ({
|
||||
replace: mockHistoryReplace,
|
||||
}),
|
||||
}));
|
||||
const mockLocationReload = jest.fn();
|
||||
const { location } = window;
|
||||
|
||||
describe("SignIn page", () => {
|
||||
beforeEach(() => mockHistoryReplace.mockReset());
|
||||
beforeAll(() => {
|
||||
delete (window as Partial<Window>).location;
|
||||
window.location = { ...window.location, reload: mockLocationReload };
|
||||
});
|
||||
|
||||
afterAll(() => (window.location = location));
|
||||
beforeEach(() => mockLocationReload.mockReset());
|
||||
|
||||
test("initial state", () => {
|
||||
render(
|
||||
|
@ -36,7 +36,7 @@ describe("SignIn page", () => {
|
|||
const button = screen.getByRole("button");
|
||||
expect(button).not.toBeDisabled();
|
||||
expect(button).toHaveTextContent("Sign In");
|
||||
expect(mockHistoryReplace).not.toHaveBeenCalled();
|
||||
expect(mockLocationReload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("successful login", async () => {
|
||||
|
@ -59,7 +59,7 @@ describe("SignIn page", () => {
|
|||
fireEvent.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryReplace).toHaveBeenCalledWith("/");
|
||||
expect(mockLocationReload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -89,7 +89,7 @@ describe("SignIn page", () => {
|
|||
// it displays error text
|
||||
const errorText = screen.getByText(/Wrong username or password/);
|
||||
expect(errorText).toBeInTheDocument();
|
||||
expect(mockHistoryReplace).not.toHaveBeenCalled();
|
||||
expect(mockLocationReload).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { client } from "../backend/helper";
|
||||
|
||||
type UserRole = "candymat_editor" | "candymat_candidate" | "candymat_person";
|
||||
export type UserRole =
|
||||
| "candymat_editor"
|
||||
| "candymat_candidate"
|
||||
| "candymat_person";
|
||||
|
||||
interface JwtPayload {
|
||||
export interface JwtPayload {
|
||||
role: UserRole;
|
||||
person_row_id: number;
|
||||
exp: number;
|
||||
|
@ -59,3 +62,5 @@ export const logoutUser = async (): Promise<void> => {
|
|||
localStorage.removeItem("token");
|
||||
location.reload();
|
||||
};
|
||||
|
||||
export const isLoggedIn = (): boolean => !!getJsonWebToken();
|
||||
|
|
Loading…
Reference in New Issue