From 94327c78428521a708957f52e66fe1c7cfa5b40c Mon Sep 17 00:00:00 2001 From: Emmanuel A Akalo <124416278+NueloSE@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:01:36 +0100 Subject: [PATCH] test: create test for useScaffoldWriteContract hook (#316) --- .../useScaffoldMultiWriteContract.test.ts | 243 ++++++++++++++++++ .../useScaffoldWriteContract.test.ts | 146 +++++++++++ 2 files changed, 389 insertions(+) create mode 100644 packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldMultiWriteContract.test.ts create mode 100644 packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldWriteContract.test.ts diff --git a/packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldMultiWriteContract.test.ts b/packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldMultiWriteContract.test.ts new file mode 100644 index 00000000..6e879807 --- /dev/null +++ b/packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldMultiWriteContract.test.ts @@ -0,0 +1,243 @@ +import { renderHook, act } from "@testing-library/react"; + +import { vi, describe, it, expect, beforeEach, type Mock } from "vitest"; + +import { + useScaffoldMultiWriteContract, + createContractCall, +} from "../useScaffoldMultiWriteContract"; + +import { useTargetNetwork } from "../useTargetNetwork"; + +import { useNetwork, useSendTransaction } from "@starknet-react/core"; + +import { useTransactor } from "../useTransactor"; + +import { useDeployedContractInfo } from "~~/hooks/scaffold-stark"; + +import { Contract, RpcProvider } from "starknet"; + +// Mock the external dependencies + +vi.mock("~~/hooks/scaffold-stark/useTargetNetwork", () => ({ + useTargetNetwork: vi.fn(), +})); + +vi.mock("@starknet-react/core", () => ({ + useSendTransaction: vi.fn(), + + useNetwork: vi.fn().mockReturnValue({ chain: { id: 1 } }), +})); + +vi.mock("starknet", () => { + const actualStarknet = vi.importActual("starknet"); + + return { + ...actualStarknet, + + Contract: vi.fn(), + + RpcProvider: vi.fn(), + }; +}); + +vi.mock("../useTransactor"); + +vi.mock("~~/hooks/scaffold-stark", () => ({ + useDeployedContractInfo: vi.fn(), + + useTransactor: vi.fn(), +})); + +const mockSendTransaction = vi.fn(); + +const mockTransactor = vi.fn((fn) => fn()); + +const mockedUseNetwork = useNetwork as Mock; + +const useTargetNetworkMock = useTargetNetwork as Mock; +const useSendTransactionMock = useSendTransaction as Mock; +const useTransactorMock = useTransactor as Mock; +const useDeployedContractInfoMock = useDeployedContractInfo as Mock; +const ContractMock = Contract as Mock; +const useNetworkMock = useNetwork as Mock; + +describe("useScaffoldMultiWriteContract Hook", () => { + const mockAbi = [ + { type: "function", name: "mockFunction", inputs: [], outputs: [] }, + ]; + + const mockAddress = "0x12345"; + + beforeEach(() => { + vi.resetAllMocks(); + + useTargetNetworkMock.mockReturnValue({ + targetNetwork: { network: "testNetwork", id: 1 }, + }); + + mockedUseNetwork.mockReturnValue({ chain: { id: 1 } }); + + useSendTransactionMock.mockReturnValue({ + sendAsync: mockSendTransaction, + }); + + useTransactorMock.mockReturnValue(mockTransactor); + + useDeployedContractInfoMock.mockReturnValue({ + data: { + address: "0x123", + + abi: [{ name: "testFunction" }], + }, + }); + + ContractMock.mockImplementation(() => ({ + address: mockAddress, + + abi: mockAbi, + })); + }); + + it("should correctly parse contract calls", () => { + // Mock contract and ABI + + const { result } = renderHook(() => + useScaffoldMultiWriteContract({ + calls: [ + { contractName: "Strk", functionName: "transfer", args: ["arg1", 1] }, + ], + }), + ); + + expect(result.current.sendAsync).toBeInstanceOf(Function); + + expect(mockSendTransaction).not.toHaveBeenCalled(); + }); + + it("should return error if wallet is not connected", async () => { + useNetworkMock.mockReturnValueOnce({ chain: null }); + + const { result } = renderHook(() => + useScaffoldMultiWriteContract({ + calls: [ + { contractName: "Strk", functionName: "transfer", args: ["arg1", 1] }, + ], + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + vi.spyOn(result.current, "sendAsync").mockRejectedValue( + new Error("Please connect your wallet"), + ); + + await expect(result.current.sendAsync).rejects.toThrowError( + "Please connect your wallet", + ); + }); + + it("should handle wrong network", async () => { + useNetworkMock.mockReturnValueOnce({ chain: { id: 2 } }); + + const { result } = renderHook(() => + useScaffoldMultiWriteContract({ + calls: [ + { contractName: "Strk", functionName: "transfer", args: ["arg1", 1] }, + ], + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + vi.spyOn(result.current, "sendAsync").mockRejectedValue( + new Error("You are on the wrong network"), + ); + + await expect(result.current.sendAsync).rejects.toThrowError( + "You are on the wrong network", + ); + }); + + it("should show error if contract ABI is missing", async () => { + const { result } = renderHook(() => + useScaffoldMultiWriteContract({ + calls: [ + { contractName: "Strk", functionName: "transfer", args: ["arg1", 1] }, + ], + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + vi.spyOn(result.current, "sendAsync").mockRejectedValue( + new Error("Function myFunction not found in contract ABI"), + ); + + await expect(result.current.sendAsync).rejects.toThrowError( + "Function myFunction not found in contract ABI", + ); + }); + + it("should send contract write transaction", async () => { + useTransactorMock.mockReturnValue((fn: any) => fn()); + + const { result } = renderHook(() => + useScaffoldMultiWriteContract({ + calls: [ + { contractName: "Strk", functionName: "transfer", args: ["arg1", 1] }, + ], + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + expect(mockSendTransaction).toHaveBeenCalled(); + }); + + it("should show error notification if sendAsync is not available", async () => { + useSendTransactionMock.mockReturnValueOnce({ sendAsync: null }); + + const { result } = renderHook(() => + useScaffoldMultiWriteContract({ + calls: [ + { contractName: "Strk", functionName: "transfer", args: ["arg1", 1] }, + ], + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + vi.spyOn(result.current, "sendAsync").mockRejectedValue( + new Error("Contract writer error. Try again."), + ); + + await expect(result.current.sendAsync).rejects.toThrowError( + "Contract writer error. Try again.", + ); + }); +}); + +describe("createContractCall Function", () => { + it("should create a contract call object", () => { + const contractCall = createContractCall("Strk", "transfer", ["arg1", 1]); + + expect(contractCall).toEqual({ + contractName: "Strk", + + functionName: "transfer", + + args: ["arg1", 1], + }); + }); +}); diff --git a/packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldWriteContract.test.ts b/packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldWriteContract.test.ts new file mode 100644 index 00000000..722e830a --- /dev/null +++ b/packages/nextjs/hooks/scaffold-stark/__tests__/useScaffoldWriteContract.test.ts @@ -0,0 +1,146 @@ +import { renderHook, act } from "@testing-library/react"; +import { useScaffoldWriteContract } from "~~/hooks/scaffold-stark/useScaffoldWriteContract"; +import { + useDeployedContractInfo, + useTransactor, +} from "~~/hooks/scaffold-stark"; +import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork"; +import { useSendTransaction } from "@starknet-react/core"; +import { vi, describe, beforeEach, afterAll, it, expect } from "vitest"; +import { Mock } from "vitest"; + +// Mock dependencies +vi.mock("~~/hooks/scaffold-stark", () => ({ + useDeployedContractInfo: vi.fn(), + useTransactor: vi.fn(), +})); + +vi.mock("~~/hooks/scaffold-stark/useTargetNetwork", () => ({ + useTargetNetwork: vi.fn(), +})); + +vi.mock("@starknet-react/core", () => ({ + useSendTransaction: vi.fn(), + useNetwork: vi.fn().mockReturnValue({ chain: { id: 1 } }), +})); + +describe("useScaffoldWriteContract", () => { + const contractName = "Eth"; + const functionName = "transfer"; + const args: readonly [string, number] = ["0x1234", 1000]; + + const mockUseDeployedContractInfo = + useDeployedContractInfo as unknown as Mock; + const mochUseSendTransaction = useSendTransaction as unknown as Mock; + const mockUseTransactor = useTransactor as unknown as Mock; + const mockUseTargetNetwork = useTargetNetwork as unknown as Mock; + + beforeEach(() => { + // Reset all mocks before each test + vi.clearAllMocks(); + }); + + afterAll(() => { + vi.restoreAllMocks(); + }); + + it("should handle case where contract is not deployed", async () => { + mockUseDeployedContractInfo.mockReturnValue({ data: undefined }); + const mockSendTransaction = { sendAsync: vi.fn() }; + mochUseSendTransaction.mockReturnValue(mockSendTransaction); + mockUseTransactor.mockReturnValue(vi.fn()); + mockUseTargetNetwork.mockReturnValue({ + targetNetwork: { id: 1 }, + }); + + const { result } = renderHook(() => + useScaffoldWriteContract({ + contractName, + functionName, + args, + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + expect(mockSendTransaction.sendAsync).not.toHaveBeenCalled(); + }); + + it("should handle case where user is on the wrong network", async () => { + mockUseDeployedContractInfo.mockReturnValue({ + data: { + address: "0x123", + abi: [{ name: "testFunction" }], + }, + }); + const mockSendTransaction = { sendAsync: vi.fn() }; + mochUseSendTransaction.mockReturnValue(mockSendTransaction); + mockUseTransactor.mockReturnValue(vi.fn()); + mockUseTargetNetwork.mockReturnValue({ + targetNetwork: { id: 2 }, // Different network ID + }); + + const { result } = renderHook(() => + useScaffoldWriteContract({ + contractName, + functionName, + args, + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + expect(mockSendTransaction.sendAsync).not.toHaveBeenCalled(); + }); + + it("should send transaction when contract is deployed and user is on correct network", async () => { + mockUseDeployedContractInfo.mockReturnValue({ + data: { + address: "0x123", + abi: [{ name: "testFunction" }], + }, + }); + const mockSendTransaction = { sendAsync: vi.fn() }; + mochUseSendTransaction.mockReturnValue(mockSendTransaction); + mockUseTransactor.mockReturnValue(vi.fn((fn) => fn())); + mockUseTargetNetwork.mockReturnValue({ + targetNetwork: { id: 1 }, + }); + + const { result } = renderHook(() => + useScaffoldWriteContract({ + contractName, + functionName, + args, + }), + ); + + await act(async () => { + await result.current.sendAsync(); + }); + + expect(mockSendTransaction.sendAsync).toHaveBeenCalledWith([ + { + contractAddress: "0x123", + entrypoint: functionName, + calldata: expect.any(Array), + }, + ]); + }); + + it("should call useDeployedContractInfo with the correct contract name", () => { + renderHook(() => + useScaffoldWriteContract({ + contractName, + functionName, + args, + }), + ); + + expect(useDeployedContractInfo).toHaveBeenCalledWith(contractName); + }); +});