Add summary bar for accounts view (#5876)
* Show basic balance info * Show bonded summary * Show redeemable summary * Show unbonding summary * Adopt linter suggestions * Hide cards with zero balance * Apply simplifications Co-authored-by: Jaco <jacogr@gmail.com> * Remove dead code * Simplify balance==undefined case * Add summary bar tests (#5890) * Add tests for summary bar * Improve test case name Co-authored-by: Krzysztof Jelski <kjelski@gmail.com> * Factor empty render into a beforeEach block * Improve name of helper variable Co-authored-by: Krzysztof Jelski <kjelski@gmail.com> * Construct correct instance for StakingLedger.active * Override only the relevant parts in each test case * Simplify test cases Co-authored-by: Krzysztof Jelski <kjelski@gmail.com> Co-authored-by: Jaco <jacogr@gmail.com> Co-authored-by: Krzysztof Jelski <kjelski@gmail.com>
This commit is contained in:
parent
20620dbdde
commit
c9aef5cfe9
@ -1,23 +1,23 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { SubmittableExtrinsic } from '@polkadot/api/types';
|
||||
import type { DeriveDemocracyLock } from '@polkadot/api-derive/types';
|
||||
import type { DeriveDemocracyLock, DeriveStakingAccount } from '@polkadot/api-derive/types';
|
||||
import type { Ledger } from '@polkadot/hw-ledger';
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { ThemeDef } from '@polkadot/react-components/types';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { ProxyDefinition, RecoveryConfig } from '@polkadot/types/interfaces';
|
||||
import type { KeyringAddress, KeyringJson$Meta } from '@polkadot/ui-keyring/types';
|
||||
import type { Delegation } from '../types';
|
||||
import type { AccountBalance, Delegation } from '../types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import styled, { ThemeContext } from 'styled-components';
|
||||
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, LinkExternal, Menu, Popup, StatusContext, Tags } from '@polkadot/react-components';
|
||||
import { useAccountInfo, useApi, useBalancesAll, useBestNumber, useCall, useLedger, useToggle } from '@polkadot/react-hooks';
|
||||
import { useAccountInfo, useApi, useBalancesAll, useBestNumber, useCall, useLedger, useStakingInfo, useToggle } from '@polkadot/react-hooks';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
import { BN_ZERO, formatBalance, formatNumber, isFunction } from '@polkadot/util';
|
||||
|
||||
@ -45,7 +45,7 @@ interface Props {
|
||||
filter: string;
|
||||
isFavorite: boolean;
|
||||
proxy?: [ProxyDefinition[], BN];
|
||||
setBalance: (address: string, value: BN) => void;
|
||||
setBalance: (address: string, value: AccountBalance) => void;
|
||||
toggleFavorite: (address: string) => void;
|
||||
}
|
||||
|
||||
@ -66,6 +66,19 @@ function calcVisible (filter: string, name: string, tags: string[]): boolean {
|
||||
}, name.toLowerCase().includes(_filter));
|
||||
}
|
||||
|
||||
function calcUnbonding (stakingInfo?: DeriveStakingAccount) {
|
||||
if (!stakingInfo?.unlocking) {
|
||||
return BN_ZERO;
|
||||
}
|
||||
|
||||
const filtered = stakingInfo.unlocking
|
||||
.filter(({ remainingEras, value }) => value.gt(BN_ZERO) && remainingEras.gt(BN_ZERO))
|
||||
.map((unlock) => unlock.value);
|
||||
const total = filtered.reduce((total, value) => total.iadd(value), new BN(0));
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
function createClearDemocracyTx (api: ApiPromise, address: string, unlockableIds: BN[]): SubmittableExtrinsic<'promise'> | null {
|
||||
return api.tx.utility
|
||||
? api.tx.utility.batch(
|
||||
@ -94,6 +107,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
|
||||
const { getLedger } = useLedger();
|
||||
const bestNumber = useBestNumber();
|
||||
const balancesAll = useBalancesAll(address);
|
||||
const stakingInfo = useStakingInfo(address);
|
||||
const democracyLocks = useCall<DeriveDemocracyLock[]>(api.api.derive.democracy?.locks, [address]);
|
||||
const recoveryInfo = useCall<RecoveryConfig | null>(api.api.query.recovery?.recoverable, [address], transformRecovery);
|
||||
const multiInfos = useMultisigApprovals(address);
|
||||
@ -118,7 +132,14 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
|
||||
|
||||
useEffect((): void => {
|
||||
if (balancesAll) {
|
||||
setBalance(address, balancesAll.freeBalance.add(balancesAll.reservedBalance));
|
||||
setBalance(address, {
|
||||
bonded: stakingInfo?.stakingLedger.active.unwrap() ?? BN_ZERO,
|
||||
locked: balancesAll.lockedBalance,
|
||||
redeemable: stakingInfo?.redeemable ?? BN_ZERO,
|
||||
total: balancesAll.freeBalance.add(balancesAll.reservedBalance),
|
||||
transferrable: balancesAll.availableBalance,
|
||||
unbonding: calcUnbonding(stakingInfo)
|
||||
});
|
||||
|
||||
api.api.tx.vesting?.vest && setVestingTx(() =>
|
||||
balancesAll.vestingLocked.isZero()
|
||||
@ -126,7 +147,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
|
||||
: api.api.tx.vesting.vest()
|
||||
);
|
||||
}
|
||||
}, [address, api, balancesAll, setBalance]);
|
||||
}, [address, api, balancesAll, setBalance, stakingInfo]);
|
||||
|
||||
useEffect((): void => {
|
||||
bestNumber && democracyLocks && setUnlockableIds(
|
||||
|
||||
53
packages/page-accounts/src/Accounts/Summary.tsx
Normal file
53
packages/page-accounts/src/Accounts/Summary.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountBalance } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useTranslation } from '@polkadot/app-treasury/translate';
|
||||
import { CardSummary, SummaryBox } from '@polkadot/react-components';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
balance?: AccountBalance;
|
||||
}
|
||||
|
||||
function Summary ({ balance, className }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SummaryBox className={className}>
|
||||
{balance && (
|
||||
<>
|
||||
{balance.total.gtn(0) &&
|
||||
<CardSummary label={t<string>('total balance')}>
|
||||
<FormatBalance value={balance.total} />
|
||||
</CardSummary>}
|
||||
{balance.transferrable.gtn(0) &&
|
||||
<CardSummary label={t<string>('total transferrable')}>
|
||||
<FormatBalance value={balance.transferrable} />
|
||||
</CardSummary>}
|
||||
{balance.locked.gtn(0) &&
|
||||
<CardSummary label={t<string>('total locked')}>
|
||||
<FormatBalance value={balance.locked} />
|
||||
</CardSummary>}
|
||||
{balance.bonded.gtn(0) &&
|
||||
<CardSummary label={t<string>('bonded')}>
|
||||
<FormatBalance value={balance.bonded} />
|
||||
</CardSummary>}
|
||||
{balance.redeemable.gtn(0) &&
|
||||
<CardSummary label={t<string>('redeemable')}>
|
||||
<FormatBalance value={balance.redeemable} />
|
||||
</CardSummary>}
|
||||
{balance.unbonding.gtn(0) &&
|
||||
<CardSummary label={t<string>('unbonding')}>
|
||||
<FormatBalance value={balance.unbonding} />
|
||||
</CardSummary>}
|
||||
</>)}
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
@ -2,11 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Balance } from '@polkadot/types/interfaces';
|
||||
import type { AccountOverrides } from '../../test/hooks/default';
|
||||
|
||||
import { within } from '@testing-library/react';
|
||||
import { screen, within } from '@testing-library/react';
|
||||
import BN from 'bn.js';
|
||||
|
||||
import i18next from '@polkadot/react-components/i18n';
|
||||
import { balanceOf } from '@polkadot/test-support/creation/balance';
|
||||
import { makeStakingLedger as ledger } from '@polkadot/test-support/creation/stakingInfo/stakingLedger';
|
||||
import { MemoryStore } from '@polkadot/test-support/keyring';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
@ -25,49 +28,50 @@ describe('Accounts page', () => {
|
||||
});
|
||||
|
||||
describe('when no accounts', () => {
|
||||
it('shows a table', async () => {
|
||||
beforeEach(() => {
|
||||
accountsPage.renderPage([]);
|
||||
});
|
||||
|
||||
it('shows a table', async () => {
|
||||
const accountsTable = await accountsPage.findAccountsTable();
|
||||
|
||||
expect(accountsTable).not.toBeNull();
|
||||
});
|
||||
|
||||
it('the accounts table contains no account rows', async () => {
|
||||
accountsPage.renderPage([]);
|
||||
|
||||
const accountRows = await accountsPage.findAccountRows();
|
||||
|
||||
expect(accountRows).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('the accounts table contains a message about no accounts available', async () => {
|
||||
accountsPage.renderPage([]);
|
||||
|
||||
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.');
|
||||
|
||||
expect(noAccountsMessage).not.toBeNull();
|
||||
});
|
||||
|
||||
it('no summary is displayed', () => {
|
||||
const summaries = screen.queryAllByTestId(/card-summary:total \w+/i);
|
||||
|
||||
expect(summaries).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when some accounts exist', () => {
|
||||
it('the accounts table contains some account rows', async () => {
|
||||
accountsPage.renderPage([
|
||||
defaultRender(
|
||||
{
|
||||
address: '5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy',
|
||||
balance: {
|
||||
freeBalance: balance(10000)
|
||||
}
|
||||
},
|
||||
{
|
||||
address: '5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw',
|
||||
balance: {
|
||||
freeBalance: balance(999)
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
const accountRows = await accountsPage.findAccountRows();
|
||||
|
||||
@ -75,21 +79,18 @@ describe('Accounts page', () => {
|
||||
});
|
||||
|
||||
it('account rows display the total balance info', async () => {
|
||||
accountsPage.renderPage([
|
||||
defaultRender(
|
||||
{
|
||||
address: '5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy',
|
||||
balance: {
|
||||
freeBalance: balance(500)
|
||||
}
|
||||
},
|
||||
{
|
||||
address: '5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw',
|
||||
balance: {
|
||||
freeBalance: balance(200),
|
||||
reservedBalance: balance(150)
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
const rows = await accountsPage.findAccountRows();
|
||||
|
||||
const row1TotalActual = await within(rows[0]).findByTestId('balance-summary');
|
||||
@ -102,6 +103,130 @@ describe('Accounts page', () => {
|
||||
|
||||
expect(row2TotalActual).toHaveTextContent(row2TotalExpected);
|
||||
});
|
||||
|
||||
it('displays some summary', () => {
|
||||
defaultRender(
|
||||
{
|
||||
balance: {
|
||||
freeBalance: balance(500)
|
||||
}
|
||||
},
|
||||
{
|
||||
balance: {
|
||||
freeBalance: balance(200),
|
||||
reservedBalance: balance(150)
|
||||
}
|
||||
});
|
||||
|
||||
const summaries = screen.queryAllByTestId(/card-summary:total \w+/i);
|
||||
|
||||
expect(summaries).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
it('displays balance summary', async () => {
|
||||
defaultRender(
|
||||
{
|
||||
balance: {
|
||||
freeBalance: balance(500)
|
||||
}
|
||||
},
|
||||
{
|
||||
balance: {
|
||||
freeBalance: balance(200),
|
||||
reservedBalance: balance(150)
|
||||
}
|
||||
});
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?balance/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(500 + 200 + 150));
|
||||
});
|
||||
|
||||
it('displays transferrable summary', async () => {
|
||||
defaultRender({ balance: { availableBalance: balance(400) } }, { balance: { availableBalance: balance(600) } });
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?transferrable/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(400 + 600));
|
||||
});
|
||||
|
||||
it('displays locked summary', async () => {
|
||||
defaultRender({ balance: { lockedBalance: balance(400) } }, { balance: { lockedBalance: balance(600) } });
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?locked/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(400 + 600));
|
||||
});
|
||||
|
||||
it('displays bonded summary', async () => {
|
||||
defaultRender(
|
||||
{
|
||||
staking: {
|
||||
stakingLedger: ledger(balance(70))
|
||||
}
|
||||
},
|
||||
{
|
||||
staking: {
|
||||
stakingLedger: ledger(balance(20))
|
||||
}
|
||||
});
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?bonded/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(70 + 20));
|
||||
});
|
||||
|
||||
it('displays unbonding summary', async () => {
|
||||
defaultRender(
|
||||
{
|
||||
staking: {
|
||||
unlocking: [
|
||||
{
|
||||
remainingEras: new BN('1000000000'),
|
||||
value: balance(200)
|
||||
},
|
||||
{
|
||||
remainingEras: new BN('2000000000'),
|
||||
value: balance(300)
|
||||
},
|
||||
{
|
||||
remainingEras: new BN('3000000000'),
|
||||
value: balance(400)
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
staking: {
|
||||
unlocking: [
|
||||
{
|
||||
remainingEras: new BN('1000000000'),
|
||||
value: balance(100)
|
||||
},
|
||||
{
|
||||
remainingEras: new BN('2000000000'),
|
||||
value: balance(200)
|
||||
},
|
||||
{
|
||||
remainingEras: new BN('3000000000'),
|
||||
value: balance(300)
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?unbonding/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(200 + 300 + 400 + 100 + 200 + 300));
|
||||
});
|
||||
|
||||
it('displays redeemable summary', async () => {
|
||||
defaultRender({ staking: { redeemable: balance(4000) } }, { staking: { redeemable: balance(5000) } });
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?redeemable/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(4000 + 5000));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
@ -115,4 +240,23 @@ describe('Accounts page', () => {
|
||||
|
||||
return balanceOf(amountInt.toString() + decimalsPadded);
|
||||
};
|
||||
|
||||
const showBalance = function (amount: number): string {
|
||||
return accountsPage.format(balance(amount));
|
||||
};
|
||||
|
||||
const defaultAddresses = [
|
||||
'5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy',
|
||||
'5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw'
|
||||
];
|
||||
|
||||
const defaultRender = function (...overrides: AccountOverrides[]): void {
|
||||
const accounts: [string, AccountOverrides][] = [];
|
||||
|
||||
for (let i = 0; i < overrides.length; i++) {
|
||||
accounts.push([defaultAddresses[i], overrides[i]]);
|
||||
}
|
||||
|
||||
accountsPage.renderPage(accounts);
|
||||
};
|
||||
});
|
||||
|
||||
@ -4,14 +4,13 @@
|
||||
import type BN from 'bn.js';
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { AccountId, ProxyDefinition, ProxyType, Voting } from '@polkadot/types/interfaces';
|
||||
import type { Delegation, SortedAccount } from '../types';
|
||||
import type { AccountBalance, Delegation, SortedAccount } from '../types';
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button, Input, Table } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useCall, useFavorites, useIpfs, useLedger, useLoadingDelay, useToggle } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import CreateModal from '../modals/Create';
|
||||
@ -25,10 +24,11 @@ import { sortAccounts } from '../util';
|
||||
import Account from './Account';
|
||||
import BannerClaims from './BannerClaims';
|
||||
import BannerExtension from './BannerExtension';
|
||||
import Summary from './Summary';
|
||||
|
||||
interface Balances {
|
||||
accounts: Record<string, BN>;
|
||||
balanceTotal?: BN;
|
||||
accounts: Record<string, AccountBalance>;
|
||||
summary?: AccountBalance;
|
||||
}
|
||||
|
||||
interface Sorted {
|
||||
@ -56,7 +56,7 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
const [isProxyOpen, toggleProxy] = useToggle();
|
||||
const [isQrOpen, toggleQr] = useToggle();
|
||||
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
||||
const [{ balanceTotal }, setBalances] = useState<Balances>({ accounts: {} });
|
||||
const [balances, setBalances] = useState<Balances>({ accounts: {} });
|
||||
const [filterOn, setFilter] = useState<string>('');
|
||||
const [sortedAccountsWithDelegation, setSortedAccountsWithDelegation] = useState<SortedAccount[] | undefined>();
|
||||
const [{ sortedAccounts, sortedAddresses }, setSorted] = useState<Sorted>({ sortedAccounts: [], sortedAddresses: [] });
|
||||
@ -113,32 +113,28 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
}, [api, delegations, sortedAccounts]);
|
||||
|
||||
const _setBalance = useCallback(
|
||||
(account: string, balance: BN) =>
|
||||
(account: string, balance: AccountBalance) =>
|
||||
setBalances(({ accounts }: Balances): Balances => {
|
||||
accounts[account] = balance;
|
||||
|
||||
const aggregate = (key: keyof AccountBalance) =>
|
||||
Object.values(accounts).reduce((total: BN, value: AccountBalance) => total.add(value[key]), BN_ZERO);
|
||||
|
||||
return {
|
||||
accounts,
|
||||
balanceTotal: Object.values(accounts).reduce((total: BN, value: BN) => total.add(value), BN_ZERO)
|
||||
summary: {
|
||||
bonded: aggregate('bonded'),
|
||||
locked: aggregate('locked'),
|
||||
redeemable: aggregate('redeemable'),
|
||||
total: aggregate('total'),
|
||||
transferrable: aggregate('transferrable'),
|
||||
unbonding: aggregate('unbonding')
|
||||
}
|
||||
};
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const footer = useMemo(() => (
|
||||
<tr>
|
||||
<td colSpan={3} />
|
||||
<td className='media--1400' />
|
||||
<td colSpan={2} />
|
||||
<td className='media--1500' />
|
||||
<td className='number'>
|
||||
{balanceTotal && <FormatBalance value={balanceTotal} />}
|
||||
</td>
|
||||
<td />
|
||||
<td className='media--1400' />
|
||||
</tr>
|
||||
), [balanceTotal]);
|
||||
|
||||
const filter = useMemo(() => (
|
||||
<div className='filter--tags'>
|
||||
<Input
|
||||
@ -228,10 +224,10 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
</Button.Group>
|
||||
<BannerExtension />
|
||||
<BannerClaims />
|
||||
<Summary balance={balances.summary} />
|
||||
<Table
|
||||
empty={!isLoading && sortedAccountsWithDelegation && t<string>("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")}
|
||||
filter={filter}
|
||||
footer={footer}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{!isLoading && sortedAccountsWithDelegation?.map(({ account, delegation, isFavorite }, index): React.ReactNode => (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { Balance, Conviction } from '@polkadot/types/interfaces';
|
||||
import type { KeyringAddress } from '@polkadot/ui-keyring/types';
|
||||
@ -33,6 +34,15 @@ export interface SortedAccount {
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
export interface AccountBalance {
|
||||
total: BN;
|
||||
locked: BN;
|
||||
transferrable: BN;
|
||||
bonded: BN;
|
||||
redeemable: BN;
|
||||
unbonding: BN;
|
||||
}
|
||||
|
||||
export type PairType = 'ecdsa' | 'ed25519' | 'ed25519-ledger' | 'ethereum' | 'sr25519';
|
||||
|
||||
export interface CreateProps extends ModalProps {
|
||||
|
||||
@ -7,6 +7,7 @@ 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';
|
||||
|
||||
export interface Account {
|
||||
balance: DeriveBalancesAll,
|
||||
@ -19,8 +20,6 @@ export type AccountsMap = { [address: string]: Account };
|
||||
* Test inputs structure
|
||||
*/
|
||||
export interface AccountOverrides {
|
||||
address: string;
|
||||
|
||||
balance?: {
|
||||
[P in keyof DeriveBalancesAll]?: DeriveBalancesAll[P];
|
||||
};
|
||||
@ -58,12 +57,7 @@ export const defaultStakingAccount: DeriveStakingAccount = {
|
||||
nominators: [],
|
||||
redeemable: balanceOf(0),
|
||||
sessionIds: [],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
stakingLedger: {
|
||||
active: {
|
||||
unwrap: () => new BN(0)
|
||||
}
|
||||
} as any,
|
||||
stakingLedger: makeStakingLedger(0),
|
||||
unlocking: [
|
||||
{
|
||||
remainingEras: new BN('1000000000'),
|
||||
@ -86,16 +80,16 @@ class MockAccountHooks {
|
||||
|
||||
public nonce: BN = new BN(1);
|
||||
|
||||
public setAccounts (accounts: AccountOverrides[]): void {
|
||||
public setAccounts (accounts: [string, AccountOverrides][]): void {
|
||||
this.useAccounts = {
|
||||
allAccounts: accounts.map((account) => account.address),
|
||||
allAccounts: accounts.map(([address]) => address),
|
||||
allAccountsHex: [],
|
||||
areAccountsLoaded: true,
|
||||
hasAccounts: accounts && accounts.length !== 0,
|
||||
isAccount: () => true
|
||||
};
|
||||
|
||||
for (const account of accounts) {
|
||||
for (const [address, props] of accounts) {
|
||||
const staking = { ...defaultStakingAccount };
|
||||
const balance = { ...defaultBalanceAccount };
|
||||
|
||||
@ -103,11 +97,11 @@ class MockAccountHooks {
|
||||
// so we have to use "any" here.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
Object.entries(account.balance || balance).forEach(function ([key, value]) { (balance as any)[key] = value; });
|
||||
Object.entries(props.balance || balance).forEach(function ([key, value]) { (balance as any)[key] = value; });
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
Object.entries(account.staking || staking).forEach(function ([key, value]) { (staking as any)[key] = value; });
|
||||
Object.entries(props.staking || staking).forEach(function ([key, value]) { (staking as any)[key] = value; });
|
||||
|
||||
this.accountsMap[account.address] = {
|
||||
this.accountsMap[address] = {
|
||||
balance: balance,
|
||||
staking: staking
|
||||
};
|
||||
|
||||
@ -50,7 +50,7 @@ jest.mock('@polkadot/react-hooks/useBestNumber', () => ({
|
||||
export class AccountsPage {
|
||||
private renderResult?: RenderResult
|
||||
|
||||
renderPage (accounts: AccountOverrides[]): void {
|
||||
renderPage (accounts: [string, AccountOverrides][]): void {
|
||||
mockAccountHooks.setAccounts(accounts);
|
||||
const mockApi: ApiProps = {
|
||||
api: {
|
||||
@ -121,8 +121,8 @@ export class AccountsPage {
|
||||
return rows.filter((r) => r.className.startsWith('Account-'));
|
||||
}
|
||||
|
||||
format (amount: Balance): string {
|
||||
return formatBalance(amount, { decimals: 12, withUnit: true });
|
||||
format (amount: Balance | BN): string {
|
||||
return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true });
|
||||
}
|
||||
|
||||
private assertRendered () {
|
||||
|
||||
@ -54,9 +54,11 @@ function CardSummary ({ children, className = '', help, label, progress }: Props
|
||||
}
|
||||
|
||||
const isTimed = progress && progress.withTime && !isUndefined(progress.total);
|
||||
const testidSuffix = (label ?? '').toString();
|
||||
|
||||
return (
|
||||
<article className={className}>
|
||||
<article className={className}
|
||||
data-testid={`card-summary:${testidSuffix}`}>
|
||||
<Labelled
|
||||
help={help}
|
||||
isSmall
|
||||
|
||||
@ -42,6 +42,7 @@ export { useIsParasLinked, useParaEndpoints } from './useParaEndpoints';
|
||||
export { usePassword } from './usePassword';
|
||||
export { useRegistrars } from './useRegistrars';
|
||||
export { useSavedFlags } from './useSavedFlags';
|
||||
export { useStakingInfo } from './useStakingInfo';
|
||||
export { useStepper } from './useStepper';
|
||||
export { useSudo } from './useSudo';
|
||||
export { useTeleport } from './useTeleport';
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
|
||||
import type { DeriveStakingAccount } from '@polkadot/api-derive/types';
|
||||
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { useApi } from './useApi';
|
||||
import { useCall } from './useCall';
|
||||
|
||||
export function useStakingInfo (accountId: string | null): DeriveStakingAccount | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2021 @polkadot/app-bounties authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { StakingLedger } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
|
||||
import { TypeRegistry } from '@polkadot/types/create';
|
||||
|
||||
export function makeStakingLedger (active: BN | number | string): StakingLedger {
|
||||
const reg = new TypeRegistry();
|
||||
|
||||
// Constructing the whole StakingLedger structure is hard,
|
||||
// so we fill out just the fields that are definitely required,
|
||||
// and hope that nothing more is required.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
return {
|
||||
active: reg.createType('Compact<Balance>', reg.createType('Balance', new BN(active)))
|
||||
} as StakingLedger;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user