Accounts, Contacts and Sidebar specs: follow the current table layout, run them

The three suites were skipped since the table rework. What changed under them:

- An account or contact spans three rows (main, total balance, details),
  marked from isFirst; the helpers paired rows by two and filtered on a
  row class the rows no longer carry. A collapsed details row is hidden,
  so rows are read including hidden ones.
- Accounts render one table per account group; the page object reads the
  rows of every group table in order, and the sorting fixture keeps its
  three accounts in one group, since sorting applies within a group.
- The expand toggle is the row's expand column, not a row-toggle element.
- useAccountInfo reads ownership from the keyring context, which the test
  page did not provide, so saving a name did nothing; the page now renders
  the real KeyringCtxRoot. The mock api gains consts.system.ss58Prefix.
- Badge popups (react-tooltip v6) render only while hovered; the specs
  hover the badge first. The multisig badge needs the isMultisig flag.
- Addresses are shown in full and shortened by css; the fixtures with
  shortened addresses are gone.
- The total balance, transferable and locked cards are always shown, as
  placeholders without accounts; the specs check that instead of their
  absence.
- Checks that needed matchers the runner lacks (not.toHaveLength,
  toHaveProperty, toThrow with a contained message) are written against
  the DOM directly.
This commit is contained in:
Satoshi Qazi Muhammed authored and SatoshiQaziMuhammed committed 2026-10-10 16:28:39 +04:00
1 parent 61c681567d
commit 3767c819e3
11 files changed
+136 -107

No files matched your search

@@ -41,7 +41,3 @@ 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 -6
View File
@@ -16,6 +16,7 @@ import { ThemeProvider } from 'styled-components';
import { PEZKUWI_GENESIS } from '@pezkuwi/apps-config';
import { AccountSidebar, lightTheme } from '@pezkuwi/react-components';
import { ApiCtx } from '@pezkuwi/react-hooks/ctx/Api';
import { KeyringCtxRoot } from '@pezkuwi/react-hooks/ctx/Keyring';
import { QueueCtx } from '@pezkuwi/react-hooks/ctx/Queue';
import { TypeRegistry } from '@pezkuwi/types/create';
import { keyring } from '@pezkuwi/ui-keyring';
@@ -34,9 +35,8 @@ export abstract class Page {
private renderResult?: RenderResult;
protected readonly defaultAddresses = [alice, bob, charlie, ferdie];
protected constructor (private readonly overview: React.ReactElement, private readonly rowClassName: string) {
protected constructor (private readonly overview: React.ReactElement) {
this.overview = overview;
this.rowClassName = rowClassName;
}
render (accounts: [string, AccountOverrides][]): void {
@@ -59,6 +59,9 @@ export abstract class Page {
proxy: {
proxyDepositBase: new BN(1),
proxyDepositFactor: new BN(1)
},
system: {
ss58Prefix: new BN(42)
}
},
createType: () => ({
@@ -141,9 +144,11 @@ export abstract class Page {
<MemoryRouter>
<ThemeProvider theme={lightTheme}>
<ApiCtx.Provider value={mockApi}>
<AccountSidebar>
{React.cloneElement(this.overview, { onStatusChange: noop }) }
</AccountSidebar>
<KeyringCtxRoot>
<AccountSidebar>
{React.cloneElement(this.overview, { onStatusChange: noop }) }
</AccountSidebar>
</KeyringCtxRoot>
</ApiCtx.Provider>
</ThemeProvider>
</MemoryRouter>
@@ -156,7 +161,14 @@ export abstract class Page {
async getTable (): Promise<Table> {
this.assertRendered();
return new Table(await screen.findByRole('table'), this.rowClassName);
return new Table(await screen.findByRole('table'));
}
/** All tables, in document order, for a page that splits its items over several */
async getTables (): Promise<Table[]> {
this.assertRendered();
return (await screen.findAllByRole('table')).map((table) => new Table(table));
}
clearAccounts (): void {
+11 -4
View File
@@ -14,10 +14,12 @@ import { Sidebar } from './Sidebar.js';
// utility wrapper over an account item in accounts table, serves basic assertions about an account row
export class Row {
public primaryRow: HTMLElement;
public balanceRow: HTMLElement;
public detailsRow: HTMLElement;
constructor (primaryRow: HTMLElement, detailsRow: HTMLElement) {
constructor (primaryRow: HTMLElement, balanceRow: HTMLElement, detailsRow: HTMLElement) {
this.primaryRow = primaryRow;
this.balanceRow = balanceRow;
this.detailsRow = detailsRow;
}
@@ -29,7 +31,7 @@ export class Row {
}
async getBalanceSummary (): Promise<HTMLElement> {
return within(this.primaryRow).findByTestId('balance-summary');
return within(this.balanceRow).findByTestId('balance-summary');
}
async assertAccountName (expectedName: string): Promise<void> {
@@ -64,8 +66,13 @@ export class Row {
assertTextContent(actualShortAddress, expectedShortAddress);
}
async expand (): Promise<void> {
const toggle = await within(this.primaryRow).findByTestId('row-toggle');
expand (): void {
// the toggle is the row's expand column (Table.Column.Expand)
const toggle = this.primaryRow.querySelector('td.ui--Table-Column-Expand');
if (!toggle) {
throw new Error('Expected an expand toggle in the row');
}
fireEvent.click(toggle);
}
@@ -1,9 +1,8 @@
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* global expect */
import { fireEvent, screen, within } from '@testing-library/react';
import assert from 'node:assert/strict';
import { assertTextContent } from '../utils/domAssertions.js';
import { JudgementTag } from './JudgementTag.js';
@@ -42,7 +41,7 @@ export class Sidebar {
async assertAccountInput (expectedInput: string): Promise<void> {
const nameInput = await this.findByTestId('name-input');
expect(nameInput).toHaveProperty('value', expectedInput);
assert.equal((nameInput as HTMLInputElement).value, expectedInput);
}
async assertAccountName (expectedAccountName: string): Promise<void> {
@@ -10,9 +10,8 @@ import { assertTextContent } from '../utils/domAssertions.js';
import { Row } from './Row.js';
export class Table {
constructor (private readonly table: HTMLElement, private readonly rowClassName: string) {
constructor (private readonly table: HTMLElement) {
this.table = table;
this.rowClassName = rowClassName;
}
async assertRowsOrder (balancesExpectedOrder: number[]): Promise<void> {
@@ -26,18 +25,28 @@ export class Table {
}
}
/**
* An item (account, contact) spans the rows from one marked isFirst up to
* the next: the main row, the total balance, and the expandable details
*/
async getRows (): Promise<Row[]> {
const htmlRows = await this.getFilteredHtmlRows();
const collapsibleRows: Row[] = [];
const items: HTMLElement[][] = [];
for (let rowIdx = 0; rowIdx < htmlRows.length; rowIdx = rowIdx + 2) {
const primaryRow = htmlRows[rowIdx];
const detailsRow = htmlRows[rowIdx + 1];
collapsibleRows.push(new Row(primaryRow, detailsRow));
for (const htmlRow of await this.getBodyRows()) {
if (htmlRow.classList.contains('isFirst')) {
items.push([htmlRow]);
} else if (items.length) {
items[items.length - 1].push(htmlRow);
}
}
return collapsibleRows;
return items.map((rows) => {
if (rows.length !== 3) {
throw new Error(`Expected an item to span 3 rows, found ${rows.length}`);
}
return new Row(rows[0], rows[1], rows[2]);
});
}
assertColumnNotExist (columnName: string): void {
@@ -52,15 +61,16 @@ export class Table {
return within(this.table).findByText(text);
}
private async getFilteredHtmlRows (): Promise<HTMLElement[]> {
const htmlRows = await this.getAllHtmlRows();
return htmlRows.filter((row) => row.className.startsWith(this.rowClassName));
}
private async getAllHtmlRows (): Promise<HTMLElement[]> {
private async getBodyRows (): Promise<HTMLElement[]> {
const tableBody = this.table.getElementsByTagName('tbody')[0];
return within(tableBody).findAllByRole('row');
if (!tableBody) {
return [];
}
// a collapsed details row is hidden, and still part of its item; rows of
// tables nested in a cell (e.g. a badge popup) are not items
return (await within(tableBody).findAllByRole('row', { hidden: true }))
.filter((row) => row.parentElement === tableBody);
}
}
@@ -24,6 +24,16 @@ export function assertHasClass (node: Node | null, className: string): void {
assert.ok(classList.contains(className), `Expected class "${className}" on ${describeElement(node)}`);
}
/** As not.toHaveClass: the element is there, without the class */
export function assertNotHasClass (node: Node | null, className: string): void {
assert.ok(node, `Expected an element without class "${className}", found none`);
const classList = (node as Partial<Element>).classList;
assert.ok(classList, `Expected an element without class "${className}", found ${describeElement(node)}`);
assert.ok(!classList.contains(className), `Expected no class "${className}" on ${describeElement(node)}`);
}
/** As toHaveTextContent: the whitespace-normalized text contains the string, or matches the pattern */
export function assertTextContent (element: Node | null, expected: string | RegExp): void {
assert.ok(element, `Expected an element with text ${String(expected)}, found none`);