mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-10-20 18:56:30 +00:00
Integration specs: node:test module mocks instead of jest.mock
The account, address, sidebar and bounties specs replaced hooks with jest.mock, which the node:test runner does not have, so all five failed to load and were excluded from yarn test. They now register mocks with mock.module from node:test and import the page under test afterwards, since Node only swaps a module that has not been loaded yet. - test-support/mocks: mockPageHooks() replaces the hooks a Page reads. An ES module mock replaces every export, so each mock lists them all (useBestNumber's useBestNumberRelay included). The real useAccountInfo is wrapped, typed now, with the fixture identity merged in a typed helper. - The page-specific mocks live with their page: useMultisigApprovals in page-accounts/test, useAddresses in page-addresses/test. - jest-dom matchers were never registered for this runner; specs and page elements use assertHasClass, assertTextContent and assertVisible from test-support, with the same meaning. - Page's mock api gains apiIdentity, which useAccountInfo reads. - The bounties voting tooltip renders hidden and fades in, so the spec waits for it to become visible. - jest/expect-expect counts the assert* helpers as assertions; the disable comments that only existed for that are removed. Create and Bounties run green. Accounts, Addresses and Sidebar load and stay skipped as upstream left them (their FIXMEs are about the reworked tables). Needs @pezkuwi/dev 0.86.6 for the module mocks flag and the fuller browser env.
This commit is contained in:
1 parent
8539e30888
commit
533a17e88e
20 files changed
+330
-192
No files matched your search
@@ -20,5 +20,13 @@ export default [
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
// the same files the base config enables the jest plugin for
|
||||
files: ['**/*.spec.ts', '**/*.spec.tsx'],
|
||||
rules: {
|
||||
// the specs assert through expect() and the assert* helpers in test-support
|
||||
'jest/expect-expect': ['warn', { assertFunctionNames: ['expect', 'assert*', '*.assert*', '*.expect*'] }]
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -17,17 +17,21 @@ import { toShortAddress } from '@pezkuwi/react-components/util';
|
||||
import { anAccountWithBalance, anAccountWithBalanceAndMeta, anAccountWithInfo, anAccountWithInfoAndMeta, anAccountWithMeta, anAccountWithStaking } from '@pezkuwi/test-support/creation/account';
|
||||
import { makeStakingLedger as ledger } from '@pezkuwi/test-support/creation/staking';
|
||||
import { alice, bob, MemoryStore } from '@pezkuwi/test-support/keyring';
|
||||
import { balance, mockApiHooks, showBalance } from '@pezkuwi/test-support/utils';
|
||||
import { assertHasClass, assertTextContent, balance, mockApiHooks, showBalance } from '@pezkuwi/test-support/utils';
|
||||
import { TypeRegistry } from '@pezkuwi/types/create';
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { AccountsPage } from '../../test/pages/accountsPage.js';
|
||||
import { mockAccountsPageHooks } from '../../test/mockAccountsPageHooks.js';
|
||||
|
||||
await mockAccountsPageHooks();
|
||||
|
||||
const { AccountsPage } = await import('../../test/pages/accountsPage.js');
|
||||
|
||||
// FIXME isSplit Table
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
describe.skip('Accounts page', () => {
|
||||
let accountsPage: AccountsPage;
|
||||
let accountsPage: InstanceType<typeof AccountsPage>;
|
||||
|
||||
beforeAll(async () => {
|
||||
await i18next.changeLanguage('en');
|
||||
@@ -64,7 +68,6 @@ describe.skip('Accounts page', () => {
|
||||
expect(accountRows).toHaveLength(0);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the accounts table contains a message about no accounts available', async () => {
|
||||
const noAccountsMessage = 'You don\'t have any accounts. Some features are currently hidden and will only become available once you have accounts.';
|
||||
const accountsTable = await accountsPage.getTable();
|
||||
@@ -132,7 +135,7 @@ describe.skip('Accounts page', () => {
|
||||
});
|
||||
|
||||
// FIXME broken after column rework
|
||||
// eslint-disable-next-line jest/no-disabled-tests, jest/expect-expect
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('a separate column for parent account is not displayed', async () => {
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
const accountsTable = await accountsPage.getTable();
|
||||
@@ -176,11 +179,11 @@ describe.skip('Accounts page', () => {
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
const row = (await accountsPage.getAccountRows())[0];
|
||||
|
||||
expect(row.detailsRow).toHaveClass('isCollapsed');
|
||||
assertHasClass(row.detailsRow, 'isCollapsed');
|
||||
|
||||
await row.expand();
|
||||
|
||||
expect(row.detailsRow).toHaveClass('isExpanded');
|
||||
assertHasClass(row.detailsRow, 'isExpanded');
|
||||
});
|
||||
|
||||
it('displays some summary', () => {
|
||||
@@ -202,7 +205,7 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?balance/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(500 + 200 + 150));
|
||||
assertTextContent(summary, showBalance(500 + 200 + 150));
|
||||
});
|
||||
|
||||
it('displays transferable summary', async () => {
|
||||
@@ -213,7 +216,7 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?transferable/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(400 + 600));
|
||||
assertTextContent(summary, showBalance(400 + 600));
|
||||
});
|
||||
|
||||
it('displays locked summary', async () => {
|
||||
@@ -224,7 +227,7 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?locked/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(400 + 600));
|
||||
assertTextContent(summary, showBalance(400 + 600));
|
||||
});
|
||||
|
||||
it('displays bonded summary', async () => {
|
||||
@@ -235,7 +238,7 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?bonded/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(70 + 20));
|
||||
assertTextContent(summary, showBalance(70 + 20));
|
||||
});
|
||||
|
||||
it('displays unbonding summary', async () => {
|
||||
@@ -276,7 +279,7 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?unbonding/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(200 + 300 + 400 + 100 + 200 + 300));
|
||||
assertTextContent(summary, showBalance(200 + 300 + 400 + 100 + 200 + 300));
|
||||
});
|
||||
|
||||
it('displays redeemable summary', async () => {
|
||||
@@ -287,7 +290,7 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
const summary = await screen.findByTestId(/card-summary:(total )?redeemable/i);
|
||||
|
||||
expect(summary).toHaveTextContent(showBalance(4000 + 5000));
|
||||
assertTextContent(summary, showBalance(4000 + 5000));
|
||||
});
|
||||
|
||||
it('sorts accounts by date by default', async () => {
|
||||
@@ -296,7 +299,7 @@ describe.skip('Accounts page', () => {
|
||||
anAccountWithBalanceAndMeta({ freeBalance: balance(2) }, { whenCreated: 300 }),
|
||||
anAccountWithBalanceAndMeta({ freeBalance: balance(3) }, { whenCreated: 100 })
|
||||
);
|
||||
expect(await accountsPage.getCurrentSortCategory()).toHaveTextContent('date');
|
||||
assertTextContent(await accountsPage.getCurrentSortCategory(), 'date');
|
||||
|
||||
const accountsTable = await accountsPage.getTable();
|
||||
|
||||
@@ -326,35 +329,29 @@ describe.skip('Accounts page', () => {
|
||||
|
||||
it('changes default dropdown value', async () => {
|
||||
await accountsPage.sortBy('balances');
|
||||
expect(await accountsPage.getCurrentSortCategory())
|
||||
.toHaveTextContent('balances');
|
||||
assertTextContent(await accountsPage.getCurrentSortCategory(), 'balances');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('sorts by parent if asked', async () => {
|
||||
await accountsPage.sortBy('parent');
|
||||
await accountsTable.assertRowsOrder([3, 1, 2]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('sorts by name if asked', async () => {
|
||||
await accountsPage.sortBy('name');
|
||||
await accountsTable.assertRowsOrder([3, 2, 1]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('sorts by date if asked', async () => {
|
||||
await accountsPage.sortBy('date');
|
||||
await accountsTable.assertRowsOrder([3, 1, 2]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('sorts by balances if asked', async () => {
|
||||
await accountsPage.sortBy('balances');
|
||||
await accountsTable.assertRowsOrder([1, 2, 3]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('implements stable sort', async () => {
|
||||
await accountsPage.sortBy('name');
|
||||
await accountsTable.assertRowsOrder([3, 2, 1]);
|
||||
@@ -362,7 +359,6 @@ describe.skip('Accounts page', () => {
|
||||
await accountsTable.assertRowsOrder([1, 2, 3]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('respects reverse button', async () => {
|
||||
await accountsPage.sortBy('name');
|
||||
await accountsTable.assertRowsOrder([3, 2, 1]);
|
||||
@@ -410,17 +406,14 @@ describe.skip('Accounts page', () => {
|
||||
await aliceRow.assertAccountName('ALICE');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the development badge is displayed', async () => {
|
||||
await aliceRow.assertBadge('wrench-badge');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the all networks badge is not displayed', () => {
|
||||
aliceRow.assertNoBadge('exclamation-triangle-badge');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the regular badge is not displayed', () => {
|
||||
aliceRow.assertNoBadge('transparent-badge');
|
||||
});
|
||||
@@ -434,17 +427,14 @@ describe.skip('Accounts page', () => {
|
||||
await bobRow.assertAccountName('BOB');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the development badge is not displayed', () => {
|
||||
bobRow.assertNoBadge('wrench-badge');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the all networks badge is displayed', async () => {
|
||||
await bobRow.assertBadge('exclamation-triangle-badge');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the regular badge is not displayed', () => {
|
||||
bobRow.assertNoBadge('transparent-badge');
|
||||
});
|
||||
@@ -500,7 +490,7 @@ describe.skip('Accounts page', () => {
|
||||
const modal = await screen.findByTestId('modal');
|
||||
|
||||
within(modal).getByText('Pending call hashes');
|
||||
expect(approvalsModalToggle).toHaveClass('purpleColor');
|
||||
assertHasClass(approvalsModalToggle, 'purpleColor');
|
||||
});
|
||||
|
||||
it('delegate democracy vote', async () => {
|
||||
@@ -512,7 +502,7 @@ describe.skip('Accounts page', () => {
|
||||
const modal = await screen.findByTestId('modal');
|
||||
|
||||
within(modal).getByText('democracy vote delegation');
|
||||
expect(delegateModalToggle).toHaveClass('normalColor');
|
||||
assertHasClass(delegateModalToggle, 'normalColor');
|
||||
});
|
||||
|
||||
it('proxy overview', async () => {
|
||||
@@ -524,7 +514,7 @@ describe.skip('Accounts page', () => {
|
||||
const modal = await screen.findByTestId('modal');
|
||||
|
||||
within(modal).getByText('Proxy overview');
|
||||
expect(proxyOverviewToggle).toHaveClass('normalColor');
|
||||
assertHasClass(proxyOverviewToggle, 'normalColor');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -11,7 +11,11 @@ import { assertButtonDisabled, assertText, clickButton, fillInput } from '@pezku
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
import { cryptoWaitReady } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { AccountsPage } from '../../test/pages/accountsPage.js';
|
||||
import { mockAccountsPageHooks } from '../../test/mockAccountsPageHooks.js';
|
||||
|
||||
await mockAccountsPageHooks();
|
||||
|
||||
const { AccountsPage } = await import('../../test/pages/accountsPage.js');
|
||||
|
||||
const spy = jest.spyOn(keyring, 'addUri');
|
||||
|
||||
@@ -19,7 +23,7 @@ const newAccountName = 'NEW ACCOUNT NAME';
|
||||
const newAccountPassword = 'mySecretPassword';
|
||||
|
||||
describe('Create an account modal', () => {
|
||||
let accountsPage: AccountsPage;
|
||||
let accountsPage: InstanceType<typeof AccountsPage>;
|
||||
|
||||
beforeAll(async () => {
|
||||
await cryptoWaitReady();
|
||||
@@ -33,7 +37,6 @@ describe('Create an account modal', () => {
|
||||
accountsPage = new AccountsPage();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('creates an account', async () => {
|
||||
await accountsPage.enterCreateAccountModal();
|
||||
|
||||
@@ -53,7 +56,6 @@ describe('Create an account modal', () => {
|
||||
expectCreateAnAccountCall();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('navigates through the modal flow with enter key', async () => {
|
||||
await accountsPage.enterCreateAccountModal();
|
||||
|
||||
@@ -74,7 +76,6 @@ describe('Create an account modal', () => {
|
||||
expectCreateAnAccountCall();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('gives an error message when entering invalid derivation path', async () => {
|
||||
await accountsPage.enterCreateAccountModal();
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2026 @pezkuwi/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { mock } from 'node:test';
|
||||
|
||||
import { mockPageHooks } from '@pezkuwi/test-support/mocks';
|
||||
import { mockApiHooks } from '@pezkuwi/test-support/utils';
|
||||
|
||||
/**
|
||||
* The shared page hooks, plus the multisig lookup that an account row makes.
|
||||
* Await it before importing AccountsPage.
|
||||
*/
|
||||
export async function mockAccountsPageHooks (): Promise<void> {
|
||||
await mockPageHooks();
|
||||
|
||||
mock.module('../src/Accounts/useMultisigApprovals', {
|
||||
defaultExport: () => mockApiHooks.multisigApprovals
|
||||
});
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
// Copyright 2017-2026 @pezkuwi/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* global expect */
|
||||
|
||||
import { within } from '@testing-library/react';
|
||||
|
||||
import { Row } from '@pezkuwi/test-support/pagesElements';
|
||||
import { assertTextContent } from '@pezkuwi/test-support/utils';
|
||||
|
||||
export class AccountRow extends Row {
|
||||
async assertParentAccountName (expectedParentAccount: string): Promise<void> {
|
||||
const parentAccount = await within(this.primaryRow).findByTestId('parent');
|
||||
|
||||
expect(parentAccount).toHaveTextContent(expectedParentAccount);
|
||||
assertTextContent(parentAccount, expectedParentAccount);
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,19 @@ import { screen } from '@testing-library/react';
|
||||
import i18next from '@pezkuwi/react-components/i18n';
|
||||
import { aContactWithBalance } from '@pezkuwi/test-support/creation/contact';
|
||||
import { MemoryStore } from '@pezkuwi/test-support/keyring';
|
||||
import { balance } from '@pezkuwi/test-support/utils';
|
||||
import { assertHasClass, balance } from '@pezkuwi/test-support/utils';
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
|
||||
import { AddressesPage } from '../../test/pages/addressesPage.js';
|
||||
import { mockAddressesPageHooks } from '../../test/mockAddressesPageHooks.js';
|
||||
|
||||
await mockAddressesPageHooks();
|
||||
|
||||
const { AddressesPage } = await import('../../test/pages/addressesPage.js');
|
||||
|
||||
// FIXME isSplit Table
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
describe.skip('Addresses page', () => {
|
||||
let addressesPage: AddressesPage;
|
||||
let addressesPage: InstanceType<typeof AddressesPage>;
|
||||
|
||||
beforeAll(async () => {
|
||||
await i18next.changeLanguage('en');
|
||||
@@ -49,7 +53,6 @@ describe.skip('Addresses page', () => {
|
||||
expect(await addressesTable.getRows()).toHaveLength(0);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('the contacts table contains a message about no contacts available', async () => {
|
||||
const noContactsMessage = 'no addresses saved yet, add any existing address';
|
||||
|
||||
@@ -128,11 +131,11 @@ describe.skip('Addresses page', () => {
|
||||
addressesPage.renderDefaultContacts(1);
|
||||
const row = (await addressesPage.getAddressesRows())[0];
|
||||
|
||||
expect(row.detailsRow).toHaveClass('isCollapsed');
|
||||
assertHasClass(row.detailsRow, 'isCollapsed');
|
||||
|
||||
await row.expand();
|
||||
|
||||
expect(row.detailsRow).toHaveClass('isExpanded');
|
||||
assertHasClass(row.detailsRow, 'isExpanded');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2026 @pezkuwi/app-addresses authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { mock } from 'node:test';
|
||||
|
||||
import { mockPageHooks } from '@pezkuwi/test-support/mocks';
|
||||
import { mockAccountHooks } from '@pezkuwi/test-support/utils';
|
||||
|
||||
/**
|
||||
* The shared page hooks, with the contacts served from the same fixture as
|
||||
* the accounts. Await it before importing AddressesPage.
|
||||
*/
|
||||
export async function mockAddressesPageHooks (): Promise<void> {
|
||||
await mockPageHooks();
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useAddresses', {
|
||||
namedExports: {
|
||||
useAddresses: () => ({
|
||||
allAddresses: mockAccountHooks.useAccounts.allAccounts,
|
||||
hasAddresses: mockAccountHooks.useAccounts.hasAccounts,
|
||||
isAddress: true
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2017-2026 @pezkuwi/app-addresses authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* global jest */
|
||||
|
||||
import type { Row } from '@pezkuwi/test-support/pagesElements';
|
||||
import type { AccountOverrides as ContactOverrides } from '@pezkuwi/test-support/types';
|
||||
|
||||
@@ -10,20 +8,11 @@ import React from 'react';
|
||||
|
||||
import { aContact } from '@pezkuwi/test-support/creation/contact';
|
||||
import { Page } from '@pezkuwi/test-support/pages/Page';
|
||||
import { mockAccountHooks } from '@pezkuwi/test-support/utils';
|
||||
|
||||
import AddressOverview from '../../src/Contacts/index.js';
|
||||
|
||||
const NOOP_CHANGE = () => undefined;
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useAddresses', () => ({
|
||||
useAddresses: () => ({
|
||||
allAddresses: mockAccountHooks.useAccounts.allAccounts,
|
||||
hasAddresses: mockAccountHooks.useAccounts.hasAccounts,
|
||||
isAddress: true
|
||||
})
|
||||
}));
|
||||
|
||||
export class AddressesPage extends Page {
|
||||
constructor () {
|
||||
super(
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
/* eslint-disable jest/expect-expect */
|
||||
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import type { DeriveCollectiveProposal } from '@pezkuwi/api-derive/types';
|
||||
import type { BountyIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletBountiesBounty, PezpalletBountiesBountyStatus } from '@pezkuwi/types/lookup';
|
||||
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { mock } from 'node:test';
|
||||
|
||||
import i18next from '@pezkuwi/react-components/i18n';
|
||||
import { createAugmentedApi } from '@pezkuwi/test-support/api';
|
||||
@@ -19,36 +18,43 @@ import { BountyFactory } from '@pezkuwi/test-support/creation/bounties';
|
||||
import { proposalFactory } from '@pezkuwi/test-support/creation/treasury';
|
||||
import { mockHooks } from '@pezkuwi/test-support/hooks';
|
||||
import { alice, bob, MemoryStore } from '@pezkuwi/test-support/keyring';
|
||||
import { assertHasClass } from '@pezkuwi/test-support/utils';
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import { defaultBountyUpdatePeriod, mockBountyHooks } from '../test/hooks/defaults.js';
|
||||
import { BountiesPage } from '../test/pages/bountiesPage.js';
|
||||
import { BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING } from './BountyNextActionInfo/BountyActionMessage.js';
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useTreasury', () => ({
|
||||
useTreasury: () => mockHooks.treasury
|
||||
}));
|
||||
// Node only swaps a module that has not been loaded yet, so the page and
|
||||
// anything that loads @pezkuwi/react-hooks are imported after these. A module
|
||||
// mock replaces every export, so each one lists them all.
|
||||
mock.module('@pezkuwi/react-hooks/useTreasury', {
|
||||
namedExports: { useTreasury: () => mockHooks.treasury }
|
||||
});
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useCollectiveInstance', () => ({
|
||||
useCollectiveInstance: () => 'council'
|
||||
}));
|
||||
mock.module('@pezkuwi/react-hooks/useCollectiveInstance', {
|
||||
namedExports: { useCollectiveInstance: () => 'council' }
|
||||
});
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useCollectiveMembers', () => ({
|
||||
useCollectiveMembers: () => mockHooks.members
|
||||
}));
|
||||
mock.module('@pezkuwi/react-hooks/useCollectiveMembers', {
|
||||
namedExports: { useCollectiveMembers: () => mockHooks.members }
|
||||
});
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useBlockTime', () => ({
|
||||
useBlockTime: () => mockHooks.blockTime
|
||||
}));
|
||||
const { calcBlockTime } = await import('@pezkuwi/react-hooks/useBlockTime');
|
||||
|
||||
jest.mock('./hooks/useBalance', () => ({
|
||||
useBalance: () => mockBountyHooks.balance
|
||||
}));
|
||||
mock.module('@pezkuwi/react-hooks/useBlockTime', {
|
||||
namedExports: { calcBlockTime, useBlockTime: () => mockHooks.blockTime }
|
||||
});
|
||||
|
||||
jest.mock('./hooks/useBounties', () => ({
|
||||
useBounties: () => mockBountyHooks.bountyApi
|
||||
}));
|
||||
mock.module('./hooks/useBalance', {
|
||||
namedExports: { useBalance: () => mockBountyHooks.balance }
|
||||
});
|
||||
|
||||
mock.module('./hooks/useBounties', {
|
||||
namedExports: { useBounties: () => mockBountyHooks.bountyApi }
|
||||
});
|
||||
|
||||
const { BountiesPage } = await import('../test/pages/bountiesPage.js');
|
||||
const { BLOCKS_PERCENTAGE_LEFT_TO_SHOW_WARNING } = await import('./BountyNextActionInfo/BountyActionMessage.js');
|
||||
|
||||
let aProposal: (extrinsic: SubmittableExtrinsic<'promise'>, ayes?: string[], nays?: string[]) => DeriveCollectiveProposal;
|
||||
let augmentedApi: any;
|
||||
@@ -58,7 +64,7 @@ let bountyStatusWith: ({ curator, status, updateDue }: { curator?: string, statu
|
||||
let bountyWith: ({ status, value }: { status?: string, value?: number }) => PezpalletBountiesBounty;
|
||||
|
||||
describe('Bounties', () => {
|
||||
let bountiesPage: BountiesPage;
|
||||
let bountiesPage: InstanceType<typeof BountiesPage>;
|
||||
|
||||
beforeAll(async () => {
|
||||
await i18next.changeLanguage('en');
|
||||
@@ -309,7 +315,7 @@ describe('Bounties', () => {
|
||||
it('disables Assign Curator button if validation fails', async () => {
|
||||
await bountiesPage.enterCuratorsFee('6');
|
||||
|
||||
expect(await bountiesPage.assignCuratorButton()).toHaveClass('isDisabled');
|
||||
assertHasClass(await bountiesPage.assignCuratorButton(), 'isDisabled');
|
||||
});
|
||||
|
||||
it('queues propose extrinsic on submit', async () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { BountyIndex } from '@pezkuwi/types/interfaces';
|
||||
import type { PezpalletBountiesBounty, PezpalletBountiesBountyStatus } from '@pezkuwi/types/lookup';
|
||||
import type { BountyApi } from '../../src/hooks/index.js';
|
||||
|
||||
import { fireEvent, render, within } from '@testing-library/react';
|
||||
import { fireEvent, render, waitFor, within } from '@testing-library/react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
@@ -24,6 +24,7 @@ import { ApiCtx } from '@pezkuwi/react-hooks/ctx/Api';
|
||||
import { QueueCtx } from '@pezkuwi/react-hooks/ctx/Queue';
|
||||
import { balanceOf } from '@pezkuwi/test-support/creation/balance';
|
||||
import { BountyFactory } from '@pezkuwi/test-support/creation/bounties';
|
||||
import { assertVisible } from '@pezkuwi/test-support/utils';
|
||||
import { TypeRegistry } from '@pezkuwi/types/create';
|
||||
|
||||
import Bounties from '../../src/Bounties.js';
|
||||
@@ -330,6 +331,10 @@ export class BountiesPage {
|
||||
const icon = await within(votingInfo).findByTestId('question-circle');
|
||||
|
||||
fireEvent.mouseEnter(icon);
|
||||
expect(await this.findByText(description)).toBeVisible();
|
||||
|
||||
const text = await this.findByText(description);
|
||||
|
||||
// the tooltip renders hidden, then fades in
|
||||
await waitFor(() => assertVisible(text));
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,20 @@ import { fireEvent, screen, waitFor, within } from '@testing-library/react';
|
||||
import i18next from '@pezkuwi/react-components/i18n';
|
||||
import { anAccount, anAccountWithInfo, anAccountWithMeta } from '@pezkuwi/test-support/creation/account';
|
||||
import { alice, bob, MemoryStore } from '@pezkuwi/test-support/keyring';
|
||||
import { charlieShortAddress, ferdieShortAddress, mockRegistration, registrars } from '@pezkuwi/test-support/mockData';
|
||||
import { mockApiHooks } from '@pezkuwi/test-support/utils';
|
||||
import { assertHasClass, assertTextContent, mockApiHooks } from '@pezkuwi/test-support/utils';
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
|
||||
import { AccountsPage } from '../../../page-accounts/test/pages/accountsPage.js';
|
||||
import { mockAccountsPageHooks } from '../../../page-accounts/test/mockAccountsPageHooks.js';
|
||||
|
||||
await mockAccountsPageHooks();
|
||||
|
||||
const { charlieShortAddress, ferdieShortAddress, mockRegistration, registrars } = await import('@pezkuwi/test-support/mockData');
|
||||
const { AccountsPage } = await import('../../../page-accounts/test/pages/accountsPage.js');
|
||||
|
||||
// FIXME: these all need to be wrapped in waitFor ....
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
describe.skip('Sidebar', () => {
|
||||
let accountsPage: AccountsPage;
|
||||
let accountsPage: InstanceType<typeof AccountsPage>;
|
||||
let sideBar: Sidebar;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -62,7 +66,6 @@ describe.skip('Sidebar', () => {
|
||||
expect(changedAccount?.meta?.name).toEqual(newName);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('within sidebar', async () => {
|
||||
await sideBar.assertAccountName(newName);
|
||||
});
|
||||
@@ -105,7 +108,6 @@ describe.skip('Sidebar', () => {
|
||||
sideBar.edit();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('restores tags and name to state from keyring', async () => {
|
||||
await sideBar.typeAccountName(newName);
|
||||
await sideBar.selectTag(defaultTag);
|
||||
@@ -149,7 +151,6 @@ describe.skip('Sidebar', () => {
|
||||
expect(sideBar.queryByRole('button', { name: 'Cancel' })).toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('cancels editing and changes name when opening sidebar for another account', async () => {
|
||||
await waitFor(() => sideBar.assertAccountInput('alice'));
|
||||
|
||||
@@ -188,12 +189,11 @@ describe.skip('Sidebar', () => {
|
||||
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');
|
||||
assertHasClass(subsNumber, 'subs-number');
|
||||
assertTextContent(subsNumber, '1');
|
||||
assertTextContent(subAccount, 'BOB');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('displays picked sub in sidebar', async () => {
|
||||
const subAccount = subs[0].childNodes[1];
|
||||
|
||||
@@ -205,7 +205,6 @@ describe.skip('Sidebar', () => {
|
||||
});
|
||||
|
||||
describe('judgements', () => {
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('displays several judgements', async () => {
|
||||
mockApiHooks.setJudgements(mockRegistration.judgements as RegistrationJudgement[]);
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
@@ -216,7 +215,6 @@ describe.skip('Sidebar', () => {
|
||||
await sideBar.assertJudgement('1 Erroneous');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('displays no judgements', async () => {
|
||||
accountsPage.renderDefaultAccounts(1);
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
@@ -235,14 +233,12 @@ describe.skip('Sidebar', () => {
|
||||
sideBar = await accountsPage.openSidebarForRow(0);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('singular registrar', async () => {
|
||||
const judgementTag = await sideBar.getJudgement('1 Known good');
|
||||
|
||||
await judgementTag.assertRegistrars([charlieShortAddress]);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('multiple registrars', async () => {
|
||||
const judgementTag = await sideBar.getJudgement('2 Reasonable');
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-support authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AddressIdentity, UseAccountInfo } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import { mock } from 'node:test';
|
||||
|
||||
import { mockAccountHooks } from '../utils/accountDefaults.js';
|
||||
import { mockApiHooks } from '../utils/mockApiHooks.js';
|
||||
|
||||
/**
|
||||
* The fixture identity over the real one, with the fixture judgements added
|
||||
* to the real ones
|
||||
*/
|
||||
function withMockIdentity (actual?: AddressIdentity, mocked?: AddressIdentity): AddressIdentity | undefined {
|
||||
const identity = mocked
|
||||
? { ...actual, ...mocked }
|
||||
: actual;
|
||||
|
||||
return identity && {
|
||||
...identity,
|
||||
judgements: [
|
||||
...(actual?.judgements || []),
|
||||
...(mockApiHooks.judgements || [])
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the hooks that a Page reads with the shared fixtures in
|
||||
* mockAccountHooks and mockApiHooks.
|
||||
*
|
||||
* Node only swaps a module that has not been loaded yet, so a spec awaits
|
||||
* this before anything that loads @pezkuwi/react-hooks, and then imports
|
||||
* the page under test with a dynamic import(). Needs node's
|
||||
* --experimental-test-module-mocks flag.
|
||||
*
|
||||
* A module mock replaces every export of the module, and the barrel in
|
||||
* @pezkuwi/react-hooks re-exports them by name, so each mock lists all of
|
||||
* them. Loading the real module to copy the rest is not an option: some of
|
||||
* them load the barrel, which would bind it before the mocks are in place.
|
||||
*/
|
||||
export async function mockPageHooks (): Promise<void> {
|
||||
mock.module('@pezkuwi/react-hooks/useAccounts', {
|
||||
namedExports: { useAccounts: () => mockAccountHooks.useAccounts }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useNextTick', {
|
||||
namedExports: { useNextTick: () => true }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useBalancesAll', {
|
||||
namedExports: { useBalancesAll: (address: string) => mockAccountHooks.accountsMap[address].balance }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useStakingInfo', {
|
||||
namedExports: { useStakingInfo: (address: string) => mockAccountHooks.accountsMap[address].staking }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useBestNumber', {
|
||||
namedExports: {
|
||||
useBestNumber: () => 1,
|
||||
useBestNumberRelay: () => 1
|
||||
}
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useSubidentities', {
|
||||
namedExports: { useSubidentities: () => mockApiHooks.subs }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useDelegations', {
|
||||
namedExports: { useDelegations: () => mockApiHooks.delegations }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useProxies', {
|
||||
namedExports: { useProxies: () => mockApiHooks.proxies }
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useRegistrars', {
|
||||
namedExports: {
|
||||
useRegistrars: () => ({
|
||||
isRegistrar: false,
|
||||
registrars: mockApiHooks.registrars
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useTheme', {
|
||||
namedExports: {
|
||||
useTheme: () => ({
|
||||
theme: 'light',
|
||||
themeClassName: 'theme--light'
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// The fixture is layered over the real hook. It does not load the barrel,
|
||||
// so it can be loaded here, once the hooks above have been replaced.
|
||||
const { useAccountInfo: useActualAccountInfo } = await import('@pezkuwi/react-hooks/useAccountInfo');
|
||||
|
||||
mock.module('@pezkuwi/react-hooks/useAccountInfo', {
|
||||
namedExports: {
|
||||
useAccountInfo: (address: string): UseAccountInfo => {
|
||||
const mockInfo = mockAccountHooks.accountsMap[address];
|
||||
const info = useActualAccountInfo(address);
|
||||
|
||||
return mockInfo
|
||||
? {
|
||||
...info,
|
||||
flags: { ...info.flags, ...(mockInfo.info.flags) },
|
||||
identity: withMockIdentity(info.identity, mockInfo.info.identity),
|
||||
tags: [...info.tags, ...(mockInfo.info.tags)]
|
||||
}
|
||||
: info;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
import type { RenderResult } from '@testing-library/react';
|
||||
import type { ApiProps } from '@pezkuwi/react-api/types';
|
||||
import type { PartialQueueTxExtrinsic, QueueProps, QueueTxExtrinsicAdd } from '@pezkuwi/react-components/Status/types';
|
||||
import type { UseAccountInfo } from '@pezkuwi/react-hooks/types';
|
||||
import type { AccountOverrides } from '../utils/accountDefaults.js';
|
||||
|
||||
import { queryByAttribute, render, screen } from '@testing-library/react';
|
||||
@@ -25,94 +24,12 @@ import { BN } from '@pezkuwi/util';
|
||||
import { alice, bob, charlie, ferdie } from '../keyring/index.js';
|
||||
import { Table } from '../pagesElements/index.js';
|
||||
import { mockAccountHooks } from '../utils/accountDefaults.js';
|
||||
import { mockApiHooks } from '../utils/mockApiHooks.js';
|
||||
|
||||
let queueExtrinsic: (value: PartialQueueTxExtrinsic) => void;
|
||||
|
||||
class NotYetRendered extends Error {
|
||||
}
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useAccounts', () => ({
|
||||
useAccounts: () => mockAccountHooks.useAccounts
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useAccountInfo', () => {
|
||||
const actual = jest.requireActual<{ useAccountInfo: (address: string) => UseAccountInfo }>('@pezkuwi/react-hooks/useAccountInfo');
|
||||
|
||||
return ({
|
||||
useAccountInfo: (address: string) => {
|
||||
const mockInfo = mockAccountHooks.accountsMap[address];
|
||||
|
||||
return mockInfo
|
||||
? {
|
||||
...actual.useAccountInfo(address),
|
||||
flags: { ...actual.useAccountInfo(address).flags, ...(mockInfo.info.flags) },
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useNextTick', () => ({
|
||||
useNextTick: () => true
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useBalancesAll', () => ({
|
||||
useBalancesAll: (address: string) => mockAccountHooks.accountsMap[address].balance
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useStakingInfo', () => ({
|
||||
useStakingInfo: (address: string) => mockAccountHooks.accountsMap[address].staking
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useBestNumber', () => ({
|
||||
useBestNumber: () => 1
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useSubidentities', () => ({
|
||||
useSubidentities: () => mockApiHooks.subs
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/app-accounts/Accounts/useMultisigApprovals', () => ({
|
||||
__esModule: true,
|
||||
default: () => mockApiHooks.multisigApprovals
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useDelegations', () => ({
|
||||
useDelegations: () => mockApiHooks.delegations
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useProxies', () => ({
|
||||
useProxies: () => mockApiHooks.proxies
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useSubidentities', () => ({
|
||||
useSubidentities: () => mockApiHooks.subs
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useRegistrars', () => ({
|
||||
useRegistrars: () => ({
|
||||
isRegistrar: false,
|
||||
registrars: mockApiHooks.registrars
|
||||
})
|
||||
}));
|
||||
|
||||
jest.mock('@pezkuwi/react-hooks/useTheme', () => ({
|
||||
useTheme: () => ({
|
||||
theme: 'light',
|
||||
themeClassName: 'theme--light'
|
||||
})
|
||||
}));
|
||||
|
||||
export abstract class Page {
|
||||
private renderResult?: RenderResult;
|
||||
protected readonly defaultAddresses = [alice, bob, charlie, ferdie];
|
||||
@@ -198,6 +115,8 @@ export abstract class Page {
|
||||
};
|
||||
const mockApi: ApiProps = {
|
||||
api,
|
||||
// identities live on the people chain; here both are the same mock
|
||||
apiIdentity: api,
|
||||
apiSystem: {
|
||||
...api,
|
||||
isReady: Promise.resolve(api)
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { Balance } from '@pezkuwi/types/interfaces';
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
|
||||
import { format } from '../utils/balance.js';
|
||||
import { assertTextContent } from '../utils/domAssertions.js';
|
||||
import { Sidebar } from './Sidebar.js';
|
||||
|
||||
// utility wrapper over an account item in accounts table, serves basic assertions about an account row
|
||||
@@ -24,7 +25,7 @@ export class Row {
|
||||
const actualBalanceText = await this.getBalanceSummary();
|
||||
const expectedBalanceText = format(expectedTotalBalance);
|
||||
|
||||
expect(actualBalanceText).toHaveTextContent(expectedBalanceText);
|
||||
assertTextContent(actualBalanceText, expectedBalanceText);
|
||||
}
|
||||
|
||||
async getBalanceSummary (): Promise<HTMLElement> {
|
||||
@@ -34,7 +35,7 @@ export class Row {
|
||||
async assertAccountName (expectedName: string): Promise<void> {
|
||||
const accountName = await this.getAccountName();
|
||||
|
||||
expect(accountName).toHaveTextContent(expectedName);
|
||||
assertTextContent(accountName, expectedName);
|
||||
}
|
||||
|
||||
async assertBalancesDetails (expectedBalanceComponents: { name: string, amount: Balance }[]): Promise<void> {
|
||||
@@ -54,13 +55,13 @@ export class Row {
|
||||
async assertTags (expectedTagsContent: string): Promise<void> {
|
||||
const actualTags = await within(this.detailsRow).findByTestId('tags');
|
||||
|
||||
expect(actualTags).toHaveTextContent(expectedTagsContent);
|
||||
assertTextContent(actualTags, expectedTagsContent);
|
||||
}
|
||||
|
||||
async assertShortAddress (expectedShortAddress: string): Promise<void> {
|
||||
const actualShortAddress = await within(this.primaryRow).findByTestId('short-address');
|
||||
|
||||
expect(actualShortAddress).toHaveTextContent(expectedShortAddress);
|
||||
assertTextContent(actualShortAddress, expectedShortAddress);
|
||||
}
|
||||
|
||||
async expand (): Promise<void> {
|
||||
@@ -85,7 +86,7 @@ export class Row {
|
||||
const balanceElement = await this.getBalanceElementByLabelName(expectedBalanceComponent.name);
|
||||
const balanceText = format(expectedBalanceComponent.amount);
|
||||
|
||||
expect(balanceElement).toHaveTextContent(balanceText);
|
||||
assertTextContent(balanceElement, balanceText);
|
||||
}
|
||||
|
||||
private async getBalanceElementByLabelName (labelName: string): Promise<ChildNode | null> {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { fireEvent, screen, within } from '@testing-library/react';
|
||||
|
||||
import { assertTextContent } from '../utils/domAssertions.js';
|
||||
import { JudgementTag } from './JudgementTag.js';
|
||||
|
||||
export class Sidebar {
|
||||
@@ -48,19 +49,19 @@ export class Sidebar {
|
||||
const sideBarAddressSection = await this.findByTestId('sidebar-address-menu');
|
||||
const sideBarName = await within(sideBarAddressSection).findByTestId('account-name');
|
||||
|
||||
expect(sideBarName).toHaveTextContent(expectedAccountName);
|
||||
assertTextContent(sideBarName, expectedAccountName);
|
||||
}
|
||||
|
||||
async assertJudgement (judgement: string): Promise<void> {
|
||||
const judgementsSection = await this.findByTestId('judgements');
|
||||
|
||||
expect(judgementsSection).toHaveTextContent(judgement);
|
||||
assertTextContent(judgementsSection, judgement);
|
||||
}
|
||||
|
||||
async assertTags (tagsContent: string): Promise<void> {
|
||||
const sideBarTags = await this.findByTestId('sidebar-tags');
|
||||
|
||||
expect(sideBarTags).toHaveTextContent(tagsContent);
|
||||
assertTextContent(sideBarTags, tagsContent);
|
||||
}
|
||||
|
||||
close (): Promise<void> {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { within } from '@testing-library/react';
|
||||
|
||||
import { showBalance } from '../utils/balance.js';
|
||||
import { assertTextContent } from '../utils/domAssertions.js';
|
||||
import { Row } from './Row.js';
|
||||
|
||||
export class Table {
|
||||
@@ -21,7 +22,7 @@ export class Table {
|
||||
const row = orderedRows[index];
|
||||
const expectedBalanceTextContent = showBalance(balancesExpectedOrder[index]);
|
||||
|
||||
expect(await row.getBalanceSummary()).toHaveTextContent(expectedBalanceTextContent);
|
||||
assertTextContent(await row.getBalanceSummary(), expectedBalanceTextContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DOM checks with the meaning of the matching @testing-library/jest-dom
|
||||
// matchers, which the node:test based runner does not provide
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
function describeElement (node: Node): string {
|
||||
const html = (node as Partial<Element>).outerHTML ?? node.nodeName;
|
||||
|
||||
return html.length > 200
|
||||
? `${html.slice(0, 200)}...`
|
||||
: html;
|
||||
}
|
||||
|
||||
/** As toHaveClass: the element carries the class */
|
||||
export function assertHasClass (node: Node | null, className: string): void {
|
||||
assert.ok(node, `Expected an element with class "${className}", found none`);
|
||||
|
||||
const classList = (node as Partial<Element>).classList;
|
||||
|
||||
assert.ok(classList, `Expected an element with class "${className}", found ${describeElement(node)}`);
|
||||
assert.ok(classList.contains(className), `Expected 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`);
|
||||
|
||||
const text = (element.textContent ?? '').replace(/\s+/g, ' ').trim();
|
||||
|
||||
if (typeof expected === 'string') {
|
||||
assert.ok(text.includes(expected), `Expected text containing "${expected}", found "${text}"`);
|
||||
} else {
|
||||
assert.match(text, expected);
|
||||
}
|
||||
}
|
||||
|
||||
/** As toBeVisible: in the document, and neither it nor an ancestor is hidden */
|
||||
export function assertVisible (element: Element | null): void {
|
||||
assert.ok(element, 'Expected a visible element, found none');
|
||||
assert.ok(element.isConnected, `Expected ${describeElement(element)} to be in the document`);
|
||||
|
||||
for (let current: Element | null = element; current; current = current.parentElement) {
|
||||
const style = current.ownerDocument.defaultView?.getComputedStyle(current);
|
||||
const isHidden = current.hasAttribute('hidden') ||
|
||||
style?.display === 'none' ||
|
||||
style?.visibility === 'hidden' ||
|
||||
style?.visibility === 'collapse' ||
|
||||
style?.opacity === '0';
|
||||
|
||||
assert.ok(!isHidden, `Expected ${describeElement(element)} to be visible, hidden by ${describeElement(current)}`);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
export * from './accountDefaults.js';
|
||||
export * from './balance.js';
|
||||
export * from './domAssertions.js';
|
||||
export * from './mockApiHooks.js';
|
||||
export * from './renderedScreenUtils.js';
|
||||
export * from './waitFor.js';
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* global expect */
|
||||
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
import { assertHasClass } from './domAssertions.js';
|
||||
|
||||
export const clickButton = async (buttonName: string): Promise<void> => {
|
||||
const button = await screen.findByRole('button', { name: buttonName });
|
||||
|
||||
@@ -24,5 +24,5 @@ export const fillInput = (inputTestId: string, value: string): void => {
|
||||
export const assertButtonDisabled = (buttonName: string): void => {
|
||||
const button = screen.getByRole('button', { name: buttonName });
|
||||
|
||||
expect(button).toHaveClass('isDisabled');
|
||||
assertHasClass(button, 'isDisabled');
|
||||
};
|
||||
@@ -127,6 +127,7 @@
|
||||
"@pezkuwi/test-support/hooks": ["test-support/src/hooks/index.ts"],
|
||||
"@pezkuwi/test-support/keyring": ["test-support/src/keyring/index.ts"],
|
||||
"@pezkuwi/test-support/mockData": ["test-support/src/mockData/index.ts"],
|
||||
"@pezkuwi/test-support/mocks": ["test-support/src/mocks/index.ts"],
|
||||
"@pezkuwi/test-support/pages/*": ["test-support/src/pages/*.tsx"],
|
||||
"@pezkuwi/test-support/pagesElements": ["test-support/src/pagesElements/index.ts"],
|
||||
"@pezkuwi/test-support/react": ["test-support/src/react/index.ts"],
|
||||
|
||||
Reference in new issue
Block a user