diff --git a/app/components/UI/Predict/components/PredictBalance/PredictBalance.test.tsx b/app/components/UI/Predict/components/PredictBalance/PredictBalance.test.tsx
index 2a25804f3541..76b4a5440e52 100644
--- a/app/components/UI/Predict/components/PredictBalance/PredictBalance.test.tsx
+++ b/app/components/UI/Predict/components/PredictBalance/PredictBalance.test.tsx
@@ -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', () => ({
@@ -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',
},
}));
@@ -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(),
@@ -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
@@ -300,7 +311,7 @@ describe('PredictBalance', () => {
});
mockUsePredictDeposit.mockReturnValue({
deposit: mockDeposit,
- status: 'IDLE',
+ status: 'idle',
});
// Act
@@ -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(, {
+ 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(, {
+ 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(, {
+ 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(
+ ,
+ {
+ 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(
+ ,
+ {
+ 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(, {
+ 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', () => {
@@ -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(, {
+ state: initialState,
+ });
+
+ // Assert
+ expect(getByText(/\$123,456,789\.12/)).toBeOnTheScreen();
+ });
+
it('handles adding funds state', () => {
// Arrange
mockUsePredictBalance.mockReturnValue({
@@ -348,7 +523,7 @@ describe('PredictBalance', () => {
});
mockUsePredictDeposit.mockReturnValue({
deposit: jest.fn(),
- status: 'PENDING',
+ status: 'pending',
});
// Act
@@ -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(, {
+ 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(, {
+ 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(, {
+ state: initialState,
+ });
+
+ expect(getByText(/\$0\.00/)).toBeOnTheScreen();
+ });
});
});
diff --git a/app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx b/app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx
index 6093d0e8324b..ccaca00a2afc 100644
--- a/app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx
+++ b/app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx
@@ -75,8 +75,13 @@ const PredictBalance: React.FC = ({ onLayout }) => {
}, [deposit, executeGuardedAction]);
const handleWithdraw = useCallback(() => {
- withdraw();
- }, [withdraw]);
+ executeGuardedAction(
+ () => {
+ withdraw();
+ },
+ { checkBalance: true },
+ );
+ }, [withdraw, executeGuardedAction]);
if (isLoading) {
return (
diff --git a/app/components/UI/Predict/controllers/PredictController.ts b/app/components/UI/Predict/controllers/PredictController.ts
index 23634c19e2ba..81d24d748d4c 100644
--- a/app/components/UI/Predict/controllers/PredictController.ts
+++ b/app/components/UI/Predict/controllers/PredictController.ts
@@ -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 = {
@@ -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) => {