From efa2df060ba1ab13520137ec13e0dc80a1860828 Mon Sep 17 00:00:00 2001 From: Jan Jaroszczak Date: Fri, 5 Apr 2024 14:13:46 +0200 Subject: [PATCH] [#637] Create atoms components ui tests --- govtool/frontend/package.json | 1 + .../src/components/atoms/ActionRadio.test.tsx | 90 ++++++++++++++ .../src/components/atoms/CopyButton.test.tsx | 69 +++++++++++ .../src/components/atoms/DrawerLink.test.tsx | 90 ++++++++++++++ .../components/atoms/LoadingButton.test.tsx | 53 +++++++++ .../src/components/atoms/VotePill.test.tsx | 55 +++++++++ .../atoms/VotingPowerChips.test.tsx | 111 ++++++++++++++++++ 7 files changed, 469 insertions(+) create mode 100644 govtool/frontend/src/components/atoms/ActionRadio.test.tsx create mode 100644 govtool/frontend/src/components/atoms/CopyButton.test.tsx create mode 100644 govtool/frontend/src/components/atoms/DrawerLink.test.tsx create mode 100644 govtool/frontend/src/components/atoms/LoadingButton.test.tsx create mode 100644 govtool/frontend/src/components/atoms/VotePill.test.tsx create mode 100644 govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index b27e98257..70ded1242 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -14,6 +14,7 @@ "preview": "vite preview", "storybook": "storybook dev -p 6006", "test": "vitest", + "test:ui": "vitest --ui", "test-storybook": "test-storybook", "test:watch": "vitest watch", "tsc": "npx tsc --noEmit --skipLibCheck" diff --git a/govtool/frontend/src/components/atoms/ActionRadio.test.tsx b/govtool/frontend/src/components/atoms/ActionRadio.test.tsx new file mode 100644 index 000000000..2d4e1a5dd --- /dev/null +++ b/govtool/frontend/src/components/atoms/ActionRadio.test.tsx @@ -0,0 +1,90 @@ +import { describe, expect, it, vi } from "vitest"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { ActionRadio } from "@atoms"; + +describe("ActionRadio", () => { + it("should execute onChange with the correct value on click", () => { + const handleChange = vi.fn(); + render( + , + ); + + const radio = screen.getByTestId("action-radio"); + fireEvent.click(radio); + + expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith("test-value"); + }); + + it("should change styles based on isChecked change", () => { + const { rerender } = render( + {}} + dataTestId="action-radio" + />, + ); + let radio = screen.getByTestId("action-radio"); + + expect(radio).toHaveStyle("borderColor: white"); + expect(radio).toHaveStyle("backgroundColor: rgb(255, 255, 255)"); + + rerender( + {}} + dataTestId="action-radio" + />, + ); + radio = screen.getByTestId("action-radio"); + + expect(radio).toHaveStyle("borderColor: specialCyanBorder"); + expect(radio).toHaveStyle("backgroundColor: specialCyan"); + }); + + it("should display correct title and optional subtitle", () => { + render( + {}} + dataTestId="action-radio" + />, + ); + + const title = screen.getByText("Main Title"); + const subtitle = screen.getByText("Sub Title"); + + expect(title).toBeInTheDocument(); + expect(subtitle).toBeInTheDocument(); + }); + + it("should display tooltip text when InfoOutlinedIcon is hovered over", async () => { + render( + {}} + dataTestId="action-radio" + />, + ); + + const icon = screen.getByTestId("InfoOutlinedIcon"); + fireEvent.mouseOver(icon); + + const tooltip = await screen.findByText("Info Here", {}, { timeout: 500 }); + expect(tooltip).toBeInTheDocument(); + }); +}); diff --git a/govtool/frontend/src/components/atoms/CopyButton.test.tsx b/govtool/frontend/src/components/atoms/CopyButton.test.tsx new file mode 100644 index 000000000..8f7666858 --- /dev/null +++ b/govtool/frontend/src/components/atoms/CopyButton.test.tsx @@ -0,0 +1,69 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { SnackbarProvider } from "@context"; +import { CopyButton } from "@atoms"; + +Object.defineProperty(global.navigator, "clipboard", { + value: { + writeText: vi.fn(), + }, + writable: true, +}); + +vi.mock("@hooks", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), + useScreenDimension: () => ({ + isMobile: false, + }), +})); + +const writeTextMock = navigator.clipboard.writeText as unknown as { + mockClear: () => void; +}; + +describe("CopyButton", () => { + beforeEach(() => { + writeTextMock.mockClear(); + }); + + it("renders correctly with the default icon", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/Copy.svg"); + }); + + it("renders the blue icon when variant is 'blue'", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/CopyBlue.svg"); + }); + + it("renders the blue thin icon when variant is 'blueThin'", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/CopyBlueThin.svg"); + }); + + it("renders the white icon when isChecked prop is true", () => { + render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", "/icons/CopyWhite.svg"); + }); + + it("copies text to clipboard and shows success alert on click", async () => { + render( + + , + , + ); + + const copyButton = screen.getByTestId("copy-button"); + await userEvent.click(copyButton); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith("Example Text"); + + expect(screen.getByText("alerts.copiedToClipboard")).toBeInTheDocument(); + }); +}); diff --git a/govtool/frontend/src/components/atoms/DrawerLink.test.tsx b/govtool/frontend/src/components/atoms/DrawerLink.test.tsx new file mode 100644 index 000000000..70449e3a8 --- /dev/null +++ b/govtool/frontend/src/components/atoms/DrawerLink.test.tsx @@ -0,0 +1,90 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; +import { DrawerLink } from "@atoms"; +import { theme } from "@/theme"; + +describe("DrawerLink", () => { + const mockOnClick = vi.fn(); + + it("renders correctly with mandatory props", () => { + render( + + + , + ); + + const linkElement = screen.getByRole("link"); + expect(linkElement).toHaveAttribute("href", "/home"); + expect(screen.getByText("Home")).toBeInTheDocument(); + }); + + it("applies active styles correctly when active", () => { + render( + + + } + /> + + , + ); + + const linkElement = screen.getByRole("link"); + expect(linkElement).toHaveStyle( + `backgroundColor: ${theme.palette.highlightBlue}`, + ); + }); + + it("does not apply active styles when not active", () => { + render( + + + , + ); + + const linkElement = screen.getByRole("link"); + expect(linkElement).not.toHaveStyle( + `backgroundColor: ${theme.palette.highlightBlue}`, + ); + }); + + it("renders with an icon and activeIcon", () => { + const icon = "icon-path.png"; + const activeIcon = "active-icon-path.png"; + + render( + + + + } + /> + + , + ); + + const img = screen.getByAltText("icon") as HTMLImageElement; + expect(img.src).toContain("active-icon-path.png"); + }); + + it("executes onClick callback when clicked", () => { + render( + + + , + ); + + const linkElement = screen.getByRole("link"); + fireEvent.click(linkElement); + expect(mockOnClick).toHaveBeenCalled(); + }); +}); diff --git a/govtool/frontend/src/components/atoms/LoadingButton.test.tsx b/govtool/frontend/src/components/atoms/LoadingButton.test.tsx new file mode 100644 index 000000000..f6e543bd9 --- /dev/null +++ b/govtool/frontend/src/components/atoms/LoadingButton.test.tsx @@ -0,0 +1,53 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { LoadingButton } from "@atoms"; + +describe("LoadingButton", () => { + it("renders its children", () => { + render(Click me); + expect(screen.getByText("Click me")).toBeInTheDocument(); + }); + + it("is disabled when isLoading is true", () => { + render(Loading...); + expect(screen.getByRole("button", { name: "Loading..." })).toBeDisabled(); + }); + + it("is disabled when disabled prop is true", () => { + render(Disabled); + expect(screen.getByRole("button", { name: "Disabled" })).toBeDisabled(); + }); + + it("shows a CircularProgress when isLoading", () => { + render(Loading...); + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + }); + + it("applies different heights based on size prop", () => { + const { rerender } = render( + Small Button, + ); + + expect(screen.getByText("Small Button")).toHaveStyle({ height: "32px" }); + + rerender(Medium Button); + expect(screen.getByText("Medium Button")).toHaveStyle({ height: "36px" }); + + rerender(Large Button); + expect(screen.getByText("Large Button")).toHaveStyle({ height: "40px" }); + + rerender( + Extra Large Button, + ); + expect(screen.getByText("Extra Large Button")).toHaveStyle("height: 48px"); + }); + + it("applies custom styles via sx prop", () => { + const customStyles = { backgroundColor: "specialCyan" }; + render(Styled Button); + + expect(screen.getByText("Styled Button")).toHaveStyle({ + backgroundColor: "specialCyan", + }); + }); +}); diff --git a/govtool/frontend/src/components/atoms/VotePill.test.tsx b/govtool/frontend/src/components/atoms/VotePill.test.tsx new file mode 100644 index 000000000..24f09a4d0 --- /dev/null +++ b/govtool/frontend/src/components/atoms/VotePill.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect } from "vitest"; +import { render } from "@testing-library/react"; +import { VotePill } from "@atoms"; + +describe("VotePill", () => { + it('renders the VotePill component with "yes" vote correctly', () => { + const { getByText } = render(); + const voteText = getByText("yes"); + expect(voteText).toBeInTheDocument(); + expect(voteText.parentNode).toHaveStyle({ + borderColor: "#C0E4BA", + backgroundColor: "#F0F9EE", + }); + }); + + it('renders the VotePill component with "no" vote correctly', () => { + const { getByText } = render(); + const voteText = getByText("no"); + expect(voteText).toBeInTheDocument(); + expect(voteText.parentNode).toHaveStyle({ + borderColor: "#EDACAC", + backgroundColor: "#FBEBEB", + }); + }); + + it('renders the VotePill component with "abstain" vote correctly', () => { + const { getByText } = render(); + const voteText = getByText("abstain"); + expect(voteText).toBeInTheDocument(); + expect(voteText.parentNode).toHaveStyle({ + borderColor: "#99ADDE", + backgroundColor: "#E6EBF7", + }); + }); + + it("handles custom width and maxWidth props correctly", () => { + const { container } = render( + , + ); + const pillBox = container.firstChild; + expect(pillBox).toHaveStyle({ + width: "100px", + maxWidth: "120px", + }); + }); + + it("defaults width and maxWidth when not provided", () => { + const { container } = render(); + const pillBox = container.firstChild; + expect(pillBox).toHaveStyle({ + width: "auto", + maxWidth: "auto", + }); + }); +}); diff --git a/govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx b/govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx new file mode 100644 index 000000000..2272cdd6f --- /dev/null +++ b/govtool/frontend/src/components/atoms/VotingPowerChips.test.tsx @@ -0,0 +1,111 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import * as Hooks from "@hooks"; +import * as Context from "@context"; +import * as Utils from "@utils"; +import { VotingPowerChips } from "@atoms"; + +describe("VotingPowerChips", () => { + const mockUseCardano = vi.spyOn(Context, "useCardano"); + const mockUseGetDRepVotingPowerQuery = vi.spyOn( + Hooks, + "useGetDRepVotingPowerQuery", + ); + const mockUseGetAdaHolderVotingPowerQuery = vi.spyOn( + Hooks, + "useGetAdaHolderVotingPowerQuery", + ); + const mockUseScreenDimension = vi.spyOn(Hooks, "useScreenDimension"); + const mockCorrectAdaFormat = vi.spyOn(Utils, "correctAdaFormat"); + const mockUseTranslation = vi.spyOn(Hooks, "useTranslation"); + const mockUseGetVoterInfo = vi.spyOn(Hooks, "useGetVoterInfo"); + + it("renders loading spinner when data is loading", () => { + mockUseCardano.mockReturnValue({ + stakeKey: "fake_key", + isEnableLoading: "demos", + } as ReturnType); + mockUseGetDRepVotingPowerQuery.mockReturnValue( + {} as ReturnType, + ); + mockUseGetAdaHolderVotingPowerQuery.mockReturnValue( + {} as ReturnType, + ); + mockUseScreenDimension.mockReturnValue({ + isMobile: false, + screenWidth: 1024, + } as ReturnType); + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + } as ReturnType); + mockUseGetVoterInfo.mockReturnValue( + {} as ReturnType, + ); + + render(); + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + }); + + it("displays formatted ADA amount when data is available and not loading", () => { + mockUseCardano.mockReturnValue({ + stakeKey: "fake_key", + isEnableLoading: null, + } as ReturnType); + mockUseGetDRepVotingPowerQuery.mockReturnValue({ + dRepVotingPower: 1000, + } as ReturnType); + mockUseGetAdaHolderVotingPowerQuery.mockReturnValue({ + votingPower: 500, + } as ReturnType); + mockUseScreenDimension.mockReturnValue({ + isMobile: false, + screenWidth: 1024, + } as ReturnType); + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + } as ReturnType); + mockUseGetVoterInfo.mockReturnValue({ + voter: { isRegisteredAsDRep: true }, + } as ReturnType); + mockCorrectAdaFormat.mockReturnValue(1000); + + render(); + expect(screen.getByText(/₳ 1000/)).toBeInTheDocument(); + }); + + it("displays the tooltip correctly for DRep registered users", async () => { + mockUseCardano.mockReturnValue({ + stakeKey: "fake_key", + isEnableLoading: null, + } as ReturnType); + mockUseGetDRepVotingPowerQuery.mockReturnValue({ + dRepVotingPower: 1000, + } as ReturnType); + mockUseGetAdaHolderVotingPowerQuery.mockReturnValue({ + votingPower: 500, + } as ReturnType); + mockUseScreenDimension.mockReturnValue({ + isMobile: true, + screenWidth: 800, + } as ReturnType); + mockUseTranslation.mockReturnValue({ + t: (key: string) => key, + } as ReturnType); + mockUseGetVoterInfo.mockReturnValue({ + voter: { isRegisteredAsDRep: true }, + } as ReturnType); + mockCorrectAdaFormat.mockReturnValue(1000); + + render(); + + const icon = screen.getByTestId("InfoOutlinedIcon"); + fireEvent.mouseOver(icon); + + const tooltip = await screen.findByText( + "tooltips.votingPower.heading", + {}, + { timeout: 500 }, + ); + expect(tooltip).toBeInTheDocument(); + }); +});