mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-11-05 23:11:11 +00:00
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:
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';
|
||||
@@ -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 {
|
||||
|
||||
@@ -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`);
|
||||
|
||||
Reference in new issue
Block a user