Skip to content

Commit

Permalink
Merge pull request #3242 from OpenNeuroOrg/user-routes
Browse files Browse the repository at this point in the history
User routes
  • Loading branch information
thinknoack authored Dec 19, 2024
2 parents 08e28d2 + 6624011 commit 7cd560f
Show file tree
Hide file tree
Showing 29 changed files with 1,265 additions and 10 deletions.
8 changes: 8 additions & 0 deletions packages/openneuro-app/src/@types/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ declare module "*.svg" {
export = value
}



// Allow custom scss modules
declare module "*.module.scss" {
const classes: { [key: string]: string };
export default classes;
}

// Allow .scss imports
declare module "*.scss" {
const value: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import React from "react"
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
import { MockedProvider } from "@apollo/client/testing"
import {
isValidOrcid,
UPDATE_ORCID_PERMISSIONS,
UPDATE_PERMISSIONS,
UpdateDatasetPermissions,
} from "../update-permissions"

import { isValidOrcid } from "../../../utils/validationUtils.ts";

function permissionMocksFactory(
updatePermissionsCalled,
updateOrcidPermissionsCalled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@ import ToastContent from "../../common/partials/toast-content"
import { validate as isValidEmail } from "email-validator"
import { Button } from "@openneuro/components/button"

export function isValidOrcid(orcid: string) {
if (orcid) {
return /^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$/.test(orcid)
? true
: false
} else {
return false
}
}
import { isValidOrcid } from "../../utils/validationUtils";

export const UPDATE_PERMISSIONS = gql`
mutation updatePermissions(
Expand Down
4 changes: 4 additions & 0 deletions packages/openneuro-app/src/scripts/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Navigate, Route, Routes } from "react-router-dom"
import DatasetQuery from "./dataset/dataset-query"
//import PreRefactorDatasetProps from './dataset/dataset-pre-refactor-container'



import FaqPage from "./pages/faq/faq"
import FrontPageContainer from "./pages/front-page/front-page"
import Admin from "./pages/admin/admin"
Expand All @@ -17,6 +19,7 @@ import FourOFourPage from "./errors/404page"
import { ImportDataset } from "./pages/import-dataset"
import { DatasetMetadata } from "./pages/metadata/dataset-metadata"
import { TermsPage } from "./pages/terms"
import { UserQuery } from "./users/user-query"

const AppRoutes: React.VoidFunctionComponent = () => (
<Routes>
Expand All @@ -33,6 +36,7 @@ const AppRoutes: React.VoidFunctionComponent = () => (
<Route path="/import" element={<ImportDataset />} />
<Route path="/metadata" element={<DatasetMetadata />} />
<Route path="/public" element={<Navigate to="/search" replace />} />
<Route path="/user/:orcid/*" element={<UserQuery />} />
<Route
path="/saved"
element={<Navigate to="/search?bookmarks" replace />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import { render, screen, fireEvent, within, waitFor} from '@testing-library/react';
import { UserAccountView } from '../user-account-view';

const baseUser = {
name: "John Doe",
email: "[email protected]",
orcid: "0000-0001-2345-6789",
location: "San Francisco, CA",
institution: "University of California",
links: ["https://example.com", "https://example.org"],
github: "johndoe",
};

describe('<UserAccountView />', () => {
it('should render the user details correctly', () => {
render(<UserAccountView user={baseUser} />);

// Check if user details are rendered
expect(screen.getByText('Name:')).toBeInTheDocument();
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Email:')).toBeInTheDocument();
expect(screen.getByText('[email protected]')).toBeInTheDocument();
expect(screen.getByText('ORCID:')).toBeInTheDocument();
expect(screen.getByText('0000-0001-2345-6789')).toBeInTheDocument();
expect(screen.getByText('johndoe')).toBeInTheDocument();
});

it('should render links with EditableContent', async () => {
render(<UserAccountView user={baseUser} />);
const institutionSection = within(screen.getByText('Institution').closest('.user-meta-block'));
expect(screen.getByText('Institution')).toBeInTheDocument();
const editButton = institutionSection.getByText('Edit');
fireEvent.click(editButton);
const textbox = institutionSection.getByRole('textbox');
fireEvent.change(textbox, { target: { value: 'New University' } });
const saveButton = institutionSection.getByText('Save');
const closeButton = institutionSection.getByText('Close');
fireEvent.click(saveButton);
fireEvent.click(closeButton);
// Add debug step
await waitFor(() => screen.debug());
// Use a flexible matcher to check for text
await waitFor(() =>
expect(institutionSection.getByText('New University')).toBeInTheDocument()
);
});


it('should render location with EditableContent', async () => {
render(<UserAccountView user={baseUser} />);
const locationSection = within(screen.getByText('Location').closest('.user-meta-block'));
expect(screen.getByText('Location')).toBeInTheDocument();
const editButton = locationSection.getByText('Edit');
fireEvent.click(editButton);
const textbox = locationSection.getByRole('textbox');
fireEvent.change(textbox, { target: { value: 'Marin, CA' } });
const saveButton = locationSection.getByText('Save');
const closeButton = locationSection.getByText('Close');
fireEvent.click(saveButton);
fireEvent.click(closeButton);
// Add debug step
await waitFor(() => screen.debug());
// Use a flexible matcher to check for text
await waitFor(() =>
expect(locationSection.getByText('Marin, CA')).toBeInTheDocument()
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import type { User } from "../user-card";
import { UserCard } from "../user-card";

describe("UserCard Component", () => {
const baseUser: User = {
name: "John Doe",
email: "[email protected]",
orcid: "0000-0001-2345-6789",
location: "San Francisco, CA",
institution: "University of California",
links: ["https://example.com", "https://example.org"],
github: "johndoe",
};

it("renders all user details when all data is provided", () => {

render(<UserCard user={baseUser} />);

const orcidLink = screen.getByRole("link", {
name: "ORCID profile of John Doe",
});
expect(orcidLink).toHaveAttribute("href", "https://orcid.org/0000-0001-2345-6789");
expect(screen.getByText("University of California")).toBeInTheDocument();
expect(screen.getByText("San Francisco, CA")).toBeInTheDocument();

const emailLink = screen.getByRole("link", { name: "[email protected]" });
expect(emailLink).toHaveAttribute("href", "mailto:[email protected]");

const githubLink = screen.getByRole("link", { name: "Github profile of John Doe", });
expect(githubLink).toHaveAttribute("href", "https://github.com/johndoe");
expect(
screen.getByRole("link", { name: "https://example.com" })
).toHaveAttribute("href", "https://example.com");
expect(
screen.getByRole("link", { name: "https://example.org" })
).toHaveAttribute("href", "https://example.org");
});

it("renders without optional fields", () => {
const minimalUser: User = {
name: "Jane Doe",
email: "[email protected]",
orcid: "0000-0002-3456-7890",
links: [],
};

render(<UserCard user={minimalUser} />);

const orcidLink = screen.getByRole("link", {
name: "ORCID profile of Jane Doe",
});
expect(orcidLink).toHaveAttribute("href", "https://orcid.org/0000-0002-3456-7890");
const emailLink = screen.getByRole("link", { name: "[email protected]" });
expect(emailLink).toHaveAttribute("href", "mailto:[email protected]");
expect(screen.queryByText("University of California")).not.toBeInTheDocument();
expect(screen.queryByText("San Francisco, CA")).not.toBeInTheDocument();
expect(screen.queryByRole("link", { name: "Github profile of Jane Doe" })).not.toBeInTheDocument();
});

it("renders correctly when links are empty", () => {
const userWithEmptyLinks: User = {
...baseUser,
links: [],
};

render(<UserCard user={userWithEmptyLinks} />);

expect(screen.queryByRole("link", { name: "https://example.com" })).not.toBeInTheDocument();
expect(screen.queryByRole("link", { name: "https://example.org" })).not.toBeInTheDocument();
});

it("renders correctly when location and institution are missing", () => {
const userWithoutLocationAndInstitution: User = {
name: "Emily Doe",
email: "[email protected]",
orcid: "0000-0003-4567-8901",
links: ["https://example.com"],
};

render(<UserCard user={userWithoutLocationAndInstitution} />);

const orcidLink = screen.getByRole("link", {
name: "ORCID profile of Emily Doe",
});
expect(orcidLink).toHaveAttribute("href", "https://orcid.org/0000-0003-4567-8901");
const emailLink = screen.getByRole("link", { name: "[email protected]" });
expect(emailLink).toHaveAttribute("href", "mailto:[email protected]");
const link = screen.getByRole("link", { name: "https://example.com" });
expect(link).toHaveAttribute("href", "https://example.com");
expect(screen.queryByText("San Francisco, CA")).not.toBeInTheDocument();
expect(screen.queryByText("University of California")).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { UserQuery } from "../user-query";
import FourOFourPage from "../../errors/404page";


// TODO update these once the correct query is in place and dummy data is not used.
// maybe there is a better way to do this
const VALID_ORCID = "0000-0001-6755-0259";
const INVALID_ORCID = "0000-000X-1234-5678";
const UNKNOWN_ORCID = "0000-0000-0000-0000";

const renderWithRouter = (orcid: string) => {
return render(
<MemoryRouter initialEntries={[`/user/${orcid}`]}>
<Routes>
<Route path="/user/:orcid" element={<UserQuery />} />
<Route path="*" element={<FourOFourPage />} />
</Routes>
</MemoryRouter>
);
};

describe("UserQuery Component", () => {
// TODO update these once the correct query is in place and dummy data is not used.
// maybe there is a better way to do this
it("renders UserRoutes for a valid ORCID", async () => {
renderWithRouter(VALID_ORCID);
const userName = await screen.findByText("Gregory Noack");
expect(userName).toBeInTheDocument();

const userLocation = screen.getByText("Stanford, CA");
expect(userLocation).toBeInTheDocument();
});

it("renders FourOFourPage for an invalid ORCID", async () => {
renderWithRouter(INVALID_ORCID);
const errorMessage = await screen.findByText(
/404: The page you are looking for does not exist./i
);
expect(errorMessage).toBeInTheDocument();
});

it("renders FourOFourPage for a missing ORCID", async () => {
renderWithRouter("");
const errorMessage = await screen.findByText(
/404: The page you are looking for does not exist./i
);
expect(errorMessage).toBeInTheDocument();
});

it("renders FourOFourPage for an unknown ORCID", async () => {
renderWithRouter(UNKNOWN_ORCID);
const errorMessage = await screen.findByText(
/404: The page you are looking for does not exist./i
);
expect(errorMessage).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from "react";
import { render, screen, cleanup } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { UserRoutes } from "../user-routes";
import type { User } from "../user-routes";

const defaultUser: User = {
id: "1",
name: "John Doe",
location: "Unknown",
github: "",
institution: "Unknown Institution",
email: "[email protected]",
avatar: "https://dummyimage.com/200x200/000/fff",
orcid: "0000-0000-0000-0000",
links: [],
};

const renderWithRouter = (user: User, route: string, hasEdit: boolean) => {
return render(
<MemoryRouter initialEntries={[route]}>
<UserRoutes user={user} hasEdit={hasEdit} />
</MemoryRouter>
);
};

describe("UserRoutes Component", () => {
const user: User = defaultUser;

it("renders UserDatasetsView for the default route", async () => {
renderWithRouter(user, "/", true);
expect(screen.getByText(`${user.name}'s Datasets`)).toBeInTheDocument();
const datasetsView = await screen.findByTestId("user-datasets-view");
expect(datasetsView).toBeInTheDocument();
});

it("renders FourOFourPage for an invalid route", async () => {
renderWithRouter(user, "/nonexistent-route", true);
const errorMessage = await screen.findByText(
/404: The page you are looking for does not exist./i
);
expect(errorMessage).toBeInTheDocument();
});

it("renders UserAccountView when hasEdit is true", async () => {
renderWithRouter(user, "/account", true);
const accountView = await screen.findByTestId("user-account-view");
expect(accountView).toBeInTheDocument();
});

it("renders UserNotificationsView when hasEdit is true", async () => {
renderWithRouter(user, "/notifications", true);
const notificationsView = await screen.findByTestId(
"user-notifications-view"
);
expect(notificationsView).toBeInTheDocument();
});

it("renders FourOThreePage when hasEdit is false for restricted routes", async () => {
const restrictedRoutes = ["/account", "/notifications"];

for (const route of restrictedRoutes) {
cleanup();
renderWithRouter(user, route, false);
const errorMessage = await screen.findByText(
/403: You do not have access to this page, you may need to sign in./i
);
expect(errorMessage).toBeInTheDocument();
}
});
});
Loading

0 comments on commit 7cd560f

Please sign in to comment.