import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { FC } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { VITALIK_WALLET } from "~test/addresses.js";
import {
fireEvent,
render,
renderHook,
screen,
waitFor,
} from "~test/react-render.js";
import { TEST_CLIENT } from "~test/test-clients.js";
import { base } from "../../../../chains/chain-definitions/base.js";
import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
import { useActiveAccount } from "../../../../react/core/hooks/wallets/useActiveAccount.js";
import { useActiveWalletChain } from "../../../../react/core/hooks/wallets/useActiveWalletChain.js";
import { AccountProvider } from "../../../core/account/provider.js";
import { ThirdwebProvider } from "../../providers/thirdweb-provider.js";
import {
ConnectedToSmartWallet,
ConnectedWalletDetails,
DetailsModal,
detailsBtn_formatFiatBalanceForButton,
detailsBtn_formatTokenBalanceForButton,
InAppWalletUserInfo,
NetworkSwitcherButton,
StyledChevronRightIcon,
SwitchNetworkButton,
useWalletDetailsModal,
} from "./Details.js";
import en from "./locale/en.js";
import { getConnectLocale } from "./locale/getConnectLocale.js";
/**
* Tests for the Details button and Details Modal (parts of the ConnectButton component)
*/
const queryClient = new QueryClient();
const client = TEST_CLIENT;
vi.mock("../../../core/hooks/wallets/useActiveAccount.js", () => ({
useActiveAccount: vi.fn(),
}));
const mockDetailsModalOptions = {};
const mockSupportedTokens = {};
const mockSupportedNFTs = {};
// biome-ignore lint/suspicious/noExplicitAny: Mock
const mockChains: any[] = [];
const mockDisplayBalanceToken = {};
const mockConnectOptions = {};
// biome-ignore lint/suspicious/noExplicitAny: Mock
const mockAssetTabs: any[] = [];
const mockOnDisconnect = vi.fn();
describe("Details button", () => {
it("should render (when a wallet is connected)", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByClassName("tw-connected-wallet");
expect(!!elements.length).toBe(true);
});
it("should render with showBalanceInFiat", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByClassName("tw-connected-wallet");
expect(!!elements.length).toBe(true);
});
it("should render with custom UI from the `render` prop", () => {
const { container } = render(
,
}}
detailsModal={undefined}
onDisconnect={() => {}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const element = container.querySelector("p.thirdweb_tw");
expect(element).not.toBe(null);
});
it("should render style properly", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const element = container.querySelector(
'[data-test="connected-wallet-details"]',
);
if (!element) {
throw new Error("Details button not rendered properly");
}
const styles = window.getComputedStyle(element);
expect(styles.color).toBe("red");
expect(styles.width).toBe("4444px");
});
it("should render the Balance section", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByClassName(
"tw-connected-wallet__balance",
);
expect(!!elements.length).toBe(true);
});
it("should render the Address section if detailsButton.connectedAccountName is not passed", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByClassName(
"tw-connected-wallet__address",
);
expect(!!elements.length).toBe(true);
});
it("should NOT render the Address section if detailsButton.connectedAccountName is passed", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByClassName(
"tw-connected-wallet__address",
);
expect(elements.length).toBe(1);
expect(elements[0]?.innerHTML).toBe("test name");
});
it("should render a custom img if detailsButton?.connectedAccountAvatarUrl is passed", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByTagName("img");
expect(elements.length).toBe(1);
expect(elements[0]?.src).toBe("https://thirdweb.com/cat.png");
});
it("should render AccountAvatar if no custom image is passed", () => {
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={undefined}
theme={"dark"}
/>
,
);
const elements = container.getElementsByClassName(
"tw-connected-wallet__account_avatar",
);
expect(elements.length).toBe(1);
});
it("should render the SwitchNetworkButton if chain is mismatched", () => {
vi.mock(
"../../../../react/core/hooks/wallets/useActiveWalletChain.js",
() => ({
useActiveWalletChain: vi.fn(),
}),
);
vi.mocked(useActiveWalletChain).mockReturnValue(base);
const { container } = render(
{}}
supportedNFTs={undefined}
supportedTokens={undefined}
switchButton={{
className: "thirdwebSwitchBtn",
label: "switchbtn",
style: {
color: "red",
},
}}
theme={"dark"}
/>
,
);
const element = container.querySelector(
"button.tw-connect-wallet--switch-network",
);
expect(element).not.toBe(null);
const element2 = container.querySelector("button.thirdwebSwitchBtn");
expect(element2).not.toBe(null);
expect(element && element.innerHTML === "switchbtn").toBe(true);
vi.resetAllMocks();
});
it("should render the fiat value properly", () => {
expect(
detailsBtn_formatFiatBalanceForButton({ balance: 12.9231, symbol: "$" }),
).toBe(" ($13)");
});
it("should render the token balance properly", () => {
expect(
detailsBtn_formatTokenBalanceForButton({
balance: 12.923111,
symbol: "ETH",
}),
).toBe("12.9231 ETH");
});
});
const thirdwebWrapper: FC = ({ children }: React.PropsWithChildren) => (
{children}
);
/**
* useWalletDetailsModal
*/
describe("useWalletDetailsModal", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should return an object with an open function", () => {
const { result } = renderHook(() => useWalletDetailsModal(), {
wrapper: thirdwebWrapper,
});
expect(result.current).toHaveProperty("open");
expect(typeof result.current.open).toBe("function");
});
it("should throw an error when opening modal without a connected wallet", () => {
const { result } = renderHook(() => useWalletDetailsModal(), {
wrapper: thirdwebWrapper,
});
expect(() =>
result.current.open({
client,
}),
).toThrow("Wallet is not connected.");
});
});
/**
* SwitchNetworkButton
*/
describe("SwitchNetworkButton", () => {
it("should render a default button", () => {
const { container } = render(
,
);
const element = container.querySelector(
"button.tw-connect-wallet--switch-network",
);
expect(element).not.toBe(null);
});
it("should apply the style properly", () => {
const { container } = render(
,
);
const element = container.querySelector(
"button.tw-connect-wallet--switch-network",
);
if (!element) {
throw new Error("Failed to render SwitchNetworkButton");
}
const styles = window.getComputedStyle(element);
expect(styles.color).toBe("red");
expect(styles.width).toBe("4444px");
});
it("should apply the className properly", () => {
const { container } = render(
,
);
const element = container.querySelector("button.thirdwebRocks");
expect(element).not.toBe(null);
});
it("should render button's text with locale.switchNetwork by default", () => {
const { container } = render(
,
);
const element = container.querySelector(
"button.tw-connect-wallet--switch-network",
);
if (!element) {
throw new Error("Failed to render SwitchNetworkButton");
}
expect(element.innerHTML).toBe(en.switchNetwork);
});
it("should render `switchNetworkBtnTitle` properly", () => {
const { container } = render(
,
);
const element = container.querySelector(
"button.tw-connect-wallet--switch-network",
);
if (!element) {
throw new Error("Failed to render SwitchNetworkButton");
}
expect(element.innerHTML).toBe("cat");
});
});
describe("ConnectedToSmartWallet", () => {
it("should render nothing since no active wallet exists in default test env", () => {
const { container } = render(
,
);
// no smart wallet exists in this env so this component should render null
const element = container.querySelector("span");
expect(element).toBe(null);
});
});
describe("InAppWalletUserInfo", () => {
it("should render a Skeleton since no active wallet exists in default test env", () => {
const { container } = render(
,
);
// no smart wallet exists in this env so this component should render null
const element = container.querySelector(
"div.InAppWalletUserInfo__skeleton",
);
expect(element).not.toBe(null);
});
});
describe("Details Modal", () => {
beforeEach(() => {
// Mock the animate method
HTMLDivElement.prototype.animate = vi.fn().mockReturnValue({
onfinish: vi.fn(),
});
});
it("should close the modal when activeAccount is falsy", async () => {
const closeModalMock = vi.fn();
const locale = await getConnectLocale("en_US");
vi.mocked(useActiveAccount).mockReturnValue(undefined);
render(
,
);
await waitFor(() => {
expect(closeModalMock).toHaveBeenCalled();
});
});
it("should render the DetailsModal with default props", async () => {
const closeModalMock = vi.fn();
const locale = await getConnectLocale("en_US");
render(
,
);
// Add assertions to check if the modal is rendered correctly
expect(screen.getByText("Manage Wallet")).toBeInTheDocument();
});
it("should call closeModal when the close button is clicked", async () => {
const closeModalMock = vi.fn();
const locale = await getConnectLocale("en_US");
render(
,
);
// Simulate clicking the close button
fireEvent.click(screen.getByRole("button", { name: /close/i }));
await waitFor(() => {
expect(closeModalMock).toHaveBeenCalled();
});
});
it("NetworkSwitcherButton should not render if no active chain", () => {
const { container } = render(
{}}
/>
,
);
const element = container.querySelector(
".tw-internal-network-switcher-button",
);
expect(element).toBeFalsy();
});
it("NetworkSwitcherButton should render if there is an active chain", async () => {
vi.mock(
"../../../../react/core/hooks/wallets/useActiveWalletChain.js",
() => ({
useActiveWalletChain: vi.fn(),
}),
);
vi.mocked(useActiveWalletChain).mockReturnValue(base);
const { container } = render(
{}}
/>
,
);
await waitFor(
() => {
const element = container.querySelector(
".tw-internal-network-switcher-button",
);
expect(element).toBeTruthy();
},
{ timeout: 2000 },
);
});
it("StyledChevronRightIcon should render a svg element", () => {
const { container } = render();
const svg = container.querySelector("svg");
expect(svg).toBeTruthy();
});
});