ghost-portal/packages/page-accounts/test/pages/accountsPage.tsx
Ewa Kowalska ad8226a5c3
Account sidebar - single button for editing name and tags (#6049)
* WIP - merge edition of account name and tags

* [WIP] - tests

* Apply test improvements

* Improve tests

* Improve tests and use useCallback

* WIP - add cancel button

* Outside click

* Add more tests

* Apply styling

* Add opacity on not editable elements

* Remove unnecessary code and fix type of sidebar ref

* Disable eslint rule

* Place edit button within account menu buttons

* Adjust test setup

* Ato-lint fixes

* Update styles

* Apply review suggestions

* Remove unused styles and change type of sidebar ref

* Remove onCancel from useAccountInfo hook

* Fix issue with edit button

* Apply review suggestions, remove tests

* Adjust tests

* Simplify tests

* Apply lint fixes

* Change layout of buttons

Co-authored-by: MiZiet <michalzietalak@o2.pl>
Co-authored-by: Jaco Greeff <jacogr@gmail.com>
2021-09-09 19:40:09 +02:00

261 lines
7.9 KiB
TypeScript

// 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 { balance } from '@polkadot/app-accounts/Accounts/index.spec';
import AccountSidebar from '@polkadot/app-accounts/Sidebar';
import { lightTheme } from '@polkadot/apps/themes';
import { POLKADOT_GENESIS } from '@polkadot/apps-config';
import { ApiContext } from '@polkadot/react-api';
import { ApiProps } from '@polkadot/react-api/types';
import { QueueProvider } from '@polkadot/react-components/Status/Context';
import { PartialQueueTxExtrinsic, QueueProps, QueueTxExtrinsicAdd } from '@polkadot/react-components/Status/types';
import { UseAccountInfo } from '@polkadot/react-hooks/types';
import { TypeRegistry } from '@polkadot/types/create';
import { Balance, BlockNumber } from '@polkadot/types/interfaces';
import { keyring } from '@polkadot/ui-keyring';
import { formatBalance } from '@polkadot/util';
import { AccountOverrides, mockAccountHooks } from '../hooks/default';
import { AccountRow } from '../pageElements/AccountRow';
import Overview from '../pages/../../src/Accounts/index';
let queueExtrinsic: (value: PartialQueueTxExtrinsic) => void;
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>
</>
);
}
async findAccountsTable (): Promise<HTMLElement> {
this.assertRendered();
return await screen.findByRole('table');
}
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[] = [];
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;
}
async findSortBySection (): 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 });
};