mirror of
https://github.com/pezkuwichain/pezkuwi-sdk-ui.git
synced 2026-06-12 17:01:07 +00:00
Initial commit: Pezkuwi SDK UI
Comprehensive web interface for interacting with Pezkuwi blockchain. Features: - Blockchain explorer - Wallet management - Staking interface - Governance participation - Developer tools Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveBalancesAll, DeriveStakingAccount } from '@pezkuwi/api-derive/types';
|
||||
import type { Accounts } from '@pezkuwi/react-hooks/ctx/types';
|
||||
import type { UseAccountInfo } from '@pezkuwi/react-hooks/types';
|
||||
import type { KeyringJson$Meta } from '@pezkuwi/ui-keyring/types';
|
||||
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { balanceOf } from '../creation/balance.js';
|
||||
import { makeStakingLedger } from '../creation/staking.js';
|
||||
|
||||
export interface Account {
|
||||
balance: DeriveBalancesAll,
|
||||
info: UseAccountInfo,
|
||||
staking: DeriveStakingAccount
|
||||
}
|
||||
|
||||
export type AccountsMap = Record<string, Account>;
|
||||
|
||||
export type Override<T> = {
|
||||
[P in keyof T]?: T[P];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inputs structure
|
||||
*/
|
||||
export interface AccountOverrides {
|
||||
meta?: Override<KeyringJson$Meta>;
|
||||
balance?: Override<DeriveBalancesAll>;
|
||||
staking?: Override<DeriveStakingAccount>;
|
||||
info?: Override<UseAccountInfo>;
|
||||
}
|
||||
|
||||
export const emptyAccounts: Accounts = {
|
||||
allAccounts: [],
|
||||
allAccountsHex: [],
|
||||
areAccountsLoaded: true,
|
||||
hasAccounts: false,
|
||||
isAccount: () => true
|
||||
};
|
||||
|
||||
// here it's extremely hard to reconstruct the entire DeriveBalancesAll upfront, so we incrementally add properties
|
||||
// instead along the way; thus the need to tell the tsc we know what we are doing here
|
||||
export const defaultBalanceAccount = {
|
||||
accountNonce: new BN(1),
|
||||
additional: [],
|
||||
availableBalance: balanceOf(0),
|
||||
freeBalance: balanceOf(0),
|
||||
lockedBalance: balanceOf(0),
|
||||
lockedBreakdown: [],
|
||||
namedReserves: [],
|
||||
reservedBalance: balanceOf(0)
|
||||
} as unknown as DeriveBalancesAll;
|
||||
|
||||
// here it's extremely hard to reconstruct the entire DeriveStakingAccount upfront,
|
||||
// so we set just the properties that we use in page-accounts
|
||||
export const defaultStakingAccount = {
|
||||
nextSessionIds: [],
|
||||
nominators: [],
|
||||
redeemable: balanceOf(0),
|
||||
sessionIds: [],
|
||||
stakingLedger: makeStakingLedger(0),
|
||||
unlocking: [
|
||||
{
|
||||
remainingEras: new BN('1000000000'),
|
||||
value: balanceOf(0)
|
||||
},
|
||||
{
|
||||
remainingEras: new BN('2000000000'),
|
||||
value: balanceOf(0)
|
||||
},
|
||||
{
|
||||
remainingEras: new BN('3000000000'),
|
||||
value: balanceOf(0)
|
||||
}
|
||||
]
|
||||
} as unknown as DeriveStakingAccount;
|
||||
|
||||
export const defaultMeta: KeyringJson$Meta = {};
|
||||
export const defaultAccountInfo = {
|
||||
flags: {},
|
||||
identity: { email: 'user@email.com', isExistent: true, judgements: [] },
|
||||
tags: []
|
||||
} as unknown as UseAccountInfo;
|
||||
|
||||
class MockAccountHooks {
|
||||
public useAccounts: Accounts = emptyAccounts;
|
||||
public accountsMap: AccountsMap = {};
|
||||
|
||||
public nonce: BN = new BN(1);
|
||||
|
||||
public setAccounts (accounts: [string, AccountOverrides][]): void {
|
||||
this.useAccounts = {
|
||||
allAccounts: accounts.map(([address]) => address),
|
||||
allAccountsHex: [],
|
||||
areAccountsLoaded: true,
|
||||
hasAccounts: accounts && accounts.length !== 0,
|
||||
isAccount: () => true
|
||||
};
|
||||
|
||||
for (const [address, props] of accounts) {
|
||||
const staking = { ...defaultStakingAccount };
|
||||
const meta = { ...defaultMeta };
|
||||
const balance = { ...defaultBalanceAccount };
|
||||
const info = { ...defaultAccountInfo };
|
||||
|
||||
Object
|
||||
.entries(props.meta || meta)
|
||||
.forEach(([key, value]) => {
|
||||
(meta as Record<string, unknown>)[key] = value;
|
||||
});
|
||||
Object
|
||||
.entries(props.balance || balance)
|
||||
.forEach(([key, value]) => {
|
||||
(balance as Record<string, unknown>)[key] = value;
|
||||
});
|
||||
Object
|
||||
.entries(props.staking || staking)
|
||||
.forEach(([key, value]) => {
|
||||
(staking as Record<string, unknown>)[key] = value;
|
||||
});
|
||||
Object
|
||||
.entries(props.info || info)
|
||||
.forEach(([key, value]) => {
|
||||
(info as Record<string, unknown>)[key] = value;
|
||||
});
|
||||
|
||||
this.accountsMap[address] = {
|
||||
balance,
|
||||
info,
|
||||
staking
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mockAccountHooks = new MockAccountHooks();
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Balance } from '@pezkuwi/types/interfaces';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import { formatBalance } from '@pezkuwi/util';
|
||||
|
||||
import { balanceOf } from '../creation/balance.js';
|
||||
|
||||
/**
|
||||
* Creates a balance instance for testing purposes which most often do not need to specify/use decimal part.
|
||||
* @param amountInt Integer part of the balance number
|
||||
* @param decimalsString Decimals part of the balance number. Note! This is a string sequence just after '.' separator
|
||||
* that is the point that separates integers from decimals. E.g. (100, 4567) => 100.45670000...00
|
||||
*/
|
||||
export function balance (amountInt: number, decimalsString?: string): Balance {
|
||||
const decimalsPadded = (decimalsString || '').padEnd(12, '0');
|
||||
|
||||
return balanceOf(amountInt.toString() + decimalsPadded);
|
||||
}
|
||||
|
||||
export function showBalance (amount: number): string {
|
||||
return format(balance(amount));
|
||||
}
|
||||
|
||||
export function format (amount: Balance | BN): string {
|
||||
return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true });
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './accountDefaults.js';
|
||||
export * from './balance.js';
|
||||
export * from './mockApiHooks.js';
|
||||
export * from './renderedScreenUtils.js';
|
||||
export * from './waitFor.js';
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registrar } from '@pezkuwi/react-hooks/types';
|
||||
import type { H256, Multisig, ProxyDefinition, RegistrationJudgement, Voting } from '@pezkuwi/types/interfaces';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
class MockApiHooks {
|
||||
public multisigApprovals: [H256, Multisig][] | undefined = [];
|
||||
public delegations: Voting[] | undefined;
|
||||
public proxies: [ProxyDefinition[], BN][] | undefined = [];
|
||||
public subs: string[] | undefined = [];
|
||||
public judgements: RegistrationJudgement[] | undefined = [];
|
||||
public registrars: Registrar[] = [];
|
||||
|
||||
public setDelegations (delegations: Voting[]) {
|
||||
this.delegations = delegations;
|
||||
}
|
||||
|
||||
public setMultisigApprovals (multisigApprovals: [H256, Multisig][]) {
|
||||
this.multisigApprovals = multisigApprovals;
|
||||
}
|
||||
|
||||
public setProxies (proxies: [ProxyDefinition[], BN][]) {
|
||||
this.proxies = proxies;
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* global expect */
|
||||
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
export const clickButton = async (buttonName: string): Promise<void> => {
|
||||
const button = await screen.findByRole('button', { name: buttonName });
|
||||
|
||||
fireEvent.click(button);
|
||||
};
|
||||
|
||||
export const assertText = async (text: string): Promise<HTMLElement> => {
|
||||
return screen.findByText(text);
|
||||
};
|
||||
|
||||
export const fillInput = (inputTestId: string, value: string): void => {
|
||||
const nameInput = screen.getByTestId(inputTestId);
|
||||
|
||||
fireEvent.change(nameInput, { target: { value } });
|
||||
};
|
||||
|
||||
export const assertButtonDisabled = (buttonName: string): void => {
|
||||
const button = screen.getByRole('button', { name: buttonName });
|
||||
|
||||
expect(button).toHaveClass('isDisabled');
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { WaitOptions } from '../types.js';
|
||||
|
||||
export async function waitFor (predicate: () => Promise<boolean> | boolean, { interval = 500, timeout = 10000 }: WaitOptions = {}): Promise<boolean> {
|
||||
const asyncPredicate = () => Promise.resolve(predicate());
|
||||
|
||||
let elapsed = 0;
|
||||
|
||||
while (!(await asyncPredicate())) {
|
||||
if (elapsed > timeout) {
|
||||
throw Error('Timeout');
|
||||
}
|
||||
|
||||
await sleep(interval);
|
||||
elapsed += interval;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function sleep (ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
Reference in New Issue
Block a user