From 4488be20d0137bb9f3f10dbf4fc77822bcee608b Mon Sep 17 00:00:00 2001 From: Neven Dyulgerov Date: Fri, 10 Jan 2025 13:44:32 +0200 Subject: [PATCH 1/2] feat(apps/web): Disable send transaction button when network is wrong --- apps/web/src/components/layout/shell.tsx | 7 +- .../web/test/components/layout/shell.test.tsx | 94 ++++++++++--------- 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/apps/web/src/components/layout/shell.tsx b/apps/web/src/components/layout/shell.tsx index 07882941e..f3f6a8ce8 100644 --- a/apps/web/src/components/layout/shell.tsx +++ b/apps/web/src/components/layout/shell.tsx @@ -32,6 +32,7 @@ import CartesiLogo from "../../components/cartesiLogo"; import Footer from "../../components/layout/footer"; import SendTransaction from "../../components/sendTransaction"; import { CartesiScanChains } from "../networks/cartesiScanNetworks"; +import getConfiguredChainId from "../../lib/getConfiguredChain"; const Shell: FC<{ children: ReactNode }> = ({ children }) => { const [opened, { toggle: toggleMobileMenu, close: closeMobileMenu }] = @@ -52,7 +53,9 @@ const Shell: FC<{ children: ReactNode }> = ({ children }) => { const hideBalanceViewport = useMediaQuery( `(min-width:${theme.breakpoints.sm}) and (max-width:${50}em)`, ); - const { isConnected } = useAccount(); + const { isConnected, chainId } = useAccount(); + const configuredChainId = getConfiguredChainId(); + const isValidNetwork = chainId === Number(configuredChainId); const { colorScheme, toggleColorScheme } = useMantineColorScheme({ keepTransitions: true, }); @@ -96,7 +99,7 @@ const Shell: FC<{ children: ReactNode }> = ({ children }) => { variant="subtle" leftSection={} onClick={openTransaction} - disabled={!isConnected} + disabled={!isConnected || !isValidNetwork} visibleFrom="sm" data-testid="transaction-button" > diff --git a/apps/web/test/components/layout/shell.test.tsx b/apps/web/test/components/layout/shell.test.tsx index a04657237..79f2b8456 100644 --- a/apps/web/test/components/layout/shell.test.tsx +++ b/apps/web/test/components/layout/shell.test.tsx @@ -8,9 +8,14 @@ import { within, } from "@testing-library/react"; import { mainnet } from "viem/chains"; -import { afterAll, describe, it } from "vitest"; +import { afterAll, beforeEach, describe, it } from "vitest"; import Shell from "../../../src/components/layout/shell"; import withMantineTheme from "../../utils/WithMantineTheme"; +import { useAccount, useConfig } from "wagmi"; +import getConfiguredChainId from "../../../src/lib/getConfiguredChain"; + +vi.mock("../../../src/lib/getConfiguredChain"); +const getConfiguredChainIdMock = vi.mocked(getConfiguredChainId, true); const Component = withMantineTheme(Shell); @@ -75,50 +80,23 @@ vi.mock("@cartesi/rollups-wagmi", async () => { }; }); -vi.mock("wagmi", async () => { - return { - useConfig: () => ({ +vi.mock("wagmi"); +const useConfigMock = vi.mocked(useConfig, { partial: true }); +const useAccountMock = vi.mocked(useAccount, { partial: true }); + +describe("Shell component", () => { + beforeEach(() => { + useConfigMock.mockReturnValue({ chains: [mainnet], - }), - useContractReads: () => ({ - isLoading: false, - isSuccess: true, - data: [ - { - result: undefined, - error: undefined, - }, - { - result: undefined, - error: undefined, - }, - { - result: undefined, - error: undefined, - }, - { - result: undefined, - error: undefined, - }, - ], - }), - useAccount: () => ({ + }); + + useAccountMock.mockReturnValue({ address: "0x8FD78976f8955D13bAA4fC99043208F4EC020D7E", - }), - usePrepareContractWrite: () => ({}), - useWaitForTransaction: () => ({}), - useContractWrite: () => ({}), - useNetwork: () => ({ - chain: { - nativeCurrency: { - decimals: 18, - }, - }, - }), - }; -}); + }); + + getConfiguredChainIdMock.mockReturnValue("31337"); + }); -describe("Shell component", () => { afterAll(() => { vi.restoreAllMocks(); }); @@ -134,7 +112,7 @@ describe("Shell component", () => { }); describe("Header", () => { - it("should display transaction link in header", () => { + it("should display 'Send Transaction' button in header", () => { render(Children); expect( @@ -144,6 +122,36 @@ describe("Shell component", () => { ).toBeInTheDocument(); }); + it("should enable 'Send Transaction' button when network is correct", () => { + useAccountMock.mockReturnValue({ + address: "0x8FD78976f8955D13bAA4fC99043208F4EC020D7E", + isConnected: true, + chainId: 31337, + }); + getConfiguredChainIdMock.mockReturnValue("31337"); + render(Children); + + const button = within(screen.getByTestId("header")).getByTestId( + "transaction-button", + ); + expect(button.hasAttribute("disabled")).toBe(false); + }); + + it("should display disable 'Send Transaction' button when network is wrong", () => { + useAccountMock.mockReturnValue({ + address: "0x8FD78976f8955D13bAA4fC99043208F4EC020D7E", + isConnected: true, + chainId: 31337, + }); + getConfiguredChainIdMock.mockReturnValue(""); + render(Children); + + const button = within(screen.getByTestId("header")).getByTestId( + "transaction-button", + ); + expect(button.hasAttribute("disabled")).toBe(true); + }); + it("should not display home and applications links in header", () => { render(Children); From e0ed7f2a251f63082d9ce890cb6376eebc2f11c8 Mon Sep 17 00:00:00 2001 From: Neven Dyulgerov Date: Fri, 10 Jan 2025 14:11:17 +0200 Subject: [PATCH 2/2] chore(apps/web): Refactoring --- .../web/test/components/layout/shell.test.tsx | 67 +------------------ 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/apps/web/test/components/layout/shell.test.tsx b/apps/web/test/components/layout/shell.test.tsx index 79f2b8456..78e02993f 100644 --- a/apps/web/test/components/layout/shell.test.tsx +++ b/apps/web/test/components/layout/shell.test.tsx @@ -17,73 +17,12 @@ import getConfiguredChainId from "../../../src/lib/getConfiguredChain"; vi.mock("../../../src/lib/getConfiguredChain"); const getConfiguredChainIdMock = vi.mocked(getConfiguredChainId, true); -const Component = withMantineTheme(Shell); - -vi.mock("../../../src/graphql", async () => { - return { - useApplicationsQuery: () => [{ data: {}, fetching: false }], - useTokensQuery: () => [{ data: {}, fetching: false }], - }; -}); - -vi.mock("@cartesi/rollups-wagmi", async () => { - return { - usePrepareInputBoxAddInput: () => ({ - config: {}, - }), - useInputBoxAddInput: () => ({ - data: {}, - wait: vi.fn(), - }), - }; -}); - -vi.mock("viem", async () => { - const actual = await vi.importActual("viem"); - return { - ...(actual as any), - getAddress: (address: string) => address, - }; -}); - -vi.mock("@rainbow-me/rainbowkit", async () => { - return { - ConnectButton: () => <>, - }; -}); - -vi.mock("@cartesi/rollups-wagmi", async () => { - const actual = await vi.importActual("@cartesi/rollups-wagmi"); - return { - ...(actual as any), - usePrepareErc20Approve: () => ({ - config: {}, - }), - useErc20Approve: () => ({ - data: {}, - wait: vi.fn(), - }), - usePrepareErc20PortalDepositErc20Tokens: () => ({ - config: {}, - }), - useErc20PortalDepositErc20Tokens: () => ({ - data: {}, - wait: vi.fn(), - }), - usePrepareEtherPortalDepositEther: () => ({ - config: {}, - }), - useEtherPortalDepositEther: () => ({ - data: {}, - wait: vi.fn(), - }), - }; -}); - vi.mock("wagmi"); const useConfigMock = vi.mocked(useConfig, { partial: true }); const useAccountMock = vi.mocked(useAccount, { partial: true }); +const Component = withMantineTheme(Shell); + describe("Shell component", () => { beforeEach(() => { useConfigMock.mockReturnValue({ @@ -166,7 +105,7 @@ describe("Shell component", () => { ).toThrow("Unable to find an element"); }); - it("should display the cartesiscan chain button", async () => { + it("should display the cartesiscan chain button", () => { render(Children); const headerEl = screen.getByTestId("header");