Skip to content

Commit

Permalink
Merge branch '3.1.0' into CRDCDH-1443-II
Browse files Browse the repository at this point in the history
  • Loading branch information
amattu2 authored Oct 7, 2024
2 parents a0d03e5 + 0d7389c commit 644a6ab
Show file tree
Hide file tree
Showing 11 changed files with 1,675 additions and 11 deletions.
28 changes: 28 additions & 0 deletions src/assets/icons/study_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions src/components/SuspenseLoader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box, CircularProgress, styled } from "@mui/material";
import { FC } from "react";
import { ComponentProps, FC } from "react";

type Props = {
/**
Expand All @@ -8,7 +8,7 @@ type Props = {
* @default true
*/
fullscreen?: boolean;
};
} & ComponentProps<typeof CircularProgress>;

const StyledBox = styled(Box, {
shouldForwardProp: (p) => p !== "fullscreen",
Expand All @@ -22,9 +22,9 @@ const StyledBox = styled(Box, {
zIndex: "9999",
}));

const SuspenseLoader: FC<Props> = ({ fullscreen = true }: Props) => (
const SuspenseLoader: FC<Props> = ({ fullscreen = true, ...rest }: Props) => (
<StyledBox display="flex" alignItems="center" justifyContent="center" fullscreen={fullscreen}>
<CircularProgress size={64} disableShrink thickness={3} aria-label="Content Loader" />
<CircularProgress size={64} disableShrink thickness={3} aria-label="Content Loader" {...rest} />
</StyledBox>
);

Expand Down
196 changes: 196 additions & 0 deletions src/content/studies/Controller.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import React, { FC, useMemo } from "react";
import { render, waitFor } from "@testing-library/react";
import { MemoryRouter, Routes, Route } from "react-router-dom";
import { MockedProvider, MockedResponse } from "@apollo/client/testing";
import {
Context as AuthContext,
ContextState as AuthContextState,
Status as AuthContextStatus,
} from "../../components/Contexts/AuthContext";
import StudiesController from "./Controller";
import { SearchParamsProvider } from "../../components/Contexts/SearchParamsContext";
import {
GET_APPROVED_STUDY,
GetApprovedStudyInput,
GetApprovedStudyResp,
LIST_APPROVED_STUDIES,
ListApprovedStudiesInput,
ListApprovedStudiesResp,
} from "../../graphql";

// NOTE: Omitting fields depended on by the component
const baseUser: Omit<User, "role"> = {
_id: "",
firstName: "",
lastName: "",
userStatus: "Active",
IDP: "nih",
email: "",
organization: null,
dataCommons: [],
createdAt: "",
updateAt: "",
studies: null,
};

type ParentProps = {
role: User["role"];
initialEntry?: string;
mocks?: MockedResponse[];
ctxStatus?: AuthContextStatus;
children: React.ReactNode;
};

const TestParent: FC<ParentProps> = ({
role,
initialEntry = "/studies",
mocks = [],
ctxStatus = AuthContextStatus.LOADED,
children,
}: ParentProps) => {
const baseAuthCtx: AuthContextState = useMemo<AuthContextState>(
() => ({
status: ctxStatus,
isLoggedIn: role !== null,
user: { ...baseUser, role },
}),
[role, ctxStatus]
);

return (
<MockedProvider mocks={mocks} showWarnings>
<MemoryRouter initialEntries={[initialEntry]}>
<Routes>
<Route
path="/studies/:studyId?"
element={
<AuthContext.Provider value={baseAuthCtx}>
<SearchParamsProvider>{children}</SearchParamsProvider>
</AuthContext.Provider>
}
/>
<Route path="/" element={<div>Root Page</div>} />
</Routes>
</MemoryRouter>
</MockedProvider>
);
};

describe("StudiesController", () => {
it("should render the page without crashing", async () => {
const listApprovedStudiesMock: MockedResponse<
ListApprovedStudiesResp,
ListApprovedStudiesInput
> = {
request: {
query: LIST_APPROVED_STUDIES,
},
variableMatcher: () => true,
result: {
data: {
listApprovedStudies: {
total: 1,
studies: [
{
_id: "study-id-1",
studyName: "Study Name 1",
studyAbbreviation: "SN1",
dbGaPID: "db123456",
controlledAccess: true,
openAccess: false,
PI: "Dr. Smith",
ORCID: "0000-0001-2345-6789",
createdAt: "2022-01-01T00:00:00Z",
originalOrg: "",
},
],
},
},
},
};

const { getByTestId } = render(
<TestParent
role="Admin"
ctxStatus={AuthContextStatus.LOADED}
mocks={[listApprovedStudiesMock]}
>
<StudiesController />
</TestParent>
);

await waitFor(() => {
expect(getByTestId("list-studies-container")).toBeInTheDocument();
});
});

it("should show a loading spinner when the AuthCtx is loading", async () => {
const { getByTestId } = render(
<TestParent role="Admin" ctxStatus={AuthContextStatus.LOADING}>
<StudiesController />
</TestParent>
);

await waitFor(() => {
expect(getByTestId("studies-suspense-loader")).toBeInTheDocument();
});
});

it.each<UserRole>([
"Data Curator",
"Data Commons POC",
"Federal Lead",
"User",
"fake role" as User["role"],
])("should redirect the user role %p to the home page", (role) => {
const { getByText } = render(
<TestParent role={role}>
<StudiesController />
</TestParent>
);

expect(getByText("Root Page")).toBeInTheDocument();
});

it("should render the StudyView when a studyId param is provided", async () => {
const studyId = "study-id-1";

const getApprovedStudyMock: MockedResponse<GetApprovedStudyResp, GetApprovedStudyInput> = {
request: {
query: GET_APPROVED_STUDY,
},
variableMatcher: () => true,
result: {
data: {
getApprovedStudy: {
_id: studyId,
studyName: "Study Name 1",
studyAbbreviation: "SN1",
dbGaPID: "db123456",
controlledAccess: true,
openAccess: false,
PI: "Dr. Smith",
ORCID: "0000-0001-2345-6789",
createdAt: "2022-01-01T00:00:00Z",
originalOrg: "",
},
},
},
};

const { getByTestId } = render(
<TestParent
role="Admin"
ctxStatus={AuthContextStatus.LOADED}
mocks={[getApprovedStudyMock]}
initialEntry={`/studies/${studyId}`}
>
<StudiesController />
</TestParent>
);

await waitFor(() => {
expect(getByTestId("study-view-container")).toBeInTheDocument();
});
});
});
12 changes: 9 additions & 3 deletions src/content/studies/Controller.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { FC } from "react";
import { Navigate, useParams } from "react-router-dom";
import { useAuthContext } from "../../components/Contexts/AuthContext";
import { Status, useAuthContext } from "../../components/Contexts/AuthContext";
import ListView from "./ListView";
import StudyView from "./StudyView";
import SuspenseLoader from "../../components/SuspenseLoader";

/**
* Renders the correct view based on the URL and permissions-tier
Expand All @@ -11,15 +13,19 @@ import ListView from "./ListView";
*/
const StudiesController: FC = () => {
const { studyId } = useParams<{ studyId?: string }>();
const { user } = useAuthContext();
const { user, status: authStatus } = useAuthContext();
const isAdministrative = user?.role === "Admin";

if (authStatus === Status.LOADING) {
return <SuspenseLoader data-testid="studies-suspense-loader" />;
}

if (!isAdministrative) {
return <Navigate to="/" />;
}

if (studyId) {
return null;
return <StudyView _id={studyId} />;
}

return <ListView />;
Expand Down
8 changes: 4 additions & 4 deletions src/content/studies/ListView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ElementType, useRef, useState } from "react";
import { Alert, Button, Container, Stack, styled, TableCell, TableHead } from "@mui/material";
import { Alert, Box, Button, Container, Stack, styled, TableCell, TableHead } from "@mui/material";
import { Link, LinkProps, useLocation } from "react-router-dom";
import { useLazyQuery } from "@apollo/client";
import PageBanner from "../../components/PageBanner";
Expand Down Expand Up @@ -240,7 +240,7 @@ const ListView = () => {
};

return (
<>
<Box data-testid="list-studies-container">
<Container maxWidth="xl">
{(state?.error || error) && (
<Alert sx={{ mt: 2, mx: "auto", p: 2 }} severity="error">
Expand All @@ -256,7 +256,7 @@ const ListView = () => {
body={
<StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
<StyledButton component={Link} to="/studies/new">
Add Approved Study
Add Study
</StyledButton>
</StyledBannerBody>
}
Expand All @@ -282,7 +282,7 @@ const ListView = () => {
CustomTableBodyCell={StyledTableCell}
/>
</StyledContainer>
</>
</Box>
);
};

Expand Down
Loading

0 comments on commit 644a6ab

Please sign in to comment.