From 52b706ef1454263510f19e25c5292890925db4cb Mon Sep 17 00:00:00 2001 From: Ewa Kowalska <30404388+ekowalsk@users.noreply.github.com> Date: Wed, 15 Sep 2021 08:54:48 +0200 Subject: [PATCH] Apply collapsible row styling for addresses (#6122) * Apply collapsible row for addresses * Apply merge improvements * Style buttons * Restore unused export Co-authored-by: MiZiet --- .../page-accounts/src/Accounts/index.spec.ts | 456 ++++++++---------- .../page-accounts/src/modals/Create.spec.tsx | 27 +- .../test/pageElements/AccountRow.ts | 70 --- .../test/pageElements/AccountRow.tsx | 14 + .../page-accounts/test/pages/accountsPage.tsx | 312 +++--------- .../page-addresses/src/Contacts/Address.tsx | 230 ++++++--- .../page-addresses/src/Contacts/index.spec.ts | 135 ++++++ .../page-addresses/src/Contacts/index.tsx | 8 +- .../test/pages/addressesPage.tsx | 56 +++ packages/test-support/src/creation/account.ts | 31 ++ packages/test-support/src/creation/balance.ts | 2 +- packages/test-support/src/creation/contact.ts | 26 + .../test-support/src/keyring/addresses.ts | 1 + packages/test-support/src/keyring/index.ts | 1 + packages/test-support/src/pages/Page.tsx | 161 +++++++ .../test-support/src/pagesElements/Row.ts | 99 ++++ .../src/pagesElements}/Sidebar.ts | 8 +- .../test-support/src/pagesElements/Table.ts | 75 +++ .../test-support/src/pagesElements/index.ts | 5 + packages/test-support/src/types.ts | 17 + .../src/utils/accountDefaults.ts} | 7 +- packages/test-support/src/utils/balance.ts | 29 ++ .../src/utils/renderedScreenUtils.ts | 26 + 23 files changed, 1110 insertions(+), 686 deletions(-) delete mode 100644 packages/page-accounts/test/pageElements/AccountRow.ts create mode 100644 packages/page-accounts/test/pageElements/AccountRow.tsx create mode 100644 packages/page-addresses/src/Contacts/index.spec.ts create mode 100644 packages/page-addresses/test/pages/addressesPage.tsx create mode 100644 packages/test-support/src/creation/account.ts create mode 100644 packages/test-support/src/creation/contact.ts create mode 100644 packages/test-support/src/pages/Page.tsx create mode 100644 packages/test-support/src/pagesElements/Row.ts rename packages/{page-accounts/test/pageElements => test-support/src/pagesElements}/Sidebar.ts (93%) create mode 100644 packages/test-support/src/pagesElements/Table.ts create mode 100644 packages/test-support/src/pagesElements/index.ts create mode 100644 packages/test-support/src/types.ts rename packages/{page-accounts/test/hooks/default.ts => test-support/src/utils/accountDefaults.ts} (94%) create mode 100644 packages/test-support/src/utils/balance.ts create mode 100644 packages/test-support/src/utils/renderedScreenUtils.ts diff --git a/packages/page-accounts/src/Accounts/index.spec.ts b/packages/page-accounts/src/Accounts/index.spec.ts index b11ca749ee..b972585f85 100644 --- a/packages/page-accounts/src/Accounts/index.spec.ts +++ b/packages/page-accounts/src/Accounts/index.spec.ts @@ -1,31 +1,24 @@ // Copyright 2017-2021 @polkadot/page-accounts authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { Balance } from '@polkadot/types/interfaces'; - -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; +import { fireEvent, screen, waitFor } 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 { AddressFlags, UseAccountInfo } from '@polkadot/react-hooks/types'; -import { balanceOf } from '@polkadot/test-support/creation/balance'; +import { AddressFlags } from '@polkadot/react-hooks/types'; +import { anAccountWithBalance, anAccountWithBalanceAndMeta, anAccountWithInfo, anAccountWithMeta, anAccountWithStaking } from '@polkadot/test-support/creation/account'; import { makeStakingLedger as ledger } from '@polkadot/test-support/creation/stakingInfo/stakingLedger'; -import { MemoryStore } from '@polkadot/test-support/keyring'; +import { alice, MemoryStore } from '@polkadot/test-support/keyring'; +import { Table } from '@polkadot/test-support/pagesElements'; +import { Sidebar } from '@polkadot/test-support/pagesElements/Sidebar'; +import { balance, showBalance } from '@polkadot/test-support/utils/balance'; 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'; +import { AccountsPage } from '../../test/pages/accountsPage'; describe('Accounts page', () => { - const aliceAddress = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; - const bobAddress = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; - const charlieAddress = '5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy'; - let accountsPage: AccountsPage; beforeAll(async () => { @@ -37,43 +30,36 @@ describe('Accounts page', () => { }); beforeEach(() => { - defaultAddresses.forEach((address) => keyring.forgetAccount(address)); accountsPage = new AccountsPage(); + accountsPage.clearAccounts(); }); describe('when no accounts', () => { beforeEach(() => { - accountsPage.renderPage([]); + accountsPage.render([]); }); it('shows sort-by controls', async () => { - const section = await accountsPage.findSortBySection(); - - expect(section).not.toBeNull(); - - const button = await accountsPage.findSortByReverseButton(); - - expect(button).not.toBeNull(); + await accountsPage.reverseSortingOrder(); }); it('shows a table', async () => { - const accountsTable = await accountsPage.findAccountsTable(); + const accountsTable = await accountsPage.getTable(); expect(accountsTable).not.toBeNull(); }); it('the accounts table contains no account rows', async () => { - const accountRows = await accountsPage.findAccountRows(); + const accountRows = await accountsPage.getAccountRows(); expect(accountRows).toHaveLength(0); }); it('the accounts table contains a message about no accounts available', async () => { - const accountsTable = await accountsPage.findAccountsTable(); - const noAccountsMessage = await within(accountsTable).findByText( - 'You don\'t have any accounts. Some features are currently hidden and will only become available once you have accounts.'); + const noAccountsMessage = 'You don\'t have any accounts. Some features are currently hidden and will only become available once you have accounts.'; + const accountsTable = await accountsPage.getTable(); - expect(noAccountsMessage).not.toBeNull(); + await accountsTable.assertText(noAccountsMessage); }); it('no summary is displayed', () => { @@ -85,31 +71,31 @@ describe('Accounts page', () => { describe('when some accounts exist', () => { it('the accounts table contains some account rows', async () => { - renderDefaultAccounts(2); - const accountRows = await accountsPage.findAccountRows(); + accountsPage.renderDefaultAccounts(2); + const accountRows = await accountsPage.getAccountRows(); expect(accountRows).toHaveLength(2); }); it('account rows display the total balance info', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalance({ freeBalance: balance(500) }), anAccountWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) }) ); - const rows = await accountsPage.findAccountRows(); + const rows = await accountsPage.getAccountRows(); await rows[0].assertBalancesTotal(balance(500)); await rows[1].assertBalancesTotal(balance(350)); }); it('account rows display the details balance info', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalance({ freeBalance: balance(500), lockedBalance: balance(30) }), anAccountWithBalance({ availableBalance: balance(50), freeBalance: balance(200), reservedBalance: balance(150) }) ); - const rows = await accountsPage.findAccountRows(); + const rows = await accountsPage.getAccountRows(); await rows[0].assertBalancesDetails([ { amount: balance(0), name: 'transferrable' }, @@ -120,75 +106,74 @@ describe('Accounts page', () => { }); it('derived account displays parent account info', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithMeta({ isInjected: true, name: 'ALICE', whenCreated: 200 }), - anAccountWithMeta({ name: 'ALICE_CHILD', parentAddress: aliceAddress, whenCreated: 300 }) + anAccountWithMeta({ name: 'ALICE_CHILD', parentAddress: alice, whenCreated: 300 }) ); - const accountRows = await accountsPage.findAccountRows(); + const accountRows = await accountsPage.getAccountRows(); expect(accountRows).toHaveLength(2); await accountRows[1].assertParentAccountName('ALICE'); }); it('a separate column for parent account is not displayed', async () => { - renderDefaultAccounts(1); - const accountsTable = await accountsPage.findAccountsTable(); + accountsPage.renderDefaultAccounts(1); + const accountsTable = await accountsPage.getTable(); - assertColumnNotExistInTable('parent', accountsTable); - assertColumnExistsInTable('type', accountsTable); + accountsTable.assertColumnNotExist('parent'); + accountsTable.assertColumnExists('type'); }); it('account rows display the shorted address', async () => { - renderAccountsForAddresses( - aliceAddress + accountsPage.renderAccountsForAddresses( + alice ); - const accountRows = await accountsPage.findAccountRows(); + const accountRows = await accountsPage.getAccountRows(); expect(accountRows).toHaveLength(1); - const aliceShortAddress = toShortAddress(aliceAddress); + const aliceShortAddress = toShortAddress(alice); await accountRows[0].assertShortAddress(aliceShortAddress); }); it('when account is not tagged, account row details displays no tags info', async () => { - renderDefaultAccounts(1); - const rows = await accountsPage.findAccountRows(); + accountsPage.renderDefaultAccounts(1); + const rows = await accountsPage.getAccountRows(); await rows[0].assertTags('no tags'); }); it('when account is tagged, account row details displays tags', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithInfo({ tags: ['my tag', 'Super Tag'] }) ); - const rows = await accountsPage.findAccountRows(); + const rows = await accountsPage.getAccountRows(); await rows[0].assertTags('my tagSuper Tag'); }); it('account details rows keep colouring from their primary rows', async () => { - renderDefaultAccounts(3); + accountsPage.renderDefaultAccounts(3); + const accountsTable = await accountsPage.getTable(); - await accountsPage.checkRowsColoring(); + await accountsTable.assertColoring(); }); it('account details rows toggled on icon toggle click', async () => { - renderDefaultAccounts(1); - - const row = (await accountsPage.findAccountRows())[0]; - const toggle = await within(row.primaryRow).findByTestId('row-toggle'); + accountsPage.renderDefaultAccounts(1); + const row = (await accountsPage.getAccountRows())[0]; expect(row.detailsRow).toHaveClass('isCollapsed'); - fireEvent.click(toggle); + await row.expand(); expect(row.detailsRow).toHaveClass('isExpanded'); }); it('displays some summary', () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalance({ freeBalance: balance(500) }), anAccountWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) }) ); @@ -199,7 +184,7 @@ describe('Accounts page', () => { }); it('displays balance summary', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalance({ freeBalance: balance(500) }), anAccountWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) }) ); @@ -210,7 +195,7 @@ describe('Accounts page', () => { }); it('displays transferable summary', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalance({ availableBalance: balance(400) }), anAccountWithBalance({ availableBalance: balance(600) }) ); @@ -221,7 +206,7 @@ describe('Accounts page', () => { }); it('displays locked summary', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalance({ lockedBalance: balance(400) }), anAccountWithBalance({ lockedBalance: balance(600) }) ); @@ -232,7 +217,7 @@ describe('Accounts page', () => { }); it('displays bonded summary', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithStaking({ stakingLedger: ledger(balance(70)) }), anAccountWithStaking({ stakingLedger: ledger(balance(20)) }) ); @@ -243,7 +228,7 @@ describe('Accounts page', () => { }); it('displays unbonding summary', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithStaking({ unlocking: [ { @@ -284,7 +269,7 @@ describe('Accounts page', () => { }); it('displays redeemable summary', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithStaking({ redeemable: balance(4000) }), anAccountWithStaking({ redeemable: balance(5000) }) ); @@ -295,270 +280,209 @@ describe('Accounts page', () => { }); it('sorts accounts by date by default', async () => { - renderAccountsWithDefaultAddresses( + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalanceAndMeta({ freeBalance: balance(1) }, { whenCreated: 200 }), anAccountWithBalanceAndMeta({ freeBalance: balance(2) }, { whenCreated: 300 }), anAccountWithBalanceAndMeta({ freeBalance: balance(3) }, { whenCreated: 100 }) ); + expect(await accountsPage.getCurrentSortCategory()).toHaveTextContent('date'); - expect(await accountsPage.findSortByDropdownCurrent()).toHaveTextContent('date'); - await accountsPage.checkOrderAndRowsColoring([3, 1, 2]); + const accountsTable = await accountsPage.getTable(); + + await accountsTable.assertRowsOrderAndColoring([3, 1, 2]); }); describe('when sorting is used', () => { - beforeEach(() => { - renderAccountsWithDefaultAddresses( + let accountsTable: Table; + + beforeEach(async () => { + accountsPage.renderAccountsWithDefaultAddresses( anAccountWithBalanceAndMeta({ freeBalance: balance(1) }, { isInjected: true, name: 'bbb', whenCreated: 200 }), anAccountWithBalanceAndMeta({ freeBalance: balance(2) }, { hardwareType: 'ledger', isHardware: true, name: 'bb', - parentAddress: aliceAddress, + parentAddress: alice, whenCreated: 300 }), anAccountWithBalanceAndMeta({ freeBalance: balance(3) }, { isInjected: true, name: 'aaa', whenCreated: 100 }) ); + + accountsTable = await accountsPage.getTable(); }); it('changes default dropdown value', async () => { - await accountsPage.selectOrder('balances'); - expect(await accountsPage.findSortByDropdownCurrent()) + await accountsPage.sortBy('balances'); + expect(await accountsPage.getCurrentSortCategory()) .toHaveTextContent('balances'); }); it('sorts by parent if asked', async () => { - await accountsPage.selectOrder('parent'); - await accountsPage.checkOrderAndRowsColoring([3, 1, 2]); + await accountsPage.sortBy('parent'); + await accountsTable.assertRowsOrderAndColoring([3, 1, 2]); }); it('sorts by name if asked', async () => { - await accountsPage.selectOrder('name'); - await accountsPage.checkOrderAndRowsColoring([3, 2, 1]); + await accountsPage.sortBy('name'); + await accountsTable.assertRowsOrderAndColoring([3, 2, 1]); }); it('sorts by date if asked', async () => { - await accountsPage.selectOrder('date'); - await accountsPage.checkOrderAndRowsColoring([3, 1, 2]); + await accountsPage.sortBy('date'); + await accountsTable.assertRowsOrderAndColoring([3, 1, 2]); }); it('sorts by balances if asked', async () => { - await accountsPage.selectOrder('balances'); - await accountsPage.checkOrderAndRowsColoring([1, 2, 3]); + await accountsPage.sortBy('balances'); + await accountsTable.assertRowsOrderAndColoring([1, 2, 3]); }); it('sorts by type if asked', async () => { - await accountsPage.selectOrder('type'); - await accountsPage.checkOrderAndRowsColoring([3, 1, 2]); + await accountsPage.sortBy('type'); + await accountsTable.assertRowsOrderAndColoring([3, 1, 2]); }); it('implements stable sort', async () => { // Notice that sorting by 'type' results in different order // depending on the previous state. - await accountsPage.selectOrder('name'); - await accountsPage.checkOrderAndRowsColoring([3, 2, 1]); - await accountsPage.selectOrder('type'); - await accountsPage.checkOrderAndRowsColoring([3, 1, 2]); - await accountsPage.selectOrder('balances'); - await accountsPage.checkOrderAndRowsColoring([1, 2, 3]); - await accountsPage.selectOrder('type'); - await accountsPage.checkOrderAndRowsColoring([1, 3, 2]); - await accountsPage.checkRowsColoring(); + await accountsPage.sortBy('name'); + await accountsTable.assertRowsOrderAndColoring([3, 2, 1]); + await accountsPage.sortBy('type'); + await accountsTable.assertRowsOrderAndColoring([3, 1, 2]); + await accountsPage.sortBy('balances'); + await accountsTable.assertRowsOrderAndColoring([1, 2, 3]); + await accountsPage.sortBy('type'); + await accountsTable.assertRowsOrderAndColoring([1, 3, 2]); + await accountsTable.assertColoring(); }); it('respects reverse button', async () => { - await accountsPage.selectOrder('name'); - await accountsPage.checkOrderAndRowsColoring([3, 2, 1]); - await accountsPage.selectOrder('balances'); - await accountsPage.checkOrderAndRowsColoring([1, 2, 3]); - fireEvent.click(await accountsPage.findSortByReverseButton()); - await accountsPage.checkOrderAndRowsColoring([3, 2, 1]); - await accountsPage.selectOrder('name'); - await accountsPage.checkOrderAndRowsColoring([1, 2, 3]); + await accountsPage.sortBy('name'); + await accountsTable.assertRowsOrderAndColoring([3, 2, 1]); + await accountsPage.sortBy('balances'); + await accountsTable.assertRowsOrderAndColoring([1, 2, 3]); + await accountsPage.reverseSortingOrder(); + await accountsTable.assertRowsOrderAndColoring([3, 2, 1]); + await accountsPage.sortBy('name'); + await accountsTable.assertRowsOrderAndColoring([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.render([[alice, anAccountWithMeta({ isDevelopment: false, name: initialName })]]); + accountRows = await accountsPage.getAccountRows(); + sideBar = await accountRows[0].openSidebar(); + await sideBar.changeAccountName(newName); + }); + + it('within keyring', () => { + const changedAccount = keyring.getAccount(alice); + + expect(changedAccount?.meta?.name).toEqual(newName); + }); + + it('within sidebar', async () => { + await sideBar.assertAccountName(newName); + }); + + it('within account row', async () => { + await accountRows[0].assertAccountName(newName); }); }); - 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"]'; + it('cannot be edited if edit button has not been pressed', async () => { + accountsPage.renderDefaultAccounts(1); + accountRows = await accountsPage.getAccountRows(); + sideBar = await accountRows[0].openSidebar(); + await sideBar.clickByText('no tags'); + expect(sideBar.queryByRole('combobox')).toBeFalsy(); - let accountRows: AccountRow[]; - let sideBar: Sidebar; + await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError); + }); - describe('changes name', () => { - beforeEach(async () => { - accountsPage.renderPage([[aliceAddress, anAccountWithMeta({ isDevelopment: false, name: initialName })]]); - sideBar = await openSidebarForAccountRow(0); - await sideBar.changeAccountName(newName); - }); + it('when isEditable is false account name is not editable', async () => { + accountsPage.renderAccountsWithDefaultAddresses( + anAccountWithInfo({ flags: { isEditable: false } as AddressFlags }) + ); + accountRows = await accountsPage.getAccountRows(); + sideBar = await accountRows[0].openSidebar(); + sideBar.edit(); - it('within keyring', () => { - const changedAccount = keyring.getAccount(aliceAddress); + await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError); + }); - expect(changedAccount?.meta?.name).toEqual(newName); - }); + describe('on edit cancel', () => { + beforeEach(async () => { + accountsPage.render([[alice, anAccountWithMeta({ isDevelopment: false, name: initialName, tags: [] })]]); + accountRows = await accountsPage.getAccountRows(); + sideBar = await accountRows[0].openSidebar(); - 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); + await sideBar.assertTags('no tags'); 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: [] })]]); + it('restores tags and name to state from keyring', async () => { + await sideBar.typeAccountName(newName); + await sideBar.selectTag(defaultTag); - 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(); - }); + sideBar.cancel(); + await sideBar.assertTags('no tags'); + await sideBar.assertAccountName(initialName); }); - describe('outside click', () => { - beforeEach(async () => { - renderAccountsWithDefaultAddresses( - anAccountWithMeta({ name: 'alice' }), - anAccountWithMeta({ name: 'bob' }) - ); + it('Cancel button disappears', () => { + sideBar.cancel(); + expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy(); + }); + }); - sideBar = await openSidebarForAccountRow(0); - sideBar.edit(); - }); + describe('outside click', () => { + beforeEach(async () => { + accountsPage.renderAccountsWithDefaultAddresses( + anAccountWithMeta({ name: 'alice' }), + anAccountWithMeta({ name: 'bob' }) + ); - 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'); - }); + accountRows = await accountsPage.getAccountRows(); + sideBar = await accountRows[0].openSidebar(); + sideBar.edit(); }); - async function openSidebarForAccountRow (rowIndex: number) { - accountRows = await accountsPage.findAccountRows(); + it('cancels editing', async () => { + await sideBar.typeAccountName(newName); + await sideBar.selectTag(defaultTag); - return accountRows[rowIndex].openSidebar(); - } + 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 accountRows[1].openSidebar(); + await sideBar.assertAccountName('BOB'); + }); }); }); - - const anAccount = (): AccountOverrides => ({}); - - const anAccountWithBalance = (balance: Override) => ({ - balance - }); - - const anAccountWithBalanceAndMeta = (balance: Override, meta: Override) => ({ - balance, - meta - }); - - const anAccountWithInfo = (info: Override) => ({ - info - }); - - const anAccountWithMeta = (meta: Override) => ({ - meta - }); - - const anAccountWithStaking = (staking: Override) => ({ - staking - }); - - function assertColumnNotExistInTable (columnName: string, table: HTMLElement) { - expect(within(table).queryByRole('columnheader', { name: columnName })).toBeFalsy(); - } - - function assertColumnExistsInTable (columnName: string, table: HTMLElement) { - expect(within(table).getByRole('columnheader', { name: columnName })).toBeTruthy(); - } - - function showBalance (amount: number): string { - return format(balance(amount)); - } - - const defaultAddresses = [aliceAddress, bobAddress, charlieAddress]; - - function renderAccountsWithDefaultAddresses (...overrides: AccountOverrides[]): void { - const accounts = overrides.map((accountProperties, index) => - [defaultAddresses[index], accountProperties] as [string, AccountOverrides]); - - accountsPage.renderPage(accounts); - } - - function renderAccountsForAddresses (...addresses: string[]): void { - const accounts = addresses.map((address) => [address, anAccount()] as [string, AccountOverrides]); - - accountsPage.renderPage(accounts); - } - - function renderDefaultAccounts (accountsNumber: number): void { - const accounts = Array.from({ length: accountsNumber }, - (_, index) => [defaultAddresses[index], anAccount()] as [string, AccountOverrides]); - - accountsPage.renderPage(accounts); - } }); - -/** - * Creates a balance instance for testing purposes which most often do not need to specifiy/use decimal part. - * @param amountInt Integer part of the balance number - * @param decimalsString Decimals part of the balance number. Note! This is a string sequence just after '.' separator - * that is the point that separates integers from decimals. E.g. (100, 4567) => 100.45670000...00 - */ -export const balance = function (amountInt: number, decimalsString?: string): Balance { - const decimalsPadded = (decimalsString || '').padEnd(12, '0'); - - return balanceOf(amountInt.toString() + decimalsPadded); -}; diff --git a/packages/page-accounts/src/modals/Create.spec.tsx b/packages/page-accounts/src/modals/Create.spec.tsx index fc380d08ed..fe602b2eb9 100644 --- a/packages/page-accounts/src/modals/Create.spec.tsx +++ b/packages/page-accounts/src/modals/Create.spec.tsx @@ -5,6 +5,7 @@ import { fireEvent, screen, waitForElementToBeRemoved } from '@testing-library/r import i18next from '@polkadot/react-components/i18n'; import { MemoryStore } from '@polkadot/test-support/keyring'; +import { assertButtonDisabled, assertText, clickButton, fillInput } from '@polkadot/test-support/utils/renderedScreenUtils'; import { keyring } from '@polkadot/ui-keyring'; import { AccountsPage } from '../../test/pages/accountsPage'; @@ -33,15 +34,15 @@ describe('Create an account modal', () => { assertButtonDisabled('Next'); await fillFirstStep(); - clickButton('Next'); + await clickButton('Next'); await expectSecondStep(); assertButtonDisabled('Next'); fillSecondStep(); - clickButton('Next'); + await clickButton('Next'); await expectThirdStep(); - clickButton('Save'); + await clickButton('Save'); await waitForElementToBeRemoved(() => screen.queryByText('Add an account via seed 3/3')); expectCreateAnAccountCall(); @@ -75,7 +76,7 @@ describe('Create an account modal', () => { fillInput('secret derivation path', '//abc//'); - await screen.findByText('Unable to match provided value to a secret URI'); + await assertText('Unable to match provided value to a secret URI'); }); }); @@ -109,24 +110,6 @@ function expectCreateAnAccountCall () { ); } -function fillInput (testId: string, value: string) { - const nameInput = screen.getByTestId(testId); - - fireEvent.change(nameInput, { target: { value } }); -} - -function assertButtonDisabled (name: string) { - const button = screen.getByRole('button', { name }); - - expect(button).toHaveClass('isDisabled'); -} - -function clickButton (name: string) { - const button = screen.getByRole('button', { name }); - - fireEvent.click(button); -} - async function expectFirstStep () { await screen.findByText('Add an account via seed 1/3'); } diff --git a/packages/page-accounts/test/pageElements/AccountRow.ts b/packages/page-accounts/test/pageElements/AccountRow.ts deleted file mode 100644 index cc7ba53f71..0000000000 --- a/packages/page-accounts/test/pageElements/AccountRow.ts +++ /dev/null @@ -1,70 +0,0 @@ -// 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/AccountRow.tsx b/packages/page-accounts/test/pageElements/AccountRow.tsx new file mode 100644 index 0000000000..5f8a779be8 --- /dev/null +++ b/packages/page-accounts/test/pageElements/AccountRow.tsx @@ -0,0 +1,14 @@ +// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { within } from '@testing-library/react'; + +import { Row } from '@polkadot/test-support/pagesElements/Row'; + +export class AccountRow extends Row { + async assertParentAccountName (expectedParentAccount: string): Promise { + const parentAccount = await within(this.primaryRow).findByTestId('parent'); + + expect(parentAccount).toHaveTextContent(expectedParentAccount); + } +} diff --git a/packages/page-accounts/test/pages/accountsPage.tsx b/packages/page-accounts/test/pages/accountsPage.tsx index 5d3e72a2de..61559c1357 100644 --- a/packages/page-accounts/test/pages/accountsPage.tsx +++ b/packages/page-accounts/test/pages/accountsPage.tsx @@ -1,260 +1,92 @@ // Copyright 2017-2021 @polkadot/page-accounts authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { fireEvent, render, RenderResult, screen, within } from '@testing-library/react'; -import BN from 'bn.js'; -import React, { Suspense } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { ThemeProvider } from 'styled-components'; +import { fireEvent, screen, within } from '@testing-library/react'; +import React from 'react'; -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 { anAccount } from '@polkadot/test-support/creation/account'; +import { Page } from '@polkadot/test-support/pages/Page'; +import { AccountOverrides } from '@polkadot/test-support/types'; +import { assertText, clickButton } from '@polkadot/test-support/utils/renderedScreenUtils'; -import { AccountOverrides, mockAccountHooks } from '../hooks/default'; +import AccountOverview from '../../src/Accounts/index'; import { AccountRow } from '../pageElements/AccountRow'; -import Overview from '../pages/../../src/Accounts/index'; -let queueExtrinsic: (value: PartialQueueTxExtrinsic) => void; - -function noop (): void { - // ignore -} - -class NotYetRendered extends Error { -} - -jest.mock('@polkadot/react-hooks/useAccounts', () => ({ - useAccounts: () => mockAccountHooks.useAccounts -})); - -jest.mock('@polkadot/react-hooks/useLoadingDelay', () => ({ - useLoadingDelay: () => false -})); - -jest.mock('@polkadot/react-hooks/useBalancesAll', () => ({ - useBalancesAll: (address: string) => mockAccountHooks.accountsMap[address].balance -})); - -jest.mock('@polkadot/react-hooks/useStakingInfo', () => ({ - useStakingInfo: (address: string) => mockAccountHooks.accountsMap[address].staking -})); - -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 - - renderPage (accounts: [string, AccountOverrides][]): void { - mockAccountHooks.setAccounts(accounts); - - accounts.forEach(([address, { meta }]) => { - keyring.addExternal(address, meta); - }); - - const mockApi: ApiProps = { - api: { - derive: { - accounts: { - info: () => { /**/ } - }, - balances: { - all: () => ({ - accountNonce: mockAccountHooks.nonce, - additional: [], - freeBalance: 0, - lockedBreakdown: [], - reservedBalance: 0 - }) - }, - chain: { - bestNumber: () => new BN(1) as BlockNumber - } - }, - genesisHash: new TypeRegistry().createType('Hash', POLKADOT_GENESIS), - query: {}, - registry: { chainDecimals: [12], chainTokens: ['Unit'] }, - tx: { - council: { - } - } - }, - systemName: 'substrate' - } as unknown as ApiProps; - - queueExtrinsic = jest.fn() as QueueTxExtrinsicAdd; - const queue = { - queueExtrinsic - } as QueueProps; - - this.renderResult = render( - <> -
- - - - - - - - - - - - - - - ); +export class AccountsPage extends Page { + constructor () { + super(, 'Account-'); } - async findAccountsTable (): Promise { - this.assertRendered(); + async getAccountRows (): Promise { + const table = await this.getTable(); + const rows = await table.getRows(); - return await screen.findByRole('table'); + return rows.map((row) => new AccountRow(row.primaryRow, row.detailsRow, row.rowIndex)); } - async findAccountRows (): Promise { - const table = await this.findAccountsTable(); - const tableBody = table.getElementsByTagName('tbody')[0]; - const htmlRows = (await within(tableBody).findAllByRole('row')) - .filter((r) => r.className.startsWith('Account-')); - const rows: AccountRow[] = []; + async reverseSortingOrder (): Promise { + const sortingComponent = await this.getSortByComponent(); + const reverseOrderButton = await within(sortingComponent).findByRole('button'); - for (let rowIdx = 0; rowIdx < htmlRows.length; rowIdx = rowIdx + 2) { - const primary = htmlRows[rowIdx]; - const details = htmlRows[rowIdx + 1]; - - rows.push(new AccountRow(primary, details)); - } - - return rows; + fireEvent.click(reverseOrderButton); } - async findSortBySection (): Promise { + async sortBy (category: string): Promise { + const currentCategory = await this.getCurrentSortCategory(); + + fireEvent.click(currentCategory); + + const selectedCategory = await this.getSortCategory(category); + + fireEvent.click(selectedCategory); + } + + async getCurrentSortCategory (): Promise { + const sortByComponent = await this.getSortByComponent(); + + return within(sortByComponent).findByRole('alert'); + } + + async enterCreateAccountModal (): Promise { + this.render([]); + await clickButton('Add account'); + + await assertText('Add an account via seed 1/3'); + } + + renderAccountsWithDefaultAddresses (...overrides: AccountOverrides[]): void { + const accounts = overrides.map((accountProperties, index) => + [this.defaultAddresses[index], accountProperties] as [string, AccountOverrides]); + + this.render(accounts); + } + + renderAccountsForAddresses (...addresses: string[]): void { + const accounts = addresses.map((address) => [address, anAccount()] as [string, AccountOverrides]); + + this.render(accounts); + } + + renderDefaultAccounts (accountsNumber: number): void { + const accounts = Array.from({ length: accountsNumber }, + (_, index) => [this.defaultAddresses[index], anAccount()] as [string, AccountOverrides]); + + this.render(accounts); + } + + private async getSortCategory (categoryName: string): Promise { + const sortByComponent = await this.getSortByComponent(); + const availableCategories = await within(sortByComponent).findAllByRole('option'); + const selectedCategory = availableCategories.find((category) => category.textContent === categoryName); + + expect(selectedCategory).not.toBeUndefined(); + + return selectedCategory as HTMLElement; + } + + private async getSortByComponent (): Promise { this.assertRendered(); return await screen.findByTestId('sort-by-section'); } - - async findSortByDropdownCurrent (): Promise { - this.assertRendered(); - - const section = await this.findSortBySection(); - const alerts = await within(section).findAllByRole('alert'); - const names = await within(section).findAllByText(/\w+/); - const ret = names.filter((x) => alerts.includes(x)); - - expect(ret).toHaveLength(1); - - return ret[0]; - } - - async findSortByDropdownItem (name: string): Promise { - this.assertRendered(); - - const section = await this.findSortBySection(); - const alerts = await within(section).findAllByRole('alert'); - const names = await within(section).findAllByText(name); - const ret = names.filter((x) => !alerts.includes(x)); - - expect(ret).toHaveLength(1); - - return ret[0]; - } - - async findSortByReverseButton (): Promise { - this.assertRendered(); - - const section = await this.findSortBySection(); - - return await within(section).findByRole('button'); - } - - async checkOrder (order: number[]): Promise { - const rows = await this.findAccountRows(); - const rowsBalances = await Promise.all(rows.map((row) => within(row.primaryRow).findByTestId('balance-summary'))); - const expected = order.map((amount) => this.format(balance(amount))); - - for (let i = 0; i < expected.length; i++) { - expect(rowsBalances[i]).toHaveTextContent(expected[i]); - } - } - - async selectOrder (orderBy: string): Promise { - const current = await this.findSortByDropdownCurrent(); - - fireEvent.click(current); - - const target = await this.findSortByDropdownItem(orderBy); - - fireEvent.click(target); - } - - async checkRowsColoring (): Promise { - const rows = await this.findAccountRows(); - - rows.forEach((row, index) => { - const expectedClass = index % 2 ? 'isEven' : 'isOdd'; - - expect(row.primaryRow).toHaveClass(expectedClass); - expect(row.detailsRow).toHaveClass(expectedClass); - }); - } - - async checkOrderAndRowsColoring (order: number[]): Promise { - await this.checkOrder(order); - await this.checkRowsColoring(); - } - - format (amount: Balance | BN): string { - return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true }); - } - - async enterCreateAccountModal (): Promise { - this.renderPage([]); - - fireEvent.click(await screen.findByRole('button', { name: 'Add account' })); - await screen.findByText('Add an account via seed 1/3'); - } - - private assertRendered () { - if (this.renderResult === undefined) { - throw new NotYetRendered(); - } - } } - -export const format = (amount: Balance | BN): string => { - return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true }); -}; diff --git a/packages/page-addresses/src/Contacts/Address.tsx b/packages/page-addresses/src/Contacts/Address.tsx index aa71e5c2ba..590c8f1d0e 100644 --- a/packages/page-addresses/src/Contacts/Address.tsx +++ b/packages/page-addresses/src/Contacts/Address.tsx @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/app-addresses authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { DeriveAccountInfo, DeriveBalancesAll } from '@polkadot/api-derive/types'; +import type { DeriveAccountInfo } from '@polkadot/api-derive/types'; import type { ActionStatus } from '@polkadot/react-components/Status/types'; import type { ThemeDef } from '@polkadot/react-components/types'; import type { KeyringAddress } from '@polkadot/ui-keyring/types'; @@ -10,8 +10,8 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; import styled, { ThemeContext } from 'styled-components'; import Transfer from '@polkadot/app-accounts/modals/Transfer'; -import { AddressInfo, AddressSmall, Button, ChainLock, Forget, Icon, LinkExternal, Menu, Popup, Tags } from '@polkadot/react-components'; -import { useApi, useCall } from '@polkadot/react-hooks'; +import { AddressInfo, AddressSmall, Button, ChainLock, ExpandButton, Forget, Icon, LinkExternal, Menu, Popup, Tags } from '@polkadot/react-components'; +import { useApi, useBalancesAll, useCall, useToggle } from '@polkadot/react-hooks'; import { keyring } from '@polkadot/ui-keyring'; import { BN_ZERO, formatNumber, isFunction } from '@polkadot/util'; @@ -21,20 +21,19 @@ interface Props { address: string; className?: string; filter: string; + isEven: boolean; isFavorite: boolean; toggleFavorite: (address: string) => void; } -const WITH_BALANCE = { available: true, bonded: true, free: true, locked: true, reserved: true, total: true, unlocking: true }; - const isEditable = true; -function Address ({ address, className = '', filter, isFavorite, toggleFavorite }: Props): React.ReactElement | null { +function Address ({ address, className = '', filter, isEven, isFavorite, toggleFavorite }: Props): React.ReactElement | null { const { t } = useTranslation(); const { theme } = useContext(ThemeContext); const api = useApi(); const info = useCall(api.api.derive.accounts.info, [address]); - const balancesAll = useCall(api.api.derive.balances?.all, [address]); + const balancesAll = useBalancesAll(address); const [tags, setTags] = useState([]); const [accName, setAccName] = useState(''); const [current, setCurrent] = useState(null); @@ -42,6 +41,7 @@ function Address ({ address, className = '', filter, isFavorite, toggleFavorite const [isForgetOpen, setIsForgetOpen] = useState(false); const [isTransferOpen, setIsTransferOpen] = useState(false); const [isVisible, setIsVisible] = useState(true); + const [isExpanded, toggleIsExpanded] = useToggle(false); const _setTags = useCallback( (tags: string[]): void => setTags(tags.sort()), @@ -164,82 +164,160 @@ function Address ({ address, className = '', filter, isFavorite, toggleFavorite ); return ( - - - - - - - {address && current && ( - <> - {isForgetOpen && ( - - )} - {isTransferOpen && ( - - )} - - )} - - -
- -
- - - {balancesAll?.accountNonce.gt(BN_ZERO) && formatNumber(balancesAll.accountNonce)} - - - - - - - - - {isFunction(api.api.tx.balances?.transfer) && ( -
+ + + + + +
+ +
+ + + + + + + + ); } export default React.memo(styled(Address)` + &.isCollapsed { + visibility: collapse; + } + + &.isExpanded { + visibility: visible; + } + .tags { width: 100%; min-height: 1.5rem; } + + && td.button { + padding-bottom: 0.5rem; + } + + .fast-actions-addresses { + padding-left: 0.2rem; + padding-right: 1rem; + width: 1%; + + .fast-actions-row { + display: flex; + align-items: center; + justify-content: flex-end; + + & > * + * { + margin-left: 0.35rem; + } + + .send-button { + min-width: 6.5rem; + } + } + } `); diff --git a/packages/page-addresses/src/Contacts/index.spec.ts b/packages/page-addresses/src/Contacts/index.spec.ts new file mode 100644 index 0000000000..cb50298f99 --- /dev/null +++ b/packages/page-addresses/src/Contacts/index.spec.ts @@ -0,0 +1,135 @@ +// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { screen } from '@testing-library/react'; + +import i18next from '@polkadot/react-components/i18n'; +import { aContactWithBalance } from '@polkadot/test-support/creation/contact'; +import { MemoryStore } from '@polkadot/test-support/keyring'; +import { Table } from '@polkadot/test-support/pagesElements'; +import { balance } from '@polkadot/test-support/utils/balance'; +import { keyring } from '@polkadot/ui-keyring'; + +import { AddressesPage } from '../../test/pages/addressesPage'; + +describe('Addresses page', () => { + let addressesPage: AddressesPage; + + beforeAll(async () => { + await i18next.changeLanguage('en'); + + if (keyring.getAccounts().length === 0) { + keyring.loadAll({ isDevelopment: true, store: new MemoryStore() }); + } + }); + + beforeEach(() => { + addressesPage = new AddressesPage(); + addressesPage.clearAccounts(); + }); + + describe('when no contacts', () => { + let addressesTable: Table; + + beforeEach(async () => { + addressesPage.render([]); + addressesTable = await addressesPage.getTable(); + }); + + it('shows a table', () => { + expect(addressesTable).not.toBeNull(); + }); + + it('the contacts table contains no contact rows', async () => { + expect(await addressesTable.getRows()).toHaveLength(0); + }); + + it('the contacts table contains a message about no contacts available', async () => { + const noContactsMessage = 'no addresses saved yet, add any existing address'; + + await addressesTable.assertText(noContactsMessage); + }); + + it('no summary is displayed', () => { + const summaries = screen.queryAllByTestId(/card-summary:total \w+/i); + + expect(summaries).toHaveLength(0); + }); + }); + + describe('when some contacts exist', () => { + it('the contacts table contains some contact rows', async () => { + addressesPage.renderDefaultContacts(2); + const rows = await addressesPage.getAddressesRows(); + + expect(rows).toHaveLength(2); + }); + + it('contact rows display the total balance info', async () => { + addressesPage.renderContactsWithDefaultAddresses( + aContactWithBalance({ freeBalance: balance(500) }), + aContactWithBalance({ freeBalance: balance(200), reservedBalance: balance(150) }) + ); + + const rows = await addressesPage.getAddressesRows(); + + await rows[0].assertBalancesTotal(balance(500)); + await rows[1].assertBalancesTotal(balance(350)); + }); + + it('contact rows display the details balance info', async () => { + addressesPage.renderContactsWithDefaultAddresses( + aContactWithBalance({ freeBalance: balance(500), lockedBalance: balance(30) }), + aContactWithBalance({ availableBalance: balance(50), freeBalance: balance(200), reservedBalance: balance(150) }) + ); + + const rows = await addressesPage.getAddressesRows(); + + await rows[0].assertBalancesDetails([ + { amount: balance(0), name: 'transferrable' }, + { amount: balance(30), name: 'locked' }]); + await rows[1].assertBalancesDetails([ + { amount: balance(50), name: 'transferrable' }, + { amount: balance(150), name: 'reserved' }]); + }); + + it('when a contact is not tagged, details row displays no tags info', async () => { + addressesPage.renderDefaultContacts(1); + const rows = await addressesPage.getAddressesRows(); + + await rows[0].assertTags('no tags'); + }); + + it('when a contact is tagged, the details row displays tags', async () => { + const injectedAddress = '5CMCFVfsauWXmKaUB6tbznVUpBxcUZyU78DzvPTYrhdXe8Xp'; + + addressesPage.renderContacts([ + [injectedAddress, { meta: { tags: ['my tag', 'Super Tag'] } }] + ]); + + const rows = await addressesPage.getAddressesRows(); + + expect(rows).toHaveLength(1); + + await rows[0].assertTags('Super Tagmy tag'); + }); + + it('details rows keep colouring from their primary rows', async () => { + addressesPage.renderDefaultContacts(3); + const addressesTable = await addressesPage.getTable(); + + await addressesTable.assertColoring(); + }); + + it('contact details rows toggled on icon toggle click', async () => { + addressesPage.renderDefaultContacts(1); + const row = (await addressesPage.getAddressesRows())[0]; + + expect(row.detailsRow).toHaveClass('isCollapsed'); + + await row.expand(); + + expect(row.detailsRow).toHaveClass('isExpanded'); + }); + }); +}); diff --git a/packages/page-addresses/src/Contacts/index.tsx b/packages/page-addresses/src/Contacts/index.tsx index 60ece168e9..ebad83bfea 100644 --- a/packages/page-addresses/src/Contacts/index.tsx +++ b/packages/page-addresses/src/Contacts/index.tsx @@ -28,9 +28,8 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen const headerRef = useRef([ [t('contacts'), 'start', 2], - [t('tags'), 'start'], - [t('transactions'), 'media--1500'], - [t('balances'), 'expand'], + [t('transactions'), 'number media--1500'], + [t('balances'), 'balances'], [undefined, 'media--1400'], [] ]); @@ -81,10 +80,11 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen filter={filter} header={headerRef.current} > - {!isLoading && sortedAddresses?.map(({ address, isFavorite }): React.ReactNode => ( + {!isLoading && sortedAddresses?.map(({ address, isFavorite }, index): React.ReactNode => (
({ + useAddresses: () => ({ + allAddresses: mockAccountHooks.useAccounts.allAccounts, + hasAddresses: mockAccountHooks.useAccounts.hasAccounts, + isAddress: true + }) +})); + +export class AddressesPage extends Page { + constructor () { + super(, 'Address-'); + } + + async getAddressesRows (): Promise { + const addressesTable = await this.getTable(); + + return addressesTable.getRows(); + } + + renderDefaultContacts (contactsNumber: number): void { + const contacts = Array.from({ length: contactsNumber }, + (_, index) => [this.defaultAddresses[index], aContact()] as [string, ContactOverrides]); + + this.render(contacts); + } + + renderContactsWithDefaultAddresses (...overrides: ContactOverrides[]): void { + const contacts = overrides.map((contactProperties, index) => + [this.defaultAddresses[index], contactProperties] as [string, ContactOverrides]); + + this.render(contacts); + } + + renderContactsForAddresses (...addresses: string[]): void { + const contacts = addresses.map((address) => [address, aContact()] as [string, ContactOverrides]); + + this.render(contacts); + } + + renderContacts (contacts: [string, ContactOverrides][]): void { + this.render(contacts); + } +} diff --git a/packages/test-support/src/creation/account.ts b/packages/test-support/src/creation/account.ts new file mode 100644 index 0000000000..470bfa4d74 --- /dev/null +++ b/packages/test-support/src/creation/account.ts @@ -0,0 +1,31 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types'; +import { UseAccountInfo } from '@polkadot/react-hooks/types'; +import { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; + +import { AccountOverrides, Override } from '../types'; + +export const anAccount = (): AccountOverrides => ({}); + +export const anAccountWithBalance = (balance: Override): AccountOverrides => ({ + balance +}); + +export const anAccountWithBalanceAndMeta = (balance: Override, meta: Override): AccountOverrides => ({ + balance, + meta +}); + +export const anAccountWithInfo = (info: Override): AccountOverrides => ({ + info +}); + +export const anAccountWithMeta = (meta: Override): AccountOverrides => ({ + meta +}); + +export const anAccountWithStaking = (staking: Override): AccountOverrides => ({ + staking +}); diff --git a/packages/test-support/src/creation/balance.ts b/packages/test-support/src/creation/balance.ts index 8d1b50fcb4..7436d79492 100644 --- a/packages/test-support/src/creation/balance.ts +++ b/packages/test-support/src/creation/balance.ts @@ -1,4 +1,4 @@ -// Copyright 2017-2021 @polkadot/app-bounties authors & contributors +// Copyright 2017-2021 @polkadot/test-supports authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { Balance } from '@polkadot/types/interfaces'; diff --git a/packages/test-support/src/creation/contact.ts b/packages/test-support/src/creation/contact.ts new file mode 100644 index 0000000000..bb036f2bdf --- /dev/null +++ b/packages/test-support/src/creation/contact.ts @@ -0,0 +1,26 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types'; +import { UseAccountInfo } from '@polkadot/react-hooks/types'; +import { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; + +import { AccountOverrides as ContactOverrides, Override } from '../types'; + +export const aContact = (): ContactOverrides => ({}); + +export const aContactWithBalance = (balance: Override): ContactOverrides => ({ + balance +}); + +export const aContactWithInfo = (info: Override): ContactOverrides => ({ + info +}); + +export const aContactWithStaking = (staking: Override): ContactOverrides => ({ + staking +}); + +export const aContactWithMeta = (meta: Override): ContactOverrides => ({ + meta +}); diff --git a/packages/test-support/src/keyring/addresses.ts b/packages/test-support/src/keyring/addresses.ts index 702f643410..0460a338c9 100644 --- a/packages/test-support/src/keyring/addresses.ts +++ b/packages/test-support/src/keyring/addresses.ts @@ -3,5 +3,6 @@ export const alice = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; export const bob = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; +export const charlie = '5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy'; export const ferdie = '5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL'; export const defaultMembers = { isMember: true, members: [alice, bob, ferdie] }; diff --git a/packages/test-support/src/keyring/index.ts b/packages/test-support/src/keyring/index.ts index d7ae03da5c..fbb99af2e1 100644 --- a/packages/test-support/src/keyring/index.ts +++ b/packages/test-support/src/keyring/index.ts @@ -1,5 +1,6 @@ // Copyright 2017-2021 @polkadot/test-support authors & contributors // SPDX-License-Identifier: Apache-2.0 +export * from './addresses'; export * from './MemoryStore'; export * from './signers'; diff --git a/packages/test-support/src/pages/Page.tsx b/packages/test-support/src/pages/Page.tsx new file mode 100644 index 0000000000..2f9637d880 --- /dev/null +++ b/packages/test-support/src/pages/Page.tsx @@ -0,0 +1,161 @@ +// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { render, RenderResult, screen } from '@testing-library/react'; +import BN from 'bn.js'; +import React, { Suspense } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { ThemeProvider } from 'styled-components'; + +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 { BlockNumber } from '@polkadot/types/interfaces'; +import { keyring } from '@polkadot/ui-keyring'; + +import { alice, bob, charlie } from '../keyring'; +import { Table } from '../pagesElements'; +import { AccountOverrides, mockAccountHooks } from '../utils/accountDefaults'; + +let queueExtrinsic: (value: PartialQueueTxExtrinsic) => void; + +function noop (): void { + // ignore +} + +class NotYetRendered extends Error { +} + +jest.mock('@polkadot/react-hooks/useAccounts', () => ({ + useAccounts: () => mockAccountHooks.useAccounts +})); + +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); + } + }); +}); + +jest.mock('@polkadot/react-hooks/useLoadingDelay', () => ({ + useLoadingDelay: () => false +})); + +jest.mock('@polkadot/react-hooks/useBalancesAll', () => ({ + useBalancesAll: (address: string) => mockAccountHooks.accountsMap[address].balance +})); + +jest.mock('@polkadot/react-hooks/useStakingInfo', () => ({ + useStakingInfo: (address: string) => mockAccountHooks.accountsMap[address].staking +})); + +jest.mock('@polkadot/react-hooks/useBestNumber', () => ({ + useBestNumber: () => 1 +})); + +export abstract class Page { + private renderResult?: RenderResult + protected readonly defaultAddresses = [alice, bob, charlie]; + + protected constructor (private readonly overview: React.ReactElement, private readonly rowClassName: string) { + this.overview = overview; + this.rowClassName = rowClassName; + } + + render (accounts: [string, AccountOverrides][]): void { + mockAccountHooks.setAccounts(accounts); + + accounts.forEach(([address, { meta }]) => { + keyring.addExternal(address, meta); + }); + + const mockApi: ApiProps = { + api: { + derive: { + accounts: { + info: () => Promise.resolve(() => { /**/ + }) + }, + balances: { + all: () => ({ + accountNonce: mockAccountHooks.nonce, + additional: [], + freeBalance: 0, + lockedBreakdown: [], + reservedBalance: 0 + }) + }, + chain: { + bestNumber: () => new BN(1) as BlockNumber + } + }, + genesisHash: new TypeRegistry().createType('Hash', POLKADOT_GENESIS), + query: {}, + registry: { chainDecimals: [12], chainTokens: ['Unit'] }, + tx: { + council: { + } + } + }, + systemName: 'substrate' + } as unknown as ApiProps; + + queueExtrinsic = jest.fn() as QueueTxExtrinsicAdd; + const queue = { + queueExtrinsic + } as QueueProps; + + this.renderResult = render( + <> +
+ + + + + + + {React.cloneElement(this.overview, { onStatusChange: noop }) } + + + + + + + + ); + } + + async getTable (): Promise { + this.assertRendered(); + + return new Table(await screen.findByRole('table'), this.rowClassName); + } + + clearAccounts (): void { + this.defaultAddresses.forEach((address) => keyring.forgetAccount(address)); + } + + protected assertRendered (): void { + if (this.renderResult === undefined) { + throw new NotYetRendered(); + } + } +} diff --git a/packages/test-support/src/pagesElements/Row.ts b/packages/test-support/src/pagesElements/Row.ts new file mode 100644 index 0000000000..78514b168b --- /dev/null +++ b/packages/test-support/src/pagesElements/Row.ts @@ -0,0 +1,99 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +// utility wrapper over an account item in accounts table, serves basic assertions about an account row +import { fireEvent, screen, within } from '@testing-library/react'; + +import { Sidebar } from '@polkadot/test-support/pagesElements/Sidebar'; +import { Balance } from '@polkadot/types/interfaces'; + +import { format } from '../utils/balance'; + +export class Row { + public primaryRow: HTMLElement; + public detailsRow: HTMLElement; + public rowIndex: number; + + constructor (primaryRow: HTMLElement, detailsRow: HTMLElement, rowIndex: number) { + this.primaryRow = primaryRow; + this.detailsRow = detailsRow; + this.rowIndex = rowIndex; + } + + async assertBalancesTotal (expectedTotalBalance: Balance): Promise { + const actualBalanceText = await this.getBalanceSummary(); + const expectedBalanceText = format(expectedTotalBalance); + + expect(actualBalanceText).toHaveTextContent(expectedBalanceText); + } + + async getBalanceSummary (): Promise { + return within(this.primaryRow).findByTestId('balance-summary'); + } + + async assertAccountName (expectedName: string): Promise { + const accountName = await this.getAccountName(); + + expect(accountName).toHaveTextContent(expectedName); + } + + async assertBalancesDetails (expectedBalanceComponents: { name: string, amount: Balance }[]): Promise { + for (const { amount, name } of expectedBalanceComponents) { + await this.assertBalanceComponent({ amount, name }); + } + } + + async assertTags (expectedTagsContent: string): Promise { + const actualTags = await within(this.detailsRow).findByTestId('tags'); + + expect(actualTags).toHaveTextContent(expectedTagsContent); + } + + async assertShortAddress (expectedShortAddress: string): Promise { + const actualShortAddress = await within(this.primaryRow).findByTestId('short-address'); + + expect(actualShortAddress).toHaveTextContent(expectedShortAddress); + } + + assertColoring (): void { + const expectedColorClass = this.getExpectedColorClass(); + + expect(this.primaryRow).toHaveClass(expectedColorClass); + expect(this.detailsRow).toHaveClass(expectedColorClass); + } + + async expand (): Promise { + const toggle = await within(this.primaryRow).findByTestId('row-toggle'); + + fireEvent.click(toggle); + } + + async openSidebar (): Promise { + const accountName = await this.getAccountName(); + + fireEvent.click(accountName); + + return new Sidebar(await screen.findByTestId('account-sidebar')); + } + + private async assertBalanceComponent (expectedBalanceComponent: { name: string; amount: Balance }): Promise { + const balanceElement = await this.getBalanceElementByLabelName(expectedBalanceComponent.name); + const balanceText = format(expectedBalanceComponent.amount); + + expect(balanceElement).toHaveTextContent(balanceText); + } + + private async getBalanceElementByLabelName (labelName: string): Promise { + const labelElement = await within(this.detailsRow).findByText(labelName); + + return labelElement.nextSibling; + } + + private getAccountName (): Promise { + return within(this.primaryRow).findByTestId('account-name'); + } + + private getExpectedColorClass (): string { + return this.rowIndex % 2 ? 'isEven' : 'isOdd'; + } +} diff --git a/packages/page-accounts/test/pageElements/Sidebar.ts b/packages/test-support/src/pagesElements/Sidebar.ts similarity index 93% rename from packages/page-accounts/test/pageElements/Sidebar.ts rename to packages/test-support/src/pagesElements/Sidebar.ts index 78656f85fb..fcd4e40c72 100644 --- a/packages/page-accounts/test/pageElements/Sidebar.ts +++ b/packages/test-support/src/pagesElements/Sidebar.ts @@ -31,9 +31,9 @@ export class Sidebar { } async assertAccountInput (expectedInput: string): Promise { - const sideBarName = await this.findByTestId('name-input'); + const nameInput = await this.findByTestId('name-input'); - expect(sideBarName).toHaveProperty('value', expectedInput); + expect(nameInput).toHaveProperty('value', expectedInput); } async assertAccountName (expectedAccountName: string): Promise { @@ -109,9 +109,9 @@ export class Sidebar { } private clickButton (buttonName: string) { - const saveButton = this.getByRole('button', { name: buttonName }); + const button = this.getByRole('button', { name: buttonName }); - fireEvent.click(saveButton); + fireEvent.click(button); } private openTagsDropdown (): HTMLElement { diff --git a/packages/test-support/src/pagesElements/Table.ts b/packages/test-support/src/pagesElements/Table.ts new file mode 100644 index 0000000000..619d516e84 --- /dev/null +++ b/packages/test-support/src/pagesElements/Table.ts @@ -0,0 +1,75 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { within } from '@testing-library/react'; + +import { showBalance } from '../utils/balance'; +import { Row } from './Row'; + +export class Table { + constructor (private readonly table: HTMLElement, private readonly rowClassName: string) { + this.table = table; + this.rowClassName = rowClassName; + } + + async assertRowsOrderAndColoring (order: number[]): Promise { + await this.assertRowsOrder(order); + await this.assertColoring(); + } + + async assertRowsOrder (balancesExpectedOrder: number[]): Promise { + const orderedRows = await this.getRows(); + + await Promise.all(orderedRows.map(async (row, index) => { + const expectedBalanceTextContent = showBalance(balancesExpectedOrder[index]); + + expect(await row.getBalanceSummary()).toHaveTextContent(expectedBalanceTextContent); + })); + } + + async assertColoring (): Promise { + const collapsibleRows = await this.getRows(); + + for (const collapsibleRow of collapsibleRows) { + collapsibleRow.assertColoring(); + } + } + + async getRows (): Promise { + const htmlRows = await this.getFilteredHtmlRows(); + const collapsibleRows: Row[] = []; + + for (let rowIdx = 0; rowIdx < htmlRows.length; rowIdx = rowIdx + 2) { + const primaryRow = htmlRows[rowIdx]; + const detailsRow = htmlRows[rowIdx + 1]; + + collapsibleRows.push(new Row(primaryRow, detailsRow, rowIdx / 2)); + } + + return collapsibleRows; + } + + assertColumnNotExist (columnName: string): void { + expect(within(this.table).queryByRole('columnheader', { name: columnName })).toBeFalsy(); + } + + assertColumnExists (columnName: string): void { + expect(within(this.table).getByRole('columnheader', { name: columnName })).toBeTruthy(); + } + + async assertText (text: string): Promise { + return within(this.table).findByText(text); + } + + private async getFilteredHtmlRows (): Promise { + const htmlRows = await this.getAllHtmlRows(); + + return htmlRows.filter((row) => row.className.startsWith(this.rowClassName)); + } + + private async getAllHtmlRows (): Promise { + const tableBody = this.table.getElementsByTagName('tbody')[0]; + + return within(tableBody).findAllByRole('row'); + } +} diff --git a/packages/test-support/src/pagesElements/index.ts b/packages/test-support/src/pagesElements/index.ts new file mode 100644 index 0000000000..12d5fe1119 --- /dev/null +++ b/packages/test-support/src/pagesElements/index.ts @@ -0,0 +1,5 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export * from './Row'; +export * from './Table'; diff --git a/packages/test-support/src/types.ts b/packages/test-support/src/types.ts new file mode 100644 index 0000000000..8007d7ba7f --- /dev/null +++ b/packages/test-support/src/types.ts @@ -0,0 +1,17 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types'; +import { UseAccountInfo } from '@polkadot/react-hooks/types'; +import { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; + +export type Override = { + [P in keyof T]?: T[P]; +} + +export interface AccountOverrides { + meta?: Override; + balance?: Override; + staking?: Override; + info?: Override; +} diff --git a/packages/page-accounts/test/hooks/default.ts b/packages/test-support/src/utils/accountDefaults.ts similarity index 94% rename from packages/page-accounts/test/hooks/default.ts rename to packages/test-support/src/utils/accountDefaults.ts index fcf2cac448..f7946080cb 100644 --- a/packages/page-accounts/test/hooks/default.ts +++ b/packages/test-support/src/utils/accountDefaults.ts @@ -1,4 +1,4 @@ -// Copyright 2017-2021 @polkadot/page-accounts authors & contributors +// Copyright 2017-2021 @polkadot/test-supports authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types'; @@ -7,10 +7,11 @@ import type { UseAccountInfo } from '@polkadot/react-hooks/types'; import BN from 'bn.js'; import { UseAccounts } from '@polkadot/react-hooks/useAccounts'; -import { balanceOf } from '@polkadot/test-support/creation/balance'; -import { makeStakingLedger } from '@polkadot/test-support/creation/stakingInfo/stakingLedger'; import { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; +import { balanceOf } from '../creation/balance'; +import { makeStakingLedger } from '../creation/stakingInfo/stakingLedger'; + export interface Account { balance: DeriveBalancesAll, info: UseAccountInfo, diff --git a/packages/test-support/src/utils/balance.ts b/packages/test-support/src/utils/balance.ts new file mode 100644 index 0000000000..6d9dcdd028 --- /dev/null +++ b/packages/test-support/src/utils/balance.ts @@ -0,0 +1,29 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import BN from 'bn.js'; + +import { Balance } from '@polkadot/types/interfaces'; +import { formatBalance } from '@polkadot/util'; + +import { balanceOf } from '../creation/balance'; + +/** + * Creates a balance instance for testing purposes which most often do not need to specifiy/use decimal part. + * @param amountInt Integer part of the balance number + * @param decimalsString Decimals part of the balance number. Note! This is a string sequence just after '.' separator + * that is the point that separates integers from decimals. E.g. (100, 4567) => 100.45670000...00 + */ +export function balance (amountInt: number, decimalsString?: string): Balance { + const decimalsPadded = (decimalsString || '').padEnd(12, '0'); + + return balanceOf(amountInt.toString() + decimalsPadded); +} + +export function showBalance (amount: number): string { + return format(balance(amount)); +} + +export function format (amount: Balance | BN): string { + return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true }); +} diff --git a/packages/test-support/src/utils/renderedScreenUtils.ts b/packages/test-support/src/utils/renderedScreenUtils.ts new file mode 100644 index 0000000000..7470131a74 --- /dev/null +++ b/packages/test-support/src/utils/renderedScreenUtils.ts @@ -0,0 +1,26 @@ +// Copyright 2017-2021 @polkadot/test-supports authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { fireEvent, screen } from '@testing-library/react'; + +export const clickButton = async (buttonName: string): Promise => { + const button = await screen.findByRole('button', { name: buttonName }); + + fireEvent.click(button); +}; + +export const assertText = async (text: string): Promise => { + return screen.findByText(text); +}; + +export const fillInput = (inputTestId: string, value: string): void => { + const nameInput = screen.getByTestId(inputTestId); + + fireEvent.change(nameInput, { target: { value } }); +}; + +export const assertButtonDisabled = (buttonName: string): void => { + const button = screen.getByRole('button', { name: buttonName }); + + expect(button).toHaveClass('isDisabled'); +};