Skip to content

Commit

Permalink
feat: updated component to support stake-sdk v0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt561 committed Jan 15, 2025
1 parent 502a4a6 commit e9f2204
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import renderWithProvider from '../../../../../util/test/renderWithProvider';
import { MOCK_POOL_STAKING_SDK } from '../../__mocks__/mockData';
import { Metrics, SafeAreaProvider } from 'react-native-safe-area-context';
import { screen } from '@testing-library/react-native';
import { MOCK_VAULT_APRS, MOCK_VAULT_DAILY_APYS } from './mockVaultRewards';
import {
MOCK_VAULT_APY_AVERAGES,
MOCK_VAULT_DAILY_APYS,
} from './mockVaultRewards';
import { fireLayoutEvent } from './InteractiveTimespanChart/InteractiveTimespanChart.testUtils';

jest.mock('@react-navigation/native', () => {
Expand All @@ -22,12 +25,12 @@ jest.mock('../../hooks/useStakeContext', () => ({
useStakeContext: jest.fn(() => MOCK_POOL_STAKING_SDK),
}));

jest.mock('../../hooks/useVaultAprs', () => ({
jest.mock('../../hooks/useVaultApyAverages', () => ({
__esModule: true,
default: () => ({
vaultAprs: MOCK_VAULT_APRS,
isLoadingVaultAprs: false,
refreshVaultAprs: jest.fn(),
vaultApyAverages: MOCK_VAULT_APY_AVERAGES,
isLoadingVaultApyAverages: false,
refreshVaultApyAverages: jest.fn(),
}),
}));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import { MOCK_VAULT_APRS } from './mockVaultRewards';
import { parseVaultTimespanAprsResponse } from './PoolStakingLearnMoreModal.utils';
import { strings } from '../../../../../../locales/i18n';
import { MOCK_VAULT_APY_AVERAGES } from './mockVaultRewards';
import { parseVaultApyAveragesResponse } from './PoolStakingLearnMoreModal.utils';

describe('PoolStakingLearnMoreModal Utils', () => {
describe('parseVaultTimespanAprsResponse', () => {
it('parses the VaultTimespanAprsResponse', () => {
const result = parseVaultTimespanAprsResponse(MOCK_VAULT_APRS);
describe('parseVaultApyAveragesResponse', () => {
it('parses the VaultApyAverageResponse', () => {
const result = parseVaultApyAveragesResponse(MOCK_VAULT_APY_AVERAGES);

expect(result).toEqual({
'1': { apr: '3.047713358665092375', numDays: 1, label: 'Today' },
'1': {
apyAverage: '3.047713358665092375',
numDays: 1,
label: strings('stake.today'),
},
'7': {
apr: '3.25756026351317301786',
apyAverage: '3.25756026351317301786',
numDays: 7,
label: '1 week average',
label: strings('stake.one_week_average'),
},
'30': {
apr: '3.25616054301749304217',
apyAverage: '3.25616054301749304217',
numDays: 30,
label: '1 month average',
label: strings('stake.one_month_average'),
},
'90': {
apr: '3.31863306662107446672',
apyAverage: '3.31863306662107446672',
numDays: 90,
label: '3 month average',
label: strings('stake.three_month_average'),
},
'180': {
apr: '3.05557344496273894133',
apyAverage: '3.05557344496273894133',
numDays: 180,
label: '6 month average',
label: strings('stake.six_month_average'),
},
'365': {
apyAverage: '0',
numDays: 365,
label: strings('stake.one_year_average'),
},
'365': { apr: '0', numDays: 365, label: '1 year average' },
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { VaultAprs } from '@metamask/stake-sdk';
import { VaultApyAverages } from '@metamask/stake-sdk';
import { strings } from '../../../../../../locales/i18n';

export const parseVaultTimespanAprsResponse = (
vaultTimespanAprs: VaultAprs,
export const parseVaultApyAveragesResponse = (
vaultTimespanAprs: VaultApyAverages,
) => {
const numDaysMap: Record<
keyof VaultAprs,
keyof VaultApyAverages,
{ numDays: number; label: string }
> = {
oneDay: { numDays: 1, label: strings('stake.today') },
Expand All @@ -17,10 +17,10 @@ export const parseVaultTimespanAprsResponse = (
};

return Object.entries(vaultTimespanAprs).reduce<
Record<number, { apr: string; numDays: number; label: string }>
Record<number, { apyAverage: string; numDays: number; label: string }>
>((map, [key, value]) => {
const numDaysMapEntry = numDaysMap[key as keyof typeof numDaysMap];
map[numDaysMapEntry.numDays] = { apr: value, ...numDaysMapEntry };
map[numDaysMapEntry.numDays] = { apyAverage: value, ...numDaysMapEntry };
return map;
}, {});
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ import {
formatChartDate,
getGraphInsetsByDataPointLength,
} from './InteractiveTimespanChart/InteractiveTimespanChart.utils';
import useVaultAprs from '../../hooks/useVaultAprs';
import { strings } from '../../../../../../locales/i18n';
import { parseVaultTimespanAprsResponse } from './PoolStakingLearnMoreModal.utils';
import { parseVaultApyAveragesResponse } from './PoolStakingLearnMoreModal.utils';
import { EVENT_LOCATIONS, EVENT_PROVIDERS } from '../../constants/events';
import useVaultApyAverages from '../../hooks/useVaultApyAverages';

// TODO: Make sure heading is aligned on Android devices.
const BodyText = () => {
const { styles } = useStyles(styleSheet, {});

Expand Down Expand Up @@ -81,25 +80,30 @@ const PoolStakingLearnMoreModal = () => {

const { vaultApys, isLoadingVaultApys, refreshVaultApys } = useVaultApys();

const { vaultAprs, isLoadingVaultAprs, refreshVaultAprs } = useVaultAprs();
const {
vaultApyAverages,
isLoadingVaultApyAverages,
refreshVaultApyAverages,
} = useVaultApyAverages();

// Converts VaultTimespanAprs for use with interactive graph timespan buttons.
const parsedVaultTimespanAprs = useMemo(() => {
if (isLoadingVaultAprs) return;
return parseVaultTimespanAprsResponse(vaultAprs);
}, [isLoadingVaultAprs, vaultAprs]);
// Converts VaultApyAverage for use with interactive graph timespan buttons.
const parsedVaultTimespanApyAverages = useMemo(() => {
if (isLoadingVaultApyAverages) return;
return parseVaultApyAveragesResponse(vaultApyAverages);
}, [isLoadingVaultApyAverages, vaultApyAverages]);

const [activeTimespanApr, setActiveTimespanApr] = useState(
parsedVaultTimespanAprs?.[7],
const [activeTimespanApyAverage, setActiveTimespanApyAverage] = useState(
parsedVaultTimespanApyAverages?.[7],
);

useEffect(() => {
async function refreshGraphData() {
Promise.all([refreshVaultAprs(), refreshVaultApys()]).catch((err) =>
console.error(
'Failed to refresh Pool-Staking Learn More Modal Data: ',
err,
),
await Promise.all([refreshVaultApyAverages(), refreshVaultApys()]).catch(
(err) =>
console.error(
'Failed to refresh Pool-Staking Learn More Modal Data: ',
err,
),
);
setDidFetchGraphData(true);
}
Expand All @@ -108,7 +112,7 @@ const PoolStakingLearnMoreModal = () => {
if (!didFetchGraphData) {
refreshGraphData();
}
}, [didFetchGraphData, refreshVaultAprs, refreshVaultApys]);
}, [didFetchGraphData, refreshVaultApyAverages, refreshVaultApys]);

const handleClose = () => {
sheetRef.current?.onCloseBottomSheet();
Expand Down Expand Up @@ -151,7 +155,9 @@ const PoolStakingLearnMoreModal = () => {
];

const handleTimespanPressed = (numDataPointsToDisplay: number) => {
setActiveTimespanApr(parsedVaultTimespanAprs?.[numDataPointsToDisplay]);
setActiveTimespanApyAverage(
parsedVaultTimespanApyAverages?.[numDataPointsToDisplay],
);
};

return (
Expand All @@ -162,15 +168,14 @@ const PoolStakingLearnMoreModal = () => {
{strings('stake.stake_eth_and_earn')}
</Text>
</BottomSheetHeader>
{Boolean(vaultApys.length) && activeTimespanApr && (
{Boolean(vaultApys.length) && activeTimespanApyAverage && (
<InteractiveTimespanChart
dataPoints={vaultApys}
yAccessor={(point) => new BigNumber(point.daily_apy).toNumber()}
defaultTitle={`${new BigNumber(activeTimespanApr.apr).toFixed(
2,
BigNumber.ROUND_DOWN,
)}% ${strings('stake.apr')}`}
defaultSubtitle={activeTimespanApr.label}
defaultTitle={`${new BigNumber(
activeTimespanApyAverage.apyAverage,
).toFixed(2, BigNumber.ROUND_DOWN)}% ${strings('stake.apr')}`}
defaultSubtitle={activeTimespanApyAverage.label}
titleAccessor={(point) =>
`${new BigNumber(point.daily_apy).toFixed(
2,
Expand All @@ -180,9 +185,11 @@ const PoolStakingLearnMoreModal = () => {
subtitleAccessor={(point) => formatChartDate(point.timestamp)}
onTimespanPressed={handleTimespanPressed}
graphOptions={{
...getGraphInsetsByDataPointLength(activeTimespanApr.numDays),
...getGraphInsetsByDataPointLength(
activeTimespanApyAverage.numDays,
),
}}
isLoading={isLoadingVaultAprs || isLoadingVaultApys}
isLoading={isLoadingVaultApyAverages || isLoadingVaultApys}
/>
)}
<BodyText />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1962,7 +1962,7 @@ export const MOCK_VAULT_APYS_SIX_MONTHS = MOCK_VAULT_DAILY_APYS.slice(-90);

export const MOCK_VAULT_APYS_ONE_YEAR = MOCK_VAULT_DAILY_APYS.slice(-365);

export const MOCK_VAULT_APRS = {
export const MOCK_VAULT_APY_AVERAGES = {
oneDay: '3.047713358665092375',
oneWeek: '3.25756026351317301786',
oneMonth: '3.25616054301749304217',
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/Stake/hooks/useStakingEligibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const useStakingEligibility = () => {
dispatch(setStakingEligibility(eligible));
return { isEligible: eligible };
} catch (err) {
console.error(err);
setError('Failed to fetch pooled staking eligibility');
return { isEligible: false };
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { renderHookWithProvider } from '../../../../util/test/renderWithProvider';
import { MOCK_VAULT_APRS } from '../components/PoolStakingLearnMoreModal/mockVaultRewards';
import { MOCK_VAULT_APY_AVERAGES } from '../components/PoolStakingLearnMoreModal/mockVaultRewards';
import { act, waitFor } from '@testing-library/react-native';
import { backgroundState } from '../../../../util/test/initial-root-state';
import { StakingApiService } from '@metamask/stake-sdk';
import { Stake } from '../sdk/stakeSdkProvider';
import useVaultAprs from './useVaultAprs';
import useVaultApyAverages from './useVaultApyAverages';

const mockInitialState = {
settings: {},
Expand All @@ -16,7 +16,7 @@ const mockInitialState = {
};

const mockStakingApiService: Partial<StakingApiService> = {
getVaultAprs: jest.fn(),
getVaultApyAverages: jest.fn(),
};

const mockSdkContext: Stake = {
Expand All @@ -28,65 +28,71 @@ jest.mock('./useStakeContext', () => ({
useStakeContext: () => mockSdkContext as Stake,
}));

describe('useVaultAprs', () => {
describe('useVaultApyAverages', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('when fetching vaultAprs', () => {
it('fetches vaultAprs and updates state', async () => {
(mockStakingApiService.getVaultAprs as jest.Mock).mockResolvedValue(
MOCK_VAULT_APRS,
);
describe('when fetching vaultApyAverages', () => {
it('fetches vaultApyAverages and updates state', async () => {
(
mockStakingApiService.getVaultApyAverages as jest.Mock
).mockResolvedValue(MOCK_VAULT_APY_AVERAGES);

const { result } = renderHookWithProvider(() => useVaultAprs(), {
const { result } = renderHookWithProvider(() => useVaultApyAverages(), {
state: mockInitialState,
});

await waitFor(() => {
expect(result.current.vaultAprs).toStrictEqual(MOCK_VAULT_APRS);
expect(result.current.isLoadingVaultAprs).toBe(false);
expect(result.current.vaultApyAverages).toStrictEqual(
MOCK_VAULT_APY_AVERAGES,
);
expect(result.current.isLoadingVaultApyAverages).toBe(false);
expect(result.current.error).toBeNull();
});
});

it('handles error if API request fails', async () => {
(mockStakingApiService.getVaultAprs as jest.Mock).mockRejectedValue(
new Error('API Error'),
);
(
mockStakingApiService.getVaultApyAverages as jest.Mock
).mockRejectedValue(new Error('API Error'));

const { result } = renderHookWithProvider(() => useVaultAprs(), {
const { result } = renderHookWithProvider(() => useVaultApyAverages(), {
state: mockInitialState,
});

await waitFor(async () => {
expect(result.current.isLoadingVaultAprs).toBe(false);
expect(result.current.error).toBe('Failed to fetch vault APRs');
expect(result.current.vaultAprs).toStrictEqual({});
expect(result.current.isLoadingVaultApyAverages).toBe(false);
expect(result.current.error).toBe('Failed to fetch vault APY averages');
expect(result.current.vaultApyAverages).toStrictEqual({});
});
});
});

describe('when refreshing vault APRs', () => {
it('refreshes vault APRs', async () => {
(mockStakingApiService.getVaultAprs as jest.Mock).mockResolvedValue(
MOCK_VAULT_APRS,
);
describe('when refreshing vault APY averages', () => {
it('refreshes vault APY averages', async () => {
(
mockStakingApiService.getVaultApyAverages as jest.Mock
).mockResolvedValue(MOCK_VAULT_APY_AVERAGES);

const { result } = renderHookWithProvider(() => useVaultAprs(), {
const { result } = renderHookWithProvider(() => useVaultApyAverages(), {
state: mockInitialState,
});

await waitFor(async () => {
expect(result.current.vaultAprs).toStrictEqual(MOCK_VAULT_APRS);
expect(result.current.vaultApyAverages).toStrictEqual(
MOCK_VAULT_APY_AVERAGES,
);
});

await act(async () => {
result.current.refreshVaultAprs();
result.current.refreshVaultApyAverages();
});

await waitFor(() => {
expect(mockStakingApiService.getVaultAprs).toHaveBeenCalledTimes(2);
expect(mockStakingApiService.getVaultApyAverages).toHaveBeenCalledTimes(
2,
);
});
});
});
Expand Down
Loading

0 comments on commit e9f2204

Please sign in to comment.