Style identity and balance sections in sidebar (#6096)
* Add subs modal * Add tests * Add Translations * Improve closing modal * Improve tests * Improve styles * Style balances & identity * Optimize imports * Improve styles * Set greater width to balance section * Rename subs modal toggle * Change 'amount' to 'count' in test name * Set make modal header fixed while content is scrollable * Style multisig * Update multisig styles * Capitalize headers * Move sidebar tests to Sidebar.spec.tsx * Remove duplications in tests * Add UserIcon component * Revert subs modal changes and add scrollable section in Sidebar * Remove unused styles * Use accountsPage clearAccount method * Wrap subs into Expander * Add useSubidentities hook * Adjust api mock in tests * WIP - split judgements * Revert mistaken commit Co-authored-by: Krzysztof Jelski <krzysztof@ethworks.io> Co-authored-by: Ewa Kowalska <ewcia.kowalska@gmail.com>
This commit is contained in:
parent
ff06dc72d7
commit
42ef182879
@ -29,6 +29,7 @@
|
||||
--bg-table: #3b3d3f;
|
||||
--bg-tabs: #38393f;
|
||||
--bg-toggle: #58595f;
|
||||
--bg-sidebar: #1a1b20;
|
||||
--border-table: #343536;
|
||||
--border-tabs: #343536;
|
||||
--color-checkbox: rgba(78, 78, 78, 0.66);
|
||||
@ -49,6 +50,7 @@
|
||||
--bg-table: #ffffff;
|
||||
--bg-tabs: #ffffff;
|
||||
--bg-toggle: #e4e5e6;
|
||||
--bg-sidebar: #fafafa;
|
||||
--border-table: #f1efed;
|
||||
--border-tabs: #f1efed;
|
||||
--color-checkbox: rgba(34, 36, 38, 0.15);
|
||||
|
||||
@ -109,7 +109,6 @@
|
||||
"Nominator": "Nominator",
|
||||
"Normal transfer without keep-alive checks": "Normal transfer without keep-alive checks",
|
||||
"One or more extensions are detected in your browser, however no accounts has been injected.": "One or more extensions are detected in your browser, however no accounts has been injected.",
|
||||
"Owned": "Owned",
|
||||
"Password are ignored for hex seed": "Password are ignored for hex seed",
|
||||
"Pending call hashes": "Pending call hashes",
|
||||
"Please make sure to save this file in a secure location as it is required, together with your password, to restore your account.": "Please make sure to save this file in a secure location as it is required, together with your password, to restore your account.",
|
||||
@ -337,10 +336,8 @@
|
||||
"somebody@example.com": "somebody@example.com",
|
||||
"sort by": "sort by",
|
||||
"stash address": "stash address",
|
||||
"sub": "sub",
|
||||
"sub name": "sub name",
|
||||
"subs": "subs",
|
||||
"tags": "tags",
|
||||
"the account to make recoverable": "the account to make recoverable",
|
||||
"the account to recover to": "the account to recover to",
|
||||
"threshold": "threshold",
|
||||
|
||||
@ -1,21 +1,18 @@
|
||||
// Copyright 2017-2021 @polkadot/page-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import BN from 'bn.js';
|
||||
|
||||
import i18next from '@polkadot/react-components/i18n';
|
||||
import toShortAddress from '@polkadot/react-components/util/toShortAddress';
|
||||
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 { 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 { AccountRow } from '../../test/pageElements/AccountRow';
|
||||
import { AccountsPage } from '../../test/pages/accountsPage';
|
||||
|
||||
describe('Accounts page', () => {
|
||||
@ -368,121 +365,4 @@ describe('Accounts page', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError);
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError);
|
||||
});
|
||||
|
||||
describe('on edit cancel', () => {
|
||||
beforeEach(async () => {
|
||||
accountsPage.render([[alice, anAccountWithMeta({ isDevelopment: false, name: initialName, tags: [] })]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
|
||||
await sideBar.assertTags('no tags');
|
||||
sideBar.edit();
|
||||
});
|
||||
|
||||
it('restores tags and name to state from keyring', async () => {
|
||||
await sideBar.typeAccountName(newName);
|
||||
await sideBar.selectTag(defaultTag);
|
||||
|
||||
sideBar.cancel();
|
||||
await sideBar.assertTags('no tags');
|
||||
await sideBar.assertAccountName(initialName);
|
||||
});
|
||||
|
||||
it('Cancel button disappears', () => {
|
||||
sideBar.cancel();
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('outside click', () => {
|
||||
beforeEach(async () => {
|
||||
accountsPage.renderAccountsWithDefaultAddresses(
|
||||
anAccountWithMeta({ name: 'alice' }),
|
||||
anAccountWithMeta({ name: 'bob' })
|
||||
);
|
||||
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
sideBar.edit();
|
||||
});
|
||||
|
||||
it('cancels editing', async () => {
|
||||
await sideBar.typeAccountName(newName);
|
||||
await sideBar.selectTag(defaultTag);
|
||||
|
||||
fireEvent.click(await screen.findByText('accounts'));
|
||||
|
||||
await sideBar.assertTags('no tags');
|
||||
await sideBar.assertAccountName('ALICE');
|
||||
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('within sidebar does not cancel editing', async () => {
|
||||
await sideBar.clickByText('Tags');
|
||||
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('cancels editing and changes name when opening sidebar for another account', async () => {
|
||||
await waitFor(() => sideBar.assertAccountInput('alice'));
|
||||
|
||||
sideBar = await accountRows[1].openSidebar();
|
||||
await sideBar.assertAccountName('BOB');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AddressInfo, Icon } from '@polkadot/react-components';
|
||||
import { AddressInfo } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
@ -21,18 +21,14 @@ function Balances ({ address, className }: Props): React.ReactElement<Props> | n
|
||||
return (
|
||||
<section className={className}>
|
||||
<div className='ui--AddressMenu-sectionHeader'>
|
||||
<div>
|
||||
<Icon icon='sort-amount-down' />
|
||||
|
||||
{t<string>('balance')}
|
||||
</div>
|
||||
{t<string>('balance')}
|
||||
</div>
|
||||
<AddressInfo
|
||||
address={address}
|
||||
className='balanceExpander'
|
||||
withBalance={WITH_BALANCE}
|
||||
withBalanceToggle
|
||||
withExtended={false}
|
||||
withLabel
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
@ -40,10 +36,14 @@ function Balances ({ address, className }: Props): React.ReactElement<Props> | n
|
||||
|
||||
export default React.memo(styled(Balances)`
|
||||
.balanceExpander {
|
||||
.column.column--expander {
|
||||
justify-content: flex-start;
|
||||
|
||||
.column {
|
||||
width: auto;
|
||||
max-width: 18.57rem;
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
font-size: 0.93rem;
|
||||
font-weight: var(--font-weight-normal);
|
||||
|
||||
@ -2,42 +2,55 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AddressIdentity } from '@polkadot/react-hooks/types';
|
||||
import type { AccountId, BalanceOf } from '@polkadot/types/interfaces';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { AddressMini, AvatarItem, Expander, Icon, IconLink, Tag } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useRegistrars, useToggle } from '@polkadot/react-hooks';
|
||||
import { AddressMini, AvatarItem, Expander, IconLink, Tag } from '@polkadot/react-components';
|
||||
import { useApi, useRegistrars, useSubidentities, useToggle } from '@polkadot/react-hooks';
|
||||
import { isHex } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import RegistrarJudgement from './RegistrarJudgement';
|
||||
import UserIcon from './UserIcon';
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
identity?: AddressIdentity;
|
||||
}
|
||||
|
||||
const SUBS_DISPLAY_THRESHOLD = 4;
|
||||
|
||||
function Identity ({ address, identity }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { isRegistrar, registrars } = useRegistrars();
|
||||
const [isJudgementOpen, toggleIsJudgementOpen] = useToggle();
|
||||
const subs = useCall<[BalanceOf, AccountId[]]>(api.query.identity?.subsOf, [address])?.[1];
|
||||
const subs = useSubidentities(address);
|
||||
|
||||
const subsList = useMemo(() =>
|
||||
subs?.map((sub) =>
|
||||
<AddressMini
|
||||
className='subs'
|
||||
isPadded={false}
|
||||
key={sub.toString()}
|
||||
value={sub}
|
||||
/>
|
||||
)
|
||||
, [subs]
|
||||
);
|
||||
|
||||
if (!identity || !identity.isExistent || !api.query.identity?.identityOf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section
|
||||
className='withDivider'
|
||||
data-testid='identity-section'
|
||||
>
|
||||
<div className='ui--AddressMenu-section ui--AddressMenu-identity'>
|
||||
<div className='ui--AddressMenu-sectionHeader'>
|
||||
<div>
|
||||
<Icon icon='address-card' />
|
||||
|
||||
{t<string>('identity')}
|
||||
</div>
|
||||
{t<string>('identity')}
|
||||
<Tag
|
||||
color={
|
||||
identity.isBad
|
||||
@ -74,10 +87,7 @@ function Identity ({ address, identity }: Props): React.ReactElement<Props> | nu
|
||||
// ? <img src={identity.image} />
|
||||
// : <i className='icon user ui--AddressMenu-identityIcon' />
|
||||
//
|
||||
<Icon
|
||||
className='ui--AddressMenu-identityIcon'
|
||||
icon='user'
|
||||
/>
|
||||
<UserIcon />
|
||||
}
|
||||
subtitle={identity.legal}
|
||||
title={identity.display}
|
||||
@ -162,26 +172,28 @@ function Identity ({ address, identity }: Props): React.ReactElement<Props> | nu
|
||||
</div>
|
||||
)}
|
||||
{!!subs?.length && (
|
||||
<div className='tr subs'>
|
||||
{subs.length > 1
|
||||
? <div className='th top'>{t<string>('subs')}</div>
|
||||
: <div className='th'>{t<string>('sub')}</div>
|
||||
}
|
||||
<div className='td'>
|
||||
<Expander summary={`(${subs.length})`}>
|
||||
<div className='body column'>
|
||||
{subs.map((sub) =>
|
||||
<AddressMini
|
||||
className='subs'
|
||||
isPadded={false}
|
||||
key={sub.toString()}
|
||||
value={sub}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Expander>
|
||||
<div className='tr'>
|
||||
<div className='th top'>{t<string>('subs')}</div>
|
||||
<div
|
||||
className='td'
|
||||
data-testid='subs'
|
||||
>
|
||||
{subs.length > SUBS_DISPLAY_THRESHOLD
|
||||
? (
|
||||
<Expander summary={subs.length}>
|
||||
{subsList}
|
||||
</Expander>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className='subs-number'>{subs.length}</div>
|
||||
{subsList}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,7 @@ import type { KeyringJson$Meta } from '@polkadot/ui-keyring/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AddressMini, Icon, Static } from '@polkadot/react-components';
|
||||
import { AddressMini } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
@ -24,31 +24,29 @@ function Multisig ({ isMultisig, meta }: Props): React.ReactElement<Props> | nul
|
||||
const { threshold, who } = meta;
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section className='ui--AddressMenu-multisig withDivider'>
|
||||
<div className='ui--AddressMenu-sectionHeader'>
|
||||
<div>
|
||||
<Icon icon='handshake' />
|
||||
|
||||
{t<string>('multisig')}
|
||||
{t<string>('multisig')}
|
||||
</div>
|
||||
<div className='ui--AddressMenu-multisigTable'>
|
||||
<div className='tr'>
|
||||
<div className='th'>{t<string>('threshold')}</div>
|
||||
<div className='td'>
|
||||
{threshold}/{(who as string[]).length}
|
||||
</div>
|
||||
</div>
|
||||
<div className='tr'>
|
||||
<div className='th signatories'>{t<string>('signatories')}</div>
|
||||
<div className='td'>
|
||||
{(who as string[])?.map((address) => (
|
||||
<AddressMini
|
||||
key={address}
|
||||
value={address}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Static
|
||||
isFull
|
||||
label={t<string>('threshold')}
|
||||
>
|
||||
{threshold}/{(who as string[]).length}
|
||||
</Static>
|
||||
<Static
|
||||
isFull
|
||||
label={t<string>('signatories')}
|
||||
>
|
||||
{(who as string[])?.map((address) => (
|
||||
<AddressMini
|
||||
key={address}
|
||||
value={address}
|
||||
/>
|
||||
))}
|
||||
</Static>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
184
packages/page-accounts/src/Sidebar/Sidebar.spec.tsx
Normal file
184
packages/page-accounts/src/Sidebar/Sidebar.spec.tsx
Normal file
@ -0,0 +1,184 @@
|
||||
// Copyright 2017-2021 @polkadot/page-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
|
||||
import { AddressFlags } from '@polkadot/react-hooks/types';
|
||||
import { anAccount, anAccountWithInfo, anAccountWithMeta } from '@polkadot/test-support/creation/account';
|
||||
import { alice, bob, MemoryStore } from '@polkadot/test-support/keyring';
|
||||
import { Sidebar } from '@polkadot/test-support/pagesElements/Sidebar';
|
||||
import { mockApiHooks } from '@polkadot/test-support/utils/mockApiHooks';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
import { AccountRow } from '../../test/pageElements/AccountRow';
|
||||
import { AccountsPage } from '../../test/pages/accountsPage';
|
||||
|
||||
describe('Sidebar', () => {
|
||||
let accountsPage: AccountsPage;
|
||||
let accountRows: AccountRow[];
|
||||
let sideBar: Sidebar;
|
||||
|
||||
beforeAll(() => {
|
||||
if (keyring.getAccounts().length === 0) {
|
||||
keyring.loadAll({ isDevelopment: true, store: new MemoryStore() });
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
accountsPage = new AccountsPage();
|
||||
accountsPage.clearAccounts();
|
||||
});
|
||||
|
||||
describe('editing', () => {
|
||||
const initialName = 'INITIAL_NAME';
|
||||
const newName = 'NEW_NAME';
|
||||
const defaultTag = 'Default';
|
||||
const nameInputNotFoundError = 'Unable to find an element by: [data-testid="name-input"]';
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError);
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError);
|
||||
});
|
||||
|
||||
describe('on edit cancel', () => {
|
||||
beforeEach(async () => {
|
||||
accountsPage.render([[alice, anAccountWithMeta({ isDevelopment: false, name: initialName, tags: [] })]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
|
||||
await sideBar.assertTags('no tags');
|
||||
sideBar.edit();
|
||||
});
|
||||
|
||||
it('restores tags and name to state from keyring', async () => {
|
||||
await sideBar.typeAccountName(newName);
|
||||
await sideBar.selectTag(defaultTag);
|
||||
|
||||
sideBar.cancel();
|
||||
await sideBar.assertTags('no tags');
|
||||
await sideBar.assertAccountName(initialName);
|
||||
});
|
||||
|
||||
it('Cancel button disappears', () => {
|
||||
sideBar.cancel();
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('outside click', () => {
|
||||
beforeEach(async () => {
|
||||
accountsPage.renderAccountsWithDefaultAddresses(
|
||||
anAccountWithMeta({ name: 'alice' }),
|
||||
anAccountWithMeta({ name: 'bob' })
|
||||
);
|
||||
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
sideBar.edit();
|
||||
});
|
||||
|
||||
it('cancels editing', async () => {
|
||||
await sideBar.typeAccountName(newName);
|
||||
await sideBar.selectTag(defaultTag);
|
||||
|
||||
fireEvent.click(await screen.findByText('accounts'));
|
||||
|
||||
await sideBar.assertTags('no tags');
|
||||
await sideBar.assertAccountName('ALICE');
|
||||
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('within sidebar does not cancel editing', async () => {
|
||||
await sideBar.clickByText('Tags');
|
||||
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('cancels editing and changes name when opening sidebar for another account', async () => {
|
||||
await waitFor(() => sideBar.assertAccountInput('alice'));
|
||||
|
||||
sideBar = await accountRows[1].openSidebar();
|
||||
await sideBar.assertAccountName('BOB');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('identity section', () => {
|
||||
it('does not display subs when account has zero subs', async () => {
|
||||
accountsPage.render([[alice, anAccount()]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
const subs = await sideBar.findSubs();
|
||||
|
||||
expect(subs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('displays count of subs and account names', async () => {
|
||||
mockApiHooks.setSubs([bob]);
|
||||
accountsPage.render([[alice, anAccount()], [bob, anAccountWithMeta({ name: 'Bob' })]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
const subs = await sideBar.findSubs();
|
||||
|
||||
const subsNumber = subs[0].childNodes[0];
|
||||
const subAccount = subs[0].childNodes[1];
|
||||
|
||||
expect(subsNumber).toHaveClass('subs-number');
|
||||
expect(subsNumber).toHaveTextContent('1');
|
||||
expect(subAccount).toHaveTextContent('BOB');
|
||||
});
|
||||
|
||||
it('displays picked sub in sidebar', async () => {
|
||||
mockApiHooks.setSubs([bob]);
|
||||
accountsPage.render([[alice, anAccount()], [bob, anAccountWithMeta({ name: 'Bob' })]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
const subs = await sideBar.findSubs();
|
||||
const subAccount = subs[0].childNodes[1];
|
||||
|
||||
fireEvent.click(await within(subAccount as HTMLElement).findByTestId('account-name'));
|
||||
|
||||
await sideBar.assertAccountName('BOB');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -47,16 +47,18 @@ function FullSidebar ({ address, className = '', dataTestId, onClose, onUpdateNa
|
||||
sidebarRef={ref}
|
||||
/>
|
||||
</div>
|
||||
<Balances address={address} />
|
||||
<Identity
|
||||
address={address}
|
||||
identity={identity}
|
||||
/>
|
||||
<Multisig
|
||||
isMultisig={flags.isMultisig}
|
||||
meta={meta}
|
||||
/>
|
||||
<section>
|
||||
<div className='ui--ScrollSection'>
|
||||
<Balances address={address} />
|
||||
<Identity
|
||||
address={address}
|
||||
identity={identity}
|
||||
/>
|
||||
<Multisig
|
||||
isMultisig={flags.isMultisig}
|
||||
meta={meta}
|
||||
/>
|
||||
</div>
|
||||
<section className='ui--LinkSection'>
|
||||
<LinkExternal
|
||||
data={address}
|
||||
isLogo
|
||||
@ -71,9 +73,11 @@ function FullSidebar ({ address, className = '', dataTestId, onClose, onUpdateNa
|
||||
export default React.memo(styled(FullSidebar)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: var(--bg-sidebar);
|
||||
max-width: 30.42rem;
|
||||
min-width: 30.42rem;
|
||||
overflow-y: hidden;
|
||||
padding: 0 0 3.286rem;
|
||||
|
||||
input {
|
||||
width: auto !important;
|
||||
@ -86,7 +90,6 @@ export default React.memo(styled(FullSidebar)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: -1rem -1rem 1rem -1rem;
|
||||
padding: 1.35rem 1rem 1rem 1rem;
|
||||
}
|
||||
|
||||
@ -135,40 +138,61 @@ export default React.memo(styled(FullSidebar)`
|
||||
}
|
||||
|
||||
section {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.4rem;
|
||||
}
|
||||
position: relative;
|
||||
|
||||
& :last-child {
|
||||
margin-top: auto;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ui--AddressMenu-sectionHeader {
|
||||
display: inline-flex;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 0.4rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-transform: capitalize;
|
||||
|
||||
margin-bottom: 0.57rem;
|
||||
width: 100%;
|
||||
|
||||
& > :first-child {
|
||||
flex: 1;
|
||||
color: var(--color-text);
|
||||
font-size: 1.143rem;
|
||||
}
|
||||
|
||||
&.withDivider {
|
||||
padding-top: 1rem;
|
||||
|
||||
::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--border-table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressMenu-identity {
|
||||
.ui--AddressMenu-identityTable {
|
||||
.ui--AddressMenu-identity,
|
||||
.ui--AddressMenu-multisig {
|
||||
.ui--AddressMenu-identityTable,
|
||||
.ui--AddressMenu-multisigTable {
|
||||
font-size: 0.93rem;
|
||||
margin-top: 0.3rem;
|
||||
margin-top: 0.6rem;
|
||||
|
||||
.tr {
|
||||
padding: 0.25rem 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.th {
|
||||
text-transform: uppercase;
|
||||
color: var(--color-label);
|
||||
font-weight: var(--font-weight-normal);
|
||||
text-align: right;
|
||||
flex-basis: 20%;
|
||||
text-align: left;
|
||||
flex-basis: 25%;
|
||||
font-size: 0.714rem;
|
||||
|
||||
&.top {
|
||||
align-self: flex-start;
|
||||
@ -182,13 +206,46 @@ export default React.memo(styled(FullSidebar)`
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressMini, .subs-number {
|
||||
margin-bottom: 0.4rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.subs-number {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.714rem;
|
||||
}
|
||||
}
|
||||
|
||||
.parent, .subs {
|
||||
.parent {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&& .column {
|
||||
align-items: center;
|
||||
|
||||
.ui--FormatBalance:first-of-type {
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
label:first-of-type {
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--color-label);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.714rem;
|
||||
}
|
||||
|
||||
.ui--FormatBalance, label {
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressMenu-buttons {
|
||||
.ui--Button-Group {
|
||||
margin-bottom: 0;
|
||||
@ -221,7 +278,7 @@ export default React.memo(styled(FullSidebar)`
|
||||
.inline-icon {
|
||||
cursor: pointer;
|
||||
margin: 0 0 0 0.5rem;
|
||||
color: ${colorLink};
|
||||
color: ${colorLink};
|
||||
}
|
||||
|
||||
.name--input {
|
||||
@ -238,4 +295,25 @@ export default React.memo(styled(FullSidebar)`
|
||||
opacity: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AddressMenu-multisig .th.signatories {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.ui--ScrollSection {
|
||||
padding: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ui--LinkSection {
|
||||
border-top: 1px solid var(--border-table);
|
||||
padding: 0.5rem 0 0.571rem;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
span {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
45
packages/page-accounts/src/Sidebar/UserIcon.tsx
Normal file
45
packages/page-accounts/src/Sidebar/UserIcon.tsx
Normal file
File diff suppressed because one or more lines are too long
@ -2,14 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { Data, Option, Vec } from '@polkadot/types';
|
||||
import type { AccountId, Balance } from '@polkadot/types/interfaces';
|
||||
import type { Data, Option } from '@polkadot/types';
|
||||
import type { AccountId } from '@polkadot/types/interfaces';
|
||||
import type { ITuple } from '@polkadot/types/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Button, Columar, Input, InputAddress, Modal, Spinner, TxButton } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { useAccounts, useApi, useCall, useSubidentities } from '@polkadot/react-hooks';
|
||||
import { u8aToString } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
@ -80,17 +80,13 @@ function IdentitySub ({ address, index, name, setAddress, setName, t }: SubProps
|
||||
|
||||
const IdentitySubMemo = React.memo(IdentitySub);
|
||||
|
||||
const transformIds = {
|
||||
transform: ([, ids]: ITuple<[Balance, Vec<AccountId>]>) => ids.map((a) => a.toString())
|
||||
};
|
||||
|
||||
const transformInfo = { withParams: true };
|
||||
|
||||
function IdentitySubModal ({ address, className, onClose }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const queryIds = useCall<string[]>(api.query.identity.subsOf, [address], transformIds);
|
||||
const queryIds = useSubidentities(address);
|
||||
const queryInfos = useCall<[[string[]], Option<ITuple<[AccountId, Data]>>[]]>(queryIds && queryIds.length !== 0 && api.query.identity.superOf.multi, [queryIds], transformInfo);
|
||||
const [infos, setInfos] = useState<[string, string][] | undefined>();
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ interface Props {
|
||||
withExtended?: boolean | CryptoActiveType;
|
||||
withHexSessionId?: (string | null)[];
|
||||
withValidatorPrefs?: boolean | ValidatorPrefsType;
|
||||
withoutLabel?: boolean;
|
||||
withLabel?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_BALANCES: BalanceActiveType = {
|
||||
@ -222,12 +222,12 @@ function renderValidatorPrefs ({ stakingInfo, withValidatorPrefs = false }: Prop
|
||||
);
|
||||
}
|
||||
|
||||
function createBalanceItems (formatIndex: number, lookup: Record<string, string>, t: TFunction, { address, balanceDisplay, balancesAll, bestNumber, democracyLocks, isAllLocked, otherBonded, ownBonded, stakingInfo, votingOf, withBalanceToggle }: { address: string; balanceDisplay: BalanceActiveType; balancesAll?: DeriveBalancesAll | DeriveBalancesAccountData; bestNumber: BlockNumber; democracyLocks?: DeriveDemocracyLock[]; isAllLocked: boolean; otherBonded: BN[]; ownBonded: BN; stakingInfo?: DeriveStakingAccount; votingOf?: Voting; withBalanceToggle: boolean }): React.ReactNode {
|
||||
function createBalanceItems (formatIndex: number, lookup: Record<string, string>, t: TFunction, { address, balanceDisplay, balancesAll, bestNumber, democracyLocks, isAllLocked, otherBonded, ownBonded, stakingInfo, votingOf, withBalanceToggle, withLabel }: { address: string; balanceDisplay: BalanceActiveType; balancesAll?: DeriveBalancesAll | DeriveBalancesAccountData; bestNumber: BlockNumber; democracyLocks?: DeriveDemocracyLock[]; isAllLocked: boolean; otherBonded: BN[]; ownBonded: BN; stakingInfo?: DeriveStakingAccount; votingOf?: Voting; withBalanceToggle: boolean, withLabel: boolean }): React.ReactNode {
|
||||
const allItems: React.ReactNode[] = [];
|
||||
|
||||
!withBalanceToggle && balancesAll && balanceDisplay.total && allItems.push(
|
||||
<React.Fragment key={0}>
|
||||
<Label label='' />
|
||||
<Label label={withLabel ? t<string>('total') : ''} />
|
||||
<FormatBalance
|
||||
className='result'
|
||||
formatIndex={formatIndex}
|
||||
@ -423,7 +423,7 @@ function createBalanceItems (formatIndex: number, lookup: Record<string, string>
|
||||
}
|
||||
|
||||
function renderBalances (props: Props, lookup: Record<string, string>, bestNumber: BlockNumber | undefined, t: TFunction): React.ReactNode[] {
|
||||
const { address, balancesAll, democracyLocks, stakingInfo, votingOf, withBalance = true, withBalanceToggle = false } = props;
|
||||
const { address, balancesAll, democracyLocks, stakingInfo, votingOf, withBalance = true, withBalanceToggle = false, withLabel = false } = props;
|
||||
const balanceDisplay = withBalance === true
|
||||
? DEFAULT_BALANCES
|
||||
: withBalance || false;
|
||||
@ -434,7 +434,7 @@ function renderBalances (props: Props, lookup: Record<string, string>, bestNumbe
|
||||
|
||||
const [ownBonded, otherBonded] = calcBonded(stakingInfo, balanceDisplay.bonded);
|
||||
const isAllLocked = !!balancesAll && balancesAll.lockedBreakdown.some(({ amount }): boolean => amount?.isMax());
|
||||
const baseOpts = { address, balanceDisplay, bestNumber, democracyLocks, isAllLocked, otherBonded, ownBonded, votingOf, withBalanceToggle };
|
||||
const baseOpts = { address, balanceDisplay, bestNumber, democracyLocks, isAllLocked, otherBonded, ownBonded, votingOf, withBalanceToggle, withLabel };
|
||||
const items = [createBalanceItems(0, lookup, t, { ...baseOpts, balancesAll, stakingInfo })];
|
||||
|
||||
withBalanceToggle && balancesAll?.additional.length && balancesAll.additional.forEach((balancesAll, index): void => {
|
||||
|
||||
@ -27,6 +27,7 @@ interface Props {
|
||||
label?: React.ReactNode;
|
||||
labelBalance?: React.ReactNode;
|
||||
nameExtra?: React.ReactNode;
|
||||
onNameClick?: () => void;
|
||||
summary?: React.ReactNode;
|
||||
type?: KeyringItemType;
|
||||
value?: AccountId | AccountIndex | Address | string | null | Uint8Array;
|
||||
@ -39,7 +40,7 @@ interface Props {
|
||||
withShrink?: boolean;
|
||||
}
|
||||
|
||||
function AddressMini ({ balance, bonded, children, className = '', iconInfo, isHighlight, isPadded = true, label, labelBalance, nameExtra, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
|
||||
function AddressMini ({ balance, bonded, children, className = '', iconInfo, isHighlight, isPadded = true, label, labelBalance, nameExtra, onNameClick, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
@ -50,7 +51,7 @@ function AddressMini ({ balance, bonded, children, className = '', iconInfo, isH
|
||||
<label className='ui--AddressMini-label'>{label}</label>
|
||||
)}
|
||||
<div className='ui--AddressMini-icon'>
|
||||
<IdentityIcon value={value as Uint8Array} />
|
||||
<IdentityIcon value={value} />
|
||||
{iconInfo && (
|
||||
<div className='ui--AddressMini-icon-info'>
|
||||
{iconInfo}
|
||||
@ -59,7 +60,10 @@ function AddressMini ({ balance, bonded, children, className = '', iconInfo, isH
|
||||
</div>
|
||||
<div className='ui--AddressMini-info'>
|
||||
{withAddress && (
|
||||
<div className='ui--AddressMini-address'>
|
||||
<div
|
||||
className='ui--AddressMini-address'
|
||||
onClick={onNameClick}
|
||||
>
|
||||
{withName
|
||||
? (
|
||||
<AccountName
|
||||
|
||||
@ -39,57 +39,41 @@ export default React.memo(styled(AvatarItem)`
|
||||
align-items: center;
|
||||
|
||||
.ui--AvatarItem-icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin-right: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui--AvatarItem-details {
|
||||
.ui--AvatarItem-title {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ui--AvatarItem-subtitle {
|
||||
font-weight: var(--font-weight-normal);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.big {
|
||||
.ui--AvatarItem-icon {
|
||||
width: 3.4rem;
|
||||
height: 3.4rem;
|
||||
margin-right: 0.6rem;
|
||||
|
||||
> .ui--Icon {
|
||||
color: white;
|
||||
line-height: 2.4rem;
|
||||
margin-right: 0 !important;
|
||||
font-size: 1.6rem;
|
||||
line-height: 3.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AvatarItem-details {
|
||||
flex: 1;
|
||||
|
||||
.ui--AvatarItem-title {
|
||||
font-weight: var(--font-weight-normal);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ui--AvatarItem-subtitle {
|
||||
color: rgba(100, 100, 100, 0.6);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.big {
|
||||
.ui--AvatarItem-icon {
|
||||
width: 3.4rem;
|
||||
height: 3.4rem;
|
||||
margin-right: 0.6rem;
|
||||
|
||||
> .ui--Icon {
|
||||
font-size: 1.6rem;
|
||||
line-height: 3.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--AvatarItem-details {
|
||||
.ui--AvatarItem-name {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.4rem;
|
||||
}
|
||||
.ui--AvatarItem-name {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import styled from 'styled-components';
|
||||
|
||||
function Content ({ children, className = '' }: ContentProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={`${className} ui--Modal__Content`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -8,9 +8,9 @@ import styled from 'styled-components';
|
||||
|
||||
import Button from '../Button';
|
||||
|
||||
function Header ({ className, header, onClose }: HeaderProps) {
|
||||
function Header ({ className = '', header, onClose }: HeaderProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={`${className} ui--Modal__Header`}>
|
||||
{header && (
|
||||
<h1>{header}</h1>
|
||||
)}
|
||||
|
||||
@ -15,8 +15,10 @@ export interface ColumnsProps {
|
||||
hint?: React.ReactNode;
|
||||
}
|
||||
|
||||
type ModalSize = 'large' | 'medium' | 'small'
|
||||
|
||||
export interface ModalProps {
|
||||
size?: 'large' | 'medium' | 'small';
|
||||
size?: ModalSize;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
@ -32,7 +34,7 @@ export interface ActionsProps {
|
||||
}
|
||||
|
||||
export interface BodyProps {
|
||||
size: 'large' | 'medium' | 'small'
|
||||
size: ModalSize;
|
||||
}
|
||||
|
||||
export interface ContentProps {
|
||||
|
||||
@ -42,6 +42,7 @@ export { useIsParasLinked, useParaEndpoints } from './useParaEndpoints';
|
||||
export { usePassword } from './usePassword';
|
||||
export { useRegistrars } from './useRegistrars';
|
||||
export { useSavedFlags } from './useSavedFlags';
|
||||
export { useSubidentities } from './useSubidentities';
|
||||
export { useStakingInfo } from './useStakingInfo';
|
||||
export { useStepper } from './useStepper';
|
||||
export { useSudo } from './useSudo';
|
||||
|
||||
@ -34,7 +34,7 @@ interface QueryMapFn extends QueryTrackFn {
|
||||
};
|
||||
}
|
||||
|
||||
type TrackFn = RpcPromiseResult<AnyFunction> | QueryTrackFn;
|
||||
export type TrackFn = RpcPromiseResult<AnyFunction> | QueryTrackFn;
|
||||
|
||||
type CallFn = (...params: unknown[]) => Promise<VoidFn>;
|
||||
|
||||
|
||||
13
packages/react-hooks/src/useSubidentities.ts
Normal file
13
packages/react-hooks/src/useSubidentities.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2021 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { AccountId, BalanceOf } from '@polkadot/types/interfaces';
|
||||
|
||||
import { useApi } from './useApi';
|
||||
import { useCall } from './useCall';
|
||||
|
||||
export function useSubidentities (address: string): AccountId[] | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
return useCall<[BalanceOf, AccountId[]]>(api.query.identity?.subsOf, [address])?.[1];
|
||||
}
|
||||
@ -4,3 +4,4 @@
|
||||
export * from './addresses';
|
||||
export * from './MemoryStore';
|
||||
export * from './signers';
|
||||
export * from './addresses';
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
// 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';
|
||||
@ -15,8 +14,8 @@ 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 { mockApiHooks } from '@polkadot/test-support/utils/mockApiHooks';
|
||||
import { TypeRegistry } from '@polkadot/types/create';
|
||||
import { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
import { alice, bob, charlie } from '../keyring';
|
||||
@ -25,10 +24,6 @@ import { AccountOverrides, mockAccountHooks } from '../utils/accountDefaults';
|
||||
|
||||
let queueExtrinsic: (value: PartialQueueTxExtrinsic) => void;
|
||||
|
||||
function noop (): void {
|
||||
// ignore
|
||||
}
|
||||
|
||||
class NotYetRendered extends Error {
|
||||
}
|
||||
|
||||
@ -48,6 +43,7 @@ jest.mock('@polkadot/react-hooks/useAccountInfo', () => {
|
||||
? {
|
||||
...actual.useAccountInfo(address),
|
||||
flags: { ...actual.useAccountInfo(address).flags, ...(mockInfo.info.flags) },
|
||||
identity: { ...actual.useAccountInfo(address).identity, ...(mockInfo.info.identity) },
|
||||
tags: [...actual.useAccountInfo(address).tags, ...(mockInfo.info.tags)]
|
||||
}
|
||||
: actual.useAccountInfo(address);
|
||||
@ -71,6 +67,10 @@ jest.mock('@polkadot/react-hooks/useBestNumber', () => ({
|
||||
useBestNumber: () => 1
|
||||
}));
|
||||
|
||||
jest.mock('@polkadot/react-hooks/useSubidentities', () => ({
|
||||
useSubidentities: () => mockApiHooks.subs
|
||||
}));
|
||||
|
||||
export abstract class Page {
|
||||
private renderResult?: RenderResult
|
||||
protected readonly defaultAddresses = [alice, bob, charlie];
|
||||
@ -87,28 +87,32 @@ export abstract class Page {
|
||||
keyring.addExternal(address, meta);
|
||||
});
|
||||
|
||||
const noop = () => Promise.resolve(() => { /**/ });
|
||||
const mockApi: ApiProps = {
|
||||
api: {
|
||||
derive: {
|
||||
accounts: {
|
||||
info: () => Promise.resolve(() => { /**/
|
||||
})
|
||||
info: noop
|
||||
},
|
||||
balances: {
|
||||
all: () => ({
|
||||
accountNonce: mockAccountHooks.nonce,
|
||||
additional: [],
|
||||
freeBalance: 0,
|
||||
lockedBreakdown: [],
|
||||
reservedBalance: 0
|
||||
})
|
||||
all: noop
|
||||
},
|
||||
chain: {
|
||||
bestNumber: () => new BN(1) as BlockNumber
|
||||
democracy: {
|
||||
locks: noop
|
||||
},
|
||||
staking: {
|
||||
account: noop
|
||||
}
|
||||
},
|
||||
genesisHash: new TypeRegistry().createType('Hash', POLKADOT_GENESIS),
|
||||
query: {},
|
||||
query: {
|
||||
democracy: {
|
||||
votingOf: noop
|
||||
},
|
||||
identity: {
|
||||
identityOf: noop
|
||||
}
|
||||
},
|
||||
registry: { chainDecimals: [12], chainTokens: ['Unit'] },
|
||||
tx: {
|
||||
council: {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2021 @polkadot/page-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { fireEvent, within } from '@testing-library/react';
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
|
||||
export class Sidebar {
|
||||
public sidebar: HTMLElement
|
||||
@ -37,7 +37,8 @@ export class Sidebar {
|
||||
}
|
||||
|
||||
async assertAccountName (expectedAccountName: string): Promise<void> {
|
||||
const sideBarName = await this.findByTestId('account-name');
|
||||
const sideBarAddressSection = await this.findByTestId('sidebar-address-menu');
|
||||
const sideBarName = await within(sideBarAddressSection).findByTestId('account-name');
|
||||
|
||||
expect(sideBarName).toHaveTextContent(expectedAccountName);
|
||||
}
|
||||
@ -108,6 +109,21 @@ export class Sidebar {
|
||||
return within(this.sidebar).queryByTestId(testId);
|
||||
}
|
||||
|
||||
async findSubs (): Promise<HTMLElement[]> {
|
||||
const identitySection = await this.findByTestId('identity-section');
|
||||
|
||||
return within(identitySection).queryAllByTestId('subs');
|
||||
}
|
||||
|
||||
async openSubsModal (): Promise<HTMLElement> {
|
||||
const identitySection = await this.findByTestId('identity-section');
|
||||
const showSubsButton = await within(identitySection).findByText('Show list');
|
||||
|
||||
fireEvent.click(showSubsButton);
|
||||
|
||||
return screen.findByTestId('modal');
|
||||
}
|
||||
|
||||
private clickButton (buttonName: string) {
|
||||
const button = this.getByRole('button', { name: buttonName });
|
||||
|
||||
|
||||
@ -84,6 +84,7 @@ export const defaultMeta: KeyringJson$Meta = {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
export const defaultAccountInfo: UseAccountInfo = {
|
||||
flags: {},
|
||||
identity: { email: 'user@email.com', isExistent: true, judgements: [] },
|
||||
tags: []
|
||||
} as any;
|
||||
|
||||
|
||||
11
packages/test-support/src/utils/mockApiHooks.ts
Normal file
11
packages/test-support/src/utils/mockApiHooks.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2021 @polkadot/page-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class MockApiHooks {
|
||||
public subs: string[] | undefined = []
|
||||
public setSubs (subs: string[] | undefined) {
|
||||
this.subs = subs;
|
||||
}
|
||||
}
|
||||
|
||||
export const mockApiHooks = new MockApiHooks();
|
||||
Loading…
Reference in New Issue
Block a user