-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Model Navigator link to dashboard
- Loading branch information
Showing
5 changed files
with
234 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import React from "react"; | ||
import { render } from "@testing-library/react"; | ||
import { MemoryRouter } from "react-router-dom"; | ||
import { axe } from "jest-axe"; | ||
import NavigatorLink, { NavigatorLinkProps } from "./NavigatorLink"; | ||
|
||
type TestParentProps = { | ||
children: React.ReactNode; | ||
}; | ||
|
||
const TestParent: React.FC<TestParentProps> = ({ children }) => ( | ||
<MemoryRouter>{children}</MemoryRouter> | ||
); | ||
|
||
describe("Accessibility", () => { | ||
it("should not have any violations", async () => { | ||
const { container, getByTestId } = render( | ||
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link")).toBeInTheDocument(); // Sanity check | ||
expect(await axe(container)).toHaveNoViolations(); | ||
}); | ||
|
||
it("should not have any violations when disabled", async () => { | ||
const { container, getByTestId } = render( | ||
<NavigatorLink submission={{ status: "Canceled", dataCommons: "", modelVersion: "" }} />, // Disabled by invalid status and empty props | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); // Sanity check | ||
expect(await axe(container)).toHaveNoViolations(); | ||
}); | ||
}); | ||
|
||
describe("Basic Functionality", () => { | ||
it("should prepend a 'v' to the version if it is missing", () => { | ||
const { getByText } = render( | ||
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByText("v1.0.0")).toBeInTheDocument(); | ||
}); | ||
|
||
it("should not prepend a 'v' to the version if it is present already", () => { | ||
const { getByText } = render( | ||
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "v1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByText("v1.0.0")).toBeInTheDocument(); | ||
}); | ||
|
||
it("should not crash when the submission object is null", () => { | ||
expect(() => | ||
render(<NavigatorLink submission={null as NavigatorLinkProps["submission"]} />, { | ||
wrapper: TestParent, | ||
}) | ||
).not.toThrow(); | ||
}); | ||
|
||
it("should not generate a link with invalid dependent props", () => { | ||
const { getByTestId, rerender } = render( | ||
<NavigatorLink submission={{ status: "New", dataCommons: "", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); // Bad DC | ||
|
||
rerender( | ||
<NavigatorLink | ||
submission={{ status: "In Progress", dataCommons: "valid", modelVersion: "" }} | ||
/> | ||
); | ||
|
||
expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); // Bad Version | ||
}); | ||
}); | ||
|
||
describe("Implementation Requirements", () => { | ||
it("should render a hyperlink with the correct href", () => { | ||
const { getByTestId } = render( | ||
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link")).toHaveAttribute("href", "/model-navigator/test/1.0.0"); | ||
}); | ||
|
||
it("should render a hyperlink with a target of '_blank'", () => { | ||
const { getByTestId } = render( | ||
<NavigatorLink submission={{ status: "New", dataCommons: "test", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link")).toHaveAttribute("target", "_blank"); | ||
}); | ||
|
||
it.each<SubmissionStatus>([ | ||
"New", | ||
"In Progress", | ||
"Withdrawn", | ||
"Submitted", | ||
"Released", | ||
"Completed", | ||
"Rejected", | ||
])("should be enabled when the status is %s", (status) => { | ||
const { getByTestId } = render( | ||
<NavigatorLink submission={{ status, dataCommons: "test", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link")).toBeInTheDocument(); | ||
}); | ||
|
||
it.each<SubmissionStatus>(["Canceled", "Deleted"])( | ||
"should be disabled when the status is %s", | ||
(status) => { | ||
const { getByTestId } = render( | ||
<NavigatorLink submission={{ status, dataCommons: "test", modelVersion: "1.0.0" }} />, | ||
{ | ||
wrapper: TestParent, | ||
} | ||
); | ||
|
||
expect(getByTestId("navigator-link-disabled")).toBeInTheDocument(); | ||
} | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { FC, memo, useMemo } from "react"; | ||
import { Link } from "react-router-dom"; | ||
|
||
export type NavigatorLinkProps = { | ||
/** | ||
* The Data Submission object to generate the link for. | ||
* At minimum, this object should contain the status, dataCommons, and modelVersion fields. | ||
*/ | ||
submission: Submission | Pick<Submission, "status" | "dataCommons" | "modelVersion">; | ||
}; | ||
|
||
/** | ||
* The states that result in the NavigatorLink being disabled. | ||
*/ | ||
const DISABLED_STATES: SubmissionStatus[] = ["Canceled", "Deleted"]; | ||
|
||
/** | ||
* A React Router Link wrapper that links to the Model Navigator for the given submission. | ||
* | ||
* - If the submission is in a disabled state, the link will be disabled. | ||
* - If the `modelVersion` is not prefixed with a 'v', it will be added. | ||
* | ||
* @returns The NavigatorLink component | ||
*/ | ||
const NavigatorLink: FC<NavigatorLinkProps> = ({ submission }) => { | ||
const { status, dataCommons, modelVersion } = submission || {}; | ||
|
||
const formattedVersion = useMemo<string>(() => { | ||
if (!modelVersion || typeof modelVersion !== "string") { | ||
return ""; | ||
} | ||
if (modelVersion.charAt(0) === "v") { | ||
return modelVersion; | ||
} | ||
|
||
return `v${modelVersion}`; | ||
}, [modelVersion]); | ||
|
||
if (!status || !dataCommons || !modelVersion || DISABLED_STATES.includes(status)) { | ||
return <span data-testid="navigator-link-disabled">{formattedVersion}</span>; | ||
} | ||
|
||
return ( | ||
<Link | ||
to={`/model-navigator/${dataCommons}/${modelVersion}`} | ||
target="_blank" | ||
data-testid="navigator-link" | ||
> | ||
{formattedVersion} | ||
</Link> | ||
); | ||
}; | ||
|
||
export default memo<NavigatorLinkProps>( | ||
NavigatorLink, | ||
(prev, next) => | ||
prev?.submission?.status === next?.submission?.status && | ||
prev?.submission?.dataCommons === next?.submission?.dataCommons && | ||
prev?.submission?.modelVersion === next?.submission?.modelVersion | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters