Split registrars display (#6157)
* 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 * Display judgements * Handle no judgements case * [WIP] Add popup test * Extract JudgmentTag component * Add popup test * Refactor tests and remove pointer from no judgements tag * Extract JudgementTag test class * Close popup on scroll * Remove old section * Clean registration * Clean up identity in useAccountInfo and mock in Page * Remove fragment tag * Fix imports * Move SortedJudgements to /types * Move Registrar interface to types * Refactor clickRegistrar fcn * Move hasAccount check to isRegistrar * Add useJudgements hook * Rename and move getJudgementColor to utils * Fix import and casting in tests * Sort imports * Add review fixes * Fix tests Co-authored-by: Krzysztof Jelski <krzysztof@ethworks.io> Co-authored-by: Ewa Kowalska <ewcia.kowalska@gmail.com> Co-authored-by: Jaco Greeff <jacogr@gmail.com>
This commit is contained in:
parent
d043b799cd
commit
053e0efca2
@ -103,6 +103,7 @@
|
||||
"Name given to this multisig. You can edit it at any later point in time.": "Name given to this multisig. You can edit it at any later point in time.",
|
||||
"Name given to this proxied account. You can edit it at any later point in time.": "Name given to this proxied account. You can edit it at any later point in time.",
|
||||
"Next": "Next",
|
||||
"No judgements": "No judgements",
|
||||
"No judgments": "No judgments",
|
||||
"No matches found": "No matches found",
|
||||
"No sub identities set.": "No sub identities set.",
|
||||
@ -356,4 +357,4 @@
|
||||
"website": "website",
|
||||
"your current password": "your current password",
|
||||
"your new password": "your new password"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@ import type { AddressIdentity } from '@polkadot/react-hooks/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { AddressMini, AvatarItem, Expander, IconLink, Tag } from '@polkadot/react-components';
|
||||
import { AddressMini, AvatarItem, Expander, IconLink } from '@polkadot/react-components';
|
||||
import { useApi, useRegistrars, useSubidentities, useToggle } from '@polkadot/react-hooks';
|
||||
import { isHex } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import Judgements from './Judgements';
|
||||
import RegistrarJudgement from './RegistrarJudgement';
|
||||
import UserIcon from './UserIcon';
|
||||
|
||||
@ -51,33 +52,6 @@ function Identity ({ address, identity }: Props): React.ReactElement<Props> | nu
|
||||
<div className='ui--AddressMenu-section ui--AddressMenu-identity'>
|
||||
<div className='ui--AddressMenu-sectionHeader'>
|
||||
{t<string>('identity')}
|
||||
<Tag
|
||||
color={
|
||||
identity.isBad
|
||||
? 'red'
|
||||
: identity.isGood
|
||||
? 'green'
|
||||
: 'yellow'
|
||||
}
|
||||
isTag={false}
|
||||
label={
|
||||
<>
|
||||
<b>{identity.judgements.length} </b>
|
||||
{
|
||||
identity.judgements.length
|
||||
? identity.isBad
|
||||
? identity.isErroneous
|
||||
? t<string>('Erroneous')
|
||||
: t<string>('Low quality')
|
||||
: identity.isKnownGood
|
||||
? t<string>('Known good')
|
||||
: t<string>('Reasonable')
|
||||
: t<string>('No judgments')
|
||||
}
|
||||
</>
|
||||
}
|
||||
size='tiny'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<AvatarItem
|
||||
@ -92,6 +66,7 @@ function Identity ({ address, identity }: Props): React.ReactElement<Props> | nu
|
||||
subtitle={identity.legal}
|
||||
title={identity.display}
|
||||
/>
|
||||
<Judgements address={address} />
|
||||
<div className='ui--AddressMenu-identityTable'>
|
||||
{identity.parent && (
|
||||
<div className='tr parent'>
|
||||
|
||||
44
packages/page-accounts/src/Sidebar/JudgementTag.tsx
Normal file
44
packages/page-accounts/src/Sidebar/JudgementTag.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Judgement } from '@polkadot/react-hooks/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { AddressSmall, Menu, Popup, Tag } from '@polkadot/react-components';
|
||||
|
||||
import { getJudgementColor } from '../util';
|
||||
|
||||
interface Props {
|
||||
judgement: Judgement
|
||||
}
|
||||
|
||||
function JudgementTag ({ judgement: { judgementName, registrars } }: Props): React.ReactElement<Props> {
|
||||
const judgementColor = useMemo(() => getJudgementColor(judgementName), [judgementName]);
|
||||
|
||||
return (
|
||||
<Popup
|
||||
closeOnScroll
|
||||
position='center'
|
||||
value={
|
||||
<Menu>
|
||||
{registrars.map((registrar) => registrar && (
|
||||
<AddressSmall
|
||||
key={registrar.address}
|
||||
value={registrar.address}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Tag
|
||||
color={judgementColor}
|
||||
isTag={false}
|
||||
label={`${registrars.length} ${judgementName}`}
|
||||
size='tiny'
|
||||
/>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(JudgementTag);
|
||||
63
packages/page-accounts/src/Sidebar/Judgements.tsx
Normal file
63
packages/page-accounts/src/Sidebar/Judgements.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Tag } from '@polkadot/react-components';
|
||||
import { useJudgements } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import JudgementTag from './JudgementTag';
|
||||
|
||||
interface Props {
|
||||
address: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Judgements ({ address, className = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const judgements = useJudgements(address);
|
||||
|
||||
if (judgements.length === 0) {
|
||||
return (
|
||||
<div
|
||||
className={`${className} no-judgements`}
|
||||
data-testid='judgements'
|
||||
>
|
||||
<Tag
|
||||
color='yellow'
|
||||
isTag={false}
|
||||
key='NoJudgements'
|
||||
label={t<string>('No judgements')}
|
||||
size='tiny'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
data-testid='judgements'
|
||||
>
|
||||
{judgements.map((judgement) =>
|
||||
<JudgementTag
|
||||
judgement={judgement}
|
||||
key={`${address}${judgement.judgementName}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Judgements)`
|
||||
margin-top: 0.714rem;
|
||||
|
||||
&:not(.no-judgements) {
|
||||
.ui--Tag:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -3,22 +3,25 @@
|
||||
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
|
||||
import i18next from '@polkadot/react-components/i18n';
|
||||
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 { charlieShortAddress, ferdieShortAddress, mockRegistration, registrars } from '@polkadot/test-support/mockData/registrations';
|
||||
import { Sidebar } from '@polkadot/test-support/pagesElements/Sidebar';
|
||||
import { mockApiHooks } from '@polkadot/test-support/utils/mockApiHooks';
|
||||
import { RegistrationJudgement } from '@polkadot/types/interfaces';
|
||||
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(() => {
|
||||
beforeAll(async () => {
|
||||
await i18next.changeLanguage('en');
|
||||
|
||||
if (keyring.getAccounts().length === 0) {
|
||||
keyring.loadAll({ isDevelopment: true, store: new MemoryStore() });
|
||||
}
|
||||
@ -38,8 +41,7 @@ describe('Sidebar', () => {
|
||||
describe('changes name', () => {
|
||||
beforeEach(async () => {
|
||||
accountsPage.render([[alice, anAccountWithMeta({ isDevelopment: false, name: initialName })]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
await sideBar.changeAccountName(newName);
|
||||
});
|
||||
|
||||
@ -54,14 +56,15 @@ describe('Sidebar', () => {
|
||||
});
|
||||
|
||||
it('within account row', async () => {
|
||||
const accountRows = await accountsPage.getAccountRows();
|
||||
|
||||
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();
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
await sideBar.clickByText('no tags');
|
||||
expect(sideBar.queryByRole('combobox')).toBeFalsy();
|
||||
|
||||
@ -72,8 +75,7 @@ describe('Sidebar', () => {
|
||||
accountsPage.renderAccountsWithDefaultAddresses(
|
||||
anAccountWithInfo({ flags: { isEditable: false } as AddressFlags })
|
||||
);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
sideBar.edit();
|
||||
|
||||
await expect(sideBar.typeAccountName(newName)).rejects.toThrowError(nameInputNotFoundError);
|
||||
@ -81,9 +83,10 @@ describe('Sidebar', () => {
|
||||
|
||||
describe('on edit cancel', () => {
|
||||
beforeEach(async () => {
|
||||
accountsPage.render([[alice, anAccountWithMeta({ isDevelopment: false, name: initialName, tags: [] })]]);
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
accountsPage.renderAccountsWithDefaultAddresses(
|
||||
anAccountWithMeta({ isDevelopment: false, name: initialName, tags: [] })
|
||||
);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
|
||||
await sideBar.assertTags('no tags');
|
||||
sideBar.edit();
|
||||
@ -110,9 +113,7 @@ describe('Sidebar', () => {
|
||||
anAccountWithMeta({ name: 'alice' }),
|
||||
anAccountWithMeta({ name: 'bob' })
|
||||
);
|
||||
|
||||
accountRows = await accountsPage.getAccountRows();
|
||||
sideBar = await accountRows[0].openSidebar();
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
sideBar.edit();
|
||||
});
|
||||
|
||||
@ -137,48 +138,111 @@ describe('Sidebar', () => {
|
||||
it('cancels editing and changes name when opening sidebar for another account', async () => {
|
||||
await waitFor(() => sideBar.assertAccountInput('alice'));
|
||||
|
||||
sideBar = await accountRows[1].openSidebar();
|
||||
sideBar = await accountsPage.openSidebarForRow(1);
|
||||
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();
|
||||
describe('subs', () => {
|
||||
describe('when do not exist', () => {
|
||||
it('does not display subs', async () => {
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
const subs = await sideBar.findSubs();
|
||||
|
||||
expect(subs).toHaveLength(0);
|
||||
expect(subs).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when exist', () => {
|
||||
let subs: HTMLElement[];
|
||||
|
||||
beforeEach(async () => {
|
||||
mockApiHooks.setSubs([bob]);
|
||||
accountsPage.renderAccountsWithDefaultAddresses(
|
||||
anAccount(),
|
||||
anAccountWithMeta({ name: 'Bob' })
|
||||
);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
subs = await sideBar.findSubs();
|
||||
});
|
||||
|
||||
it('displays count of subs and account names', () => {
|
||||
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 () => {
|
||||
const subAccount = subs[0].childNodes[1];
|
||||
|
||||
fireEvent.click(await within(subAccount as HTMLElement).findByTestId('account-name'));
|
||||
|
||||
await sideBar.assertAccountName('BOB');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
describe('judgements', () => {
|
||||
it('displays several judgements', async () => {
|
||||
mockApiHooks.setJudgements(mockRegistration.judgements as RegistrationJudgement[]);
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
|
||||
const subsNumber = subs[0].childNodes[0];
|
||||
const subAccount = subs[0].childNodes[1];
|
||||
await sideBar.assertJudgement('1 Known good');
|
||||
await sideBar.assertJudgement('2 Reasonable');
|
||||
await sideBar.assertJudgement('1 Erroneous');
|
||||
});
|
||||
|
||||
expect(subsNumber).toHaveClass('subs-number');
|
||||
expect(subsNumber).toHaveTextContent('1');
|
||||
expect(subAccount).toHaveTextContent('BOB');
|
||||
});
|
||||
it('displays no judgements', async () => {
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
|
||||
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];
|
||||
await sideBar.assertJudgement('No judgements');
|
||||
});
|
||||
|
||||
fireEvent.click(await within(subAccount as HTMLElement).findByTestId('account-name'));
|
||||
describe('displays registrars', () => {
|
||||
beforeEach(async () => {
|
||||
mockApiHooks.setJudgements(mockRegistration.judgements as RegistrationJudgement[]);
|
||||
mockApiHooks.setRegistrars(registrars);
|
||||
accountsPage.render([
|
||||
[alice, anAccountWithMeta({ name: 'Alice' })],
|
||||
[bob, anAccountWithMeta({ name: 'Bob' })]
|
||||
]);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
});
|
||||
|
||||
await sideBar.assertAccountName('BOB');
|
||||
it('singular registrar', async () => {
|
||||
const judgementTag = await sideBar.getJudgement('1 Known good');
|
||||
|
||||
await judgementTag.assertRegistrars([charlieShortAddress]);
|
||||
});
|
||||
|
||||
it('multiple registrars', async () => {
|
||||
const judgementTag = await sideBar.getJudgement('2 Reasonable');
|
||||
|
||||
await judgementTag.assertRegistrars(['BOB', ferdieShortAddress]);
|
||||
});
|
||||
|
||||
it('opens clicked registrar in sidebar and closes popup', async () => {
|
||||
const judgementTag = await sideBar.getJudgement('2 Reasonable');
|
||||
|
||||
await judgementTag.clickRegistrar('BOB');
|
||||
|
||||
expect(screen.queryByTestId('popup-window')).toBeFalsy();
|
||||
|
||||
await sideBar.assertAccountName('BOB');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockApiHooks.setJudgements([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { DisplayedJudgement } from '@polkadot/react-components/types';
|
||||
import type { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
|
||||
import type { CreateResult } from '@polkadot/ui-keyring/types';
|
||||
import type { AccountBalance, SortedAccount } from './types';
|
||||
@ -113,3 +114,7 @@ export function sortAccounts (accountsList: SortedAccount[], accountsMap: Record
|
||||
? 1
|
||||
: -1);
|
||||
}
|
||||
|
||||
export const getJudgementColor = (name: DisplayedJudgement): 'green' | 'red' => {
|
||||
return (name === 'Erroneous' || name === 'Low quality') ? 'red' : 'green';
|
||||
};
|
||||
|
||||
@ -6,6 +6,7 @@ import React from 'react';
|
||||
|
||||
import { anAccount } from '@polkadot/test-support/creation/account';
|
||||
import { Page } from '@polkadot/test-support/pages/Page';
|
||||
import { Sidebar } from '@polkadot/test-support/pagesElements/Sidebar';
|
||||
import { AccountOverrides } from '@polkadot/test-support/types';
|
||||
import { assertText, clickButton } from '@polkadot/test-support/utils/renderedScreenUtils';
|
||||
|
||||
@ -74,6 +75,12 @@ export class AccountsPage extends Page {
|
||||
this.render(accounts);
|
||||
}
|
||||
|
||||
async openSidebarForRow (accountRowIndex: number): Promise<Sidebar> {
|
||||
const accountRows = await this.getAccountRows();
|
||||
|
||||
return accountRows[accountRowIndex].openSidebar();
|
||||
}
|
||||
|
||||
private async getSortCategory (categoryName: string): Promise<HTMLElement> {
|
||||
const sortByComponent = await this.getSortByComponent();
|
||||
const availableCategories = await within(sortByComponent).findAllByRole('option');
|
||||
|
||||
@ -15,8 +15,9 @@ function PopupWindow ({ children, className = '', position, triggerRef, windowRe
|
||||
return createPortal(
|
||||
<div
|
||||
className={`${className}${verticalPosition === 'top' ? ' pointerTop' : ' pointerBottom'}`}
|
||||
data-testid='popup-window'
|
||||
ref={windowRef}
|
||||
style={renderWindowPosition && { transform: `translate3d(${renderWindowPosition.x}px, ${renderWindowPosition.y}px, 0)`, zIndex: 300 }}
|
||||
style={renderWindowPosition && { transform: `translate3d(${renderWindowPosition.x}px, ${renderWindowPosition.y}px, 0)`, zIndex: 1000 }}
|
||||
>
|
||||
{children}
|
||||
</div>,
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
import type { PopupProps } from './types';
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button } from '@polkadot/react-components/index';
|
||||
@ -11,13 +11,21 @@ import { useOutsideClick, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import PopupWindow from './PopupWindow';
|
||||
|
||||
function Popup ({ children, className = '', isDisabled = false, onCloseAction, position = 'left', value }: PopupProps) {
|
||||
function Popup ({ children, className = '', closeOnScroll = false, isDisabled = false, onCloseAction, position = 'left', value }: PopupProps) {
|
||||
const [isOpen, toggleIsOpen, setIsOpen] = useToggle(false);
|
||||
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const closeWindow = useCallback(() => setIsOpen(false), [setIsOpen]);
|
||||
|
||||
useOutsideClick([triggerRef, dropdownRef], () => setIsOpen(false));
|
||||
useOutsideClick([triggerRef, dropdownRef], closeWindow);
|
||||
|
||||
useEffect(() => {
|
||||
if (closeOnScroll) {
|
||||
document.addEventListener('scroll', closeWindow, true);
|
||||
}
|
||||
|
||||
return () => document.removeEventListener('scroll', closeWindow, true);
|
||||
}, [closeOnScroll, closeWindow, setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen && onCloseAction) {
|
||||
|
||||
@ -17,6 +17,7 @@ export interface PopupWindowProps {
|
||||
export interface PopupProps {
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
closeOnScroll?: boolean;
|
||||
value?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
position?: HorizontalPosition;
|
||||
|
||||
@ -83,3 +83,5 @@ export interface ThemeProps {
|
||||
export type FlagColor = 'blue' | 'green' | 'grey' | 'orange' | 'pink' | 'red' | 'yellow' | 'theme';
|
||||
|
||||
export type AccountIdIsh = AccountId | AccountIndex | Address | string | Uint8Array | null;
|
||||
|
||||
export type DisplayedJudgement = 'Erroneous' | 'Low quality' | 'Known good' | 'Reasonable';
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
// Copyright 2017-2021 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { RegistrarIndex } from '@polkadot/types/interfaces/identity/types';
|
||||
|
||||
import { DisplayedJudgement } from '../types';
|
||||
|
||||
export interface DropdownOption {
|
||||
className?: string;
|
||||
key?: string;
|
||||
@ -9,3 +13,4 @@ export interface DropdownOption {
|
||||
}
|
||||
|
||||
export type DropdownOptions = DropdownOption[];
|
||||
export type SortedJudgements = ({ judgementName: DisplayedJudgement, registrarsIndexes: RegistrarIndex[] })[];
|
||||
|
||||
@ -27,6 +27,7 @@ export { useIncrement } from './useIncrement';
|
||||
export { useInflation } from './useInflation';
|
||||
export { useIpfs } from './useIpfs';
|
||||
export { useIsMountedRef } from './useIsMountedRef';
|
||||
export { useJudgements } from './useJudgements';
|
||||
export { useLedger } from './useLedger';
|
||||
export { useLoadingDelay } from './useLoadingDelay';
|
||||
export { useMapEntries } from './useMapEntries';
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
import type { SubmittableExtrinsic } from '@polkadot/api/types';
|
||||
import type { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/api-derive/types';
|
||||
import type { DisplayedJudgement } from '@polkadot/react-components/types';
|
||||
import type { AccountId, Balance, BlockNumber, Call, Exposure, Hash, RewardDestination, SessionIndex, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces';
|
||||
import type { IExtrinsic } from '@polkadot/types/types';
|
||||
import type { KeyringJson$Meta } from '@polkadot/ui-keyring/types';
|
||||
@ -99,13 +100,8 @@ export interface AddressFlags extends DeriveAccountFlags {
|
||||
}
|
||||
|
||||
export interface AddressIdentity extends DeriveAccountRegistration {
|
||||
isGood: boolean;
|
||||
isBad: boolean;
|
||||
isKnownGood: boolean;
|
||||
isReasonable: boolean;
|
||||
isErroneous: boolean;
|
||||
isLowQuality: boolean;
|
||||
isExistent: boolean;
|
||||
isKnownGood: boolean;
|
||||
waitCount: number;
|
||||
}
|
||||
|
||||
@ -150,3 +146,15 @@ export interface StakerState {
|
||||
stashId: string;
|
||||
validatorPrefs?: ValidatorPrefs;
|
||||
}
|
||||
|
||||
export interface Registrar {
|
||||
address: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface Judgement {
|
||||
judgementName: DisplayedJudgement;
|
||||
registrars: (Registrar | undefined)[];
|
||||
}
|
||||
|
||||
export type UseJudgements = Judgement[]
|
||||
|
||||
@ -100,19 +100,11 @@ export function useAccountInfo (value: string | null, isContract = false): UseAc
|
||||
if (identity) {
|
||||
const judgements = identity.judgements.filter(([, judgement]) => !judgement.isFeePaid);
|
||||
const isKnownGood = judgements.some(([, judgement]) => judgement.isKnownGood);
|
||||
const isReasonable = judgements.some(([, judgement]) => judgement.isReasonable);
|
||||
const isErroneous = judgements.some(([, judgement]) => judgement.isErroneous);
|
||||
const isLowQuality = judgements.some(([, judgement]) => judgement.isLowQuality);
|
||||
|
||||
setIdentity({
|
||||
...identity,
|
||||
isBad: isErroneous || isLowQuality,
|
||||
isErroneous,
|
||||
isExistent: !!identity.display,
|
||||
isGood: isKnownGood || isReasonable,
|
||||
isKnownGood,
|
||||
isLowQuality,
|
||||
isReasonable,
|
||||
judgements,
|
||||
waitCount: identity.judgements.length - judgements.length
|
||||
});
|
||||
|
||||
21
packages/react-hooks/src/useJudgements.tsx
Normal file
21
packages/react-hooks/src/useJudgements.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2021 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { getJudgements } from './utils/getJudgements';
|
||||
import { matchRegistrarAccountsWithIndexes } from './utils/matchRegistrarAccountsWithIndexes';
|
||||
import { UseJudgements } from './types';
|
||||
import { useAccountInfo, useRegistrars } from './';
|
||||
|
||||
export function useJudgements (address: string): UseJudgements {
|
||||
const { identity } = useAccountInfo(address);
|
||||
const { registrars: allRegistrars } = useRegistrars();
|
||||
|
||||
const judgementsWithRegistrarIndexes = useMemo(() => getJudgements(identity), [identity]);
|
||||
|
||||
return useMemo(
|
||||
() => matchRegistrarAccountsWithIndexes(judgementsWithRegistrarIndexes, allRegistrars),
|
||||
[allRegistrars, judgementsWithRegistrarIndexes]
|
||||
);
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { RegistrarInfo } from '@polkadot/types/interfaces';
|
||||
import type { Registrar } from './types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@ -15,11 +16,6 @@ interface RegistrarNull {
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface Registrar {
|
||||
address: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isRegistrar: boolean;
|
||||
registrars: Registrar[];
|
||||
@ -29,7 +25,7 @@ interface State {
|
||||
export function useRegistrars (skipQuery?: boolean): State {
|
||||
const { api } = useApi();
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const query = useCall<Option<RegistrarInfo>[]>(!skipQuery && hasAccounts && api.query.identity?.registrars);
|
||||
const query = useCall<Option<RegistrarInfo>[]>(!skipQuery && api.query.identity?.registrars);
|
||||
|
||||
// determine if we have a registrar or not - registrars are allowed to approve
|
||||
return useMemo(
|
||||
@ -44,10 +40,10 @@ export function useRegistrars (skipQuery?: boolean): State {
|
||||
.filter((registrar): registrar is Registrar => !!registrar.address);
|
||||
|
||||
return {
|
||||
isRegistrar: registrars.some(({ address }) => allAccounts.includes(address)),
|
||||
isRegistrar: hasAccounts && registrars.some(({ address }) => allAccounts.includes(address)),
|
||||
registrars
|
||||
};
|
||||
},
|
||||
[allAccounts, query]
|
||||
[allAccounts, hasAccounts, query]
|
||||
);
|
||||
}
|
||||
|
||||
64
packages/react-hooks/src/utils/getJudgements.ts
Normal file
64
packages/react-hooks/src/utils/getJudgements.ts
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2017-2021 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveAccountRegistration } from '@polkadot/api-derive/types';
|
||||
import type { DisplayedJudgement } from '@polkadot/react-components/types';
|
||||
import type { SortedJudgements } from '@polkadot/react-components/util/types';
|
||||
import type { RegistrationJudgement } from '@polkadot/types/interfaces';
|
||||
|
||||
function extractIndexes (registrars: RegistrationJudgement[]) {
|
||||
return registrars.map((judgement) => judgement[0]);
|
||||
}
|
||||
|
||||
export function getJudgements (identity: DeriveAccountRegistration | undefined): SortedJudgements {
|
||||
if (!identity) return [];
|
||||
|
||||
const judgements = groupJudgements(identity.judgements);
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const name in judgements) {
|
||||
const judgementName = name as DisplayedJudgement;
|
||||
|
||||
if (judgements[judgementName].length !== 0) {
|
||||
result.push({ judgementName, registrarsIndexes: extractIndexes(judgements[judgementName]) });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function groupJudgements (judgements: RegistrationJudgement[]): Record<DisplayedJudgement, RegistrationJudgement[]> {
|
||||
const consideredJudgements = filterConsideredJudgements(judgements);
|
||||
const knownGoodJudgements = filterKnownGood(consideredJudgements);
|
||||
const reasonableJudgements = filterReasonable(consideredJudgements);
|
||||
const erroneousJudgements = filterErroneous(consideredJudgements);
|
||||
const lowQualityJudgements = filterLowQuality(consideredJudgements);
|
||||
|
||||
return {
|
||||
Erroneous: erroneousJudgements,
|
||||
'Known good': knownGoodJudgements,
|
||||
'Low quality': lowQualityJudgements,
|
||||
Reasonable: reasonableJudgements
|
||||
};
|
||||
}
|
||||
|
||||
const filterConsideredJudgements = (judgements: RegistrationJudgement[]): RegistrationJudgement[] => {
|
||||
return judgements.filter(([, judgement]) => !judgement.isFeePaid);
|
||||
};
|
||||
|
||||
const filterKnownGood = (judgements: RegistrationJudgement[]): RegistrationJudgement[] => {
|
||||
return judgements.filter(([, judgement]) => judgement.isKnownGood);
|
||||
};
|
||||
|
||||
const filterReasonable = (judgements: RegistrationJudgement[]): RegistrationJudgement[] => {
|
||||
return judgements.filter(([, judgement]) => judgement.isReasonable);
|
||||
};
|
||||
|
||||
const filterErroneous = (judgements: RegistrationJudgement[]): RegistrationJudgement[] => {
|
||||
return judgements.filter(([, judgement]) => judgement.isErroneous);
|
||||
};
|
||||
|
||||
const filterLowQuality = (judgements: RegistrationJudgement[]): RegistrationJudgement[] => {
|
||||
return judgements.filter(([, judgement]) => judgement.isLowQuality);
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2021 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SortedJudgements } from '@polkadot/react-components/util/types';
|
||||
import type { Judgement, Registrar } from '../types';
|
||||
|
||||
export function matchRegistrarAccountsWithIndexes (
|
||||
judgementsWithRegistrarIndexes: SortedJudgements,
|
||||
allRegistrars: Registrar[]
|
||||
): Judgement[] {
|
||||
return judgementsWithRegistrarIndexes.map(({ judgementName, registrarsIndexes }) => {
|
||||
const findRegistrarByIndex = (index: number) => allRegistrars.find((registrar) => registrar.index === index);
|
||||
|
||||
return {
|
||||
judgementName,
|
||||
registrars: registrarsIndexes.map((index) => findRegistrarByIndex(index.toNumber()))
|
||||
};
|
||||
});
|
||||
}
|
||||
47
packages/test-support/src/mockData/registrations.ts
Normal file
47
packages/test-support/src/mockData/registrations.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2021 @polkadot/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registrar } from '@polkadot/react-hooks/types';
|
||||
|
||||
import registry from '@polkadot/react-api/typeRegistry';
|
||||
|
||||
import { bob, charlie, ferdie } from '../keyring';
|
||||
|
||||
export const mockRegistration = {
|
||||
judgements: [
|
||||
[
|
||||
registry.createType('RegistrarIndex', '0'),
|
||||
{
|
||||
isReasonable: true
|
||||
}
|
||||
],
|
||||
[
|
||||
registry.createType('RegistrarIndex', '1'),
|
||||
{
|
||||
isKnownGood: true
|
||||
}
|
||||
],
|
||||
[
|
||||
registry.createType('RegistrarIndex', '2'),
|
||||
{
|
||||
isErroneous: true
|
||||
}
|
||||
],
|
||||
[
|
||||
registry.createType('RegistrarIndex', '3'),
|
||||
{
|
||||
isReasonable: true
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
export const bobRegistrar: Registrar = { address: bob, index: 0 };
|
||||
export const charlieRegistrar: Registrar = { address: charlie, index: 1 };
|
||||
export const ferdieRegistrar: Registrar = { address: ferdie, index: 3 };
|
||||
|
||||
export const registrars: Registrar[] = [bobRegistrar, charlieRegistrar, ferdieRegistrar];
|
||||
|
||||
export const bobShortAddress = '5FHneW…M694ty';
|
||||
export const charlieShortAddress = '5DAAnr…3PTXFy';
|
||||
export const ferdieShortAddress = '5CiPPs…SK2DjL';
|
||||
@ -18,7 +18,7 @@ import { mockApiHooks } from '@polkadot/test-support/utils/mockApiHooks';
|
||||
import { TypeRegistry } from '@polkadot/types/create';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
import { alice, bob, charlie } from '../keyring';
|
||||
import { alice, bob, charlie, ferdie } from '../keyring';
|
||||
import { Table } from '../pagesElements';
|
||||
import { AccountOverrides, mockAccountHooks } from '../utils/accountDefaults';
|
||||
|
||||
@ -43,7 +43,14 @@ 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) },
|
||||
identity: {
|
||||
...actual.useAccountInfo(address).identity,
|
||||
...(mockInfo.info.identity),
|
||||
judgements: [
|
||||
...(actual.useAccountInfo(address).identity?.judgements || []),
|
||||
...(mockApiHooks.judgements || [])
|
||||
]
|
||||
},
|
||||
tags: [...actual.useAccountInfo(address).tags, ...(mockInfo.info.tags)]
|
||||
}
|
||||
: actual.useAccountInfo(address);
|
||||
@ -71,9 +78,20 @@ jest.mock('@polkadot/react-hooks/useSubidentities', () => ({
|
||||
useSubidentities: () => mockApiHooks.subs
|
||||
}));
|
||||
|
||||
jest.mock('@polkadot/react-hooks/useSubidentities', () => ({
|
||||
useSubidentities: () => mockApiHooks.subs
|
||||
}));
|
||||
|
||||
jest.mock('@polkadot/react-hooks/useRegistrars', () => ({
|
||||
useRegistrars: () => ({
|
||||
isRegistrar: false,
|
||||
registrars: mockApiHooks.registrars
|
||||
})
|
||||
}));
|
||||
|
||||
export abstract class Page {
|
||||
private renderResult?: RenderResult
|
||||
protected readonly defaultAddresses = [alice, bob, charlie];
|
||||
protected readonly defaultAddresses = [alice, bob, charlie, ferdie];
|
||||
|
||||
protected constructor (private readonly overview: React.ReactElement, private readonly rowClassName: string) {
|
||||
this.overview = overview;
|
||||
@ -97,6 +115,9 @@ export abstract class Page {
|
||||
balances: {
|
||||
all: noop
|
||||
},
|
||||
chain: {
|
||||
bestNumber: noop
|
||||
},
|
||||
democracy: {
|
||||
locks: noop
|
||||
},
|
||||
|
||||
36
packages/test-support/src/pagesElements/JudgementTag.ts
Normal file
36
packages/test-support/src/pagesElements/JudgementTag.ts
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2021 @polkadot/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
|
||||
export class JudgementTag {
|
||||
public judgementTag: HTMLElement
|
||||
|
||||
constructor (judgementTag: HTMLElement) {
|
||||
this.judgementTag = judgementTag;
|
||||
}
|
||||
|
||||
async assertRegistrars (expectedRegistrars: string[]): Promise<void> {
|
||||
const popup = await this.openPopup();
|
||||
|
||||
for (let index = 0; index < expectedRegistrars.length; index++) {
|
||||
await within(popup).findByText(expectedRegistrars[index]);
|
||||
}
|
||||
}
|
||||
|
||||
async clickRegistrar (registrarName: string): Promise<void> {
|
||||
const popup = await this.openPopup();
|
||||
|
||||
const registrars = await within(popup).findAllByTestId('account-name');
|
||||
|
||||
const registrar = registrars.find((reg) => reg.textContent === registrarName) ?? fail('Registrar not found');
|
||||
|
||||
fireEvent.click(registrar);
|
||||
}
|
||||
|
||||
private async openPopup (): Promise<HTMLElement> {
|
||||
fireEvent.click(this.judgementTag);
|
||||
|
||||
return screen.findByTestId('popup-window');
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
// Copyright 2017-2021 @polkadot/page-accounts authors & contributors
|
||||
// Copyright 2017-2021 @polkadot/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
|
||||
import { JudgementTag } from './JudgementTag';
|
||||
|
||||
export class Sidebar {
|
||||
public sidebar: HTMLElement
|
||||
|
||||
@ -43,6 +45,12 @@ export class Sidebar {
|
||||
expect(sideBarName).toHaveTextContent(expectedAccountName);
|
||||
}
|
||||
|
||||
async assertJudgement (judgement: string): Promise<void> {
|
||||
const judgementsSection = await this.findByTestId('judgements');
|
||||
|
||||
expect(judgementsSection).toHaveTextContent(judgement);
|
||||
}
|
||||
|
||||
async assertTags (tagsContent: string): Promise<void> {
|
||||
const sideBarTags = await this.findByTestId('sidebar-tags');
|
||||
|
||||
@ -124,6 +132,12 @@ export class Sidebar {
|
||||
return screen.findByTestId('modal');
|
||||
}
|
||||
|
||||
async getJudgement (judgementName: string): Promise<JudgementTag> {
|
||||
const judgements = await this.findByTestId('judgements');
|
||||
|
||||
return new JudgementTag(await within(judgements).findByText(judgementName));
|
||||
}
|
||||
|
||||
private clickButton (buttonName: string) {
|
||||
const button = this.getByRole('button', { name: buttonName });
|
||||
|
||||
|
||||
@ -1,11 +1,25 @@
|
||||
// Copyright 2017-2021 @polkadot/page-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registrar } from '@polkadot/react-hooks/types';
|
||||
import type { RegistrationJudgement } from '@polkadot/types/interfaces';
|
||||
|
||||
class MockApiHooks {
|
||||
public subs: string[] | undefined = []
|
||||
public subs: string[] | undefined = [];
|
||||
public judgements: RegistrationJudgement[] | undefined = [];
|
||||
public registrars: Registrar[] = [];
|
||||
|
||||
public setSubs (subs: string[] | undefined) {
|
||||
this.subs = subs;
|
||||
}
|
||||
|
||||
public setJudgements (judgements: RegistrationJudgement[] | undefined) {
|
||||
this.judgements = judgements;
|
||||
}
|
||||
|
||||
public setRegistrars (registrars: Registrar[]) {
|
||||
this.registrars = registrars;
|
||||
}
|
||||
}
|
||||
|
||||
export const mockApiHooks = new MockApiHooks();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user