Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@mui/icons-material": "^6.3.0",
"@mui/material": "^6.3.0",
"@sentry/react": "^7.117.0",
"@tanstack/react-query": "^5.90.12",
"copy-to-clipboard": "^3.3.3",
"crypto-browserify": "^3.12.0",
"crypto-js": "^4.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as Sentry from '@sentry/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { Chain, XRPLNetwork } from '@gemwallet/constants';

import { DEFAULT_RESERVE, RESERVE_PER_OWNER } from '../../../constants';
import { formatToken } from '../../../utils';
import { TokenListing, TokenListingProps } from './TokenListing';
import { vi, Mock } from 'vitest';
import { vi, Mock, describe, test, expect, beforeEach } from 'vitest';

const user = userEvent.setup();

Expand All @@ -21,6 +21,7 @@ vi.mock('@sentry/react', () => {
const mockGetBalancesPromise = vi.fn();
const mockFundWalletPromise = vi.fn();
const mockRequestPromise = vi.fn();
const mockGetAccountInfo = vi.fn();

const mockChain = Chain.XRPL;
let mockNetwork = XRPLNetwork.TESTNET;
Expand All @@ -29,16 +30,6 @@ let mockClient: { getBalances: Mock; request: Mock } | null = {
request: mockRequestPromise
};

mockGetBalancesPromise.mockResolvedValueOnce([
{ value: '100', currency: 'XRP', issuer: undefined }
]);

mockRequestPromise.mockResolvedValueOnce({
result: {
lines: []
}
});

vi.mock('../../../contexts', () => {
return {
useNetwork: () => ({
Expand All @@ -59,34 +50,57 @@ vi.mock('../../../contexts', () => {
}),
useLedger: () => ({
fundWallet: mockFundWalletPromise,
getAccountInfo: vi.fn().mockImplementation(() =>
Promise.resolve({
result: {
account_data: {
OwnerCount: 2
}
}
})
)
getAccountInfo: mockGetAccountInfo
})
};
});

// Create a wrapper component with QueryClientProvider for testing
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
gcTime: 0
}
}
});

const renderWithQueryClient = (component: React.ReactElement) => {
const queryClient = createTestQueryClient();
return render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>);
};

describe('TokenListing', () => {
let props: TokenListingProps;

beforeEach(() => {
vi.clearAllMocks();
mockClient = {
getBalances: mockGetBalancesPromise,
request: mockRequestPromise
};
props = {
address: 'r123'
};
// Default mock responses
mockRequestPromise.mockResolvedValue({
result: {
lines: []
}
});
mockGetAccountInfo.mockResolvedValue({
result: {
account_data: {
OwnerCount: 2
}
}
});
});

test('should display an error when client failed to load', () => {
mockClient = null;
render(<TokenListing {...props} />);
renderWithQueryClient(<TokenListing {...props} />);
expect(
screen.queryByText(
'There was an error attempting to connect to the network. Please refresh the page and try again.'
Expand All @@ -95,20 +109,21 @@ describe('TokenListing', () => {
});

test('should display the loading token state when the XRPBalance is not calculated', () => {
render(<TokenListing {...props} />);
mockGetBalancesPromise.mockReturnValue(new Promise(() => {})); // Never resolves
renderWithQueryClient(<TokenListing {...props} />);
expect(screen.getByTestId('token-loader')).toBeInTheDocument();
});

test('should display the XRP balance and trust line balances', async () => {
mockGetBalancesPromise.mockResolvedValueOnce([
mockGetBalancesPromise.mockResolvedValue([
{ value: '100', currency: 'XRP', issuer: undefined },
{ value: '50', currency: 'USD', issuer: 'r123' },
{ value: '20', currency: 'ETH', issuer: 'r456' }
]);

const reserve = DEFAULT_RESERVE + RESERVE_PER_OWNER * 2;

render(<TokenListing {...props} />);
renderWithQueryClient(<TokenListing {...props} />);
await waitFor(() => {
expect(screen.getByText(`${100 - reserve} XRP`)).toBeInTheDocument();
expect(screen.getByText('50 USD')).toBeInTheDocument();
Expand All @@ -117,21 +132,20 @@ describe('TokenListing', () => {
});

test('should display an error message when there is an error fetching the balances', async () => {
mockGetBalancesPromise.mockRejectedValueOnce(
mockGetBalancesPromise.mockRejectedValue(
new Error('Throw an error if there is an error fetching the balances')
);
render(<TokenListing {...props} />);
renderWithQueryClient(<TokenListing {...props} />);
await waitFor(() => {
expect(screen.getByText('Account not activated')).toBeVisible();
expect(Sentry.captureException).toHaveBeenCalled();
});
});

test('should open the explanation dialog when the explain button is clicked', async () => {
mockGetBalancesPromise.mockResolvedValueOnce([
mockGetBalancesPromise.mockResolvedValue([
{ value: '100', currency: 'XRP', issuer: undefined }
]);
render(<TokenListing {...props} />);
renderWithQueryClient(<TokenListing {...props} />);
const explainButton = await screen.findByText('Explain');
await user.click(explainButton);
expect(
Expand All @@ -143,10 +157,10 @@ describe('TokenListing', () => {

test('Should display the fund wallet button when the network is testnet and XRP balance is 0', async () => {
mockNetwork = XRPLNetwork.TESTNET;
mockGetBalancesPromise.mockRejectedValueOnce(
mockGetBalancesPromise.mockRejectedValue(
new Error('Throw an error if there is an error fetching the balances')
);
render(<TokenListing {...props} />);
renderWithQueryClient(<TokenListing {...props} />);
await waitFor(() => {
const button = screen.queryByTestId('fund-wallet-button');
expect(button).toBeInTheDocument();
Expand All @@ -155,25 +169,31 @@ describe('TokenListing', () => {

test('Should not display the fund wallet button when the network is Mainnet and XRP balance is 0', async () => {
mockNetwork = XRPLNetwork.MAINNET;
mockGetBalancesPromise.mockRejectedValueOnce(
mockGetBalancesPromise.mockRejectedValue(
new Error('Throw an error if there is an error fetching the balances')
);
render(<TokenListing {...props} />);
renderWithQueryClient(<TokenListing {...props} />);
await waitFor(() => {
const button = screen.queryByTestId('fund-wallet-button');
expect(button).not.toBeInTheDocument();
});
});

test('Should display the amount of XRP when click on Fund Wallet Button', async () => {
test('Should refetch balances when clicking Fund Wallet Button', async () => {
const reserve = DEFAULT_RESERVE + RESERVE_PER_OWNER * 2;

mockNetwork = XRPLNetwork.TESTNET;
mockFundWalletPromise.mockResolvedValueOnce({ balance: 10000 });
// First call fails (account not activated)
mockGetBalancesPromise.mockRejectedValueOnce(
new Error('Throw an error if there is an error fetching the balances')
);
render(<TokenListing {...props} />);
// After funding, return balances
mockGetBalancesPromise.mockResolvedValue([
{ value: '10000', currency: 'XRP', issuer: undefined }
]);
mockFundWalletPromise.mockResolvedValue({ balance: 10000 });

renderWithQueryClient(<TokenListing {...props} />);

const button = await screen.findByTestId('fund-wallet-button');
const format = formatToken(10000 - reserve, 'XRP');
Expand Down
Loading