diff --git a/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.test.tsx b/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.test.tsx
new file mode 100644
index 0000000000..170ecf69bc
--- /dev/null
+++ b/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.test.tsx
@@ -0,0 +1,100 @@
+import {
+ getAccountGroupLabel,
+ mockLedgerAccount,
+ mockMnemonicAccount,
+ mockSocialAccount,
+} from "@umami/core";
+import { type UmamiStore, accountsActions, addTestAccounts, makeStore } from "@umami/state";
+
+import { AccountSelectorModal } from "./AccountSelectorModal";
+import { DeriveMnemonicAccountModal } from "./DeriveMnemonicAccountModal";
+import {
+ act,
+ dynamicModalContextMock,
+ renderInModal,
+ screen,
+ userEvent,
+ waitFor,
+} from "../../testUtils";
+import { OnboardOptionsModal } from "../Onboarding/OnboardOptions";
+
+let store: UmamiStore;
+
+beforeEach(() => {
+ store = makeStore();
+ addTestAccounts(store, [mockMnemonicAccount(0), mockLedgerAccount(1), mockSocialAccount(2)]);
+});
+
+describe("", () => {
+ it("renders account groups correctly", async () => {
+ await renderInModal(, store);
+
+ await waitFor(() =>
+ expect(screen.getByText(`Seedphrase ${mockMnemonicAccount(0).seedFingerPrint}`)).toBeVisible()
+ );
+ expect(screen.getByText("Social Accounts")).toBeVisible();
+ expect(screen.getByText("Ledger Accounts")).toBeVisible();
+ });
+
+ it.each([
+ [
+ "mnemonic",
+ mockMnemonicAccount(0),
+ () => ,
+ ],
+ ["ledger", mockLedgerAccount(1), () => ],
+ ["social", mockSocialAccount(2), () => ],
+ ])(
+ "open appropriate modal when clicking 'Add %s Account' button",
+ async (_, account, getModalComponent) => {
+ const user = userEvent.setup();
+ const accountLabel = getAccountGroupLabel(account);
+ const { openWith } = dynamicModalContextMock;
+ await renderInModal(, store);
+
+ await act(() => user.click(screen.getByLabelText(`Add ${accountLabel} account`)));
+
+ expect(openWith).toHaveBeenCalledWith(getModalComponent());
+ }
+ );
+
+ it("opens appropriate modal when clicking 'Add Account' button", async () => {
+ const user = userEvent.setup();
+ const { openWith } = dynamicModalContextMock;
+ await renderInModal(, store);
+
+ await act(() => user.click(screen.getByText("Add Account")));
+
+ expect(openWith).toHaveBeenCalledWith();
+ });
+
+ it("correctly handles account selection", async () => {
+ const user = userEvent.setup();
+ const { onClose } = dynamicModalContextMock;
+ await renderInModal(, store);
+
+ const accountTile = screen.getByText(mockMnemonicAccount(0).label);
+ await act(() => user.click(accountTile));
+
+ expect(store.getState().accounts.current).toBe(mockMnemonicAccount(0).address.pkh);
+ expect(onClose).toHaveBeenCalled();
+ });
+
+ describe("when account is not verified", () => {
+ beforeEach(() => {
+ store.dispatch(accountsActions.setCurrent(mockLedgerAccount(0).address.pkh));
+ store.dispatch(
+ accountsActions.setIsVerified({
+ pkh: mockLedgerAccount(0).address.pkh,
+ isVerified: false,
+ })
+ );
+ });
+
+ it("does not render 'Add Account' button", async () => {
+ await renderInModal(, store);
+
+ expect(screen.queryByText("Add Account")).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.tsx b/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.tsx
index 402c5820af..e291ec02f0 100644
--- a/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.tsx
+++ b/apps/web/src/components/AccountSelectorModal/AccountSelectorModal.tsx
@@ -16,7 +16,7 @@ import { useDynamicModalContext } from "@umami/components";
import { type ImplicitAccount, type MnemonicAccount, getAccountGroupLabel } from "@umami/core";
import { accountsActions, useGetAccountBalance, useImplicitAccounts } from "@umami/state";
import { prettyTezAmount } from "@umami/tezos";
-import { capitalize } from "lodash";
+import { groupBy } from "lodash";
import { useDispatch } from "react-redux";
import { AccountSelectorPopover } from "./AccountSelectorPopover";
@@ -24,7 +24,7 @@ import { PlusIcon, TrashIcon } from "../../assets/icons";
import { useColor } from "../../styles/useColor";
import { AccountTile } from "../AccountTile";
import { ModalCloseButton } from "../CloseButton";
-import { DeriveMnemonicAccountModal } from "../DeriveMnemonicAccountModal";
+import { DeriveMnemonicAccountModal } from "./DeriveMnemonicAccountModal";
import { OnboardOptionsModal } from "../Onboarding/OnboardOptions";
import { useIsAccountVerified } from "../Onboarding/VerificationFlow";
@@ -37,7 +37,7 @@ export const AccountSelectorModal = () => {
const dispatch = useDispatch();
- const groupedAccounts = Object.groupBy(accounts, getAccountGroupLabel);
+ const groupedAccounts = groupBy(accounts, getAccountGroupLabel);
const handleDeriveAccount = (account?: ImplicitAccount) => {
if (!account) {
@@ -48,7 +48,7 @@ export const AccountSelectorModal = () => {
case "mnemonic":
return openWith();
default:
- break;
+ return openWith();
}
};
@@ -74,7 +74,7 @@ export const AccountSelectorModal = () => {
paddingLeft="12px"
>
- {type.split("_").map(capitalize).join(" ")}
+ {type}
{
/>
}
- onClick={() => handleDeriveAccount(accounts?.[0])}
+ onClick={() => handleDeriveAccount(accounts[0])}
size="sm"
variant="ghost"
/>
- {accounts?.map(account => {
+ {accounts.map(account => {
const address = account.address.pkh;
const balance = getBalance(address);
const onClick = () => {
diff --git a/apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.test.tsx b/apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.test.tsx
new file mode 100644
index 0000000000..fe9613bd9c
--- /dev/null
+++ b/apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.test.tsx
@@ -0,0 +1,67 @@
+import { mockMnemonicAccount } from "@umami/core";
+import { type UmamiStore, addTestAccount, makeStore, useDeriveMnemonicAccount } from "@umami/state";
+
+import { DeriveMnemonicAccountModal } from "./DeriveMnemonicAccountModal";
+import { act, renderInModal, screen, userEvent, waitFor } from "../../../testUtils";
+
+let store: UmamiStore;
+
+jest.mock("@umami/state", () => ({
+ ...jest.requireActual("@umami/state"),
+ useDeriveMnemonicAccount: jest.fn(),
+}));
+
+const mockDeriveMnemonicAccount = jest.fn();
+
+beforeEach(() => {
+ store = makeStore();
+ addTestAccount(store, mockMnemonicAccount(0));
+
+ jest.mocked(useDeriveMnemonicAccount).mockImplementation(() => mockDeriveMnemonicAccount);
+});
+
+describe("", () => {
+ it("renders the NameAccountModal with correct subtitle", async () => {
+ const account = mockMnemonicAccount(0);
+ await renderInModal(, store);
+
+ await waitFor(() => {
+ expect(screen.getByText("Name Your Account")).toBeVisible();
+ });
+
+ expect(
+ screen.getByText(`Name the new account derived from seedphrase ${account.seedFingerPrint}`)
+ ).toBeVisible();
+ });
+
+ it("handles name submission and opens confirm password modal", async () => {
+ const user = userEvent.setup();
+
+ const account = mockMnemonicAccount(0);
+ await renderInModal(, store);
+ await act(() => user.type(screen.getByLabelText("Account name"), "Test Account"));
+ await act(() => user.click(screen.getByRole("button", { name: "Continue" })));
+
+ await waitFor(() => {
+ expect(screen.getByTestId("master-password-modal")).toBeVisible();
+ });
+ });
+
+ it("derives mnemonic account on password submission", async () => {
+ const user = userEvent.setup();
+
+ const account = mockMnemonicAccount(0);
+ await renderInModal(, store);
+
+ await act(() => user.type(screen.getByLabelText("Account name"), "Test Account"));
+ await act(() => user.click(screen.getByRole("button", { name: "Continue" })));
+ await act(() => user.type(screen.getByLabelText("Password"), "test-password"));
+ await act(() => user.click(screen.getByRole("button", { name: "Submit" })));
+
+ expect(mockDeriveMnemonicAccount).toHaveBeenCalledWith({
+ fingerPrint: account.seedFingerPrint,
+ password: "test-password",
+ label: "Test Account",
+ });
+ });
+});
diff --git a/apps/web/src/components/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.tsx b/apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.tsx
similarity index 92%
rename from apps/web/src/components/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.tsx
rename to apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.tsx
index 1352555b0d..1355b0d855 100644
--- a/apps/web/src/components/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.tsx
+++ b/apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/DeriveMnemonicAccountModal.tsx
@@ -3,8 +3,8 @@ import { useDynamicModalContext } from "@umami/components";
import { DEFAULT_ACCOUNT_LABEL, type MnemonicAccount } from "@umami/core";
import { useAsyncActionHandler, useDeriveMnemonicAccount } from "@umami/state";
-import { MasterPasswordModal } from "../MasterPasswordModal";
-import { NameAccountModal } from "../NameAccountModal";
+import { MasterPasswordModal } from "../../MasterPasswordModal";
+import { NameAccountModal } from "../../NameAccountModal";
type DeriveMnemonicAccountModalProps = {
account: MnemonicAccount;
@@ -14,8 +14,8 @@ export const DeriveMnemonicAccountModal = ({ account }: DeriveMnemonicAccountMod
const { goToIndex, openWith } = useDynamicModalContext();
const { handleAsyncAction } = useAsyncActionHandler();
- const toast = useToast();
const deriveMnemonicAccount = useDeriveMnemonicAccount();
+ const toast = useToast();
const handleNameSubmit = ({ accountName }: { accountName: string }) => {
const handlePasswordSubmit = ({ password }: { password: string }) =>
diff --git a/apps/web/src/components/DeriveMnemonicAccountModal/index.ts b/apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/index.ts
similarity index 100%
rename from apps/web/src/components/DeriveMnemonicAccountModal/index.ts
rename to apps/web/src/components/AccountSelectorModal/DeriveMnemonicAccountModal/index.ts
diff --git a/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.test.tsx b/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.test.tsx
new file mode 100644
index 0000000000..365114648d
--- /dev/null
+++ b/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.test.tsx
@@ -0,0 +1,26 @@
+import { MasterPasswordModal } from "./MasterPasswordModal";
+import { renderInModal, screen, userEvent } from "../../testUtils";
+
+const mockOnSubmit = jest.fn();
+
+describe("", () => {
+ it("calls onSubmit with entered password when form is submitted", async () => {
+ const user = userEvent.setup();
+ await renderInModal();
+
+ await user.type(screen.getByLabelText("Password"), "testpassword");
+ await user.click(screen.getByRole("button", { name: "Submit" }));
+
+ expect(mockOnSubmit).toHaveBeenCalledWith({ password: "testpassword" });
+ });
+
+ it("shows validation error when submitting without a password", async () => {
+ const user = userEvent.setup();
+ await renderInModal();
+
+ await user.click(screen.getByRole("button", { name: "Submit" }));
+
+ expect(screen.getByText("Password is required")).toBeInTheDocument();
+ expect(mockOnSubmit).not.toHaveBeenCalled();
+ });
+});
diff --git a/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.tsx b/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.tsx
index 7f7b94c17d..adb9ed3480 100644
--- a/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.tsx
+++ b/apps/web/src/components/MasterPasswordModal/MasterPasswordModal.tsx
@@ -32,7 +32,7 @@ export const MasterPasswordModal = ({ onSubmit }: MasterPasswordModalProps) => {
});
return (
-
+
diff --git a/apps/web/src/components/NameAccountModal/NameAccountModal.test.tsx b/apps/web/src/components/NameAccountModal/NameAccountModal.test.tsx
new file mode 100644
index 0000000000..8c4d88ce9c
--- /dev/null
+++ b/apps/web/src/components/NameAccountModal/NameAccountModal.test.tsx
@@ -0,0 +1,28 @@
+import { NameAccountModal } from "./NameAccountModal";
+import { act, renderInModal, screen, userEvent, waitFor } from "../../testUtils";
+
+const mockOnSubmit = jest.fn();
+
+describe("NameAccountModal", () => {
+ it("renders with custom title and subtitle", async () => {
+ await renderInModal(
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText("Custom Title")).toBeVisible();
+ });
+ expect(screen.getByText("Custom Subtitle")).toBeVisible();
+ });
+
+ it("calls onSubmit with form data when submitted", async () => {
+ const user = userEvent.setup();
+
+ await renderInModal();
+
+ await act(() => user.type(screen.getByLabelText("Account name"), "Test Account"));
+ await act(() => user.click(screen.getByRole("button", { name: "Continue" })));
+
+ expect(mockOnSubmit).toHaveBeenCalled();
+ });
+});
diff --git a/apps/web/src/components/NameAccountModal/NameAccountModal.tsx b/apps/web/src/components/NameAccountModal/NameAccountModal.tsx
index f9872680b6..c4e907803b 100644
--- a/apps/web/src/components/NameAccountModal/NameAccountModal.tsx
+++ b/apps/web/src/components/NameAccountModal/NameAccountModal.tsx
@@ -42,7 +42,7 @@ export const NameAccountModal = ({
const { register, handleSubmit } = form;
return (
-
+
diff --git a/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.test.tsx b/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.test.tsx
index fec3e538cb..c3dde1e3dc 100644
--- a/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.test.tsx
+++ b/apps/web/src/components/Onboarding/SetupPassword/SetupPassword.test.tsx
@@ -130,7 +130,9 @@ describe("", () => {
describe("mnemonic mode", () => {
let store: UmamiStore;
- const allFormValues = { mnemonic: mnemonic1.split(" ").map(word => ({ val: word })) };
+ const allFormValues = {
+ current: { mnemonic: mnemonic1.split(" ").map(word => ({ val: word })) },
+ };
const mockRestoreFromMnemonic = jest.fn();
beforeEach(() => {
diff --git a/apps/web/src/testUtils.tsx b/apps/web/src/testUtils.tsx
index d8303bbbde..fcf2467884 100644
--- a/apps/web/src/testUtils.tsx
+++ b/apps/web/src/testUtils.tsx
@@ -158,7 +158,9 @@ export const renderInModal = async (
if (allFormValues) {
await act(() =>
- result.current.openWith()
+ result.current.openWith(
+
+ )
);
fireEvent.click(screen.getByRole("button"));
}