Skip to content

Commit 1b9ae1b

Browse files
committed
chore: slider with price variation
1 parent 1b09e30 commit 1b9ae1b

9 files changed

Lines changed: 222 additions & 91 deletions

app/components/UI/WhatsHappening/WhatsHappeningSection.tsx

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import ErrorState from '../../Views/Homepage/components/ErrorState';
2020
import ViewMoreCard from '../../Views/Homepage/components/ViewMoreCard';
2121
import { SectionRefreshHandle } from '../../Views/Homepage/types';
2222
import { selectWhatsHappeningEnabled } from '../../../selectors/featureFlagController/whatsHappening';
23+
import { PerpsStreamProvider } from '../Perps/providers/PerpsStreamManager';
2324
import { strings } from '../../../../locales/i18n';
2425
import Routes from '../../../constants/navigation/Routes';
2526
import {
@@ -153,36 +154,38 @@ const WhatsHappeningSection = forwardRef<
153154
onPress={handleViewAll}
154155
testID={WhatsHappeningSelectorsIDs.SECTION_TITLE}
155156
/>
156-
<ScrollView
157-
horizontal
158-
showsHorizontalScrollIndicator={false}
159-
contentContainerStyle={tw.style('px-4 gap-3')}
160-
snapToOffsets={SNAP_OFFSETS}
161-
decelerationRate="fast"
162-
onMomentumScrollEnd={handleMomentumScrollEnd}
163-
testID={WhatsHappeningSelectorsIDs.CAROUSEL}
164-
>
165-
{isLoading ? (
166-
SKELETON_KEYS.map((key) => <WhatsHappeningCardSkeleton key={key} />)
167-
) : (
168-
<>
169-
{items.map((item: WhatsHappeningItem, index: number) => (
170-
<WhatsHappeningCard
171-
key={item.id}
172-
item={item}
173-
cardIndex={index}
174-
source={source}
175-
onPress={() => handleCardPress(index)}
157+
<PerpsStreamProvider>
158+
<ScrollView
159+
horizontal
160+
showsHorizontalScrollIndicator={false}
161+
contentContainerStyle={tw.style('px-4 gap-3')}
162+
snapToOffsets={SNAP_OFFSETS}
163+
decelerationRate="fast"
164+
onMomentumScrollEnd={handleMomentumScrollEnd}
165+
testID={WhatsHappeningSelectorsIDs.CAROUSEL}
166+
>
167+
{isLoading ? (
168+
SKELETON_KEYS.map((key) => <WhatsHappeningCardSkeleton key={key} />)
169+
) : (
170+
<>
171+
{items.map((item: WhatsHappeningItem, index: number) => (
172+
<WhatsHappeningCard
173+
key={item.id}
174+
item={item}
175+
cardIndex={index}
176+
source={source}
177+
onPress={() => handleCardPress(index)}
178+
/>
179+
))}
180+
<ViewMoreCard
181+
onPress={handleViewAll}
182+
twClassName={`w-[180px] ${VIEW_MORE_MIN_HEIGHT_CLASS}`}
183+
textVariant={TextVariant.BodyLg}
176184
/>
177-
))}
178-
<ViewMoreCard
179-
onPress={handleViewAll}
180-
twClassName={`w-[180px] ${VIEW_MORE_MIN_HEIGHT_CLASS}`}
181-
textVariant={TextVariant.BodyLg}
182-
/>
183-
</>
184-
)}
185-
</ScrollView>
185+
</>
186+
)}
187+
</ScrollView>
188+
</PerpsStreamProvider>
186189
</View>
187190
);
188191
});

app/components/UI/WhatsHappening/components/WhatsHappeningAssetPill.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,39 @@ describe('WhatsHappeningAssetPill', () => {
7575
expect(screen.queryByLabelText('BTC')).toBeNull();
7676
expect(screen.getByText('BTC')).toBeOnTheScreen();
7777
});
78+
79+
it('shows positive percent change when perpsPriceEntry has a positive value', () => {
80+
renderWithProvider(
81+
<WhatsHappeningAssetPill
82+
asset={baseAsset}
83+
perpsPriceEntry={{ price: 95000, percentChange24h: 1.23 }}
84+
/>,
85+
);
86+
expect(screen.getByText('+1.23%')).toBeOnTheScreen();
87+
});
88+
89+
it('shows negative percent change when perpsPriceEntry has a negative value', () => {
90+
renderWithProvider(
91+
<WhatsHappeningAssetPill
92+
asset={baseAsset}
93+
perpsPriceEntry={{ price: 95000, percentChange24h: -2.5 }}
94+
/>,
95+
);
96+
expect(screen.getByText('-2.50%')).toBeOnTheScreen();
97+
});
98+
99+
it('does not render change text when perpsPriceEntry is undefined', () => {
100+
renderWithProvider(<WhatsHappeningAssetPill asset={baseAsset} />);
101+
expect(screen.queryByText(/%/)).toBeNull();
102+
});
103+
104+
it('does not render change text when percentChange24h is undefined', () => {
105+
renderWithProvider(
106+
<WhatsHappeningAssetPill
107+
asset={baseAsset}
108+
perpsPriceEntry={{ price: undefined, percentChange24h: undefined }}
109+
/>,
110+
);
111+
expect(screen.queryByText(/%/)).toBeNull();
112+
});
78113
});

app/components/UI/WhatsHappening/components/WhatsHappeningAssetPill.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ import { getPerpsDisplaySymbol } from '@metamask/perps-controller';
1616
import RelatedAssetAvatar from '../../../Views/WhatsHappeningDetailView/components/RelatedAssetAvatar';
1717
import { getRelatedAssetImageSource } from '../../../Views/WhatsHappeningDetailView/utils/getRelatedAssetImageSource';
1818
import useTradeNavigation from '../../../Views/WhatsHappeningDetailView/hooks/useTradeNavigation';
19+
import { formatPercentageChange } from '../../../Views/WhatsHappeningDetailView/utils/formatAssetPrice';
20+
import type { PerpsPriceEntry } from '../../../Views/WhatsHappeningDetailView/hooks/useWhatsHappeningAssetPrices';
1921

2022
const AVATAR_SIZE = 16;
2123

2224
export interface WhatsHappeningAssetPillProps {
2325
asset: RelatedAsset;
26+
perpsPriceEntry?: PerpsPriceEntry;
2427
}
2528

2629
const WhatsHappeningAssetPill: React.FC<WhatsHappeningAssetPillProps> = ({
2730
asset,
31+
perpsPriceEntry,
2832
}) => {
2933
const tw = useTailwind();
3034
const { handleTrade, canTrade } = useTradeNavigation(asset);
@@ -33,6 +37,10 @@ const WhatsHappeningAssetPill: React.FC<WhatsHappeningAssetPillProps> = ({
3337
() => getPerpsDisplaySymbol(asset.symbol),
3438
[asset.symbol],
3539
);
40+
const { text: changeText, color: changeColor } = useMemo(
41+
() => formatPercentageChange(perpsPriceEntry?.percentChange24h),
42+
[perpsPriceEntry?.percentChange24h],
43+
);
3644

3745
const inner = (
3846
<Box
@@ -53,6 +61,16 @@ const WhatsHappeningAssetPill: React.FC<WhatsHappeningAssetPillProps> = ({
5361
>
5462
{displaySymbol}
5563
</Text>
64+
{changeText ? (
65+
<Text
66+
variant={TextVariant.BodyXs}
67+
fontWeight={FontWeight.Medium}
68+
color={changeColor}
69+
numberOfLines={1}
70+
>
71+
{changeText}
72+
</Text>
73+
) : null}
5674
</Box>
5775
);
5876

app/components/UI/WhatsHappening/components/WhatsHappeningAssetSlider.test.tsx

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ import { screen } from '@testing-library/react-native';
33
import renderWithProvider from '../../../../util/test/renderWithProvider';
44
import WhatsHappeningAssetSlider from './WhatsHappeningAssetSlider';
55

6+
const mockUseWhatsHappeningAssetPrices = jest.fn();
7+
8+
jest.mock(
9+
'../../../Views/WhatsHappeningDetailView/hooks/useWhatsHappeningAssetPrices',
10+
() => ({
11+
useWhatsHappeningAssetPrices: (assets: unknown) =>
12+
mockUseWhatsHappeningAssetPrices(assets),
13+
}),
14+
);
15+
616
jest.mock(
717
'../../../Views/WhatsHappeningDetailView/hooks/useTradeNavigation',
818
() => ({
@@ -30,26 +40,88 @@ jest.mock('@metamask/perps-controller', () => ({
3040
getPerpsDisplaySymbol: (symbol: string) => symbol,
3141
}));
3242

43+
const btcAsset = {
44+
sourceAssetId: 'a1',
45+
symbol: 'BTC',
46+
name: 'Bitcoin',
47+
caip19: [],
48+
hlPerpsMarket: ['BTC'],
49+
};
50+
51+
const ethAsset = {
52+
sourceAssetId: 'a2',
53+
symbol: 'ETH',
54+
name: 'Ethereum',
55+
caip19: [],
56+
hlPerpsMarket: ['ETH'],
57+
};
58+
59+
const noPerpsAsset = {
60+
sourceAssetId: 'a3',
61+
symbol: 'FOO',
62+
name: 'Foo',
63+
caip19: ['eip155:1/erc20:0xfoo'],
64+
};
65+
3366
describe('WhatsHappeningAssetSlider', () => {
34-
it('renders a pill per asset', () => {
35-
const assets = [
36-
{
37-
sourceAssetId: 'a1',
38-
symbol: 'BTC',
39-
name: 'Bitcoin',
40-
caip19: [],
41-
hlPerpsMarket: ['BTC'],
42-
},
43-
{
44-
sourceAssetId: 'a2',
45-
symbol: 'ETH',
46-
name: 'Ethereum',
47-
caip19: [],
48-
hlPerpsMarket: ['ETH'],
49-
},
50-
];
51-
renderWithProvider(<WhatsHappeningAssetSlider assets={assets} />);
67+
beforeEach(() => {
68+
jest.clearAllMocks();
69+
mockUseWhatsHappeningAssetPrices.mockReturnValue({
70+
perpsPriceBySymbol: {},
71+
});
72+
});
73+
74+
it('renders a pill per perps asset', () => {
75+
renderWithProvider(
76+
<WhatsHappeningAssetSlider assets={[btcAsset, ethAsset]} />,
77+
);
5278
expect(screen.getByText('BTC')).toBeOnTheScreen();
5379
expect(screen.getByText('ETH')).toBeOnTheScreen();
5480
});
81+
82+
it('filters out non-perps assets', () => {
83+
renderWithProvider(
84+
<WhatsHappeningAssetSlider assets={[btcAsset, noPerpsAsset]} />,
85+
);
86+
expect(screen.getByText('BTC')).toBeOnTheScreen();
87+
expect(screen.queryByText('FOO')).toBeNull();
88+
});
89+
90+
it('calls useWhatsHappeningAssetPrices only with perps assets', () => {
91+
renderWithProvider(
92+
<WhatsHappeningAssetSlider assets={[btcAsset, noPerpsAsset]} />,
93+
);
94+
expect(mockUseWhatsHappeningAssetPrices).toHaveBeenCalledWith([btcAsset]);
95+
});
96+
97+
it('returns null when all assets are non-perps', () => {
98+
const { toJSON } = renderWithProvider(
99+
<WhatsHappeningAssetSlider assets={[noPerpsAsset]} />,
100+
);
101+
expect(toJSON()).toBeNull();
102+
});
103+
104+
it('shows the price change text when perpsPriceBySymbol has data', () => {
105+
mockUseWhatsHappeningAssetPrices.mockReturnValue({
106+
perpsPriceBySymbol: {
107+
BTC: { price: 95000, percentChange24h: 1.23 },
108+
},
109+
});
110+
renderWithProvider(<WhatsHappeningAssetSlider assets={[btcAsset]} />);
111+
expect(screen.getByText('+1.23%')).toBeOnTheScreen();
112+
});
113+
114+
it('shows negative change in red and hides change text when undefined', () => {
115+
mockUseWhatsHappeningAssetPrices.mockReturnValue({
116+
perpsPriceBySymbol: {
117+
BTC: { price: 95000, percentChange24h: -2.5 },
118+
ETH: { price: 3000, percentChange24h: undefined },
119+
},
120+
});
121+
renderWithProvider(
122+
<WhatsHappeningAssetSlider assets={[btcAsset, ethAsset]} />,
123+
);
124+
expect(screen.getByText('-2.50%')).toBeOnTheScreen();
125+
expect(screen.queryByText('undefined')).toBeNull();
126+
});
55127
});

app/components/UI/WhatsHappening/components/WhatsHappeningAssetSlider.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import React, { memo } from 'react';
1+
import React, { memo, useMemo } from 'react';
22
import { ScrollView } from 'react-native';
33
import { useTailwind } from '@metamask/design-system-twrnc-preset';
44
import type { RelatedAsset } from '@metamask/ai-controllers';
5+
import { useWhatsHappeningAssetPrices } from '../../../Views/WhatsHappeningDetailView/hooks/useWhatsHappeningAssetPrices';
56
import WhatsHappeningAssetPill from './WhatsHappeningAssetPill';
67

78
export interface WhatsHappeningAssetSliderProps {
@@ -13,7 +14,14 @@ const WhatsHappeningAssetSlider: React.FC<WhatsHappeningAssetSliderProps> = ({
1314
}) => {
1415
const tw = useTailwind();
1516

16-
if (assets.length === 0) {
17+
const perpsAssets = useMemo(
18+
() => assets.filter((a) => a.hlPerpsMarket?.[0]),
19+
[assets],
20+
);
21+
22+
const { perpsPriceBySymbol } = useWhatsHappeningAssetPrices(perpsAssets);
23+
24+
if (perpsAssets.length === 0) {
1725
return null;
1826
}
1927

@@ -24,8 +32,12 @@ const WhatsHappeningAssetSlider: React.FC<WhatsHappeningAssetSliderProps> = ({
2432
contentContainerStyle={tw.style('flex-row gap-2 mt-2')}
2533
nestedScrollEnabled
2634
>
27-
{assets.map((asset) => (
28-
<WhatsHappeningAssetPill key={asset.sourceAssetId} asset={asset} />
35+
{perpsAssets.map((asset) => (
36+
<WhatsHappeningAssetPill
37+
key={asset.sourceAssetId}
38+
asset={asset}
39+
perpsPriceEntry={perpsPriceBySymbol[asset.hlPerpsMarket?.[0] ?? '']}
40+
/>
2941
))}
3042
</ScrollView>
3143
);

app/components/UI/WhatsHappening/components/WhatsHappeningCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const WhatsHappeningCard: React.FC<WhatsHappeningCardProps> = ({
7070
'w-[280px] rounded-2xl bg-background-muted overflow-hidden p-4',
7171
)}
7272
>
73-
<Box gap={3}>
73+
<Box gap={3} twClassName="mb-2">
7474
{(item.impact || formattedDate) && (
7575
<Box
7676
flexDirection={BoxFlexDirection.Row}

app/components/Views/WhatsHappeningDetailView/components/WhatsHappeningExpandedCard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ const WhatsHappeningExpandedCard: React.FC<WhatsHappeningExpandedCardProps> = ({
8686
[item.date],
8787
);
8888

89-
const { perpsPriceBySymbol } = useWhatsHappeningAssetPrices(item);
89+
const { perpsPriceBySymbol } = useWhatsHappeningAssetPrices(
90+
item.relatedAssets,
91+
);
9092

9193
const scrollBottomFadeColors = useMemo((): string[] => {
9294
if (isDarkMode) {

0 commit comments

Comments
 (0)