Skip to content
Merged
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
@@ -1,6 +1,7 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react-native';
import PredictActionButtons from './PredictActionButtons';
import PredictBetButton from './PredictBetButton';
import renderWithProvider from '../../../../../util/test/renderWithProvider';
import { TEST_HEX_COLORS } from '../../testUtils/mockColors';
import {
Expand Down Expand Up @@ -237,6 +238,19 @@ describe('PredictActionButtons', () => {
expect(screen.queryByText('YES · 65¢')).not.toBeOnTheScreen();
expect(screen.queryByText('NO · 35¢')).not.toBeOnTheScreen();
});

it('passes carousel mode to bet buttons', () => {
const props = createDefaultProps({ isCarousel: true });

const { UNSAFE_getAllByType } = renderWithProvider(
<PredictActionButtons {...props} />,
);

const betButtons = UNSAFE_getAllByType(PredictBetButton);

expect(betButtons[0].props.size).toBe('md');
expect(betButtons[1].props.size).toBe('md');
});
});

describe('bet buttons for game markets', () => {
Expand Down Expand Up @@ -312,6 +326,17 @@ describe('PredictActionButtons', () => {
});

describe('edge cases', () => {
it('uses default testID when testID is not provided', () => {
const props = createDefaultProps();
delete (props as Partial<typeof props>).testID;

renderWithProvider(<PredictActionButtons {...props} />);

expect(
screen.getByTestId('predict-action-buttons-bet-yes'),
).toBeOnTheScreen();
});

it('renders nothing when outcome has less than 2 tokens', () => {
const outcomeWithOneToken = createMockOutcome({
tokens: [{ id: 'token-1', title: 'Yes', price: 0.65 }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const PredictActionButtons: React.FC<PredictActionButtonsProps> = ({
onClaimPress,
claimableAmount = 0,
isLoading = false,
isClaimPending = false,
testID = 'predict-action-buttons',
isCarousel,
}) => {
Expand Down Expand Up @@ -76,6 +77,7 @@ const PredictActionButtons: React.FC<PredictActionButtonsProps> = ({
<PredictClaimButton
amount={market.game ? undefined : claimableAmount}
onPress={onClaimPress}
isLoading={isClaimPending}
testID={`${testID}-claim`}
/>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface PredictClaimButtonProps {
amount?: number;
onPress: () => void;
disabled?: boolean;
isLoading?: boolean;
isHidden?: boolean;
testID?: string;
}

Expand All @@ -46,6 +48,7 @@ export interface PredictActionButtonsProps {
onClaimPress?: () => void;
claimableAmount?: number;
isLoading?: boolean;
isClaimPending?: boolean;
testID?: string;
isCarousel?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ jest.mock('../../../../../../locales/i18n', () => ({
if (key === 'predict.claim_winnings_text') {
return 'Claim winnings';
}
if (key === 'predict.claiming_text') {
return 'Claiming...';
}
return key;
}),
}));
Expand Down Expand Up @@ -75,6 +78,30 @@ describe('PredictClaimButton', () => {

expect(screen.getByText('Claim $11.00')).toBeOnTheScreen();
});

it('renders loading state with claiming text when isLoading is true', () => {
// Arrange
const props = createDefaultProps({ amount: 25.5, isLoading: true });

// Act
renderWithProvider(<PredictClaimButton {...props} />);

// Assert
expect(screen.getByText('Claiming...')).toBeOnTheScreen();
expect(screen.queryByText('Claim $25.50')).not.toBeOnTheScreen();
});

it('renders SensitiveText when isHidden is true and amount is provided', () => {
// Arrange
const props = createDefaultProps({ amount: 25.5, isHidden: true });

// Act
renderWithProvider(<PredictClaimButton {...props} />);

// Assert
expect(screen.getByText('•••••••••')).toBeOnTheScreen();
expect(screen.queryByText('Claim $25.50')).not.toBeOnTheScreen();
});
});

describe('without amount (Button Secondary variant)', () => {
Expand Down Expand Up @@ -123,6 +150,18 @@ describe('PredictClaimButton', () => {

expect(mockOnPress).not.toHaveBeenCalled();
});

it('renders loading state with claiming text when isLoading is true', () => {
// Arrange
const props = createDefaultProps({ amount: undefined, isLoading: true });

// Act
renderWithProvider(<PredictClaimButton {...props} />);

// Assert
expect(screen.getByText('Claiming...')).toBeOnTheScreen();
expect(screen.queryByText('Claim winnings')).not.toBeOnTheScreen();
});
});

describe('common behavior', () => {
Expand Down Expand Up @@ -165,5 +204,21 @@ describe('PredictClaimButton', () => {

expect(mockOnPress).not.toHaveBeenCalled();
});

it('disables button when isLoading is true', () => {
// Arrange
const mockOnPress = jest.fn();
const props = createDefaultProps({
onPress: mockOnPress,
isLoading: true,
});

// Act
renderWithProvider(<PredictClaimButton {...props} />);
fireEvent.press(screen.getByTestId('claim-button'));

// Assert
expect(mockOnPress).not.toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,51 +1,102 @@
import React from 'react';
import { ActivityIndicator } from 'react-native';
import {
Button,
ButtonSize,
Text,
TextVariant,
TextColor,
Box,
} from '@metamask/design-system-react-native';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import { strings } from '../../../../../../locales/i18n';
import SensitiveText, {
SensitiveTextLength,
} from '../../../../../component-library/components/Texts/SensitiveText';
import { TextVariant as ComponentTextVariant } from '../../../../../component-library/components/Texts/Text/Text.types';
import ButtonHero from '../../../../../component-library/components-temp/Buttons/ButtonHero';
import { PredictClaimButtonProps } from './PredictActionButtons.types';

const LoadingContent = () => (
<Box twClassName="flex-row items-center gap-1">
<ActivityIndicator color="white" size="small" />
<Text variant={TextVariant.BodyMd} color={TextColor.PrimaryInverse}>
{strings('predict.claiming_text')}
</Text>
</Box>
);

const AmountLabel = ({
label,
isHidden,
}: {
label: string;
isHidden: boolean;
}) => {
if (isHidden) {
return (
<SensitiveText
variant={ComponentTextVariant.BodyMD}
color="white"
isHidden={isHidden}
length={SensitiveTextLength.Medium}
>
{label}
</SensitiveText>
);
}

return (
<Text variant={TextVariant.BodyMd} color={TextColor.PrimaryInverse}>
{label}
</Text>
);
};

const PredictClaimButton: React.FC<PredictClaimButtonProps> = ({
amount,
onPress,
disabled = false,
isLoading = false,
isHidden = false,
testID = 'predict-claim-button',
}) => {
const tw = useTailwind();

if (amount === undefined) {
return (
<Button
<ButtonHero

Check warning on line 67 in app/components/UI/Predict/components/PredictActionButtons/PredictClaimButton.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'ButtonHero' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZzTDe8ubUJJ94dztGgw&open=AZzTDe8ubUJJ94dztGgw&pullRequest=27133
size={ButtonSize.Lg}
onPress={onPress}
isDisabled={disabled}
isDisabled={disabled || isLoading}
testID={testID}
style={tw.style('w-full')}
>
{strings('predict.claim_winnings_text')}
</Button>
{isLoading ? (
<LoadingContent />
) : (
strings('predict.claim_winnings_text')
)}
</ButtonHero>

Check warning on line 79 in app/components/UI/Predict/components/PredictActionButtons/PredictClaimButton.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'ButtonHero' is deprecated.

See more on https://sonarcloud.io/project/issues?id=metamask-mobile&issues=AZzTDe8ubUJJ94dztGgx&open=AZzTDe8ubUJJ94dztGgx&pullRequest=27133
);
}

const amountLabel = strings('predict.claim_amount_text', {
amount: amount.toFixed(2),
});

return (
<ButtonHero
size={ButtonSize.Lg}
onPress={onPress}
isDisabled={disabled}
isDisabled={disabled || isLoading}
testID={testID}
style={tw.style('w-full')}
>
<Text variant={TextVariant.BodyMd} color={TextColor.PrimaryInverse}>
{strings('predict.claim_amount_text', {
amount: amount.toFixed(2),
})}
</Text>
{isLoading ? (
<LoadingContent />
) : (
<AmountLabel label={amountLabel} isHidden={isHidden} />
)}
</ButtonHero>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const PredictGameDetailsContent: React.FC<PredictGameDetailsContentProps> = ({
onClaimPress,
claimableAmount = 0,
isLoading = false,
isClaimPending = false,
}) => {
const tw = useTailwind();
const { colors } = useTheme();
Expand Down Expand Up @@ -133,6 +134,7 @@ const PredictGameDetailsContent: React.FC<PredictGameDetailsContentProps> = ({
onInfoPress={handleInfoPress}
claimableAmount={claimableAmount}
isLoading={isLoading}
isClaimPending={isClaimPending}
/>

{isVisible && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export interface PredictGameDetailsContentProps {
onClaimPress?: () => void;
claimableAmount?: number;
isLoading?: boolean;
isClaimPending?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const PredictGameDetailsFooter: React.FC<PredictGameDetailsFooterProps> = ({
onInfoPress,
claimableAmount = 0,
isLoading = false,
isClaimPending = false,
testID = 'predict-game-details-footer',
}) => {
const insets = useSafeAreaInsets();
Expand Down Expand Up @@ -95,6 +96,7 @@ const PredictGameDetailsFooter: React.FC<PredictGameDetailsFooterProps> = ({
onClaimPress={onClaimPress}
claimableAmount={claimableAmount}
isLoading={isLoading}
isClaimPending={isClaimPending}
testID={`${testID}-action-buttons`}
/>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface PredictGameDetailsFooterProps {
onInfoPress: () => void;
claimableAmount?: number;
isLoading?: boolean;
isClaimPending?: boolean;
testID?: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
BoxJustifyContent,
Text,
TextVariant,
ButtonSize as ButtonSizeHero,
} from '@metamask/design-system-react-native';
import { useTailwind } from '@metamask/design-system-twrnc-preset';
import {
Expand Down Expand Up @@ -51,8 +50,8 @@ import { selectPredictWonPositions } from '../../selectors/predictController';
import { PredictPosition } from '../../types';
import { PredictNavigationParamList } from '../../types/navigation';
import { formatPercentage, formatPrice } from '../../utils/format';
import ButtonHero from '../../../../../component-library/components-temp/Buttons/ButtonHero';
import Skeleton from '../../../../../component-library/components/Skeleton/Skeleton';
import PredictClaimButton from '../PredictActionButtons/PredictClaimButton';
import { PredictEventValues } from '../../constants/eventNames';
import { getEvmAccountFromSelectedAccountGroup } from '../../utils/accounts';

Expand All @@ -73,7 +72,7 @@ const PredictPositionsHeader = forwardRef<
>((props, ref) => {
const { onError } = props;
const privacyMode = useSelector(selectPrivacyMode);
const { claim } = usePredictClaim();
const { claim, isClaimPending } = usePredictClaim();
const navigation =
useNavigation<NavigationProp<PredictNavigationParamList>>();
const tw = useTailwind();
Expand Down Expand Up @@ -202,23 +201,13 @@ const PredictPositionsHeader = forwardRef<
return (
<Box twClassName="gap-4 pb-4 pt-2">
{hasClaimableAmount && (
<ButtonHero
size={ButtonSizeHero.Lg}
testID={PredictPositionsHeaderSelectorsIDs.CLAIM_BUTTON}
<PredictClaimButton
amount={totalClaimableAmount}
onPress={handleClaim}
style={tw.style('w-full')}
>
<SensitiveText
variant={ComponentTextVariant.BodyMD}
color="white"
isHidden={privacyMode}
length={SensitiveTextLength.Medium}
>
{strings('predict.claim_amount_text', {
amount: totalClaimableAmount.toFixed(2),
})}
</SensitiveText>
</ButtonHero>
isLoading={isClaimPending}
isHidden={privacyMode}
testID={PredictPositionsHeaderSelectorsIDs.CLAIM_BUTTON}
/>
)}

{(hasAvailableBalance ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ describe('PredictSportCardFooter', () => {

mockUsePredictClaim.mockReturnValue({
claim: mockClaim,
isClaimPending: false,
});

mockExecuteGuardedAction.mockImplementation((callback) => callback());
Expand Down
Loading
Loading