Apply collapsible row styling for addresses (#6122)
* Apply collapsible row for addresses * Apply merge improvements * Style buttons * Restore unused export Co-authored-by: MiZiet <michalzietalak@o2.pl>
This commit is contained in:
parent
2bbe1b9d34
commit
52b706ef14
@ -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<DeriveBalancesAll>) => ({
|
||||
balance
|
||||
});
|
||||
|
||||
const anAccountWithBalanceAndMeta = (balance: Override<DeriveBalancesAll>, meta: Override<KeyringJson$Meta>) => ({
|
||||
balance,
|
||||
meta
|
||||
});
|
||||
|
||||
const anAccountWithInfo = (info: Override<UseAccountInfo>) => ({
|
||||
info
|
||||
});
|
||||
|
||||
const anAccountWithMeta = (meta: Override<KeyringJson$Meta>) => ({
|
||||
meta
|
||||
});
|
||||
|
||||
const anAccountWithStaking = (staking: Override<DeriveStakingAccount>) => ({
|
||||
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);
|
||||
};
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
const accountName = await within(this.primaryRow).findByTestId('account-name');
|
||||
|
||||
expect(accountName).toHaveTextContent(expectedName);
|
||||
}
|
||||
|
||||
async assertParentAccountName (expectedParentAccount: string): Promise<void> {
|
||||
const parentAccount = await within(this.primaryRow).findByTestId('parent');
|
||||
|
||||
expect(parentAccount).toHaveTextContent(expectedParentAccount);
|
||||
}
|
||||
|
||||
async assertTags (tagsContent: string): Promise<void> {
|
||||
const tagsActual = await within(this.detailsRow).findByTestId('tags');
|
||||
|
||||
expect(tagsActual).toHaveTextContent(tagsContent);
|
||||
}
|
||||
|
||||
async assertShortAddress (expectedShortAddress: string): Promise<void> {
|
||||
const actualShortAddress = await within(this.primaryRow).findByTestId('short-address');
|
||||
|
||||
expect(actualShortAddress).toHaveTextContent(expectedShortAddress);
|
||||
}
|
||||
|
||||
async openSidebar (): Promise<Sidebar> {
|
||||
const accountName = await within(this.primaryRow).findByTestId('account-name');
|
||||
|
||||
fireEvent.click(accountName);
|
||||
|
||||
return new Sidebar(await screen.findByTestId('account-sidebar'));
|
||||
}
|
||||
}
|
||||
14
packages/page-accounts/test/pageElements/AccountRow.tsx
Normal file
14
packages/page-accounts/test/pageElements/AccountRow.tsx
Normal file
@ -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<void> {
|
||||
const parentAccount = await within(this.primaryRow).findByTestId('parent');
|
||||
|
||||
expect(parentAccount).toHaveTextContent(expectedParentAccount);
|
||||
}
|
||||
}
|
||||
@ -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(
|
||||
<>
|
||||
<div id='tooltips' />
|
||||
<Suspense fallback='...'>
|
||||
<QueueProvider value={queue}>
|
||||
<MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApiContext.Provider value={mockApi}>
|
||||
<AccountSidebar>
|
||||
<Overview onStatusChange={noop} />
|
||||
</AccountSidebar>
|
||||
</ApiContext.Provider>
|
||||
</ThemeProvider>
|
||||
</MemoryRouter>
|
||||
</QueueProvider>
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
export class AccountsPage extends Page {
|
||||
constructor () {
|
||||
super(<AccountOverview />, 'Account-');
|
||||
}
|
||||
|
||||
async findAccountsTable (): Promise<HTMLElement> {
|
||||
this.assertRendered();
|
||||
async getAccountRows (): Promise<AccountRow[]> {
|
||||
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<AccountRow[]> {
|
||||
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<void> {
|
||||
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<HTMLElement> {
|
||||
async sortBy (category: string): Promise<void> {
|
||||
const currentCategory = await this.getCurrentSortCategory();
|
||||
|
||||
fireEvent.click(currentCategory);
|
||||
|
||||
const selectedCategory = await this.getSortCategory(category);
|
||||
|
||||
fireEvent.click(selectedCategory);
|
||||
}
|
||||
|
||||
async getCurrentSortCategory (): Promise<HTMLElement> {
|
||||
const sortByComponent = await this.getSortByComponent();
|
||||
|
||||
return within(sortByComponent).findByRole('alert');
|
||||
}
|
||||
|
||||
async enterCreateAccountModal (): Promise<void> {
|
||||
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<HTMLElement> {
|
||||
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<HTMLElement> {
|
||||
this.assertRendered();
|
||||
|
||||
return await screen.findByTestId('sort-by-section');
|
||||
}
|
||||
|
||||
async findSortByDropdownCurrent (): Promise<HTMLElement> {
|
||||
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<HTMLElement> {
|
||||
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<HTMLElement> {
|
||||
this.assertRendered();
|
||||
|
||||
const section = await this.findSortBySection();
|
||||
|
||||
return await within(section).findByRole('button');
|
||||
}
|
||||
|
||||
async checkOrder (order: number[]): Promise<void> {
|
||||
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<void> {
|
||||
const current = await this.findSortByDropdownCurrent();
|
||||
|
||||
fireEvent.click(current);
|
||||
|
||||
const target = await this.findSortByDropdownItem(orderBy);
|
||||
|
||||
fireEvent.click(target);
|
||||
}
|
||||
|
||||
async checkRowsColoring (): Promise<void> {
|
||||
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<void> {
|
||||
await this.checkOrder(order);
|
||||
await this.checkRowsColoring();
|
||||
}
|
||||
|
||||
format (amount: Balance | BN): string {
|
||||
return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true });
|
||||
}
|
||||
|
||||
async enterCreateAccountModal (): Promise<void> {
|
||||
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 });
|
||||
};
|
||||
|
||||
@ -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<Props> | null {
|
||||
function Address ({ address, className = '', filter, isEven, isFavorite, toggleFavorite }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useContext<ThemeDef>(ThemeContext);
|
||||
const api = useApi();
|
||||
const info = useCall<DeriveAccountInfo>(api.api.derive.accounts.info, [address]);
|
||||
const balancesAll = useCall<DeriveBalancesAll>(api.api.derive.balances?.all, [address]);
|
||||
const balancesAll = useBalancesAll(address);
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [accName, setAccName] = useState('');
|
||||
const [current, setCurrent] = useState<KeyringAddress | null>(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 (
|
||||
<tr className={className}>
|
||||
<td className='favorite'>
|
||||
<Icon
|
||||
color={isFavorite ? 'orange' : 'gray'}
|
||||
icon='star'
|
||||
onClick={_onFavorite}
|
||||
/>
|
||||
</td>
|
||||
<td className='address'>
|
||||
<AddressSmall value={address} />
|
||||
{address && current && (
|
||||
<>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={current.address}
|
||||
key='modal-forget-account'
|
||||
mode='address'
|
||||
onClose={_toggleForget}
|
||||
onForget={_onForget}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
recipientId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className='all'>
|
||||
<div className='tags'>
|
||||
<Tags value={tags} />
|
||||
</div>
|
||||
</td>
|
||||
<td className='number media--1500'>
|
||||
{balancesAll?.accountNonce.gt(BN_ZERO) && formatNumber(balancesAll.accountNonce)}
|
||||
</td>
|
||||
<td className='number'>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={WITH_BALANCE}
|
||||
withBalanceToggle
|
||||
withExtended={false}
|
||||
/>
|
||||
</td>
|
||||
<td className='links media--1400'>
|
||||
<LinkExternal
|
||||
className='ui--AddressCard-exporer-link'
|
||||
data={address}
|
||||
isLogo
|
||||
type='address'
|
||||
/>
|
||||
</td>
|
||||
<td className='button'>
|
||||
{isFunction(api.api.tx.balances?.transfer) && (
|
||||
<Button
|
||||
icon='paper-plane'
|
||||
key='send'
|
||||
label={t<string>('send')}
|
||||
onClick={_toggleTransfer}
|
||||
<>
|
||||
<tr className={`${className}${isExpanded ? ' noBorder' : ''} ${isEven ? 'isEven' : 'isOdd'}`}>
|
||||
<td className='favorite'>
|
||||
<Icon
|
||||
color={isFavorite ? 'orange' : 'gray'}
|
||||
icon='star'
|
||||
onClick={_onFavorite}
|
||||
/>
|
||||
)}
|
||||
<Popup
|
||||
className={`theme--${theme}`}
|
||||
value={PopupDropdown}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
<td className='address'>
|
||||
<AddressSmall value={address} />
|
||||
{address && current && (
|
||||
<>
|
||||
{isForgetOpen && (
|
||||
<Forget
|
||||
address={current.address}
|
||||
key='modal-forget-account'
|
||||
mode='address'
|
||||
onClose={_toggleForget}
|
||||
onForget={_onForget}
|
||||
/>
|
||||
)}
|
||||
{isTransferOpen && (
|
||||
<Transfer
|
||||
key='modal-transfer'
|
||||
onClose={_toggleTransfer}
|
||||
recipientId={address}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className='number media--1500'>
|
||||
{balancesAll?.accountNonce.gt(BN_ZERO) && formatNumber(balancesAll.accountNonce)}
|
||||
</td>
|
||||
<td className='number'>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
balancesAll={balancesAll}
|
||||
withBalance={{
|
||||
available: false,
|
||||
bonded: false,
|
||||
locked: false,
|
||||
redeemable: false,
|
||||
reserved: false,
|
||||
total: true,
|
||||
unlocking: false,
|
||||
vested: false
|
||||
}}
|
||||
withExtended={false}
|
||||
/>
|
||||
</td>
|
||||
<td className='links media--1400'>
|
||||
<LinkExternal
|
||||
className='ui--AddressCard-exporer-link'
|
||||
data={address}
|
||||
isLogo
|
||||
type='address'
|
||||
/>
|
||||
</td>
|
||||
<td className='fast-actions-addresses'>
|
||||
<div className='fast-actions-row'>
|
||||
{isFunction(api.api.tx.balances?.transfer) && (
|
||||
<Button
|
||||
className='send-button'
|
||||
icon='paper-plane'
|
||||
key='send'
|
||||
label={t<string>('send')}
|
||||
onClick={_toggleTransfer}
|
||||
/>
|
||||
)}
|
||||
<Popup
|
||||
className={`theme--${theme}`}
|
||||
value={PopupDropdown}
|
||||
/>
|
||||
<ExpandButton
|
||||
expanded={isExpanded}
|
||||
onClick={toggleIsExpanded}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className={`${className} ${isExpanded ? 'isExpanded' : 'isCollapsed'} ${isEven ? 'isEven' : 'isOdd'}`}>
|
||||
<td />
|
||||
<td>
|
||||
<div
|
||||
className='tags'
|
||||
data-testid='tags'
|
||||
>
|
||||
<Tags
|
||||
value={tags}
|
||||
withTitle
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className='number media--1500' />
|
||||
<td>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
balancesAll={balancesAll}
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: true,
|
||||
locked: true,
|
||||
redeemable: true,
|
||||
reserved: true,
|
||||
total: false,
|
||||
unlocking: true,
|
||||
vested: true
|
||||
}}
|
||||
withExtended={false}
|
||||
/>
|
||||
</td>
|
||||
<td colSpan={2} />
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
135
packages/page-addresses/src/Contacts/index.spec.ts
Normal file
135
packages/page-addresses/src/Contacts/index.spec.ts
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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 => (
|
||||
<Address
|
||||
address={address}
|
||||
filter={filterOn}
|
||||
isEven={!!(index % 2) }
|
||||
isFavorite={isFavorite}
|
||||
key={address}
|
||||
toggleFavorite={toggleFavorite}
|
||||
|
||||
56
packages/page-addresses/test/pages/addressesPage.tsx
Normal file
56
packages/page-addresses/test/pages/addressesPage.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2021 @polkadot/page-addresses authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { aContact } from '@polkadot/test-support/creation/contact';
|
||||
import { Page } from '@polkadot/test-support/pages/Page';
|
||||
import { Row } from '@polkadot/test-support/pagesElements';
|
||||
import { AccountOverrides as ContactOverrides } from '@polkadot/test-support/types';
|
||||
import { mockAccountHooks } from '@polkadot/test-support/utils/accountDefaults';
|
||||
|
||||
import AddressOverview from '../../src/Contacts/index';
|
||||
|
||||
jest.mock('@polkadot/react-hooks/useAddresses', () => ({
|
||||
useAddresses: () => ({
|
||||
allAddresses: mockAccountHooks.useAccounts.allAccounts,
|
||||
hasAddresses: mockAccountHooks.useAccounts.hasAccounts,
|
||||
isAddress: true
|
||||
})
|
||||
}));
|
||||
|
||||
export class AddressesPage extends Page {
|
||||
constructor () {
|
||||
super(<AddressOverview />, 'Address-');
|
||||
}
|
||||
|
||||
async getAddressesRows (): Promise<Row[]> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
31
packages/test-support/src/creation/account.ts
Normal file
31
packages/test-support/src/creation/account.ts
Normal file
@ -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<DeriveBalancesAll>): AccountOverrides => ({
|
||||
balance
|
||||
});
|
||||
|
||||
export const anAccountWithBalanceAndMeta = (balance: Override<DeriveBalancesAll>, meta: Override<KeyringJson$Meta>): AccountOverrides => ({
|
||||
balance,
|
||||
meta
|
||||
});
|
||||
|
||||
export const anAccountWithInfo = (info: Override<UseAccountInfo>): AccountOverrides => ({
|
||||
info
|
||||
});
|
||||
|
||||
export const anAccountWithMeta = (meta: Override<KeyringJson$Meta>): AccountOverrides => ({
|
||||
meta
|
||||
});
|
||||
|
||||
export const anAccountWithStaking = (staking: Override<DeriveStakingAccount>): AccountOverrides => ({
|
||||
staking
|
||||
});
|
||||
@ -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';
|
||||
|
||||
26
packages/test-support/src/creation/contact.ts
Normal file
26
packages/test-support/src/creation/contact.ts
Normal file
@ -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<DeriveBalancesAll>): ContactOverrides => ({
|
||||
balance
|
||||
});
|
||||
|
||||
export const aContactWithInfo = (info: Override<UseAccountInfo>): ContactOverrides => ({
|
||||
info
|
||||
});
|
||||
|
||||
export const aContactWithStaking = (staking: Override<DeriveStakingAccount>): ContactOverrides => ({
|
||||
staking
|
||||
});
|
||||
|
||||
export const aContactWithMeta = (meta: Override<KeyringJson$Meta>): ContactOverrides => ({
|
||||
meta
|
||||
});
|
||||
@ -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] };
|
||||
|
||||
@ -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';
|
||||
|
||||
161
packages/test-support/src/pages/Page.tsx
Normal file
161
packages/test-support/src/pages/Page.tsx
Normal file
@ -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(
|
||||
<>
|
||||
<div id='tooltips' />
|
||||
<Suspense fallback='...'>
|
||||
<QueueProvider value={queue}>
|
||||
<MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApiContext.Provider value={mockApi}>
|
||||
<AccountSidebar>
|
||||
{React.cloneElement(this.overview, { onStatusChange: noop }) }
|
||||
</AccountSidebar>
|
||||
</ApiContext.Provider>
|
||||
</ThemeProvider>
|
||||
</MemoryRouter>
|
||||
</QueueProvider>
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async getTable (): Promise<Table> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
99
packages/test-support/src/pagesElements/Row.ts
Normal file
99
packages/test-support/src/pagesElements/Row.ts
Normal file
@ -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<void> {
|
||||
const actualBalanceText = await this.getBalanceSummary();
|
||||
const expectedBalanceText = format(expectedTotalBalance);
|
||||
|
||||
expect(actualBalanceText).toHaveTextContent(expectedBalanceText);
|
||||
}
|
||||
|
||||
async getBalanceSummary (): Promise<HTMLElement> {
|
||||
return within(this.primaryRow).findByTestId('balance-summary');
|
||||
}
|
||||
|
||||
async assertAccountName (expectedName: string): Promise<void> {
|
||||
const accountName = await this.getAccountName();
|
||||
|
||||
expect(accountName).toHaveTextContent(expectedName);
|
||||
}
|
||||
|
||||
async assertBalancesDetails (expectedBalanceComponents: { name: string, amount: Balance }[]): Promise<void> {
|
||||
for (const { amount, name } of expectedBalanceComponents) {
|
||||
await this.assertBalanceComponent({ amount, name });
|
||||
}
|
||||
}
|
||||
|
||||
async assertTags (expectedTagsContent: string): Promise<void> {
|
||||
const actualTags = await within(this.detailsRow).findByTestId('tags');
|
||||
|
||||
expect(actualTags).toHaveTextContent(expectedTagsContent);
|
||||
}
|
||||
|
||||
async assertShortAddress (expectedShortAddress: string): Promise<void> {
|
||||
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<void> {
|
||||
const toggle = await within(this.primaryRow).findByTestId('row-toggle');
|
||||
|
||||
fireEvent.click(toggle);
|
||||
}
|
||||
|
||||
async openSidebar (): Promise<Sidebar> {
|
||||
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<void> {
|
||||
const balanceElement = await this.getBalanceElementByLabelName(expectedBalanceComponent.name);
|
||||
const balanceText = format(expectedBalanceComponent.amount);
|
||||
|
||||
expect(balanceElement).toHaveTextContent(balanceText);
|
||||
}
|
||||
|
||||
private async getBalanceElementByLabelName (labelName: string): Promise<ChildNode | null> {
|
||||
const labelElement = await within(this.detailsRow).findByText(labelName);
|
||||
|
||||
return labelElement.nextSibling;
|
||||
}
|
||||
|
||||
private getAccountName (): Promise<HTMLElement> {
|
||||
return within(this.primaryRow).findByTestId('account-name');
|
||||
}
|
||||
|
||||
private getExpectedColorClass (): string {
|
||||
return this.rowIndex % 2 ? 'isEven' : 'isOdd';
|
||||
}
|
||||
}
|
||||
@ -31,9 +31,9 @@ export class Sidebar {
|
||||
}
|
||||
|
||||
async assertAccountInput (expectedInput: string): Promise<void> {
|
||||
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<void> {
|
||||
@ -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 {
|
||||
75
packages/test-support/src/pagesElements/Table.ts
Normal file
75
packages/test-support/src/pagesElements/Table.ts
Normal file
@ -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<void> {
|
||||
await this.assertRowsOrder(order);
|
||||
await this.assertColoring();
|
||||
}
|
||||
|
||||
async assertRowsOrder (balancesExpectedOrder: number[]): Promise<void> {
|
||||
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<void> {
|
||||
const collapsibleRows = await this.getRows();
|
||||
|
||||
for (const collapsibleRow of collapsibleRows) {
|
||||
collapsibleRow.assertColoring();
|
||||
}
|
||||
}
|
||||
|
||||
async getRows (): Promise<Row[]> {
|
||||
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<HTMLElement> {
|
||||
return within(this.table).findByText(text);
|
||||
}
|
||||
|
||||
private async getFilteredHtmlRows (): Promise<HTMLElement[]> {
|
||||
const htmlRows = await this.getAllHtmlRows();
|
||||
|
||||
return htmlRows.filter((row) => row.className.startsWith(this.rowClassName));
|
||||
}
|
||||
|
||||
private async getAllHtmlRows (): Promise<HTMLElement[]> {
|
||||
const tableBody = this.table.getElementsByTagName('tbody')[0];
|
||||
|
||||
return within(tableBody).findAllByRole('row');
|
||||
}
|
||||
}
|
||||
5
packages/test-support/src/pagesElements/index.ts
Normal file
5
packages/test-support/src/pagesElements/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// Copyright 2017-2021 @polkadot/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './Row';
|
||||
export * from './Table';
|
||||
17
packages/test-support/src/types.ts
Normal file
17
packages/test-support/src/types.ts
Normal file
@ -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<T> = {
|
||||
[P in keyof T]?: T[P];
|
||||
}
|
||||
|
||||
export interface AccountOverrides {
|
||||
meta?: Override<KeyringJson$Meta>;
|
||||
balance?: Override<DeriveBalancesAll>;
|
||||
staking?: Override<DeriveStakingAccount>;
|
||||
info?: Override<UseAccountInfo>;
|
||||
}
|
||||
@ -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,
|
||||
29
packages/test-support/src/utils/balance.ts
Normal file
29
packages/test-support/src/utils/balance.ts
Normal file
@ -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 });
|
||||
}
|
||||
26
packages/test-support/src/utils/renderedScreenUtils.ts
Normal file
26
packages/test-support/src/utils/renderedScreenUtils.ts
Normal file
@ -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<void> => {
|
||||
const button = await screen.findByRole('button', { name: buttonName });
|
||||
|
||||
fireEvent.click(button);
|
||||
};
|
||||
|
||||
export const assertText = async (text: string): Promise<HTMLElement> => {
|
||||
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');
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user