Skip to content

Commit cdb9ae7

Browse files
committed
feat(dapp): add tests for status pills in auctions
1 parent ac10bf8 commit cdb9ae7

File tree

8 files changed

+231
-7
lines changed

8 files changed

+231
-7
lines changed

dapp/src/app/(protected)/my-names/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export default function MyNamesPage(): JSX.Element {
160160
}
161161
onClick={handleRefresh}
162162
disabled={isRefreshing}
163+
testId="refresh-button"
163164
/>
164165
) : null}
165166
</div>

dapp/src/auctions/components/AuctionItem.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ export function AuctionItem({ auction, auctionStatus, onBidClick }: AuctionItemP
4949
}
5050

5151
return (
52-
<NameCard name={auction.name} displaySrc={auctionDisplayImage}>
52+
<NameCard name={auction.name} displaySrc={auctionDisplayImage} testId="auction-name-card">
5353
<NameCardBody name={normalizeIotaName(auction.name)}>
5454
<div className="flex flex-row items-center justify-between gap-x-xs">
5555
<ExpiryDateIndicator auction={auction} />
56-
<AuctionStatusBadge status={auctionStatus} />
56+
<div data-testid="auction-status-badge">
57+
<AuctionStatusBadge status={auctionStatus} />
58+
</div>
5759
</div>
5860

5961
<div className="min-h-[44px] flex w-full">

dapp/src/auctions/components/AuctionPublicItem.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,11 @@ export function AuctionPublicItem({ auction, onBidClick }: AuctionPublicItemProp
8989
size="full"
9090
displaySrc={auctionDisplayImage}
9191
blurImage={shouldCensor}
92+
testId="auction-name-card"
9293
>
9394
<NameCardBody name={censoredName}>
9495
{auctionStatus === 'top_bidder' ? (
95-
<div className="absolute top-2 left-2">
96+
<div className="absolute top-2 left-2" data-testid="auction-status-badge">
9697
<AuctionStatusBadge status={auctionStatus} />
9798
</div>
9899
) : null}

dapp/src/components/name-card/ExtendedNameCard.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,13 @@ export function ExtendedNameCard({
8383

8484
return (
8585
<>
86-
<NameCard name={nft.name} badge={badge} menuOptions={menuOptions} isSelected={isActive}>
86+
<NameCard
87+
name={nft.name}
88+
badge={badge}
89+
menuOptions={menuOptions}
90+
isSelected={isActive}
91+
testId="name-card"
92+
>
8793
<NameCardBody name={label}>
8894
<SubnameCountIndicator
8995
onSubnameListClick={onSubnameListClick}

dapp/src/components/name-card/NameCard.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface NameCardProps extends NftDisplayProps {
1414
isSelected?: boolean;
1515
displaySrc?: string | null;
1616
blurImage?: boolean;
17+
testId?: string;
1718
}
1819

1920
export function NameCard({
@@ -25,6 +26,7 @@ export function NameCard({
2526
children,
2627
displaySrc,
2728
blurImage,
29+
testId,
2830
}: React.PropsWithChildren<NameCardProps>) {
2931
return (
3032
<div
@@ -33,6 +35,7 @@ export function NameCard({
3335
isSelected && 'name-card-selected',
3436
nftDisplayVariants({ size }),
3537
)}
38+
data-testid={testId}
3639
>
3740
<div
3841
className={cx(

dapp/src/sections/AuctionCarousel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export function AuctionCarousel() {
116116
loop
117117
className="mySwiper"
118118
modules={[Autoplay]}
119+
data-testid="auction-carousel"
119120
>
120121
{auctionsToRender.map((auction) => (
121122
<SwiperSlide key={auction.key} className="pb-xs">
Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,130 @@
11
// Copyright (c) 2025 IOTA Stiftung
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import 'dotenv/config';
5+
6+
import { normalizeIotaName } from '@iota/iota-names-sdk';
7+
import { Ed25519Keypair } from '@iota/iota-sdk/keypairs/ed25519';
8+
import { formatAddress } from '@iota/iota-sdk/utils';
49
import { expect } from '@playwright/test';
510

611
import { test } from '../helpers/fixtures';
12+
import {
13+
bidOnExistingAuction,
14+
connectWallet,
15+
createAndSendAuctionTransaction,
16+
createWallet,
17+
requestFaucetTokens,
18+
} from '../utils';
19+
20+
test.beforeAll(async ({ appPage, context, extensionPage, extensionName, sharedState }) => {
21+
const { address, mnemonic } = await createWallet(extensionPage);
22+
23+
await appPage.bringToFront();
24+
25+
await connectWallet(appPage, context, extensionName);
26+
27+
await expect(appPage.getByRole('button', { name: formatAddress(address) })).toBeVisible({
28+
timeout: 10_000,
29+
});
30+
31+
sharedState.wallet.address = address;
32+
sharedState.wallet.mnemonic = mnemonic;
33+
});
34+
35+
test('Check "Outbid" pills', async ({ sharedState, appPage: page }) => {
36+
const name = `outbid${Date.now().toString().slice(-6)}`;
37+
const nameToAuction = `${name}.iota`;
38+
await page.bringToFront();
39+
const navPromise = page.goto(`/my-names`);
40+
41+
const walletSigner = Ed25519Keypair.deriveKeypair(sharedState.wallet.mnemonic!);
42+
const newSigner = Ed25519Keypair.deriveKeypair(
43+
sharedState.wallet.mnemonic!,
44+
`m/44'/4218'/0'/0'/1'`,
45+
);
46+
47+
await Promise.all([
48+
requestFaucetTokens(walletSigner.toIotaAddress()),
49+
requestFaucetTokens(newSigner.toIotaAddress()),
50+
]);
51+
52+
const startAuctionResult = await createAndSendAuctionTransaction({
53+
name: nameToAuction,
54+
signer: walletSigner,
55+
});
56+
expect(startAuctionResult.effects?.status.status).toBe('success');
57+
58+
const bidResult = await bidOnExistingAuction({
59+
name: nameToAuction,
60+
signer: newSigner,
61+
});
62+
expect(bidResult.effects?.status.status).toBe('success');
63+
64+
await navPromise;
65+
await page.locator("h2:has-text('My Names')").waitFor({ state: 'visible', timeout: 10_000 });
66+
67+
await page.getByTestId('refresh-button').click();
68+
await page.getByText('Refreshed successfully!').waitFor({ state: 'visible', timeout: 10_000 });
69+
70+
const auctionNameCard = page
71+
.getByTestId('auction-name-card')
72+
.filter({ hasText: normalizeIotaName(nameToAuction, 'at') });
73+
74+
await expect(auctionNameCard.getByTestId('auction-status-badge')).toHaveText('Outbid', {
75+
timeout: 10_000,
76+
});
77+
});
78+
79+
test('Check "Top Bidder" pills', async ({ sharedState, appPage: page, context }) => {
80+
const name = `topbid${Date.now().toString().slice(-6)}`;
81+
const nameToAuction = `${name}.iota`;
82+
await page.bringToFront();
83+
const navPromise = page.goto(`/my-names`);
84+
85+
const walletSigner = Ed25519Keypair.deriveKeypair(sharedState.wallet.mnemonic!);
86+
87+
await requestFaucetTokens(walletSigner.toIotaAddress());
88+
89+
const startAuctionResult = await createAndSendAuctionTransaction({
90+
name: nameToAuction,
91+
signer: walletSigner,
92+
});
93+
expect(startAuctionResult.effects?.status.status).toBe('success');
94+
95+
await navPromise;
96+
await page.locator("h2:has-text('My Names')").waitFor({ state: 'visible', timeout: 10_000 });
97+
98+
await page.getByTestId('refresh-button').click();
99+
await page.getByText('Refreshed successfully!').waitFor({ state: 'visible', timeout: 10_000 });
100+
101+
const auctionNameCard = page
102+
.getByTestId('auction-name-card')
103+
.filter({ hasText: normalizeIotaName(nameToAuction, 'at') });
104+
105+
await expect(auctionNameCard.getByTestId('auction-status-badge')).toHaveText('Top Bidder', {
106+
timeout: 10_000,
107+
});
108+
109+
await page.getByRole('link', { name: 'Auctions' }).click();
110+
await page.getByPlaceholder('Search auction').fill(nameToAuction);
111+
const auctionCard = page.getByTestId('auction-name-card').filter({
112+
hasText: normalizeIotaName(nameToAuction, 'at'),
113+
});
114+
115+
await expect(auctionCard).toBeVisible({ timeout: 10_000 });
116+
await expect(auctionCard.getByTestId('auction-status-badge')).toHaveText('Top Bidder', {
117+
timeout: 10_000,
118+
});
119+
120+
await page.getByLabel('Go to homepage').filter({ visible: true }).first().click();
121+
await page.getByText('Live Auctions').scrollIntoViewIfNeeded();
7122

8-
test('Auction flow works', async ({ appPage }) => {
9-
await expect(appPage).toHaveTitle(/IOTA Names/);
10-
await expect(appPage.getByText('Your On-Chain Name')).toBeVisible();
123+
const carousel = page.getByTestId('auction-carousel');
124+
const auctionInCarousel = carousel.getByTestId('auction-name-card').filter({
125+
hasText: normalizeIotaName(nameToAuction, 'at'),
126+
});
127+
await expect(auctionInCarousel.getByTestId('auction-status-badge')).toHaveText('Top Bidder', {
128+
timeout: 10_000,
129+
});
11130
});

dapp/tests/utils.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import type { BrowserContext, Page } from '@playwright/test';
77

88
import 'dotenv/config';
99

10+
import { Signer } from '@iota/iota-sdk/cryptography';
11+
import { NANOS_PER_IOTA } from '@iota/iota-sdk/utils';
12+
13+
import { buildCreateAuctionTransaction, buildPlaceBidTransaction } from '@/auctions';
1014
import { CONFIG } from '@/config';
1115

1216
import { expect } from './helpers/fixtures';
17+
import { iotaClient, iotaNamesClient } from './setup/utils';
1318

1419
export async function connectWallet(page: Page, context: BrowserContext, extensionName: string) {
1520
await page.getByRole('button', { name: /Connect/i }).click();
@@ -108,3 +113,89 @@ export function deriveAddressFromMnemonic(mnemonic: string, path?: string) {
108113
export function getAddressByIndexPath(mnemonic: string, index: number) {
109114
return deriveAddressFromMnemonic(mnemonic, `m/44'/4218'/0'/0'/${index}'`);
110115
}
116+
117+
interface CreateAndSendAuctionTransaction {
118+
name: string;
119+
signer: Signer;
120+
}
121+
export async function createAndSendAuctionTransaction({
122+
name,
123+
signer,
124+
}: CreateAndSendAuctionTransaction) {
125+
try {
126+
const tx = buildCreateAuctionTransaction(
127+
iotaNamesClient.config.auctionPackageId,
128+
iotaNamesClient.config.iotaNamesObjectId,
129+
iotaNamesClient.config.auctionHouseObjectId,
130+
signer.toIotaAddress(),
131+
BigInt(80) * NANOS_PER_IOTA,
132+
name,
133+
);
134+
135+
const txBytes = await tx.build({ client: iotaClient });
136+
const txDryRun = await iotaClient.dryRunTransactionBlock({
137+
transactionBlock: txBytes,
138+
});
139+
140+
if (txDryRun.effects.status.status !== 'success') {
141+
throw new Error(txDryRun.effects.status.error || 'Transaction dry run failed');
142+
}
143+
144+
const response = await iotaClient.signAndExecuteTransaction({
145+
transaction: txBytes,
146+
signer,
147+
options: {
148+
showEffects: true,
149+
},
150+
});
151+
152+
console.log('Transaction sent. Digest:', response.digest);
153+
console.log(`Successfully created auction for name: ${name}`);
154+
155+
return response;
156+
} catch (error) {
157+
console.error('Error creating initial auction:', error);
158+
throw error;
159+
}
160+
}
161+
162+
interface BidOnExistingAuction {
163+
name: string;
164+
signer: Signer;
165+
}
166+
export async function bidOnExistingAuction({ name, signer }: BidOnExistingAuction) {
167+
try {
168+
const tx = buildPlaceBidTransaction(
169+
iotaNamesClient.config.auctionPackageId,
170+
iotaNamesClient.config.auctionHouseObjectId,
171+
signer.toIotaAddress(),
172+
BigInt(81) * NANOS_PER_IOTA,
173+
name,
174+
);
175+
176+
const txBytes = await tx.build({ client: iotaClient });
177+
const txDryRun = await iotaClient.dryRunTransactionBlock({
178+
transactionBlock: txBytes,
179+
});
180+
181+
if (txDryRun.effects.status.status !== 'success') {
182+
throw new Error(txDryRun.effects.status.error || 'Transaction dry run failed');
183+
}
184+
185+
const response = await iotaClient.signAndExecuteTransaction({
186+
transaction: txBytes,
187+
signer,
188+
options: {
189+
showEffects: true,
190+
},
191+
});
192+
193+
console.log('Transaction sent. Digest:', response.digest);
194+
console.log(`Successfully bid on existing auction for name: ${name}`);
195+
196+
return response;
197+
} catch (error) {
198+
console.error('Error bidding on auction:', error);
199+
throw error;
200+
}
201+
}

0 commit comments

Comments
 (0)