From 28137798b6fb6e37d389938d2f260a615cda3288 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Tue, 13 Jun 2023 10:07:01 -0500 Subject: [PATCH] UX: Multichain: Move Add Account and Import Account into Account Menu Popover (#19346) * UX: Multichain: Move Add Account and Import Account into Account Menu Popover * Create a new CreateAccount component for the Account Menu * Add actions for import form * Use separate actions for cancel vs. submit * Fix jest tests * Remove commented route navigation * Accommodate for failing import * Fix tests * Remove routes for new account and import * Remove old create account page * Move import-account files to multichain directory * Fix paths on the import files * Remove deprecated component library variables * Fix error property of add form * Fix user-actions-benchmark --- test/e2e/tests/add-account.spec.js | 12 +- test/e2e/tests/from-import-ui.spec.js | 21 +- test/e2e/user-actions-benchmark.js | 2 +- .../account-list-menu/account-list-menu.js | 382 ++++++++++-------- .../account-list-menu.test.js | 26 +- .../create-account/create-account.js | 109 +++++ .../create-account/create-account.stories.js | 10 + .../create-account/create-account.test.js | 67 +++ .../multichain/create-account/index.js | 1 + .../__snapshots__/json.test.tsx.snap | 1 + .../import-account/bottom-buttons.js | 24 +- .../import-account/bottom-buttons.stories.js | 2 +- .../import-account/import-account.js | 128 +++--- .../import-account/import-account.stories.js | 11 + .../multichain/import-account/index.js | 1 + .../multichain}/import-account/json.js | 9 +- .../import-account/json.stories.js | 2 +- .../multichain}/import-account/json.test.tsx | 22 +- .../multichain}/import-account/private-key.js | 9 +- .../import-account/private-key.stories.js | 0 ui/components/multichain/index.js | 2 + ui/helpers/constants/routes.ts | 6 - .../create-account.component.js | 14 - .../import-account/import-account.stories.js | 11 - .../create-account/import-account/index.js | 1 - ui/pages/create-account/index.scss | 83 ---- .../create-account/new-account.component.js | 119 ------ .../create-account/new-account.container.js | 36 -- .../create-account/new-account.stories.js | 22 - ui/pages/pages.scss | 1 - ui/pages/routes/routes.component.js | 3 - 31 files changed, 562 insertions(+), 575 deletions(-) create mode 100644 ui/components/multichain/create-account/create-account.js create mode 100644 ui/components/multichain/create-account/create-account.stories.js create mode 100644 ui/components/multichain/create-account/create-account.test.js create mode 100644 ui/components/multichain/create-account/index.js rename ui/{pages/create-account => components/multichain}/import-account/__snapshots__/json.test.tsx.snap (98%) rename ui/{pages/create-account => components/multichain}/import-account/bottom-buttons.js (62%) rename ui/{pages/create-account => components/multichain}/import-account/bottom-buttons.stories.js (84%) rename ui/{pages/create-account => components/multichain}/import-account/import-account.js (57%) create mode 100644 ui/components/multichain/import-account/import-account.stories.js create mode 100644 ui/components/multichain/import-account/index.js rename ui/{pages/create-account => components/multichain}/import-account/json.js (92%) rename ui/{pages/create-account => components/multichain}/import-account/json.stories.js (77%) rename ui/{pages/create-account => components/multichain}/import-account/json.test.tsx (83%) rename ui/{pages/create-account => components/multichain}/import-account/private-key.js (86%) rename ui/{pages/create-account => components/multichain}/import-account/private-key.stories.js (100%) delete mode 100644 ui/pages/create-account/import-account/import-account.stories.js delete mode 100644 ui/pages/create-account/import-account/index.js delete mode 100644 ui/pages/create-account/index.scss delete mode 100644 ui/pages/create-account/new-account.component.js delete mode 100644 ui/pages/create-account/new-account.container.js delete mode 100644 ui/pages/create-account/new-account.stories.js diff --git a/test/e2e/tests/add-account.spec.js b/test/e2e/tests/add-account.spec.js index 38535b0f1..61a903653 100644 --- a/test/e2e/tests/add-account.spec.js +++ b/test/e2e/tests/add-account.spec.js @@ -45,7 +45,7 @@ describe('Add account', function () { '[data-testid="multichain-account-menu-add-account"]', ); - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); const accountName = await driver.waitForSelector({ css: '[data-testid="account-menu-icon"]', @@ -86,7 +86,7 @@ describe('Add account', function () { await driver.clickElement( '[data-testid="multichain-account-menu-add-account"]', ); - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); await waitForAccountRendered(driver); @@ -190,8 +190,7 @@ describe('Add account', function () { await driver.clickElement( '[data-testid="multichain-account-menu-add-account"]', ); - - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); // Wait for 2nd account to be created @@ -220,7 +219,10 @@ describe('Add account', function () { await driver.clickElement('.menu__background'); await driver.clickElement({ text: 'Import account', tag: 'button' }); await driver.fill('#private-key-box', testPrivateKey); - await driver.clickElement({ text: 'Import', tag: 'button' }); + + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', + ); // Wait for 3rd account to be created await waitForAccountRendered(driver); diff --git a/test/e2e/tests/from-import-ui.spec.js b/test/e2e/tests/from-import-ui.spec.js index ed6066a0d..02d67b8af 100644 --- a/test/e2e/tests/from-import-ui.spec.js +++ b/test/e2e/tests/from-import-ui.spec.js @@ -86,7 +86,7 @@ describe('MetaMask Import UI', function () { await driver.clickElement({ text: 'Add account', tag: 'button' }); // set account name - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.delay(regularDelayMs); await driver.clickElement({ text: 'Create', tag: 'button' }); @@ -203,7 +203,9 @@ describe('MetaMask Import UI', function () { // enter private key await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey1); - await driver.clickElement({ text: 'Import', tag: 'button' }); + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', + ); // should show the correct account name const importedAccountName = await driver.findElement( @@ -230,8 +232,9 @@ describe('MetaMask Import UI', function () { // enter private key await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey2); - await driver.findClickableElement({ text: 'Import', tag: 'button' }); - await driver.clickElement({ text: 'Import', tag: 'button' }); + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', + ); // should see new account in account menu const importedAccount2Name = await driver.findElement( @@ -303,8 +306,9 @@ describe('MetaMask Import UI', function () { fileInput.sendKeys(importJsonFile); await driver.fill('#json-password-box', 'foobarbazqux'); - - await driver.clickElement({ text: 'Import', tag: 'button' }); + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', + ); // should show the correct account name const importedAccountName = await driver.findElement( @@ -358,8 +362,9 @@ describe('MetaMask Import UI', function () { // enter private key await driver.findClickableElement('#private-key-box'); await driver.fill('#private-key-box', testPrivateKey); - await driver.findClickableElement({ text: 'Import', tag: 'button' }); - await driver.clickElement({ text: 'Import', tag: 'button' }); + await driver.clickElement( + '[data-testid="import-account-confirm-button"]', + ); // error should occur await driver.waitForSelector({ diff --git a/test/e2e/user-actions-benchmark.js b/test/e2e/user-actions-benchmark.js index 32658f3d6..00a53c8d0 100644 --- a/test/e2e/user-actions-benchmark.js +++ b/test/e2e/user-actions-benchmark.js @@ -38,7 +38,7 @@ async function loadNewAccount() { await driver.clickElement( '[data-testid="multichain-account-menu-add-account"]', ); - await driver.fill('.new-account-create-form input', '2nd account'); + await driver.fill('[placeholder="Account 2"]', '2nd account'); await driver.clickElement({ text: 'Create', tag: 'button' }); await driver.waitForSelector({ css: '.currency-display-component__text', diff --git a/ui/components/multichain/account-list-menu/account-list-menu.js b/ui/components/multichain/account-list-menu/account-list-menu.js index 1c7e6c45e..823e1d679 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.js +++ b/ui/components/multichain/account-list-menu/account-list-menu.js @@ -10,7 +10,7 @@ import { TextFieldSearch, Text, } from '../../component-library'; -import { AccountListItem } from '..'; +import { AccountListItem, CreateAccount, ImportAccount } from '..'; import { BLOCK_SIZES, Size, @@ -38,8 +38,6 @@ import { MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { - IMPORT_ACCOUNT_ROUTE, - NEW_ACCOUNT_ROUTE, CONNECT_HARDWARE_ROUTE, ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) CUSTODY_ACCOUNT_ROUTE, @@ -66,6 +64,7 @@ export const AccountListMenu = ({ onClose }) => { ///: END:ONLY_INCLUDE_IN const [searchQuery, setSearchQuery] = useState(''); + const [actionMode, setActionMode] = useState(''); let searchResults = accounts; if (searchQuery) { @@ -88,209 +87,244 @@ export const AccountListMenu = ({ onClose }) => { } }, [inputRef]); + let title = t('selectAnAccount'); + if (actionMode === 'add') { + title = t('addAccount'); + } else if (actionMode === 'import') { + title = t('importAccount'); + } + return ( setActionMode('')} > - - {/* Search box */} - {accounts.length > 1 ? ( - - setSearchQuery(e.target.value)} - clearButtonOnClick={() => setSearchQuery('')} - clearButtonProps={{ - size: Size.SM, - }} - /> - - ) : null} - {/* Account list block */} - - {searchResults.length === 0 && searchQuery !== '' ? ( - + { + if (confirmed) { + dispatch(toggleAccountMenu()); + } else { + setActionMode(''); + } + }} + /> + + ) : null} + {actionMode === 'import' ? ( + + { + if (confirmed) { + dispatch(toggleAccountMenu()); + } else { + setActionMode(''); + } + }} + /> + + ) : null} + {actionMode === '' ? ( + + {/* Search box */} + {accounts.length > 1 ? ( + - {t('noAccountsFound')} - + setSearchQuery(e.target.value)} + clearButtonOnClick={() => setSearchQuery('')} + clearButtonProps={{ + size: Size.SM, + }} + /> + ) : null} - {searchResults.map((account) => { - const connectedSite = connectedSites[account.address]?.find( - ({ origin }) => origin === currentTabOrigin, - ); + {/* Account list block */} + + {searchResults.length === 0 && searchQuery !== '' ? ( + + {t('noAccountsFound')} + + ) : null} + {searchResults.map((account) => { + const connectedSite = connectedSites[account.address]?.find( + ({ origin }) => origin === currentTabOrigin, + ); - return ( - { + dispatch(toggleAccountMenu()); + trackEvent({ + category: MetaMetricsEventCategory.Navigation, + event: MetaMetricsEventName.NavAccountSwitched, + properties: { + location: 'Main Menu', + }, + }); + dispatch(setSelectedAccount(account.address)); + }} + identity={account} + key={account.address} + selected={selectedAccount.address === account.address} + closeMenu={onClose} + connectedAvatar={connectedSite?.iconUrl} + connectedAvatarName={connectedSite?.name} + /> + ); + })} + + {/* Add / Import / Hardware */} + + + { + trackEvent({ + category: MetaMetricsEventCategory.Navigation, + event: MetaMetricsEventName.AccountAddSelected, + properties: { + account_type: MetaMetricsEventAccountType.Default, + location: 'Main Menu', + }, + }); + setActionMode('add'); + }} + data-testid="multichain-account-menu-add-account" + > + {t('addAccount')} + + + + { + trackEvent({ + category: MetaMetricsEventCategory.Navigation, + event: MetaMetricsEventName.AccountAddSelected, + properties: { + account_type: MetaMetricsEventAccountType.Imported, + location: 'Main Menu', + }, + }); + setActionMode('import'); + }} + > + {t('importAccount')} + + + + { dispatch(toggleAccountMenu()); trackEvent({ category: MetaMetricsEventCategory.Navigation, - event: MetaMetricsEventName.NavAccountSwitched, + event: MetaMetricsEventName.AccountAddSelected, properties: { + account_type: MetaMetricsEventAccountType.Hardware, location: 'Main Menu', }, }); - dispatch(setSelectedAccount(account.address)); + if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser( + CONNECT_HARDWARE_ROUTE, + ); + } else { + history.push(CONNECT_HARDWARE_ROUTE); + } }} - identity={account} - key={account.address} - selected={selectedAccount.address === account.address} - closeMenu={onClose} - connectedAvatar={connectedSite?.iconUrl} - connectedAvatarName={connectedSite?.name} - /> - ); - })} - - {/* Add / Import / Hardware */} - - - { - dispatch(toggleAccountMenu()); - trackEvent({ - category: MetaMetricsEventCategory.Navigation, - event: MetaMetricsEventName.AccountAddSelected, - properties: { - account_type: MetaMetricsEventAccountType.Default, - location: 'Main Menu', - }, - }); - history.push(NEW_ACCOUNT_ROUTE); - }} - data-testid="multichain-account-menu-add-account" - > - {t('addAccount')} - - - - { - dispatch(toggleAccountMenu()); - trackEvent({ - category: MetaMetricsEventCategory.Navigation, - event: MetaMetricsEventName.AccountAddSelected, - properties: { - account_type: MetaMetricsEventAccountType.Imported, - location: 'Main Menu', - }, - }); - history.push(IMPORT_ACCOUNT_ROUTE); - }} - > - {t('importAccount')} - - - - { - dispatch(toggleAccountMenu()); - trackEvent({ - category: MetaMetricsEventCategory.Navigation, - event: MetaMetricsEventName.AccountAddSelected, - properties: { - account_type: MetaMetricsEventAccountType.Hardware, - location: 'Main Menu', - }, - }); - if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser( - CONNECT_HARDWARE_ROUTE, - ); - } else { - history.push(CONNECT_HARDWARE_ROUTE); - } - }} - > - {t('hardwareWallet')} - - { - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - <> - { - dispatch(toggleAccountMenu()); - trackEvent({ - category: MetaMetricsEventCategory.Navigation, - event: - MetaMetricsEventName.UserClickedConnectCustodialAccount, - }); - if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser( - CUSTODY_ACCOUNT_ROUTE, - ); - } else { - history.push(CUSTODY_ACCOUNT_ROUTE); - } - }} - > - {t('connectCustodialAccountMenu')} - - {mmiPortfolioEnabled && ( + > + {t('hardwareWallet')} + + { + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + <> { dispatch(toggleAccountMenu()); trackEvent({ category: MetaMetricsEventCategory.Navigation, - event: MetaMetricsEventName.UserClickedPortfolioButton, + event: + MetaMetricsEventName.UserClickedConnectCustodialAccount, }); - window.open(mmiPortfolioUrl, '_blank'); + if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser( + CUSTODY_ACCOUNT_ROUTE, + ); + } else { + history.push(CUSTODY_ACCOUNT_ROUTE); + } }} > - {t('portfolioDashboard')} + {t('connectCustodialAccountMenu')} - )} - { - dispatch(toggleAccountMenu()); - trackEvent({ - category: MetaMetricsEventCategory.Navigation, - event: MetaMetricsEventName.UserClickedCompliance, - }); - if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { - global.platform.openExtensionInBrowser( - COMPLIANCE_FEATURE_ROUTE, - ); - } else { - history.push(COMPLIANCE_FEATURE_ROUTE); - } - }} - > - {t('compliance')} - - - ///: END:ONLY_INCLUDE_IN - } + {mmiPortfolioEnabled && ( + { + dispatch(toggleAccountMenu()); + trackEvent({ + category: MetaMetricsEventCategory.Navigation, + event: + MetaMetricsEventName.UserClickedPortfolioButton, + }); + window.open(mmiPortfolioUrl, '_blank'); + }} + > + {t('portfolioDashboard')} + + )} + { + dispatch(toggleAccountMenu()); + trackEvent({ + category: MetaMetricsEventCategory.Navigation, + event: MetaMetricsEventName.UserClickedCompliance, + }); + if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) { + global.platform.openExtensionInBrowser( + COMPLIANCE_FEATURE_ROUTE, + ); + } else { + history.push(COMPLIANCE_FEATURE_ROUTE); + } + }} + > + {t('compliance')} + + + ///: END:ONLY_INCLUDE_IN + } + - + ) : null} ); }; diff --git a/ui/components/multichain/account-list-menu/account-list-menu.test.js b/ui/components/multichain/account-list-menu/account-list-menu.test.js index 297ada6cb..9c5ed96cc 100644 --- a/ui/components/multichain/account-list-menu/account-list-menu.test.js +++ b/ui/components/multichain/account-list-menu/account-list-menu.test.js @@ -4,11 +4,7 @@ import reactRouterDom from 'react-router-dom'; import { fireEvent, renderWithProvider } from '../../../../test/jest'; import configureStore from '../../../store/store'; import mockState from '../../../../test/data/mock-state.json'; -import { - NEW_ACCOUNT_ROUTE, - IMPORT_ACCOUNT_ROUTE, - CONNECT_HARDWARE_ROUTE, -} from '../../../helpers/constants/routes'; +import { CONNECT_HARDWARE_ROUTE } from '../../../helpers/constants/routes'; import { AccountListMenu } from '.'; const render = (props = { onClose: () => jest.fn() }) => { @@ -48,16 +44,24 @@ describe('AccountListMenu', () => { expect(getByText('Hardware wallet')).toBeInTheDocument(); }); - it('navigates to new account screen when clicked', () => { - const { getByText } = render(); + it('shows the account creation UI when Add Account is clicked', () => { + const { getByText, getByPlaceholderText } = render(); fireEvent.click(getByText('Add account')); - expect(historyPushMock).toHaveBeenCalledWith(NEW_ACCOUNT_ROUTE); + expect(getByText('Create')).toBeInTheDocument(); + expect(getByText('Cancel')).toBeInTheDocument(); + + fireEvent.click(getByText('Cancel')); + expect(getByPlaceholderText('Search accounts')).toBeInTheDocument(); }); - it('navigates to import account screen when clicked', () => { - const { getByText } = render(); + it('shows the account import UI when Import Account is clicked', () => { + const { getByText, getByPlaceholderText } = render(); fireEvent.click(getByText('Import account')); - expect(historyPushMock).toHaveBeenCalledWith(IMPORT_ACCOUNT_ROUTE); + expect(getByText('Import')).toBeInTheDocument(); + expect(getByText('Cancel')).toBeInTheDocument(); + + fireEvent.click(getByText('Cancel')); + expect(getByPlaceholderText('Search accounts')).toBeInTheDocument(); }); it('navigates to hardware wallet connection screen when clicked', () => { diff --git a/ui/components/multichain/create-account/create-account.js b/ui/components/multichain/create-account/create-account.js new file mode 100644 index 000000000..71d951176 --- /dev/null +++ b/ui/components/multichain/create-account/create-account.js @@ -0,0 +1,109 @@ +import React, { useContext, useState } from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import { + ButtonPrimary, + ButtonSecondary, + FormTextField, +} from '../../component-library'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { getAccountNameErrorMessage } from '../../../helpers/utils/accounts'; +import { + getMetaMaskAccountsOrdered, + getMetaMaskIdentities, +} from '../../../selectors'; +import { addNewAccount, setAccountLabel } from '../../../store/actions'; +import { getMostRecentOverviewPage } from '../../../ducks/history/history'; +import Box from '../../ui/box/box'; +import { + MetaMetricsEventAccountType, + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { Display } from '../../../helpers/constants/design-system'; + +export const CreateAccount = ({ onActionComplete }) => { + const t = useI18nContext(); + const dispatch = useDispatch(); + + const history = useHistory(); + const trackEvent = useContext(MetaMetricsContext); + + const accounts = useSelector(getMetaMaskAccountsOrdered); + const identities = useSelector(getMetaMaskIdentities); + const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); + + const newAccountNumber = Object.keys(identities).length + 1; + const defaultAccountName = t('newAccountNumberName', [newAccountNumber]); + + const [newAccountName, setNewAccountName] = useState(''); + + const { isValidAccountName, errorMessage } = getAccountNameErrorMessage( + accounts, + { t }, + newAccountName, + defaultAccountName, + ); + + const onCreateAccount = async (name) => { + const newAccountAddress = await dispatch(addNewAccount()); + if (name) { + dispatch(setAccountLabel(newAccountAddress, name)); + } + }; + + return ( + { + event.preventDefault(); + + try { + await onCreateAccount(newAccountName || defaultAccountName); + onActionComplete(true); + trackEvent({ + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAdded, + properties: { + account_type: MetaMetricsEventAccountType.Default, + location: 'Home', + }, + }); + history.push(mostRecentOverviewPage); + } catch (error) { + trackEvent({ + category: MetaMetricsEventCategory.Accounts, + event: MetaMetricsEventName.AccountAddFailed, + properties: { + account_type: MetaMetricsEventAccountType.Default, + error: error.message, + }, + }); + } + }} + > + setNewAccountName(event.target.value)} + helpText={errorMessage} + error={!isValidAccountName} + /> + + onActionComplete()} block> + {t('cancel')} + + + {t('create')} + + + + ); +}; + +CreateAccount.propTypes = { + onActionComplete: PropTypes.func.isRequired, +}; diff --git a/ui/components/multichain/create-account/create-account.stories.js b/ui/components/multichain/create-account/create-account.stories.js new file mode 100644 index 000000000..17e6619a4 --- /dev/null +++ b/ui/components/multichain/create-account/create-account.stories.js @@ -0,0 +1,10 @@ +import React from 'react'; +import { CreateAccount } from '.'; + +export default { + title: 'Components/Multichain/CreateAccount', + component: CreateAccount, +}; + +export const DefaultStory = (args) => ; +DefaultStory.storyName = 'Default'; diff --git a/ui/components/multichain/create-account/create-account.test.js b/ui/components/multichain/create-account/create-account.test.js new file mode 100644 index 000000000..f93f013af --- /dev/null +++ b/ui/components/multichain/create-account/create-account.test.js @@ -0,0 +1,67 @@ +/* eslint-disable jest/require-top-level-describe */ +import React from 'react'; +import { fireEvent, renderWithProvider, waitFor } from '../../../../test/jest'; +import configureStore from '../../../store/store'; +import mockState from '../../../../test/data/mock-state.json'; +import { CreateAccount } from '.'; + +const render = (props = { onActionComplete: () => jest.fn() }) => { + const store = configureStore(mockState); + return renderWithProvider(, store); +}; + +const mockAddNewAccount = jest.fn().mockReturnValue({ type: 'TYPE' }); +const mockSetAccountLabel = jest.fn().mockReturnValue({ type: 'TYPE' }); + +jest.mock('../../../store/actions', () => ({ + addNewAccount: (...args) => mockAddNewAccount(...args), + setAccountLabel: (...args) => mockSetAccountLabel(...args), +})); + +describe('CreateAccount', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('displays account name input and suggests name', () => { + const { getByPlaceholderText } = render(); + + expect(getByPlaceholderText('Account 5')).toBeInTheDocument(); + }); + + it('fires onActionComplete when clicked', async () => { + const onActionComplete = jest.fn(); + const { getByText, getByPlaceholderText } = render({ onActionComplete }); + + const input = getByPlaceholderText('Account 5'); + const newAccountName = 'New Account Name'; + + fireEvent.change(input, { + target: { value: newAccountName }, + }); + fireEvent.click(getByText('Create')); + + await waitFor(() => expect(mockAddNewAccount).toHaveBeenCalled()); + await waitFor(() => + expect(mockSetAccountLabel).toHaveBeenCalledWith( + { type: 'TYPE' }, + newAccountName, + ), + ); + await waitFor(() => expect(onActionComplete).toHaveBeenCalled()); + }); + + it(`doesn't allow duplicate account names`, async () => { + const { getByText, getByPlaceholderText } = render(); + + const input = getByPlaceholderText('Account 5'); + const usedAccountName = 'Account 4'; + + fireEvent.change(input, { + target: { value: usedAccountName }, + }); + + const submitButton = getByText('Create'); + expect(submitButton).toHaveAttribute('disabled'); + }); +}); diff --git a/ui/components/multichain/create-account/index.js b/ui/components/multichain/create-account/index.js new file mode 100644 index 000000000..ebaafe89b --- /dev/null +++ b/ui/components/multichain/create-account/index.js @@ -0,0 +1 @@ +export { CreateAccount } from './create-account'; diff --git a/ui/pages/create-account/import-account/__snapshots__/json.test.tsx.snap b/ui/components/multichain/import-account/__snapshots__/json.test.tsx.snap similarity index 98% rename from ui/pages/create-account/import-account/__snapshots__/json.test.tsx.snap rename to ui/components/multichain/import-account/__snapshots__/json.test.tsx.snap index 4719d152c..2eec4dbbd 100644 --- a/ui/pages/create-account/import-account/__snapshots__/json.test.tsx.snap +++ b/ui/components/multichain/import-account/__snapshots__/json.test.tsx.snap @@ -49,6 +49,7 @@ exports[`Json should match snapshot 1`] = ` - - - - - ); - } -} - -NewAccountCreateForm.propTypes = { - createAccount: PropTypes.func, - newAccountNumber: PropTypes.number, - history: PropTypes.object, - mostRecentOverviewPage: PropTypes.string.isRequired, - accounts: PropTypes.array, -}; - -NewAccountCreateForm.contextTypes = { - t: PropTypes.func, - trackEvent: PropTypes.func, -}; diff --git a/ui/pages/create-account/new-account.container.js b/ui/pages/create-account/new-account.container.js deleted file mode 100644 index 2ccb5e1cf..000000000 --- a/ui/pages/create-account/new-account.container.js +++ /dev/null @@ -1,36 +0,0 @@ -import { connect } from 'react-redux'; -import * as actions from '../../store/actions'; -import { getMostRecentOverviewPage } from '../../ducks/history/history'; -import { getMetaMaskAccountsOrdered } from '../../selectors'; -import NewAccountCreateForm from './new-account.component'; - -const mapStateToProps = (state) => { - const { - metamask: { identities = {} }, - } = state; - const numberOfExistingAccounts = Object.keys(identities).length; - const newAccountNumber = numberOfExistingAccounts + 1; - - return { - newAccountNumber, - mostRecentOverviewPage: getMostRecentOverviewPage(state), - accounts: getMetaMaskAccountsOrdered(state), - }; -}; - -const mapDispatchToProps = (dispatch) => { - return { - createAccount: (newAccountName) => { - return dispatch(actions.addNewAccount()).then((newAccountAddress) => { - if (newAccountName) { - dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)); - } - }); - }, - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps, -)(NewAccountCreateForm); diff --git a/ui/pages/create-account/new-account.stories.js b/ui/pages/create-account/new-account.stories.js deleted file mode 100644 index 11fb8b543..000000000 --- a/ui/pages/create-account/new-account.stories.js +++ /dev/null @@ -1,22 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import React from 'react'; -import NewAccountCreateForm from './new-account.component'; - -export default { - title: 'Pages/CreateAccount/NewAccount', - component: NewAccountCreateForm, - argTypes: { - accounts: { - control: 'array', - }, - }, - args: { - accounts: [], - }, -}; - -export const DefaultStory = (args) => ( - -); - -DefaultStory.storyName = 'Default'; diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss index 41be4f458..039f0edbe 100644 --- a/ui/pages/pages.scss +++ b/ui/pages/pages.scss @@ -11,7 +11,6 @@ @import 'connected-sites/index'; @import 'connected-accounts/index'; @import 'connected-sites/index'; -@import 'create-account/index'; @import "institutional/connect-custody/index"; @import "institutional/custody/index"; @import "institutional/institutional-entity-done-page/index"; diff --git a/ui/pages/routes/routes.component.js b/ui/pages/routes/routes.component.js index 740247e88..33c05dd72 100644 --- a/ui/pages/routes/routes.component.js +++ b/ui/pages/routes/routes.component.js @@ -22,7 +22,6 @@ import ImportTokenPage from '../import-token'; import AddNftPage from '../add-nft'; import ConfirmImportTokenPage from '../confirm-import-token'; import ConfirmAddSuggestedTokenPage from '../confirm-add-suggested-token'; -import CreateAccountPage from '../create-account'; import Loading from '../../components/ui/loading-screen'; import LoadingNetwork from '../../components/app/loading-network-screen'; import { Modal } from '../../components/app/modals'; @@ -63,7 +62,6 @@ import { CONNECT_ROUTE, DEFAULT_ROUTE, LOCK_ROUTE, - NEW_ACCOUNT_ROUTE, RESTORE_VAULT_ROUTE, REVEAL_SEED_ROUTE, SEND_ROUTE, @@ -326,7 +324,6 @@ export default class Routes extends Component { { ///: END:ONLY_INCLUDE_IN } -