From ad8226a5c3b3dffc1d251f66a4beffab02fc5458 Mon Sep 17 00:00:00 2001 From: Ewa Kowalska <30404388+ekowalsk@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:40:09 +0200 Subject: [PATCH] Account sidebar - single button for editing name and tags (#6049) * WIP - merge edition of account name and tags * [WIP] - tests * Apply test improvements * Improve tests * Improve tests and use useCallback * WIP - add cancel button * Outside click * Add more tests * Apply styling * Add opacity on not editable elements * Remove unnecessary code and fix type of sidebar ref * Disable eslint rule * Place edit button within account menu buttons * Adjust test setup * Ato-lint fixes * Update styles * Apply review suggestions * Remove unused styles and change type of sidebar ref * Remove onCancel from useAccountInfo hook * Fix issue with edit button * Apply review suggestions, remove tests * Adjust tests * Simplify tests * Apply lint fixes * Change layout of buttons Co-authored-by: MiZiet Co-authored-by: Jaco Greeff --- .../apps/public/locales/en/app-accounts.json | 2 + .../page-accounts/src/Accounts/index.spec.ts | 121 ++++++++++++++- .../src/Sidebar/AccountMenuButtons.tsx | 136 +++++++++++++++++ .../page-accounts/src/Sidebar/Sidebar.tsx | 138 ++++-------------- .../src/Sidebar/SidebarEditableSection.tsx | 99 +++++++++++++ packages/page-accounts/src/Sidebar/index.tsx | 1 + .../test/pageElements/AccountRow.ts | 70 +++++++++ .../test/pageElements/Sidebar.ts | 124 ++++++++++++++++ .../page-accounts/test/pages/accountsPage.tsx | 79 ++++------ packages/react-components/src/AccountName.tsx | 1 + .../react-components/src/Button/Button.tsx | 2 +- packages/react-components/src/Button/types.ts | 2 +- packages/react-components/src/Icon.tsx | 13 +- packages/react-components/src/Sidebar.tsx | 11 +- packages/react-components/src/Tags.tsx | 7 +- packages/react-hooks/src/types.ts | 3 + packages/react-hooks/src/useAccountInfo.ts | 9 +- packages/react-hooks/src/useOutsideClick.ts | 4 +- 18 files changed, 643 insertions(+), 179 deletions(-) create mode 100644 packages/page-accounts/src/Sidebar/AccountMenuButtons.tsx create mode 100644 packages/page-accounts/src/Sidebar/SidebarEditableSection.tsx create mode 100644 packages/page-accounts/test/pageElements/AccountRow.ts create mode 100644 packages/page-accounts/test/pageElements/Sidebar.ts diff --git a/packages/apps/public/locales/en/app-accounts.json b/packages/apps/public/locales/en/app-accounts.json index 5dc3e36875..51863480e5 100644 --- a/packages/apps/public/locales/en/app-accounts.json +++ b/packages/apps/public/locales/en/app-accounts.json @@ -32,6 +32,7 @@ "Approve this call hash": "Approve this call hash", "Backup": "Backup", "Backup account": "Backup account", + "Cancel": "Cancel", "Cancel this call hash": "Cancel this call hash", "Change": "Change", "Change account password": "Change account password", @@ -55,6 +56,7 @@ "Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use \"ed25519\".": "Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use \"ed25519\".", "Development": "Development", "Download": "Download", + "Edit": "Edit", "Either approve or reject this call.": "Either approve or reject this call.", "Ensure that the extension has accounts, some accounts are visible globally and available for this chain and that you gave the application permission to access accounts from the extension to use them.": "Ensure that the extension has accounts, some accounts are visible globally and available for this chain and that you gave the application permission to access accounts from the extension to use them.", "Ensure that you utilized the \"Save\" functionality before using a generated address to receive funds. Without saving the address any funds and the associated seed any funds sent to it will be lost.": "Ensure that you utilized the \"Save\" functionality before using a generated address to receive funds. Without saving the address any funds and the associated seed any funds sent to it will be lost.", diff --git a/packages/page-accounts/src/Accounts/index.spec.ts b/packages/page-accounts/src/Accounts/index.spec.ts index 82326fa1c1..b11ca749ee 100644 --- a/packages/page-accounts/src/Accounts/index.spec.ts +++ b/packages/page-accounts/src/Accounts/index.spec.ts @@ -3,13 +3,13 @@ import type { Balance } from '@polkadot/types/interfaces'; -import { fireEvent, screen, within } from '@testing-library/react'; +import { fireEvent, screen, waitFor, within } from '@testing-library/react'; import BN from 'bn.js'; import { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types'; import i18next from '@polkadot/react-components/i18n'; import toShortAddress from '@polkadot/react-components/util/toShortAddress'; -import { UseAccountInfo } from '@polkadot/react-hooks/types'; +import { AddressFlags, UseAccountInfo } from '@polkadot/react-hooks/types'; import { balanceOf } from '@polkadot/test-support/creation/balance'; import { makeStakingLedger as ledger } from '@polkadot/test-support/creation/stakingInfo/stakingLedger'; import { MemoryStore } from '@polkadot/test-support/keyring'; @@ -17,6 +17,8 @@ import { keyring } from '@polkadot/ui-keyring'; import { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; import { AccountOverrides, Override } from '../../test/hooks/default'; +import { AccountRow } from '../../test/pageElements/AccountRow'; +import { Sidebar } from '../../test/pageElements/Sidebar'; import { AccountsPage, format } from '../../test/pages/accountsPage'; describe('Accounts page', () => { @@ -374,6 +376,121 @@ describe('Accounts page', () => { await accountsPage.checkOrderAndRowsColoring([1, 2, 3]); }); }); + + describe('sidebar', () => { + const initialName = 'INITIAL_NAME'; + const newName = 'NEW_NAME'; + const defaultTag = 'Default'; + const nameInputNotFoundError = 'Unable to find an element by: [data-testid="name-input"]'; + + let accountRows: AccountRow[]; + let sideBar: Sidebar; + + describe('changes name', () => { + beforeEach(async () => { + accountsPage.renderPage([[aliceAddress, anAccountWithMeta({ isDevelopment: false, name: initialName })]]); + sideBar = await openSidebarForAccountRow(0); + await sideBar.changeAccountName(newName); + }); + + it('within keyring', () => { + const changedAccount = keyring.getAccount(aliceAddress); + + expect(changedAccount?.meta?.name).toEqual(newName); + }); + + it('within sidebar', async () => { + await sideBar.assertAccountName(newName); + }); + + it('within account row', async () => { + await accountRows[0].assertAccountName(newName); + }); + }); + + it('cannot be edited if edit button has not been pressed', async () => { + await sideBar.clickByText('no tags'); + expect(sideBar.queryByRole('combobox')).toBeFalsy(); + + await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError); + }); + + it('when isEditable is false account name is not editable', async () => { + renderAccountsWithDefaultAddresses( + anAccountWithInfo({ flags: { isEditable: false } as AddressFlags }) + ); + sideBar = await openSidebarForAccountRow(0); + sideBar.edit(); + await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError); + }); + + describe('on edit cancel', () => { + beforeEach(async () => { + accountsPage.renderPage([[aliceAddress, anAccountWithMeta({ isDevelopment: false, name: initialName, tags: [] })]]); + + sideBar = await openSidebarForAccountRow(0); + await sideBar.assertTags('no tags'); + sideBar.edit(); + }); + + it('restores tags and name to state from keyring', async () => { + await sideBar.typeAccountName(newName); + await sideBar.selectTag(defaultTag); + + sideBar.cancel(); + await sideBar.assertTags('no tags'); + await sideBar.assertAccountName(initialName); + }); + + it('Cancel button disappears', () => { + sideBar.cancel(); + expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy(); + }); + }); + + describe('outside click', () => { + beforeEach(async () => { + renderAccountsWithDefaultAddresses( + anAccountWithMeta({ name: 'alice' }), + anAccountWithMeta({ name: 'bob' }) + ); + + sideBar = await openSidebarForAccountRow(0); + sideBar.edit(); + }); + + it('cancels editing', async () => { + await sideBar.typeAccountName(newName); + await sideBar.selectTag(defaultTag); + + fireEvent.click(await screen.findByText('accounts')); + + await sideBar.assertTags('no tags'); + await sideBar.assertAccountName('ALICE'); + + expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy(); + }); + + it('within sidebar does not cancel editing', async () => { + await sideBar.clickByText('Tags'); + + expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeTruthy(); + }); + + it('cancels editing and changes name when opening sidebar for another account', async () => { + await waitFor(() => sideBar.assertAccountInput('alice')); + + sideBar = await openSidebarForAccountRow(1); + await sideBar.assertAccountName('BOB'); + }); + }); + + async function openSidebarForAccountRow (rowIndex: number) { + accountRows = await accountsPage.findAccountRows(); + + return accountRows[rowIndex].openSidebar(); + } + }); }); const anAccount = (): AccountOverrides => ({}); diff --git a/packages/page-accounts/src/Sidebar/AccountMenuButtons.tsx b/packages/page-accounts/src/Sidebar/AccountMenuButtons.tsx new file mode 100644 index 0000000000..18555d9efb --- /dev/null +++ b/packages/page-accounts/src/Sidebar/AccountMenuButtons.tsx @@ -0,0 +1,136 @@ +// Copyright 2017-2021 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useCallback } from 'react'; +import styled from 'styled-components'; + +import Transfer from '@polkadot/app-accounts/modals/Transfer'; +import { useTranslation } from '@polkadot/app-accounts/translate'; +import { Button } from '@polkadot/react-components'; +import { useToggle } from '@polkadot/react-hooks'; +import { AddressFlags } from '@polkadot/react-hooks/types'; + +interface Props { + className?: string; + flags: AddressFlags; + isEditingName: boolean; + isEditing: boolean; + toggleIsEditingName: () => void; + toggleIsEditingTags: () => void; + onCancel: () => void; + onSaveName: () => void; + onSaveTags: () => void; + onForgetAddress: () => void; + onUpdateName: () => void; + recipientId: string; +} + +function AccountMenuButtons ({ className = '', flags, isEditing, isEditingName, onCancel, onForgetAddress, onSaveName, onSaveTags, onUpdateName, recipientId, toggleIsEditingName, toggleIsEditingTags }: Props) { + const { t } = useTranslation(); + const [isTransferOpen, toggleIsTransferOpen] = useToggle(); + + const _onForgetAddress = useCallback( + (): void => { + onForgetAddress(); + onUpdateName && onUpdateName(); + }, + [onForgetAddress, onUpdateName] + ); + + const toggleIsEditing = useCallback(() => { + flags.isEditable && toggleIsEditingName(); + toggleIsEditingTags(); + }, [flags.isEditable, toggleIsEditingName, toggleIsEditingTags]); + + const _onUpdateName = useCallback( + (): void => { + onSaveName(); + onUpdateName && onUpdateName(); + }, + [onSaveName, onUpdateName] + ); + + const updateName = useCallback(() => { + if (isEditingName && (flags.isInContacts || flags.isOwned)) { + _onUpdateName(); + toggleIsEditingName(); + } + }, [isEditingName, flags.isInContacts, flags.isOwned, _onUpdateName, toggleIsEditingName]); + + const onEdit = useCallback(() => { + if (isEditing) { + updateName(); + onSaveTags(); + } + + toggleIsEditing(); + }, [isEditing, toggleIsEditing, updateName, onSaveTags]); + + return ( +
+ {isEditing + ? ( + +
+ ); +} + +export default React.memo(styled(AccountMenuButtons)` + width: 100%; + + .ui--Button-Group { + display: flex; + flex-direction: row; + justify-content: space-around; + margin-bottom: 0; + } +`); diff --git a/packages/page-accounts/src/Sidebar/Sidebar.tsx b/packages/page-accounts/src/Sidebar/Sidebar.tsx index e0ad9ee481..97935a3dd0 100644 --- a/packages/page-accounts/src/Sidebar/Sidebar.tsx +++ b/packages/page-accounts/src/Sidebar/Sidebar.tsx @@ -1,55 +1,44 @@ // Copyright 2017-2021 @polkadot/app-accounts authors & contributors // SPDX-License-Identifier: Apache-2.0 -import React, { useCallback } from 'react'; +import React, { useRef, useState } from 'react'; import styled from 'styled-components'; -import { AccountName, Button, Icon, IdentityIcon, Input, LinkExternal, Sidebar, Tags } from '@polkadot/react-components'; +import SidebarEditableSection from '@polkadot/app-accounts/Sidebar/SidebarEditableSection'; +import { IdentityIcon, LinkExternal, Sidebar } from '@polkadot/react-components'; import { colorLink } from '@polkadot/react-components/styles/theme'; -import { useAccountInfo, useToggle } from '@polkadot/react-hooks'; +import { useAccountInfo } from '@polkadot/react-hooks'; -import Transfer from '../modals/Transfer'; -import { useTranslation } from '../translate'; import Balances from './Balances'; -import Flags from './Flags'; import Identity from './Identity'; import Multisig from './Multisig'; interface Props { address: string; className?: string; + dataTestId?: string; onClose: () => void; onUpdateName: () => void; } -function FullSidebar ({ address, className = '', onClose, onUpdateName }: Props): React.ReactElement { - const { t } = useTranslation(); - const { accountIndex, flags, identity, isEditingName, isEditingTags, meta, name, onForgetAddress, onSaveName, onSaveTags, setName, setTags, tags, toggleIsEditingName, toggleIsEditingTags } = useAccountInfo(address); - const [isTransferOpen, toggleIsTransferOpen] = useToggle(); +function FullSidebar ({ address, className = '', dataTestId, onClose, onUpdateName }: Props): React.ReactElement { + const [inEditMode, setInEditMode] = useState(false); + const { accountIndex, flags, identity, meta } = useAccountInfo(address); - const _onForgetAddress = useCallback( - (): void => { - onForgetAddress(); - onUpdateName && onUpdateName(); - }, - [onForgetAddress, onUpdateName] - ); - - const _onUpdateName = useCallback( - (): void => { - onSaveName(); - onUpdateName && onUpdateName(); - }, - [onSaveName, onUpdateName] - ); + const ref = useRef(null); return ( -
+
)} - - ) - : flags.isEditable - ? (name.toUpperCase() || t('')) - : undefined - } - value={address} - withSidebar={false} - > - {(!isEditingName && flags.isEditable) && ( - - )} - -
- -
- -
- -
+
input { - padding: 0 !important; - background: rgba(230, 230, 230, 0.8) !important; - border: 0 !important; - border-radius: 0 !important; - box-shadow: 0 3px 3px rgba(0,0,0,.2); } } } + + &.inEditMode { + .ui--AddressMenu-flags { + opacity: 60%; + } + } `); diff --git a/packages/page-accounts/src/Sidebar/SidebarEditableSection.tsx b/packages/page-accounts/src/Sidebar/SidebarEditableSection.tsx new file mode 100644 index 0000000000..cf7c68fd9a --- /dev/null +++ b/packages/page-accounts/src/Sidebar/SidebarEditableSection.tsx @@ -0,0 +1,99 @@ +// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useCallback, useEffect } from 'react'; + +import AccountMenuButtons from '@polkadot/app-accounts/Sidebar/AccountMenuButtons'; +import Flags from '@polkadot/app-accounts/Sidebar/Flags'; +import { useTranslation } from '@polkadot/app-accounts/translate'; +import { AccountName, Input, Tags } from '@polkadot/react-components'; +import { useAccountInfo, useOutsideClick } from '@polkadot/react-hooks'; +import { keyring } from '@polkadot/ui-keyring'; + +interface Props { + address: string; + isBeingEdited: (arg: boolean) => void; + onUpdateName: () => void; + sidebarRef: React.RefObject; +} + +function SidebarEditableSection ({ address, isBeingEdited, onUpdateName, sidebarRef }: Props): React.ReactElement { + const { t } = useTranslation(); + const { flags, isEditing, isEditingName, isEditingTags, name, onForgetAddress, onSaveName, onSaveTags, setIsEditingName, setIsEditingTags, setName, setTags, tags, toggleIsEditingName, toggleIsEditingTags } = useAccountInfo(address); + + useEffect(() => { + isBeingEdited(isEditing()); + }, [isBeingEdited, isEditing]); + + const onCancel = useCallback( + (): void => { + if (isEditing()) { + try { + const accountOrAddress = keyring.getAccount(address) || keyring.getAddress(address); + + setName(accountOrAddress?.meta.name || ''); + setTags(accountOrAddress?.meta.tags ? (accountOrAddress.meta.tags as string[]).sort() : []); + setIsEditingName(false); + setIsEditingTags(false); + } catch (error) { + // ignore + } + } + }, [isEditing, setName, setTags, setIsEditingName, setIsEditingTags, address]); + + useOutsideClick([sidebarRef], onCancel); + + return ( + <> + + ) + : flags.isEditable + ? (name.toUpperCase() || t('')) + : undefined + } + value={address} + withSidebar={false} + /> +
+ +
+ + + + ); +} + +export default React.memo(SidebarEditableSection); diff --git a/packages/page-accounts/src/Sidebar/index.tsx b/packages/page-accounts/src/Sidebar/index.tsx index 2b9d61ee02..173686b8be 100644 --- a/packages/page-accounts/src/Sidebar/index.tsx +++ b/packages/page-accounts/src/Sidebar/index.tsx @@ -29,6 +29,7 @@ function AccountSidebar ({ children }: Props): React.ReactElement { {address && ( diff --git a/packages/page-accounts/test/pageElements/AccountRow.ts b/packages/page-accounts/test/pageElements/AccountRow.ts new file mode 100644 index 0000000000..cc7ba53f71 --- /dev/null +++ b/packages/page-accounts/test/pageElements/AccountRow.ts @@ -0,0 +1,70 @@ +// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { fireEvent, screen, within } from '@testing-library/react'; + +import { Balance } from '@polkadot/types/interfaces'; + +import { format } from '../pages/accountsPage'; +import { Sidebar } from './Sidebar'; + +// utility wrapper over an account item in accounts table, serves basic assertions about an account row + +export class AccountRow { + public primaryRow: HTMLElement; + public detailsRow: HTMLElement; + + constructor (primaryRow: HTMLElement, detailsRow: HTMLElement) { + this.primaryRow = primaryRow; + this.detailsRow = detailsRow; + } + + async assertBalancesTotal (expected: Balance): Promise { + const balanceActual = await within(this.primaryRow).findByTestId('balance-summary'); + const balanceExpected = format(expected); + + expect(balanceActual).toHaveTextContent(balanceExpected); + } + + async assertBalancesDetails (expected: {name: string, amount: Balance}[]): Promise { + for (const expectedBalanceDetailsItem of expected) { + const labelElement = await within(this.detailsRow).findByText(expectedBalanceDetailsItem.name); + const balanceElement = labelElement.nextSibling; + const amount = format(expectedBalanceDetailsItem.amount); + + expect(balanceElement).toHaveTextContent(amount); + } + } + + async assertAccountName (expectedName: string): Promise { + const accountName = await within(this.primaryRow).findByTestId('account-name'); + + expect(accountName).toHaveTextContent(expectedName); + } + + async assertParentAccountName (expectedParentAccount: string): Promise { + const parentAccount = await within(this.primaryRow).findByTestId('parent'); + + expect(parentAccount).toHaveTextContent(expectedParentAccount); + } + + async assertTags (tagsContent: string): Promise { + const tagsActual = await within(this.detailsRow).findByTestId('tags'); + + expect(tagsActual).toHaveTextContent(tagsContent); + } + + async assertShortAddress (expectedShortAddress: string): Promise { + const actualShortAddress = await within(this.primaryRow).findByTestId('short-address'); + + expect(actualShortAddress).toHaveTextContent(expectedShortAddress); + } + + async openSidebar (): Promise { + const accountName = await within(this.primaryRow).findByTestId('account-name'); + + fireEvent.click(accountName); + + return new Sidebar(await screen.findByTestId('account-sidebar')); + } +} diff --git a/packages/page-accounts/test/pageElements/Sidebar.ts b/packages/page-accounts/test/pageElements/Sidebar.ts new file mode 100644 index 0000000000..78656f85fb --- /dev/null +++ b/packages/page-accounts/test/pageElements/Sidebar.ts @@ -0,0 +1,124 @@ +// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { fireEvent, within } from '@testing-library/react'; + +export class Sidebar { + public sidebar: HTMLElement + + constructor (sidebar: HTMLElement) { + this.sidebar = sidebar; + } + + async changeAccountName (accountName: string): Promise { + this.edit(); + await this.typeAccountName(accountName); + this.save(); + } + + async typeAccountName (accountName: string): Promise { + const accountNameInput = await this.findByTestId('name-input'); + + fireEvent.change(accountNameInput, { target: { value: accountName } }); + } + + async selectTag (tagName: string): Promise { + const tagsCombobox = this.openTagsDropdown(); + const tagOptions = await within(tagsCombobox).findAllByRole('option'); + const tag = tagOptions.find((tag) => tag.textContent === tagName) as HTMLElement; + + fireEvent.click(tag); + } + + async assertAccountInput (expectedInput: string): Promise { + const sideBarName = await this.findByTestId('name-input'); + + expect(sideBarName).toHaveProperty('value', expectedInput); + } + + async assertAccountName (expectedAccountName: string): Promise { + const sideBarName = await this.findByTestId('account-name'); + + expect(sideBarName).toHaveTextContent(expectedAccountName); + } + + async assertTags (tagsContent: string): Promise { + const sideBarTags = await this.findByTestId('sidebar-tags'); + + expect(sideBarTags).toHaveTextContent(tagsContent); + } + + close (): Promise { + return this.clickByTestId('close-sidebar-button'); + } + + cancel (): void { + this.clickButton('Cancel'); + } + + edit (): void { + this.clickButton('Edit'); + } + + save (): void { + this.clickButton('Save'); + } + + async clickByText (text: string): Promise { + const htmlElement = await this.findByText(text); + + fireEvent.click(htmlElement); + } + + async clickByTestId (testId: string): Promise { + const htmlElement = await this.findByTestId(testId); + + fireEvent.click(htmlElement); + } + + async findByText (text: string): Promise { + return within(this.sidebar).findByText(text); + } + + async findByTestId (testId: string): Promise { + return within(this.sidebar).findByTestId(testId); + } + + async findByRole (role: string): Promise { + return within(this.sidebar).findByRole(role); + } + + async findAllByRole (role: string): Promise { + return within(this.sidebar).findAllByRole(role); + } + + getByTestId (testId: string): HTMLElement { + return within(this.sidebar).getByTestId(testId); + } + + getByRole (roleName: string, options?: Record): HTMLElement { + return within(this.sidebar).getByRole(roleName, options); + } + + queryByRole (roleName: string, options?: Record): HTMLElement | null { + return within(this.sidebar).queryByRole(roleName, options); + } + + queryByTestId (testId: string): HTMLElement | null { + return within(this.sidebar).queryByTestId(testId); + } + + private clickButton (buttonName: string) { + const saveButton = this.getByRole('button', { name: buttonName }); + + fireEvent.click(saveButton); + } + + private openTagsDropdown (): HTMLElement { + const tagsDropdown = this.getByRole('combobox', { expanded: false }); + + fireEvent.click(tagsDropdown); + + return tagsDropdown; + } +} diff --git a/packages/page-accounts/test/pages/accountsPage.tsx b/packages/page-accounts/test/pages/accountsPage.tsx index ae1dfbac80..5d3e72a2de 100644 --- a/packages/page-accounts/test/pages/accountsPage.tsx +++ b/packages/page-accounts/test/pages/accountsPage.tsx @@ -8,18 +8,21 @@ import { MemoryRouter } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import { balance } from '@polkadot/app-accounts/Accounts/index.spec'; +import AccountSidebar from '@polkadot/app-accounts/Sidebar'; import { lightTheme } from '@polkadot/apps/themes'; import { POLKADOT_GENESIS } from '@polkadot/apps-config'; import { ApiContext } from '@polkadot/react-api'; import { ApiProps } from '@polkadot/react-api/types'; import { QueueProvider } from '@polkadot/react-components/Status/Context'; import { PartialQueueTxExtrinsic, QueueProps, QueueTxExtrinsicAdd } from '@polkadot/react-components/Status/types'; +import { UseAccountInfo } from '@polkadot/react-hooks/types'; import { TypeRegistry } from '@polkadot/types/create'; import { Balance, BlockNumber } from '@polkadot/types/interfaces'; import { keyring } from '@polkadot/ui-keyring'; import { formatBalance } from '@polkadot/util'; import { AccountOverrides, mockAccountHooks } from '../hooks/default'; +import { AccountRow } from '../pageElements/AccountRow'; import Overview from '../pages/../../src/Accounts/index'; let queueExtrinsic: (value: PartialQueueTxExtrinsic) => void; @@ -31,60 +34,10 @@ function noop (): void { class NotYetRendered extends Error { } -// utility wrapper over an account item in accounts table, serves basic assertions about an account row -export class AccountRow { - public primaryRow: HTMLElement; - public detailsRow: HTMLElement; - - constructor (primaryRow: HTMLElement, detailsRow: HTMLElement) { - this.primaryRow = primaryRow; - this.detailsRow = detailsRow; - } - - async assertBalancesTotal (expected: Balance): Promise { - const balanceActual = await within(this.primaryRow).findByTestId('balance-summary'); - const balanceExpected = format(expected); - - expect(balanceActual).toHaveTextContent(balanceExpected); - } - - async assertBalancesDetails (expected: {name: string, amount: Balance}[]): Promise { - for (const expectedBalanceDetailsItem of expected) { - const labelElement = await within(this.detailsRow).findByText(expectedBalanceDetailsItem.name); - const balanceElement = labelElement.nextSibling; - const amount = format(expectedBalanceDetailsItem.amount); - - expect(balanceElement).toHaveTextContent(amount); - } - } - - async assertParentAccountName (expectedParentAccount: string): Promise { - const parentAccount = await within(this.primaryRow).findByTestId('parent'); - - expect(parentAccount).toHaveTextContent(expectedParentAccount); - } - - async assertTags (tagsContent: string): Promise { - const tagsActual = await within(this.detailsRow).findByTestId('tags'); - - expect(tagsActual).toHaveTextContent(tagsContent); - } - - async assertShortAddress (expectedShortAddress: string): Promise { - const actualShortAddress = await within(this.primaryRow).findByTestId('short-address'); - - expect(actualShortAddress).toHaveTextContent(expectedShortAddress); - } -} - jest.mock('@polkadot/react-hooks/useAccounts', () => ({ useAccounts: () => mockAccountHooks.useAccounts })); -jest.mock('@polkadot/react-hooks/useAccountInfo', () => ({ - useAccountInfo: (address: string) => mockAccountHooks.accountsMap[address]?.info || {} -})); - jest.mock('@polkadot/react-hooks/useLoadingDelay', () => ({ useLoadingDelay: () => false })); @@ -101,6 +54,25 @@ jest.mock('@polkadot/react-hooks/useBestNumber', () => ({ useBestNumber: () => 1 })); +jest.mock('@polkadot/react-hooks/useAccountInfo', () => { + // eslint-disable-next-line func-call-spacing + const actual = jest.requireActual<{useAccountInfo: (address: string) => UseAccountInfo}>('@polkadot/react-hooks/useAccountInfo'); + + return ({ + useAccountInfo: (address: string) => { + const mockInfo = mockAccountHooks.accountsMap[address]; + + return mockInfo + ? { + ...actual.useAccountInfo(address), + flags: { ...actual.useAccountInfo(address).flags, ...(mockInfo.info.flags) }, + tags: [...actual.useAccountInfo(address).tags, ...(mockInfo.info.tags)] + } + : actual.useAccountInfo(address); + } + }); +}); + export class AccountsPage { private renderResult?: RenderResult @@ -115,8 +87,7 @@ export class AccountsPage { api: { derive: { accounts: { - info: () => Promise.resolve(() => { /**/ - }) + info: () => { /**/ } }, balances: { all: () => ({ @@ -155,7 +126,9 @@ export class AccountsPage { - + + + diff --git a/packages/react-components/src/AccountName.tsx b/packages/react-components/src/AccountName.tsx index 9ffa4a7b4f..07d333d43f 100644 --- a/packages/react-components/src/AccountName.tsx +++ b/packages/react-components/src/AccountName.tsx @@ -171,6 +171,7 @@ function AccountName ({ children, className = '', defaultName, label, onClick, o return (
- + {icon && } {label} {children} { - const extraProps = tooltip - ? { 'data-for': tooltip, 'data-testid': icon, 'data-tip': true } - : {}; + const extraProps: Record = { + 'data-testid': icon, + ...(tooltip + ? { + 'data-for': tooltip, + 'data-tip': true + } + : {} + ) + }; return ( void; position: 'left' | 'right'; + sidebarRef: React.RefObject; } -function Sidebar ({ button, children, className = '', onClose }: Props): React.ReactElement { +function Sidebar ({ button, children, className = '', dataTestId = '', onClose, sidebarRef }: Props): React.ReactElement { return ( -
+
{button}