Skip to content

Commit e45d136

Browse files
committed
fix: refactor and update unit tests
1 parent 001152a commit e45d136

8 files changed

Lines changed: 69 additions & 17 deletions

File tree

app/components/UI/Predict/controllers/PredictController.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
PredictTradeStatus,
5757
PredictTradeStatusValue,
5858
} from '../constants/eventNames';
59+
import { findAddressKey } from '../utils/accounts';
5960
import { validateDepositTransactions } from '../utils/validateTransactions';
6061
import { PolymarketProvider } from '../providers/polymarket/PolymarketProvider';
6162
import { Signer } from '../providers/types';
@@ -1569,10 +1570,7 @@ export class PredictController extends BaseController<
15691570
private getClaimablePositionsByAddress(
15701571
address: string,
15711572
): { positions: PredictPosition[]; matchedKey: string } | undefined {
1572-
const normalizedAddress = address.toLowerCase();
1573-
const matchedKey = Object.keys(this.state.claimablePositions).find(
1574-
(key) => key.toLowerCase() === normalizedAddress,
1575-
);
1573+
const matchedKey = findAddressKey(this.state.claimablePositions, address);
15761574
if (!matchedKey) return undefined;
15771575
return { positions: this.state.claimablePositions[matchedKey], matchedKey };
15781576
}

app/components/UI/Predict/hooks/usePredictClaim.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ export const usePredictClaim = () => {
2121
const { toastRef } = useContext(ToastContext);
2222
const navigation = useNavigation();
2323

24-
const claim = useCallback(async (): Promise<{ wasCancelled: boolean }> => {
24+
const claim = useCallback(async (): Promise<{
25+
wasCancelled: boolean;
26+
succeeded: boolean;
27+
}> => {
2528
try {
2629
navigateToConfirmation({
2730
headerShown: false,
@@ -30,7 +33,10 @@ export const usePredictClaim = () => {
3033
stack: Routes.PREDICT.ROOT,
3134
});
3235
const result = await claimWinnings({});
33-
return { wasCancelled: result?.status === PredictClaimStatus.CANCELLED };
36+
return {
37+
wasCancelled: result?.status === PredictClaimStatus.CANCELLED,
38+
succeeded: result?.status === PredictClaimStatus.PENDING,
39+
};
3440
} catch (err) {
3541
// Log error with claim context
3642
Logger.error(ensureError(err), {
@@ -72,7 +78,7 @@ export const usePredictClaim = () => {
7278
},
7379
},
7480
});
75-
return { wasCancelled: false };
81+
return { wasCancelled: false, succeeded: false };
7682
}
7783
}, [
7884
claimWinnings,

app/components/UI/Predict/selectors/predictController/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createSelector } from 'reselect';
22
import { RootState } from '../../../../../reducers';
33
import { PredictPositionStatus } from '../../types';
4+
import { findAddressKey } from '../../utils/accounts';
45

56
const selectPredictControllerState = (state: RootState) =>
67
state.engine.backgroundState.PredictController;
@@ -26,10 +27,7 @@ const selectPredictClaimablePositionsByAddress = ({
2627
address: string;
2728
}) =>
2829
createSelector(selectPredictClaimablePositions, (claimablePositions) => {
29-
const normalizedAddress = address.toLowerCase();
30-
const matchedKey = Object.keys(claimablePositions).find(
31-
(key) => key.toLowerCase() === normalizedAddress,
32-
);
30+
const matchedKey = findAddressKey(claimablePositions, address);
3331
return matchedKey ? claimablePositions[matchedKey] : [];
3432
});
3533

app/components/UI/Predict/utils/accounts.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,41 @@ export const createMockEngineContext = () => ({
5858
const mockEngineContext = createMockEngineContext();
5959
jest.mock('../../../../core/Engine', () => ({ context: mockEngineContext }));
6060

61-
import { getEvmAccountFromSelectedAccountGroup } from './accounts';
61+
import {
62+
findAddressKey,
63+
getEvmAccountFromSelectedAccountGroup,
64+
} from './accounts';
6265
import Engine from '../../../../core/Engine';
6366

67+
describe('findAddressKey', () => {
68+
const CHECKSUMMED = '0xABCDEF1234567890ABCDeF1234567890ABCDEF12';
69+
const LOWERCASE = CHECKSUMMED.toLowerCase();
70+
71+
it('finds a key when the map uses checksummed casing and query is lowercase', () => {
72+
const map = { [CHECKSUMMED]: ['pos1'] };
73+
expect(findAddressKey(map, LOWERCASE)).toBe(CHECKSUMMED);
74+
});
75+
76+
it('finds a key when the map uses lowercase casing and query is checksummed', () => {
77+
const map = { [LOWERCASE]: ['pos1'] };
78+
expect(findAddressKey(map, CHECKSUMMED)).toBe(LOWERCASE);
79+
});
80+
81+
it('finds a key when both casings match exactly', () => {
82+
const map = { [CHECKSUMMED]: ['pos1'] };
83+
expect(findAddressKey(map, CHECKSUMMED)).toBe(CHECKSUMMED);
84+
});
85+
86+
it('returns undefined when address is not in the map', () => {
87+
const map = { '0x1111111111111111111111111111111111111111': [] };
88+
expect(findAddressKey(map, CHECKSUMMED)).toBeUndefined();
89+
});
90+
91+
it('returns undefined for an empty map', () => {
92+
expect(findAddressKey({}, CHECKSUMMED)).toBeUndefined();
93+
});
94+
});
95+
6496
describe('accountUtils', () => {
6597
beforeEach(() => {
6698
jest.clearAllMocks();

app/components/UI/Predict/utils/accounts.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
import { isEvmAccountType } from '@metamask/keyring-api';
66
import Engine from '../../../../core/Engine';
77

8+
/**
9+
* Finds the key in a map whose address matches the given address case-insensitively.
10+
* Polymarket and EVM tooling can return addresses in checksummed or lowercase form,
11+
* so direct key lookups may miss a valid entry.
12+
*
13+
* @param map - Object keyed by Ethereum addresses
14+
* @param address - Address to match (any casing)
15+
* @returns The matching key as stored in the map, or undefined if not found
16+
*/
17+
export const findAddressKey = (
18+
map: Record<string, unknown>,
19+
address: string,
20+
): string | undefined => {
21+
const normalized = address.toLowerCase();
22+
return Object.keys(map).find((key) => key.toLowerCase() === normalized);
23+
};
24+
825
/**
926
* Gets the EVM account from the selected account group
1027
* Extracts the duplicated pattern used throughout PerpsController

app/components/Views/Homepage/Sections/Predictions/PredictionsSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ const PredictionsSection = forwardRef<
120120
const handleClaim = useCallback(async () => {
121121
setIsClaiming(true);
122122
try {
123-
const { wasCancelled } = await claim();
124-
if (!wasCancelled) {
123+
const { succeeded } = await claim();
124+
if (succeeded) {
125125
setHasClaimed(true);
126126
}
127127
await refreshPositions();

app/components/Views/Homepage/Sections/Predictions/hooks/usePredictPositionsForHomepage.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ jest.mock('react-redux', () => ({
2323
useSelector: (selector: (...args: unknown[]) => unknown) => {
2424
if (
2525
selector ===
26-
jest.requireMock('../../../../../UI/Predict').selectPredictEnabledFlag
26+
jest.requireMock('../../../../../UI/Predict/selectors/featureFlags')
27+
.selectPredictEnabledFlag
2728
) {
2829
return mockIsPredictEnabled;
2930
}
@@ -38,7 +39,7 @@ jest.mock('react-redux', () => ({
3839
},
3940
}));
4041

41-
jest.mock('../../../../../UI/Predict', () => ({
42+
jest.mock('../../../../../UI/Predict/selectors/featureFlags', () => ({
4243
selectPredictEnabledFlag: jest.fn(),
4344
}));
4445

app/components/Views/Homepage/Sections/Predictions/hooks/usePredictPositionsForHomepage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
22
import { useSelector } from 'react-redux';
33
import Engine from '../../../../../../core/Engine';
44
import { selectSelectedInternalAccountFormattedAddress } from '../../../../../../selectors/accountsController';
5-
import { selectPredictEnabledFlag } from '../../../../../UI/Predict';
5+
import { selectPredictEnabledFlag } from '../../../../../UI/Predict/selectors/featureFlags';
66
import type { PredictPosition } from '../../../../../UI/Predict/types';
77

88
export interface UsePredictPositionsForHomepageResult {

0 commit comments

Comments
 (0)