Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User routes #3242

Merged
merged 28 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
923a180
initial routing for user pages
thinknoack Dec 2, 2024
599dc25
adding test link to new route
thinknoack Dec 2, 2024
bbc8ac0
removing unused var
thinknoack Dec 2, 2024
d544fb7
update import name
thinknoack Dec 2, 2024
3db5617
another update to the user routes logic
thinknoack Dec 3, 2024
2b17cb3
removing some comments and the error condition
thinknoack Dec 3, 2024
c4a9423
Merge branch 'master' into user-routes
thinknoack Dec 6, 2024
32069f6
adding some init scss modules for layout and styling work
thinknoack Dec 7, 2024
e46993c
final commit of day
thinknoack Dec 7, 2024
5178a4e
adding container and sidebar styles
thinknoack Dec 9, 2024
c32196d
adding placeholder account data and editing
thinknoack Dec 10, 2024
e224471
adding the beginings of the datasets with dummy data
thinknoack Dec 10, 2024
89100b9
code formating and cleanup
thinknoack Dec 10, 2024
1ea15c7
update dir structure and adding tests for user-routes and various tem…
thinknoack Dec 11, 2024
e72e9ea
Merge branch 'master' into user-routes
thinknoack Dec 12, 2024
3bb6979
update template
thinknoack Dec 12, 2024
eaf25a9
chore: Update lockfiles
nellh Dec 13, 2024
210dfba
working on ts lint errors
thinknoack Dec 16, 2024
8b05436
update users
thinknoack Dec 16, 2024
f17d318
comment out user routes test until I can get yarn to work
thinknoack Dec 16, 2024
c288095
comment out user-card-test until I can get yarn to work, and update u…
thinknoack Dec 16, 2024
80a72b0
uncomment tests to see if errors still exisit
thinknoack Dec 16, 2024
2487c4c
update user-routes test
thinknoack Dec 16, 2024
9920d12
explicitly import user type from routes
thinknoack Dec 16, 2024
068db59
adjust imports
thinknoack Dec 16, 2024
70d9a43
update file names and add new util for validation of orcid
thinknoack Dec 18, 2024
192f867
update import for isValidOrcid in update-permissions test
thinknoack Dec 18, 2024
6624011
updates to fix test errors and imports
thinknoack Dec 19, 2024
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
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
Loading