Skip to content

Commit c4ddd63

Browse files
committed
test: add unit and e2e test
1 parent b59561e commit c4ddd63

File tree

7 files changed

+905
-12
lines changed

7 files changed

+905
-12
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import React from 'react'
2+
import { render, screen, within } from '@testing-library/react'
3+
import { createMemoryRouter, RouterProvider } from 'react-router-dom'
4+
5+
import AddressList from './AddressList'
6+
import { MintlayerContext, BitcoinContext, SettingsContext } from '@Contexts'
7+
8+
const renderWithProviders = ({
9+
coinType = 'Mintlayer',
10+
search = '',
11+
networkType = 'testnet',
12+
mintlayer = {},
13+
bitcoin = {},
14+
} = {}) => {
15+
const { fetchingBalances = false, addressData = [] } = mintlayer
16+
17+
const { formatedAddresses = [] } = bitcoin
18+
19+
const routes = [
20+
{
21+
path: '/addresses/:coinType',
22+
element: (
23+
<SettingsContext.Provider value={{ networkType }}>
24+
<MintlayerContext.Provider value={{ fetchingBalances, addressData }}>
25+
<BitcoinContext.Provider value={{ formatedAddresses }}>
26+
<AddressList search={search} />
27+
</BitcoinContext.Provider>
28+
</MintlayerContext.Provider>
29+
</SettingsContext.Provider>
30+
),
31+
},
32+
]
33+
34+
const router = createMemoryRouter(routes, {
35+
initialEntries: [`/addresses/${coinType}`],
36+
future: { v7_relativeSplatPath: true },
37+
})
38+
39+
return render(<RouterProvider router={router} />)
40+
}
41+
42+
describe('AddressList', () => {
43+
test('renders and sorts Mintlayer addresses by used, available, locked, tokens', () => {
44+
const mlAddresses = [
45+
{
46+
id: 'tmt1qAAA',
47+
coin_balance: { decimal: 0.5 },
48+
locked_coin_balance: { decimal: 0.1 },
49+
tokens: [{ token_id: 'token-1', amount: { decimal: 1 } }],
50+
transaction_history: [], // used = false
51+
},
52+
{
53+
id: 'tmt1qBBB',
54+
coin_balance: { decimal: 1.2 },
55+
locked_coin_balance: { decimal: 0 },
56+
tokens: [],
57+
transaction_history: [1], // used = true
58+
},
59+
{
60+
id: 'tmt1qCCC',
61+
coin_balance: { decimal: 1.2 },
62+
locked_coin_balance: { decimal: 0.5 },
63+
tokens: [
64+
{ token_id: 'token-2', amount: { decimal: 5 } },
65+
{ token_id: 'token-3', amount: { decimal: 10 } },
66+
],
67+
transaction_history: [], // used = false
68+
},
69+
]
70+
71+
renderWithProviders({
72+
coinType: 'Mintlayer',
73+
search: '',
74+
mintlayer: { fetchingBalances: false, addressData: mlAddresses },
75+
})
76+
77+
// Expect order: used first (BBB), then by available (CCC), then (AAA)
78+
const row0 = screen.getByTestId('address-row-0')
79+
const row1 = screen.getByTestId('address-row-1')
80+
const row2 = screen.getByTestId('address-row-2')
81+
82+
expect(within(row0).getByTitle('tmt1qBBB')).toBeInTheDocument()
83+
expect(within(row1).getByTitle('tmt1qCCC')).toBeInTheDocument()
84+
expect(within(row2).getByTitle('tmt1qAAA')).toBeInTheDocument()
85+
})
86+
87+
test('filters Mintlayer addresses by token id substring', () => {
88+
const mlAddresses = [
89+
{
90+
id: 'tmt1qADDR1',
91+
coin_balance: { decimal: 0.1 },
92+
locked_coin_balance: { decimal: 0 },
93+
tokens: [
94+
{ token_id: 'ABC123', amount: { decimal: 1 } },
95+
{ token_id: 'XYZ999', amount: { decimal: 2 } },
96+
],
97+
transaction_history: [],
98+
},
99+
{
100+
id: 'tmt1qADDR2',
101+
coin_balance: { decimal: 0.2 },
102+
locked_coin_balance: { decimal: 0 },
103+
tokens: [{ token_id: 'LMN000', amount: { decimal: 3 } }],
104+
transaction_history: [],
105+
},
106+
]
107+
108+
renderWithProviders({
109+
coinType: 'Mintlayer',
110+
search: 'XYZ9',
111+
mintlayer: { fetchingBalances: false, addressData: mlAddresses },
112+
})
113+
114+
// Only the first address has a token id containing 'XYZ9'
115+
// rows include header row; we check specific rendered rows by data-testid
116+
const addressRow0 = screen.getByTestId('address-row-0')
117+
expect(within(addressRow0).getByTitle('tmt1qADDR1')).toBeInTheDocument()
118+
expect(screen.queryByTestId('address-row-1')).not.toBeInTheDocument()
119+
})
120+
121+
test('renders and sorts Bitcoin addresses', () => {
122+
const btcAddresses = [
123+
{
124+
id: 'tb1qAAA',
125+
coin_balance: { available: 0.1, locked: 0 },
126+
tokens: [],
127+
used: false,
128+
},
129+
{
130+
id: 'tb1qBBB',
131+
coin_balance: { available: 1.5, locked: 0 },
132+
tokens: [],
133+
used: true,
134+
},
135+
{
136+
id: 'tb1qCCC',
137+
coin_balance: { available: 0.9, locked: 0.2 },
138+
tokens: [{ token_id: 'btc-token', amount: { decimal: 1 } }],
139+
used: false,
140+
},
141+
]
142+
143+
renderWithProviders({
144+
coinType: 'Bitcoin',
145+
bitcoin: { formatedAddresses: btcAddresses },
146+
})
147+
148+
// Expect order: used first (BBB), then highest available (CCC), then (AAA)
149+
const row0 = screen.getByTestId('address-row-0')
150+
const row1 = screen.getByTestId('address-row-1')
151+
const row2 = screen.getByTestId('address-row-2')
152+
153+
expect(within(row0).getByTitle('tb1qBBB')).toBeInTheDocument()
154+
expect(within(row1).getByTitle('tb1qCCC')).toBeInTheDocument()
155+
expect(within(row2).getByTitle('tb1qAAA')).toBeInTheDocument()
156+
})
157+
158+
test('shows empty state when no addresses (Mintlayer)', () => {
159+
renderWithProviders({
160+
coinType: 'Mintlayer',
161+
mintlayer: { fetchingBalances: false, addressData: [] },
162+
})
163+
164+
expect(screen.getByText('No addresses found')).toBeInTheDocument()
165+
})
166+
167+
test('shows loading indicator when fetching balances', () => {
168+
renderWithProviders({
169+
coinType: 'Mintlayer',
170+
mintlayer: { fetchingBalances: true, addressData: [] },
171+
})
172+
173+
expect(screen.getByTestId('loading')).toBeInTheDocument()
174+
expect(screen.queryByTestId('address-table')).not.toBeInTheDocument()
175+
})
176+
})
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React from 'react'
2+
import { render, screen, fireEvent } from '@testing-library/react'
3+
import { createMemoryRouter, RouterProvider } from 'react-router-dom'
4+
import AddressListItem from './AddressListItem'
5+
import { SettingsContext } from '@Contexts'
6+
import { ML, BTC } from '@Helpers'
7+
8+
// Suppress only the specific React Router future flag warning
9+
const realWarn = console.warn
10+
beforeAll(() => {
11+
jest.spyOn(console, 'warn').mockImplementation((...args) => {
12+
if (typeof args[0] === 'string' && args[0].includes('v7_startTransition')) {
13+
return
14+
}
15+
realWarn(...args)
16+
})
17+
})
18+
19+
afterAll(() => {
20+
console.warn.mockRestore()
21+
})
22+
23+
// Unified future flags
24+
const memoryRouterFeature = {
25+
v7_startTransition: true,
26+
v7_relativeSplatPath: true,
27+
v7_partialHydration: true,
28+
}
29+
30+
// Helper to render with router + context
31+
const renderWithProviders = ({
32+
ui,
33+
coinType = 'Mintlayer',
34+
networkType = 'testnet',
35+
}) => {
36+
const routes = [
37+
{
38+
path: '/:coinType',
39+
element: (
40+
<SettingsContext.Provider value={{ networkType }}>
41+
<table>
42+
<tbody>{ui}</tbody>
43+
</table>
44+
</SettingsContext.Provider>
45+
),
46+
},
47+
]
48+
49+
const router = createMemoryRouter(routes, {
50+
initialEntries: [`/${coinType}`],
51+
future: memoryRouterFeature,
52+
})
53+
54+
return render(<RouterProvider router={router} />)
55+
}
56+
57+
describe('AddressListItem', () => {
58+
const baseAddress = {
59+
id: 'ADDR1',
60+
used: false,
61+
coin_balance: { available: '1.23', locked: 0 },
62+
tokens: [],
63+
}
64+
65+
it('renders Mintlayer link and formatted text', () => {
66+
const networkType = 'testnet'
67+
const expectedHref = ML.getMlAddressLink('ADDR1', networkType)
68+
const expectedText = ML.formatAddress('ADDR1', 18)
69+
70+
renderWithProviders({
71+
ui: (
72+
<AddressListItem
73+
address={baseAddress}
74+
index={0}
75+
/>
76+
),
77+
coinType: 'Mintlayer',
78+
networkType,
79+
})
80+
81+
const link = screen.getByRole('link')
82+
expect(link).toHaveAttribute('href', expectedHref)
83+
expect(link).toHaveTextContent(expectedText)
84+
expect(screen.getByText(/1\.23 ML/)).toBeInTheDocument()
85+
})
86+
87+
it('renders BTC link and formatted text', () => {
88+
const networkType = 'testnet'
89+
const expectedHref = BTC.getBtcAddressLink('ADDR1', networkType)
90+
const expectedText = ML.formatAddress('ADDR1', 18)
91+
92+
renderWithProviders({
93+
ui: (
94+
<AddressListItem
95+
address={baseAddress}
96+
index={1}
97+
/>
98+
),
99+
coinType: 'Bitcoin',
100+
networkType,
101+
})
102+
103+
const link = screen.getByRole('link')
104+
expect(link).toHaveAttribute('href', expectedHref)
105+
expect(link).toHaveTextContent(expectedText)
106+
expect(screen.getByText(/1\.23 BTC/)).toBeInTheDocument()
107+
})
108+
109+
it('expands and shows tokens when toggled', () => {
110+
const addressWithTokens = {
111+
...baseAddress,
112+
coin_balance: { available: '5.00', locked: 2 },
113+
tokens: [
114+
{ amount: { decimal: '10.5' }, token_id: 'TOKEN1' },
115+
{ amount: { decimal: '3.14' }, token_id: 'TOKEN2' },
116+
],
117+
}
118+
119+
renderWithProviders({
120+
ui: (
121+
<AddressListItem
122+
address={addressWithTokens}
123+
index={2}
124+
/>
125+
),
126+
coinType: 'Mintlayer',
127+
networkType: 'testnet',
128+
})
129+
130+
const formatted1 = ML.formatAddress('TOKEN1', 12)
131+
const formatted2 = ML.formatAddress('TOKEN2', 12)
132+
133+
expect(screen.queryByText(`(${formatted1})`)).not.toBeInTheDocument()
134+
135+
const toggle = screen.getByRole('button', { name: /2 tokens/i })
136+
fireEvent.click(toggle)
137+
138+
expect(screen.getByText(`(${formatted1})`)).toBeInTheDocument()
139+
expect(screen.getByText(`(${formatted2})`)).toBeInTheDocument()
140+
expect(screen.getByText(/Locked: 2 ML/)).toBeInTheDocument()
141+
})
142+
})

src/components/composed/Header/Header.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const setup = async (location, mode = 'default') => {
8787
v7_partialHydration: true,
8888
}
8989

90-
await render(
90+
render(
9191
<AccountProvider value={value}>
9292
<SettingsProvider value={{ networkType: 'testnet', toggleNetworkType }}>
9393
<MintlayerProvider value={mintlayerProviderValue}>

0 commit comments

Comments
 (0)