Skip to content

Commit 68b4a40

Browse files
authored
Merge branch 'main' into rn-upgrade/0.81.5-no-unit-tests
2 parents 6b43d08 + a81c1ea commit 68b4a40

15 files changed

Lines changed: 482 additions & 109 deletions

File tree

app/components/UI/Money/Views/MoneyHomeView/MoneyHomeView.test.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { strings } from '../../../../../../locales/i18n';
2323
import MOCK_MONEY_TRANSACTIONS from '../../constants/mockActivityData';
2424
import useMoneyAccountBalance from '../../hooks/useMoneyAccountBalance';
2525
import { selectIsCardholder } from '../../../../../selectors/cardController';
26+
import { getDetectedGeolocation } from '../../../../../reducers/fiatOrders';
2627
import { moneyFormatFiat } from '../../utils/moneyFormatFiat';
2728

2829
const mockGoBack = jest.fn();
@@ -85,7 +86,13 @@ jest.mock('../../../../../selectors/cardController', () => ({
8586
selectIsCardholder: jest.fn(),
8687
}));
8788

89+
jest.mock('../../../../../reducers/fiatOrders', () => ({
90+
...jest.requireActual('../../../../../reducers/fiatOrders'),
91+
getDetectedGeolocation: jest.fn(),
92+
}));
93+
8894
const mockSelectIsCardholder = jest.mocked(selectIsCardholder);
95+
const mockGetDetectedGeolocation = jest.mocked(getDetectedGeolocation);
8996

9097
const mockUseMoneyAccountTransactions = jest.mocked(
9198
useMoneyAccountTransactions,
@@ -134,6 +141,7 @@ describe('MoneyHomeView', () => {
134141
global.alert = jest.fn();
135142

136143
mockSelectIsCardholder.mockReturnValue(false);
144+
mockGetDetectedGeolocation.mockReturnValue('US');
137145

138146
mockUseMoneyAccountBalance.mockReturnValue({
139147
totalFiatFormatted: '$3.00',
@@ -592,4 +600,82 @@ describe('MoneyHomeView', () => {
592600
});
593601
});
594602
});
603+
604+
describe('Metal card geolocation gating', () => {
605+
it('renders the Metal card row when geolocation is US', () => {
606+
mockGetDetectedGeolocation.mockReturnValue('US');
607+
608+
const { getByTestId } = renderWithProvider(<MoneyHomeView />);
609+
610+
expect(
611+
getByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
612+
).toBeOnTheScreen();
613+
expect(
614+
getByTestId(MoneyMetaMaskCardTestIds.VIRTUAL_CARD_ROW),
615+
).toBeOnTheScreen();
616+
});
617+
618+
it('renders the Metal card row when geolocation is a US sub-region (e.g. US-CA)', () => {
619+
mockGetDetectedGeolocation.mockReturnValue('us-ca');
620+
621+
const { getByTestId } = renderWithProvider(<MoneyHomeView />);
622+
623+
expect(
624+
getByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
625+
).toBeOnTheScreen();
626+
});
627+
628+
it('hides the Metal card row when geolocation is GB', () => {
629+
mockGetDetectedGeolocation.mockReturnValue('GB');
630+
631+
const { queryByTestId, getByTestId } = renderWithProvider(
632+
<MoneyHomeView />,
633+
);
634+
635+
expect(
636+
queryByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
637+
).not.toBeOnTheScreen();
638+
expect(
639+
getByTestId(MoneyMetaMaskCardTestIds.VIRTUAL_CARD_ROW),
640+
).toBeOnTheScreen();
641+
});
642+
643+
it('hides the Metal card row when geolocation is undefined (loading/unknown - fail closed)', () => {
644+
mockGetDetectedGeolocation.mockReturnValue(undefined);
645+
646+
const { queryByTestId, getByTestId } = renderWithProvider(
647+
<MoneyHomeView />,
648+
);
649+
650+
expect(
651+
queryByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
652+
).not.toBeOnTheScreen();
653+
expect(
654+
getByTestId(MoneyMetaMaskCardTestIds.VIRTUAL_CARD_ROW),
655+
).toBeOnTheScreen();
656+
});
657+
});
658+
659+
describe('Get now navigation', () => {
660+
it('navigates to the card sign-up flow when the virtual card Get now button is pressed', () => {
661+
mockGetDetectedGeolocation.mockReturnValue('GB');
662+
663+
const { getByText } = renderWithProvider(<MoneyHomeView />);
664+
665+
fireEvent.press(getByText(strings('money.metamask_card.get_now')));
666+
667+
expect(mockNavigate).toHaveBeenCalledWith(Routes.CARD.ROOT);
668+
});
669+
670+
it('navigates to the card sign-up flow when the metal card Get now button is pressed', () => {
671+
mockGetDetectedGeolocation.mockReturnValue('US');
672+
673+
const { getAllByText } = renderWithProvider(<MoneyHomeView />);
674+
const buttons = getAllByText(strings('money.metamask_card.get_now'));
675+
676+
fireEvent.press(buttons[1]);
677+
678+
expect(mockNavigate).toHaveBeenCalledWith(Routes.CARD.ROOT);
679+
});
680+
});
595681
});

app/components/UI/Money/Views/MoneyHomeView/MoneyHomeView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { TokenDetailsSource } from '../../../TokenDetails/constants/constants';
3636
import AppConstants from '../../../../../core/AppConstants';
3737
import NavigationService from '../../../../../core/NavigationService';
3838
import { selectIsCardholder } from '../../../../../selectors/cardController';
39+
import { getDetectedGeolocation } from '../../../../../reducers/fiatOrders';
3940
import Logger from '../../../../../util/Logger';
4041
import { AssetType } from '../../../../Views/confirmations/types/token';
4142
import { Hex } from '@metamask/utils';
@@ -73,6 +74,8 @@ const MoneyHomeView = () => {
7374
const { allTransactions, moneyAddress } = useMoneyAccountTransactions();
7475

7576
const isCardholder = useSelector(selectIsCardholder);
77+
const geolocation = useSelector(getDetectedGeolocation);
78+
const isUS = geolocation?.toUpperCase().split('-')[0] === 'US';
7679

7780
const homeState = getMoneyHomeState(allTransactions.length);
7881
const isMilestone = homeState === 'milestone' || homeState === 'filled';
@@ -296,6 +299,7 @@ const MoneyHomeView = () => {
296299
onHeaderPress={handleHeaderPress}
297300
onLinkPress={handleLinkCardPress}
298301
apy={apyPercent}
302+
showMetalCard={isUS}
299303
/>
300304
<Divider />
301305
{isMilestone && (

app/components/UI/Money/components/MoneyMetaMaskCard/MoneyMetaMaskCard.styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const styles = StyleSheet.create({
55
width: 104,
66
height: 66,
77
},
8+
linkCardImage: {
9+
width: 152,
10+
height: 96,
11+
},
812
});
913

1014
export default styles;

app/components/UI/Money/components/MoneyMetaMaskCard/MoneyMetaMaskCard.test.tsx

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ describe('MoneyMetaMaskCard', () => {
3232
).toBeOnTheScreen();
3333
});
3434

35-
it('renders metal card row', () => {
35+
it('renders metal card row when showMetalCard is true', () => {
3636
const { getByText, getByTestId } = render(
37-
<MoneyMetaMaskCard onGetNowPress={jest.fn()} />,
37+
<MoneyMetaMaskCard onGetNowPress={jest.fn()} showMetalCard />,
3838
);
3939

4040
expect(
@@ -48,14 +48,36 @@ describe('MoneyMetaMaskCard', () => {
4848
).toBeOnTheScreen();
4949
});
5050

51+
it('hides metal card row by default (showMetalCard not provided)', () => {
52+
const { queryByTestId, queryByText } = render(
53+
<MoneyMetaMaskCard onGetNowPress={jest.fn()} />,
54+
);
55+
56+
expect(
57+
queryByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
58+
).not.toBeOnTheScreen();
59+
expect(
60+
queryByText(strings('money.metamask_card.metal_card')),
61+
).not.toBeOnTheScreen();
62+
});
63+
64+
it('hides metal card row when showMetalCard is false', () => {
65+
const { queryByTestId } = render(
66+
<MoneyMetaMaskCard onGetNowPress={jest.fn()} showMetalCard={false} />,
67+
);
68+
69+
expect(
70+
queryByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
71+
).not.toBeOnTheScreen();
72+
});
73+
5174
it('calls onGetNowPress when virtual card Get now is pressed', () => {
5275
const mockGetNow = jest.fn();
53-
const { getAllByText } = render(
76+
const { getByText } = render(
5477
<MoneyMetaMaskCard onGetNowPress={mockGetNow} />,
5578
);
56-
const getNowButtons = getAllByText(strings('money.metamask_card.get_now'));
5779

58-
fireEvent.press(getNowButtons[0]);
80+
fireEvent.press(getByText(strings('money.metamask_card.get_now')));
5981

6082
expect(mockGetNow).toHaveBeenCalledTimes(1);
6183
expect(mockGetNow.mock.calls[0]).toEqual([]);
@@ -64,7 +86,7 @@ describe('MoneyMetaMaskCard', () => {
6486
it('calls onGetNowPress when metal card Get now is pressed', () => {
6587
const mockGetNow = jest.fn();
6688
const { getAllByText } = render(
67-
<MoneyMetaMaskCard onGetNowPress={mockGetNow} />,
89+
<MoneyMetaMaskCard onGetNowPress={mockGetNow} showMetalCard />,
6890
);
6991
const getNowButtons = getAllByText(strings('money.metamask_card.get_now'));
7092

@@ -174,9 +196,9 @@ describe('MoneyMetaMaskCard', () => {
174196
});
175197

176198
describe('upsell mode (default)', () => {
177-
it('renders virtual and metal card rows', () => {
199+
it('renders virtual and metal card rows when showMetalCard is true', () => {
178200
const { getByTestId } = render(
179-
<MoneyMetaMaskCard onGetNowPress={jest.fn()} />,
201+
<MoneyMetaMaskCard onGetNowPress={jest.fn()} showMetalCard />,
180202
);
181203

182204
expect(
@@ -187,6 +209,19 @@ describe('MoneyMetaMaskCard', () => {
187209
).toBeOnTheScreen();
188210
});
189211

212+
it('renders only the virtual card row when showMetalCard is false', () => {
213+
const { getByTestId, queryByTestId } = render(
214+
<MoneyMetaMaskCard onGetNowPress={jest.fn()} showMetalCard={false} />,
215+
);
216+
217+
expect(
218+
getByTestId(MoneyMetaMaskCardTestIds.VIRTUAL_CARD_ROW),
219+
).toBeOnTheScreen();
220+
expect(
221+
queryByTestId(MoneyMetaMaskCardTestIds.METAL_CARD_ROW),
222+
).not.toBeOnTheScreen();
223+
});
224+
190225
it('does not render link mode elements', () => {
191226
const { queryByTestId } = render(
192227
<MoneyMetaMaskCard onGetNowPress={jest.fn()} />,

app/components/UI/Money/components/MoneyMetaMaskCard/MoneyMetaMaskCard.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
IconColor,
1414
IconName,
1515
IconSize,
16+
Tag,
17+
TagSeverity,
1618
Text,
1719
TextColor,
1820
TextVariant,
@@ -34,6 +36,12 @@ interface MoneyMetaMaskCardProps {
3436
onLinkPress?: () => void;
3537
/** Current APY value displayed in the link mode bullet. */
3638
apy?: number;
39+
/**
40+
* Whether to render the Metal card row in upsell mode. Defaults to `false`
41+
* because the Metal card is currently only available to US users; the parent
42+
* is expected to pass the geolocation-derived flag.
43+
*/
44+
showMetalCard?: boolean;
3745
}
3846

3947
const CardRow = ({
@@ -66,15 +74,11 @@ const CardRow = ({
6674
<Text variant={TextVariant.BodyMd} fontWeight={FontWeight.Medium}>
6775
{cardName}
6876
</Text>
69-
<Text
70-
variant={TextVariant.BodySm}
71-
fontWeight={FontWeight.Regular}
72-
color={TextColor.SuccessDefault}
73-
>
77+
<Tag severity={TagSeverity.Success}>
7478
{strings('money.metamask_card.cashback', {
7579
percentage: cashbackPercentage,
7680
})}
77-
</Text>
81+
</Tag>
7882
</Box>
7983
</Box>
8084
<Button
@@ -127,12 +131,13 @@ const LinkContent = ({
127131
</Text>
128132
<Box
129133
flexDirection={BoxFlexDirection.Row}
134+
alignItems={BoxAlignItems.Center}
130135
twClassName="gap-4"
131136
testID={MoneyMetaMaskCardTestIds.LINK_CONTAINER}
132137
>
133138
<Image
134139
source={mmCardMetal}
135-
style={styles.cardImage}
140+
style={styles.linkCardImage}
136141
testID={MoneyMetaMaskCardTestIds.LINK_CARD_IMAGE}
137142
/>
138143
<Box twClassName="gap-2 flex-1 justify-center">
@@ -166,6 +171,7 @@ const MoneyMetaMaskCard = ({
166171
onHeaderPress,
167172
onLinkPress,
168173
apy,
174+
showMetalCard = false,
169175
}: MoneyMetaMaskCardProps) => {
170176
const handleLinkPress = useCallback(() => onLinkPress?.(), [onLinkPress]);
171177

@@ -200,13 +206,15 @@ const MoneyMetaMaskCard = ({
200206
onPress={onGetNowPress}
201207
testID={MoneyMetaMaskCardTestIds.VIRTUAL_CARD_ROW}
202208
/>
203-
<CardRow
204-
imageSource={mmCardMetal}
205-
cardName={strings('money.metamask_card.metal_card')}
206-
cashbackPercentage="3"
207-
onPress={onGetNowPress}
208-
testID={MoneyMetaMaskCardTestIds.METAL_CARD_ROW}
209-
/>
209+
{showMetalCard && (
210+
<CardRow
211+
imageSource={mmCardMetal}
212+
cardName={strings('money.metamask_card.metal_card')}
213+
cashbackPercentage="3"
214+
onPress={onGetNowPress}
215+
testID={MoneyMetaMaskCardTestIds.METAL_CARD_ROW}
216+
/>
217+
)}
210218
</>
211219
)}
212220
</Box>

app/core/Engine/Engine.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,4 +1368,17 @@ describe('Engine', () => {
13681368
expect(sortedControllersInState).toEqual(sortedExpectedControllers);
13691369
});
13701370
});
1371+
1372+
describe('resetState', () => {
1373+
it('calls MoneyAccountController.clearState', async () => {
1374+
const engine = Engine.init(TEST_ANALYTICS_ID, backgroundState);
1375+
const clearStateSpy = jest
1376+
.spyOn(engine.context.MoneyAccountController, 'clearState')
1377+
.mockImplementation(() => undefined);
1378+
1379+
await engine.resetState();
1380+
1381+
expect(clearStateSpy).toHaveBeenCalled();
1382+
});
1383+
});
13711384
});

app/core/Engine/Engine.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,7 @@ export class Engine {
11591159
SnapController,
11601160
///: END:ONLY_INCLUDE_IF
11611161
LoggingController,
1162+
MoneyAccountController,
11621163
} = this.context;
11631164

11641165
// Remove all permissions.
@@ -1189,6 +1190,9 @@ export class Engine {
11891190
}));
11901191

11911192
LoggingController.clear();
1193+
1194+
// Accounts:
1195+
MoneyAccountController.clearState();
11921196
};
11931197

11941198
removeAllListeners() {

0 commit comments

Comments
 (0)