Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions test/e2e/flask/multi-srp/import-srp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ const TEST_SRP_WORDS_FOR_UI_TEST = [
];

describe('Multi SRP - Import SRP', function (this: Suite) {
it('successfully imports a new srp', async function () {
it.only('successfully imports a new srp', async function () {
await withMultiSrp(
{
title: this.test?.fullTitle(),
testSpecificMock: mockActiveNetworks,
},
async (driver: Driver) => {
const accountListPage = new AccountListPage(driver);
await accountListPage.checkAccountBelongsToSrp('Account 2', 2);
await accountListPage.checkAccountBelongsToSrp('Account 1', 2);
},
);
});
Expand Down
8 changes: 8 additions & 0 deletions test/e2e/page-objects/pages/account-list-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,14 @@ class AccountListPage {
if (srpIndex === 0) {
throw new Error('SRP index must be > 0');
}

// Wait for the expected number of SRP cards to be rendered
// This is needed because the accountTree is updated asynchronously after importing a new SRP
await this.driver.wait(async () => {
const srps = await this.driver.findElements('.select-srp__container');
return srps.length >= srpIndex;
}, 10000);

const srps = await this.driver.findElements('.select-srp__container');
const selectedSrp = srps[srpIndex - 1];
const showAccountsButton = await this.driver.waitForSelector(
Expand Down
2 changes: 1 addition & 1 deletion ui/components/multichain/multi-srp/srp-list/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
&__account-name {
overflow: hidden;
white-space: nowrap;
max-width: 80px;
max-width: 120px;
}
}
1 change: 1 addition & 0 deletions ui/components/multichain/multi-srp/srp-list/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { SrpList } from './srp-list';
export { SrpListItem } from './srp-list-item';
99 changes: 99 additions & 0 deletions ui/components/multichain/multi-srp/srp-list/srp-card.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fireEvent } from '@testing-library/react';

import { KeyringTypes } from '@metamask/keyring-controller';
import type { AccountGroupId, AccountWalletId } from '@metamask/account-api';

import { renderWithProvider } from '../../../../../test/lib/render-helpers-navigate';
import mockState from '../../../../../test/data/mock-state.json';
import { FirstTimeFlowType } from '../../../../../shared/constants/onboarding';
import { SrpCard } from './srp-card';

const mockWalletId = 'mock-wallet-id' as AccountWalletId;
const mockTotalFiatBalance = '$100.00';

const mocks = {
useSingleWalletAccountsBalanceCallback: jest
.fn()
.mockReturnValue((_: AccountGroupId) => mockTotalFiatBalance),
onActionComplete: jest.fn(),
useWalletInfoCallback: jest.fn().mockReturnValue({
multichainAccounts: [
{
id: 'mock-account-id-1' as AccountGroupId,
metadata: {
name: 'Mock Account 1',
},
},
],
keyringId: '01JKAF3DSGM3AB87EM9N0K41AJ',
isSRPBackedUp: true,
}),
};

jest.mock('../../../../hooks/multichain-accounts/useWalletBalance', () => ({
useSingleWalletAccountsBalanceCallback: (walletId: AccountWalletId) =>
mocks.useSingleWalletAccountsBalanceCallback(walletId),
}));

jest.mock('../../../../hooks/multichain-accounts/useWalletInfo', () => ({
useWalletInfo: (walletId: AccountWalletId) =>
mocks.useWalletInfoCallback(walletId),
}));

const mockSecondHdKeyring = {
accounts: [],
type: KeyringTypes.hd,
metadata: {
id: '01JN31PKMJ3ANWYFJZM3Z8MYT4',
name: '',
},
};

const render = (shouldTriggerBackup: boolean) => {
const store = configureMockStore([thunk])({
...mockState,
metamask: {
...mockState.metamask,
keyrings: [...mockState.metamask.keyrings, mockSecondHdKeyring],
firstTimeFlowType: FirstTimeFlowType.create,
seedPhraseBackedUp: false,
},
});

return renderWithProvider(
<SrpCard
index={0}
walletId={mockWalletId}
shouldTriggerBackup={shouldTriggerBackup}
onActionComplete={mocks.onActionComplete}
/>,
store,
);
};

describe('SrpCard', () => {
it('renders the secret recovery phrases card', () => {
const { getByText } = render(false);
expect(getByText('Secret Recovery Phrase 1')).toBeInTheDocument();
});

it('shows/hides accounts when clicking show/hide text', () => {
const { getByText } = render(false);
const showAccountsButton = getByText('Show 1 account');
fireEvent.click(showAccountsButton);
expect(getByText('Hide 1 account')).toBeInTheDocument();
});

it('calls onActionComplete when clicking a keyring', () => {
const { getByTestId } = render(true);
const firstKeyringId = mockState.metamask.keyrings[0].metadata.id;

const keyring = getByTestId(`hd-keyring-${firstKeyringId}`);
fireEvent.click(keyring);

expect(mocks.onActionComplete).toHaveBeenCalledWith(firstKeyringId, true);
});
});
174 changes: 174 additions & 0 deletions ui/components/multichain/multi-srp/srp-list/srp-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, { useState, useContext, useCallback } from 'react';
import type { AccountWalletId } from '@metamask/account-api';

import { MetaMetricsContext } from '../../../../contexts/metametrics';
import { useWalletInfo } from '../../../../hooks/multichain-accounts/useWalletInfo';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { useSingleWalletAccountsBalanceCallback } from '../../../../hooks/multichain-accounts/useWalletBalance';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../../shared/constants/metametrics';

import Card from '../../../ui/card';
import {
Box,
IconName,
Icon,
Text,
IconSize,
} from '../../../component-library';
import {
JustifyContent,
Display,
TextColor,
FlexDirection,
AlignItems,
BlockSize,
TextVariant,
IconColor,
} from '../../../../helpers/constants/design-system';
import { SrpListItem } from './srp-list-item';

/**
* Props for the SrpCard component.
*/
type SrpCardProps = {
index: number;
walletId: AccountWalletId;
shouldTriggerBackup: boolean;
onActionComplete: (id: string, triggerBackup?: boolean) => void;
isSettingsPage?: boolean;
hideShowAccounts?: boolean;
};

export const SrpCard = ({
index,
walletId,
shouldTriggerBackup,
onActionComplete,
isSettingsPage = false,
hideShowAccounts = false,
}: SrpCardProps) => {
const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext);
const { multichainAccounts, keyringId } = useWalletInfo(walletId);
const [showAccounts, setShowAccounts] = useState<boolean>(false);
const walletAccountBalance = useSingleWalletAccountsBalanceCallback(walletId);

const showHideText = useCallback(
(numberOfAccounts: number): string => {
if (numberOfAccounts > 1) {
return showAccounts
? t('SrpListHideAccounts', [numberOfAccounts])
: t('SrpListShowAccounts', [numberOfAccounts]);
}
return showAccounts
? t('SrpListHideSingleAccount', [numberOfAccounts])
: t('SrpListShowSingleAccount', [numberOfAccounts]);
},
[showAccounts, t],
);

return (
<Card
key={`srp-${index}-${keyringId}`}
data-testid={`hd-keyring-${keyringId}`}
onClick={() => {
trackEvent({
category: MetaMetricsEventCategory.Accounts,
event: MetaMetricsEventName.SecretRecoveryPhrasePickerClicked,
properties: {
// TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860
// eslint-disable-next-line @typescript-eslint/naming-convention
button_type: 'srp_select',
},
});
keyringId && onActionComplete(keyringId, shouldTriggerBackup);
}}
className="select-srp__container"
marginBottom={3}
>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
alignItems={AlignItems.center}
justifyContent={JustifyContent.spaceBetween}
>
<Box>
<Text variant={TextVariant.bodyMdMedium}>
{t('srpListName', [index + 1])}
</Text>
{!hideShowAccounts && (
<Text
variant={TextVariant.bodySm}
color={TextColor.primaryDefault}
className="srp-list__show-accounts"
data-testid={`srp-list-show-accounts-${index}`}
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
trackEvent({
category: MetaMetricsEventCategory.Accounts,
event: MetaMetricsEventName.SecretRecoveryPhrasePickerClicked,
properties: {
// TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860
// eslint-disable-next-line @typescript-eslint/naming-convention
button_type: 'details',
},
});
setShowAccounts((prevState) => !prevState);
}}
>
{showHideText(multichainAccounts.length)}
</Text>
)}
</Box>
<Box display={Display.Flex} alignItems={AlignItems.center} gap={1}>
{isSettingsPage && (
<Text
variant={TextVariant.bodyMdMedium}
color={
shouldTriggerBackup
? TextColor.errorDefault
: TextColor.textAlternative
}
>
{shouldTriggerBackup
? t('srpListStateNotBackedUp')
: t('srpListStateBackedUp')}
</Text>
)}
<Icon
name={IconName.ArrowRight}
size={IconSize.Sm}
color={
shouldTriggerBackup && isSettingsPage
? IconColor.errorDefault
: IconColor.iconAlternative
}
/>
</Box>
</Box>
{showAccounts && (
<Box>
<Box
width={BlockSize.Full}
className="srp-list__divider"
marginTop={2}
marginBottom={2}
/>
{multichainAccounts.map((group) => {
return (
<SrpListItem
key={`account-${group.id}`}
accountId={group.id}
accountName={group.metadata.name}
balance={walletAccountBalance(group.id) ?? ''}
/>
);
})}
</Box>
)}
</Card>
);
};
Loading
Loading