Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { backgroundState } from '../../../../../util/test/initial-root-state';
import renderWithProvider from '../../../../../util/test/renderWithProvider';
import PredictBalance from './PredictBalance';
import { strings } from '../../../../../../locales/i18n';

// Mock React Navigation
jest.mock('@react-navigation/native', () => ({
Expand All @@ -26,10 +27,10 @@ const mockUsePredictDeposit = jest.fn();
jest.mock('../../hooks/usePredictDeposit', () => ({
usePredictDeposit: () => mockUsePredictDeposit(),
PredictDepositStatus: {
IDLE: 'IDLE',
PENDING: 'PENDING',
CONFIRMED: 'CONFIRMED',
FAILED: 'FAILED',
IDLE: 'idle',
PENDING: 'pending',
CONFIRMED: 'confirmed',
FAILED: 'failed',
},
}));

Expand All @@ -43,6 +44,12 @@ jest.mock('../../hooks/usePredictActionGuard', () => ({
}),
}));

// Mock usePredictWithdraw hook
const mockUsePredictWithdraw = jest.fn();
jest.mock('../../hooks/usePredictWithdraw', () => ({
usePredictWithdraw: () => mockUsePredictWithdraw(),
}));

// Mock Clipboard
jest.mock('@react-native-clipboard/clipboard', () => ({
setString: jest.fn(),
Expand Down Expand Up @@ -70,7 +77,11 @@ describe('PredictBalance', () => {

mockUsePredictDeposit.mockReturnValue({
deposit: jest.fn(),
status: 'IDLE',
status: 'idle',
});

mockUsePredictWithdraw.mockReturnValue({
withdraw: jest.fn(),
});

// Reset executeGuardedAction mock to default behavior
Expand Down Expand Up @@ -300,7 +311,7 @@ describe('PredictBalance', () => {
});
mockUsePredictDeposit.mockReturnValue({
deposit: mockDeposit,
status: 'IDLE',
status: 'idle',
});

// Act
Expand All @@ -313,6 +324,150 @@ describe('PredictBalance', () => {
// Assert
expect(mockDeposit).toHaveBeenCalledTimes(1);
});

it('calls executeGuardedAction when Add Funds button is pressed', () => {
// Arrange
const mockDeposit = jest.fn();
mockUsePredictBalance.mockReturnValue({
balance: 100,
isLoading: false,
isRefreshing: false,
error: null,
loadBalance: jest.fn(),
hasNoBalance: false,
});
mockUsePredictDeposit.mockReturnValue({
deposit: mockDeposit,
status: 'idle',
});

// Act
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});
const addFundsButton = getByText(/Add funds/i);
fireEvent.press(addFundsButton);

// Assert - executeGuardedAction is called (it executes the deposit function)
expect(mockDeposit).toHaveBeenCalled();
});

it('calls executeGuardedAction with checkBalance option when Withdraw button is pressed', () => {
// Arrange
const mockWithdraw = jest.fn();
mockUsePredictBalance.mockReturnValue({
balance: 100,
isLoading: false,
isRefreshing: false,
error: null,
loadBalance: jest.fn(),
hasNoBalance: false,
});
mockUsePredictWithdraw.mockReturnValue({
withdraw: mockWithdraw,
});

// Act
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});
const withdrawButton = getByText(/Withdraw/i);
fireEvent.press(withdrawButton);

// Assert - executeGuardedAction is called with checkBalance option (it executes the withdraw function)
expect(mockWithdraw).toHaveBeenCalled();
});
});

describe('balance refresh', () => {
it('component renders with adding funds state when deposit is pending', () => {
// Arrange - set up CONFIRMED status to test the adding funds UI
mockUsePredictDeposit.mockReturnValue({
deposit: jest.fn(),
status: 'pending',
});

// Act
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});

// Assert - should show adding funds message
expect(
getByText(strings('predict.deposit.adding_your_funds')),
).toBeOnTheScreen();
});

it('component renders normally when deposit status is idle', () => {
// Arrange - set up IDLE status
mockUsePredictDeposit.mockReturnValue({
deposit: jest.fn(),
status: 'idle',
});

// Act
const { getByTestId, queryByText } = renderWithProvider(
<PredictBalance />,
{
state: initialState,
},
);

// Assert - should render balance card normally, no adding funds message
expect(getByTestId('predict-balance-card')).toBeOnTheScreen();
expect(
queryByText(strings('predict.deposit.adding_your_funds')),
).not.toBeOnTheScreen();
});
});

describe('onLayout callback', () => {
it('calls onLayout callback when provided', () => {
// Arrange
const mockOnLayout = jest.fn();

// Act
const { getByTestId } = renderWithProvider(
<PredictBalance onLayout={mockOnLayout} />,
{
state: initialState,
},
);

const balanceCard = getByTestId('predict-balance-card');

// Simulate onLayout event
fireEvent(balanceCard, 'layout', {
nativeEvent: {
layout: {
height: 200,
},
},
});

// Assert
expect(mockOnLayout).toHaveBeenCalledWith(200);
});

it('handles onLayout gracefully when no callback is provided', () => {
// Act
const { getByTestId } = renderWithProvider(<PredictBalance />, {
state: initialState,
});

const balanceCard = getByTestId('predict-balance-card');

// Assert - should not throw error when onLayout is called without a callback
expect(() => {
fireEvent(balanceCard, 'layout', {
nativeEvent: {
layout: {
height: 200,
},
},
});
}).not.toThrow();
});
});

describe('edge cases', () => {
Expand All @@ -336,6 +491,26 @@ describe('PredictBalance', () => {
expect(getByText(/\$0\.01/)).toBeOnTheScreen();
});

it('handles very large balance', () => {
// Arrange
mockUsePredictBalance.mockReturnValue({
balance: 123456789.123456,
isLoading: false,
isRefreshing: false,
error: null,
loadBalance: jest.fn(),
hasNoBalance: false,
});

// Act
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});

// Assert
expect(getByText(/\$123,456,789\.12/)).toBeOnTheScreen();
});

it('handles adding funds state', () => {
// Arrange
mockUsePredictBalance.mockReturnValue({
Expand All @@ -348,7 +523,7 @@ describe('PredictBalance', () => {
});
mockUsePredictDeposit.mockReturnValue({
deposit: jest.fn(),
status: 'PENDING',
status: 'pending',
});

// Act
Expand All @@ -363,5 +538,71 @@ describe('PredictBalance', () => {
expect(getByTestId('predict-balance-card')).toBeOnTheScreen();
expect(getByText(/Add funds/i)).toBeOnTheScreen();
});

it('shows primary button variant when balance is zero', () => {
// Arrange
mockUsePredictBalance.mockReturnValue({
balance: 0,
isLoading: false,
isRefreshing: false,
error: null,
loadBalance: jest.fn(),
hasNoBalance: true,
});

// Act
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});

// Assert
const addFundsButton = getByText(/Add funds/i);
expect(addFundsButton).toBeOnTheScreen();
// The button should exist, but we can't easily test the variant without more complex testing
});

it('shows secondary button variant when balance is greater than zero', () => {
// Arrange
mockUsePredictBalance.mockReturnValue({
balance: 10,
isLoading: false,
isRefreshing: false,
error: null,
loadBalance: jest.fn(),
hasNoBalance: false,
});

// Act
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});

// Assert
const addFundsButton = getByText(/Add funds/i);
expect(addFundsButton).toBeOnTheScreen();

const withdrawButton = getByText(/Withdraw/i);
expect(withdrawButton).toBeOnTheScreen();
});

it('handles undefined balance gracefully', () => {
// Arrange
mockUsePredictBalance.mockReturnValue({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
balance: undefined as any,
isLoading: false,
isRefreshing: false,
error: null,
loadBalance: jest.fn(),
hasNoBalance: true,
});

// Act & Assert - should not crash and render $0.00
const { getByText } = renderWithProvider(<PredictBalance />, {
state: initialState,
});

expect(getByText(/\$0\.00/)).toBeOnTheScreen();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,13 @@ const PredictBalance: React.FC<PredictBalanceProps> = ({ onLayout }) => {
}, [deposit, executeGuardedAction]);

const handleWithdraw = useCallback(() => {
withdraw();
}, [withdraw]);
executeGuardedAction(
() => {
withdraw();
},
{ checkBalance: true },
);
}, [withdraw, executeGuardedAction]);

if (isLoading) {
return (
Expand Down
21 changes: 2 additions & 19 deletions app/components/UI/Predict/controllers/PredictController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1014,15 +1014,7 @@ export class PredictController extends BaseController<
networkClientId,
disableHook: true,
disableSequential: true,
transactions: [
{
params: {
to: signer.address as Hex,
value: '0x1',
},
},
...transactions,
],
transactions,
});

const predictClaim: PredictClaim = {
Expand Down Expand Up @@ -1288,16 +1280,7 @@ export class PredictController extends BaseController<
disableHook: true,
disableSequential: true,
requireApproval: true,
transactions: [
// TODO: remove this dummy transaction when confirmation handling is implemented
{
params: {
to: signer.address as Hex,
value: '0x1',
},
},
transaction,
],
transactions: [transaction],
});

this.update((state) => {
Expand Down
Loading