diff --git a/components/shared/FollowButton.test.tsx b/components/shared/FollowButton.test.tsx new file mode 100644 index 000000000..9b7af9745 --- /dev/null +++ b/components/shared/FollowButton.test.tsx @@ -0,0 +1,58 @@ +import "@testing-library/jest-dom" +import { render, screen } from "@testing-library/react" +import userEvent from "@testing-library/user-event" +import { BaseFollowButton } from "./FollowButton" + +jest.mock("next-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }) +})) + +const push = jest.fn() +jest.mock("next/router", () => ({ + useRouter: () => ({ push, asPath: "/testimony/abc/1" }) +})) + +let uid: string | undefined +jest.mock("../auth", () => ({ + useAuth: () => ({ user: uid ? { uid } : null }) +})) + +describe("BaseFollowButton", () => { + beforeEach(() => { + push.mockClear() + uid = undefined + }) + + it("sends a logged-out visitor to login instead of following", async () => { + const followAction = jest.fn() + render( + + ) + + await userEvent.click(screen.getByRole("button")) + + expect(followAction).not.toHaveBeenCalled() + expect(push).toHaveBeenCalledWith("/login?redirect=%2Ftestimony%2Fabc%2F1") + }) + + it("follows when a logged-in user clicks the button", async () => { + uid = "user-1" + const followAction = jest.fn().mockResolvedValue(undefined) + render( + + ) + + await userEvent.click(screen.getByRole("button")) + + expect(followAction).toHaveBeenCalled() + expect(push).not.toHaveBeenCalled() + }) +}) diff --git a/components/shared/LabeledIcon.test.tsx b/components/shared/LabeledIcon.test.tsx new file mode 100644 index 000000000..8a8eebf4a --- /dev/null +++ b/components/shared/LabeledIcon.test.tsx @@ -0,0 +1,22 @@ +import "@testing-library/jest-dom" +import { render, screen } from "@testing-library/react" +import { LabeledIcon } from "./LabeledIcon" + +describe("LabeledIcon", () => { + it("renders the image with the provided src", () => { + render() + expect(screen.getByRole("img")).toHaveAttribute("src", "/avatar.png") + }) + + it("renders the main and sub text", () => { + render( + + ) + expect(screen.getByText("Jane Doe")).toBeInTheDocument() + expect(screen.getByText("Senator")).toBeInTheDocument() + }) +}) diff --git a/components/shared/MessageBanner.test.tsx b/components/shared/MessageBanner.test.tsx new file mode 100644 index 000000000..e4244d8ec --- /dev/null +++ b/components/shared/MessageBanner.test.tsx @@ -0,0 +1,21 @@ +import "@testing-library/jest-dom" +import { render, screen } from "@testing-library/react" +import { MessageBanner } from "./MessageBanner" + +describe("MessageBanner", () => { + it("renders the heading and content", () => { + render() + expect(screen.getByText("Welcome")).toBeInTheDocument() + expect(screen.getByText("Get started here")).toBeInTheDocument() + }) + + it("renders the icon image when provided", () => { + render() + expect(screen.getByRole("img")).toHaveAttribute("src", "/icon.png") + }) + + it("renders without an icon when none is provided", () => { + render() + expect(screen.queryByRole("img")).not.toBeInTheDocument() + }) +}) diff --git a/components/shared/PaginatedItemsCard.test.tsx b/components/shared/PaginatedItemsCard.test.tsx new file mode 100644 index 000000000..af65bc572 --- /dev/null +++ b/components/shared/PaginatedItemsCard.test.tsx @@ -0,0 +1,74 @@ +import "@testing-library/jest-dom" +import { render, screen } from "@testing-library/react" +import userEvent from "@testing-library/user-event" +import { PaginatedItemsCard } from "./PaginatedItemsCard" + +jest.mock("next-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }) +})) + +const ItemCard = ({ name }: { name: string }) =>
{name}
+ +const items = Array.from({ length: 25 }, (_, i) => ({ name: `Item ${i + 1}` })) + +describe("PaginatedItemsCard", () => { + it("shows only the first page of items", () => { + render( + + ) + expect(screen.getByText("Item 1")).toBeInTheDocument() + expect(screen.getByText("Item 10")).toBeInTheDocument() + expect(screen.queryByText("Item 11")).not.toBeInTheDocument() + }) + + it("moves to the next page and back", async () => { + const user = userEvent.setup() + render( + + ) + const buttons = screen.getAllByRole("button") + await user.click(buttons[2]) + expect(screen.getByText("Item 11")).toBeInTheDocument() + await user.click(buttons[0]) + expect(screen.getByText("Item 1")).toBeInTheDocument() + }) + + it("shows an error alert instead of items when an error is present", () => { + render( + + ) + expect(screen.getByText("Something went wrong")).toBeInTheDocument() + expect(screen.queryByText("Item 1")).not.toBeInTheDocument() + }) + + it("shows a spinner while loading", () => { + const { container } = render( + + ) + expect(container.querySelector(".spinner-border")).toBeInTheDocument() + }) +}) diff --git a/components/shared/TitledSectionCard.test.tsx b/components/shared/TitledSectionCard.test.tsx new file mode 100644 index 000000000..1244d92c2 --- /dev/null +++ b/components/shared/TitledSectionCard.test.tsx @@ -0,0 +1,29 @@ +import "@testing-library/jest-dom" +import { render, screen } from "@testing-library/react" +import TitledSectionCard from "./TitledSectionCard" + +describe("TitledSectionCard", () => { + it("renders the title and children", () => { + render( + +

Body content

+
+ ) + expect(screen.getByText("My Section")).toBeInTheDocument() + expect(screen.getByText("Body content")).toBeInTheDocument() + }) + + it("omits the header when no title is provided", () => { + render(Body only) + expect(screen.getByText("Body only")).toBeInTheDocument() + }) + + it("renders the footer when provided", () => { + render( + Save}> + Body + + ) + expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument() + }) +}) diff --git a/components/testimony/TestimonyDetailPage/PolicyActions.test.tsx b/components/testimony/TestimonyDetailPage/PolicyActions.test.tsx index 0464c0917..27183ba8b 100644 --- a/components/testimony/TestimonyDetailPage/PolicyActions.test.tsx +++ b/components/testimony/TestimonyDetailPage/PolicyActions.test.tsx @@ -20,12 +20,18 @@ jest.mock("components/featureFlags", () => ({ }) })) +let uid: string | undefined = "user-1" jest.mock("components/auth", () => ({ useAuth: () => ({ - user: { uid: "user-1" } + user: uid ? { uid } : null }) })) +const push = jest.fn() +jest.mock("next/router", () => ({ + useRouter: () => ({ push, asPath: "/testimony/abc/1" }) +})) + jest.mock("components/db/api", () => ({ dbService: () => ({ getBallotQuestion: mockGetBallotQuestion @@ -78,6 +84,8 @@ jest.mock("components/publish", () => ({ describe("PolicyActions", () => { beforeEach(() => { jest.clearAllMocks() + uid = "user-1" + push.mockClear() mockFollowsTopic.mockResolvedValue(false) mockFollowBill.mockResolvedValue(undefined) mockUnfollowBill.mockResolvedValue(undefined) @@ -124,4 +132,33 @@ describe("PolicyActions", () => { }) expect(mockFollowBill).not.toHaveBeenCalled() }) + + it("sends a logged-out visitor to login instead of following", async () => { + uid = undefined + + render( + + + + ) + + fireEvent.click( + screen.getByText("Follow Ballot Question 25-14: Should we do the thing?") + ) + + await waitFor(() => { + expect(push).toHaveBeenCalledWith( + "/login?redirect=%2Ftestimony%2Fabc%2F1" + ) + }) + + expect(mockFollowBallotQuestion).not.toHaveBeenCalled() + expect(mockFollowBill).not.toHaveBeenCalled() + }) })