From 2faf613152958b01068074546e49aaab5c824f4f Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Fri, 13 Sep 2024 14:18:53 +0200 Subject: [PATCH] feat(namadillo): writing tests for StakingRewards --- apps/namadillo/jest.config.ts | 10 +- apps/namadillo/package.json | 3 + .../src/App/Staking/StakingRewards.tsx | 1 + .../Staking/__tests__/StakingRewards.test.tsx | 163 ++++++++++++++++++ apps/namadillo/src/hooks/useTransaction.tsx | 2 +- packages/components/src/SkeletonLoading.tsx | 1 + yarn.lock | 28 +++ 7 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 apps/namadillo/src/App/Staking/__tests__/StakingRewards.test.tsx diff --git a/apps/namadillo/jest.config.ts b/apps/namadillo/jest.config.ts index d28872d81..cfaeaadfc 100644 --- a/apps/namadillo/jest.config.ts +++ b/apps/namadillo/jest.config.ts @@ -10,8 +10,12 @@ module.exports = { modulePathIgnorePatterns: ["e2e-tests"], moduleDirectories: ["src", "node_modules"], verbose: true, - moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { - prefix: "/src/", - }), + moduleNameMapper: { + ...pathsToModuleNameMapper(compilerOptions.paths, { + prefix: "/src/", + }), + "^.+\\.svg$": "jest-transformer-svg", + "\\.css": "identity-obj-proxy", + }, setupFilesAfterEnv: ["/src/setupTests.ts"], }; diff --git a/apps/namadillo/package.json b/apps/namadillo/package.json index 7a9bfe214..7e0b4133b 100644 --- a/apps/namadillo/package.json +++ b/apps/namadillo/package.json @@ -57,6 +57,7 @@ "test:watch": "yarn wasm:build:test && yarn jest --watchAll=true", "test:coverage": "yarn wasm:build:test && yarn test --coverage", "test:ci": "jest", + "test:watch:only": "yarn test --watchAll=true", "e2e-test": "PLAYWRIGHT_BASE_URL=http://localhost:3000 yarn playwright test", "e2e-test:headed": "PLAYWRIGHT_BASE_URL=http://localhost:3000 yarn playwright test --project=chromium --headed", "wasm:build": "node ./scripts/build.js --release", @@ -106,10 +107,12 @@ "eslint-plugin-react-hooks": "^4.6.0", "globals": "^15.9.0", "history": "^5.3.0", + "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-create-mock-instance": "^2.0.0", "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", + "jest-transformer-svg": "^2.0.2", "local-cors-proxy": "^1.1.0", "postcss": "^8.4.32", "release-it": "^17.0.1", diff --git a/apps/namadillo/src/App/Staking/StakingRewards.tsx b/apps/namadillo/src/App/Staking/StakingRewards.tsx index 1a90479cf..773c48f40 100644 --- a/apps/namadillo/src/App/Staking/StakingRewards.tsx +++ b/apps/namadillo/src/App/Staking/StakingRewards.tsx @@ -80,6 +80,7 @@ export const StakingRewards = (): JSX.Element => { }); const availableRewards = useMemo(() => { + if (!rewards || Object.keys(rewards).length === 0) return BigNumber(0); return BigNumber.sum(...Object.values(rewards || [])); }, [rewards]); diff --git a/apps/namadillo/src/App/Staking/__tests__/StakingRewards.test.tsx b/apps/namadillo/src/App/Staking/__tests__/StakingRewards.test.tsx new file mode 100644 index 000000000..3fdfce771 --- /dev/null +++ b/apps/namadillo/src/App/Staking/__tests__/StakingRewards.test.tsx @@ -0,0 +1,163 @@ +import BigNumber from "bignumber.js"; +import { mockJotai } from "test-utils"; +mockJotai(); + +import { fireEvent, render, screen } from "@testing-library/react"; +import { StakingRewards } from "App/Staking/StakingRewards"; +import { defaultAccountAtom } from "atoms/accounts"; +import { claimableRewardsAtom } from "atoms/staking"; +import { useTransaction } from "hooks/useTransaction"; +import { useAtomValue } from "jotai"; +import { AddressBalance } from "types"; + +jest.mock("hooks/useTransaction", () => ({ + useTransaction: jest.fn(), +})); + +jest.mock("hooks/useModalCloseEvent", () => ({ + useModalCloseEvent: () => ({ onCloseModal: jest.fn() }), +})); + +jest.mock("atoms/staking", () => ({ + claimableRewardsAtom: jest.fn(), + claimAndStakeRewardsAtom: jest.fn(), + claimRewardsAtom: jest.fn(), +})); + +const mockAtomValue = ( + data: AddressBalance = {}, + isLoading: boolean = true, + isSuccess: boolean = false +): void => { + (useAtomValue as jest.Mock).mockImplementation((atom) => { + if (atom === defaultAccountAtom) { + return { data: { address: "tnam1_test_account" } }; + } + + if (atom === claimableRewardsAtom) { + return { data, isLoading, isSuccess }; + } + + return null; + }); +}; + +const mockTransaction = ( + execute: jest.Mock = jest.fn(), + isEnabled: boolean = false, + isPending: boolean = false +): void => { + (useTransaction as jest.Mock).mockImplementation(() => { + return { + execute, + isEnabled, + isPending, + }; + }); +}; + +describe("Component: StakingRewards", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const setup = (): void => { + render(); + }; + + const getButtons = (): HTMLElement[] => { + return screen.getAllByRole("button"); + }; + + it("should render modal correctly", () => { + mockAtomValue(); + mockTransaction(); + setup(); + expect(screen.getByText("Claimable Staking Rewards")).toBeInTheDocument(); + expect(getButtons()).toHaveLength(2); + }); + + it("should render loading skeleton when rewards are loading", () => { + mockAtomValue({}, true); + mockTransaction(); + render(); + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + + const buttons = getButtons(); + buttons.forEach((button) => expect(button).toBeDisabled()); + }); + + it("should display available rewards when loaded", () => { + mockAtomValue( + { + validator1: new BigNumber(100), + validator2: new BigNumber(200), + }, + false, + true + ); + mockTransaction(); + render(); + expect(screen.getByText("300")).toBeInTheDocument(); + }); + + it("should enable buttons if claim rewards are available", () => { + mockAtomValue( + { + validator1: new BigNumber(100), + }, + false, + true + ); + mockTransaction(jest.fn(), true, false); + render(); + const buttons = getButtons(); + buttons.forEach((button) => expect(button).toBeEnabled()); + }); + + it("should disable buttons if claim rewards are not enabled", () => { + mockAtomValue( + { + validator1: new BigNumber(100), + }, + false, + true + ); + mockTransaction(jest.fn(), false, false); + render(); + const buttons = getButtons(); + buttons.forEach((button) => expect(button).not.toBeEnabled()); + }); + + it("should disable buttons while transaction is pending", () => { + mockAtomValue( + { + validator1: new BigNumber(100), + }, + false, + true + ); + mockTransaction(jest.fn(), true, true); + render(); + const buttons = getButtons(); + buttons.forEach((button) => expect(button).not.toBeEnabled()); + }); + + it("should call 'claimRewardsAndStake' when 'Claim & Stake' is clicked", async () => { + const executeMock = jest.fn(); + mockTransaction(executeMock, true, false); + mockAtomValue( + { + validator1: new BigNumber(100), + }, + false, + true + ); + render(); + const buttons = getButtons(); + fireEvent.click(buttons[0]); + expect(executeMock).toHaveBeenCalledTimes(1); + fireEvent.click(buttons[1]); + expect(executeMock).toHaveBeenCalledTimes(2); + }); +}); diff --git a/apps/namadillo/src/hooks/useTransaction.tsx b/apps/namadillo/src/hooks/useTransaction.tsx index 306dc22d6..e3cc05766 100644 --- a/apps/namadillo/src/hooks/useTransaction.tsx +++ b/apps/namadillo/src/hooks/useTransaction.tsx @@ -22,7 +22,7 @@ type AtomType = Atom< type PartialNotification = Pick; -type useTransactionProps = { +export type useTransactionProps = { params: T[]; createTxAtom: AtomType; eventType: TransactionEventsClasses; diff --git a/packages/components/src/SkeletonLoading.tsx b/packages/components/src/SkeletonLoading.tsx index cd132c5fc..29d79308e 100644 --- a/packages/components/src/SkeletonLoading.tsx +++ b/packages/components/src/SkeletonLoading.tsx @@ -14,6 +14,7 @@ export const SkeletonLoading = ({ const { className, ...rest } = props; return ( = 28.1.0" + react: ^17.0.0 || ^18.0.0 + checksum: f71a46b2fb35dc25df714005b2d36f82287ab518647eddbce9c9baa923478da2a25f3fd358703db01c52630f2733a375219d3d0896965856b3940788a2f2f68e + languageName: node + linkType: hard + "jest-util@npm:^29.0.0, jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0"