Skip to content

Commit 08a7949

Browse files
authored
fix: hide amount in simulations for testnets if its opt out (#9918)
## **Description** This PR aims to hide testnet fiat values if user opt out in the settings. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25167?quickstart=1) ## **Related issues** - Fixes: #9906 ## **Manual testing steps** 1. Change current network to any testnet 2. Go to setting, turn on "Show conversion on test networks" 3. Try simple send - see fiat values displayed in simulations 4. Go to setting, turn off "Show conversion on test networks" 5. Try simple send - see no fiat values displayed in simulations 6. Change current network to mainnet or any other network which is not testnet 7. Try simple send - see fiat values displayed in simulations ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [X] I've completed the PR template to the best of my ability - [X] I’ve included tests if applicable - [X] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [X] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent 3864504 commit 08a7949

File tree

8 files changed

+214
-30
lines changed

8 files changed

+214
-30
lines changed
Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,102 @@
11
import React from 'react';
2-
import useFiatFormatter from './useFiatFormatter';
3-
2+
import { merge } from 'lodash';
43
import renderWithProvider from '../../../../util/test/renderWithProvider';
54
import initialBackgroundState from '../../../../util/test/initial-background-state.json';
6-
7-
import { IndividualFiatDisplay, TotalFiatDisplay } from './FiatDisplay';
85
import { FIAT_UNAVAILABLE } from '../types';
6+
import { IndividualFiatDisplay, TotalFiatDisplay } from './FiatDisplay';
7+
import useFiatFormatter from './useFiatFormatter';
8+
import { NETWORKS_CHAIN_ID } from '../../../../constants/network';
99

1010
jest.mock('./useFiatFormatter');
11-
(useFiatFormatter as jest.Mock).mockReturnValue((value: number) => `$${value}`);
1211

1312
const mockInitialState = {
1413
engine: {
1514
backgroundState: initialBackgroundState,
1615
},
1716
};
1817

19-
describe('IndividualFiatDisplay', () => {
20-
it.each([
21-
[FIAT_UNAVAILABLE, 'Not Available'],
22-
[100, '$100'],
23-
[-100, '$100'],
24-
])('when fiatAmount is %s it renders %s', (fiatAmount, expected) => {
25-
const { getByText } = renderWithProvider(
26-
<IndividualFiatDisplay fiatAmount={fiatAmount} />,
27-
{ state: mockInitialState },
28-
);
29-
expect(getByText(expected)).toBeDefined();
30-
});
18+
const mockStateWithTestnet = merge({}, mockInitialState, {
19+
engine: {
20+
backgroundState: {
21+
NetworkController: {
22+
providerConfig: {
23+
chainId: NETWORKS_CHAIN_ID.SEPOLIA,
24+
},
25+
},
26+
},
27+
},
28+
});
29+
30+
const mockStateWithShowingFiatOnTestnets = merge({}, mockStateWithTestnet, {
31+
engine: {
32+
backgroundState: {
33+
PreferencesController: {
34+
showFiatInTestnets: true,
35+
},
36+
},
37+
},
38+
});
39+
40+
const mockStateWithHidingFiatOnTestnets = merge({}, mockStateWithTestnet, {
41+
engine: {
42+
backgroundState: {
43+
PreferencesController: {
44+
showFiatInTestnets: false,
45+
},
46+
},
47+
},
3148
});
3249

33-
describe('TotalFiatDisplay', () => {
34-
it.each([
35-
[[FIAT_UNAVAILABLE, FIAT_UNAVAILABLE], 'Not Available'],
36-
[[], 'Not Available'],
37-
[[100, 200, FIAT_UNAVAILABLE, 300], 'Total = $600'],
38-
[[-100, -200, FIAT_UNAVAILABLE, -300], 'Total = $600'],
39-
])('when fiatAmounts is %s it renders %s', (fiatAmounts, expected) => {
40-
const { getByText } = renderWithProvider(
41-
<TotalFiatDisplay fiatAmounts={fiatAmounts} />,
42-
{ state: mockInitialState },
43-
);
44-
expect(getByText(expected)).toBeDefined();
50+
describe('FiatDisplay', () => {
51+
const mockUseFiatFormatter = jest.mocked(useFiatFormatter);
52+
53+
beforeEach(() => {
54+
jest.resetAllMocks();
55+
mockUseFiatFormatter.mockReturnValue((value: number) => `$${value}`);
56+
});
57+
58+
describe('IndividualFiatDisplay', () => {
59+
it.each([
60+
[FIAT_UNAVAILABLE, 'Not Available'],
61+
[100, '$100'],
62+
[-100, '$100'],
63+
])('when fiatAmount is %s it renders %s', (fiatAmount, expected) => {
64+
const { queryByText } = renderWithProvider(
65+
<IndividualFiatDisplay fiatAmount={fiatAmount} />,
66+
{ state: mockStateWithShowingFiatOnTestnets },
67+
);
68+
expect(queryByText(expected)).toBeDefined();
69+
});
70+
71+
it('does not render anything if hideFiatForTestnet is true', () => {
72+
const { queryByText } = renderWithProvider(
73+
<IndividualFiatDisplay fiatAmount={100} />,
74+
{ state: mockStateWithHidingFiatOnTestnets },
75+
);
76+
expect(queryByText('100')).toBe(null);
77+
});
78+
});
79+
80+
describe('TotalFiatDisplay', () => {
81+
it.each([
82+
[[FIAT_UNAVAILABLE, FIAT_UNAVAILABLE], 'Not Available'],
83+
[[], 'Not Available'],
84+
[[100, 200, FIAT_UNAVAILABLE, 300], 'Total = $600'],
85+
[[-100, -200, FIAT_UNAVAILABLE, -300], 'Total = $600'],
86+
])('when fiatAmounts is %s it renders %s', (fiatAmounts, expected) => {
87+
const { queryByText } = renderWithProvider(
88+
<TotalFiatDisplay fiatAmounts={fiatAmounts} />,
89+
{ state: mockStateWithShowingFiatOnTestnets },
90+
);
91+
expect(queryByText(expected)).toBeDefined();
92+
});
93+
94+
it('does not render anything if hideFiatForTestnet is true', () => {
95+
const { queryByText } = renderWithProvider(
96+
<TotalFiatDisplay fiatAmounts={[100, 200, 300]} />,
97+
{ state: mockStateWithHidingFiatOnTestnets },
98+
);
99+
expect(queryByText('600')).toBe(null);
100+
});
45101
});
46102
});

app/components/UI/SimulationDetails/FiatDisplay/FiatDisplay.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Text, {
99
import { strings } from '../../../../../locales/i18n';
1010
import useFiatFormatter from './useFiatFormatter';
1111
import { FIAT_UNAVAILABLE, FiatAmount } from '../types';
12+
import useHideFiatForTestnet from '../../../hooks/useHideFiatForTestnet';
1213

1314
const styleSheet = () =>
1415
StyleSheet.create({
@@ -48,9 +49,14 @@ export function calculateTotalFiat(fiatAmounts: FiatAmount[]): number {
4849
export const IndividualFiatDisplay: React.FC<{ fiatAmount: FiatAmount }> = ({
4950
fiatAmount,
5051
}) => {
52+
const hideFiatForTestnet = useHideFiatForTestnet();
5153
const { styles } = useStyles(styleSheet, {});
5254
const fiatFormatter = useFiatFormatter();
5355

56+
if (hideFiatForTestnet) {
57+
return null;
58+
}
59+
5460
if (fiatAmount === FIAT_UNAVAILABLE) {
5561
return <FiatNotAvailableDisplay />;
5662
}
@@ -72,10 +78,15 @@ export const IndividualFiatDisplay: React.FC<{ fiatAmount: FiatAmount }> = ({
7278
export const TotalFiatDisplay: React.FC<{
7379
fiatAmounts: FiatAmount[];
7480
}> = ({ fiatAmounts }) => {
81+
const hideFiatForTestnet = useHideFiatForTestnet();
7582
const { styles } = useStyles(styleSheet, {});
7683
const fiatFormatter = useFiatFormatter();
7784
const totalFiat = calculateTotalFiat(fiatAmounts);
7885

86+
if (hideFiatForTestnet) {
87+
return null;
88+
}
89+
7990
return totalFiat === 0 ? (
8091
<FiatNotAvailableDisplay />
8192
) : (

app/components/UI/SimulationDetails/SimulationDetails.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const ERC721_TOKEN_MOCK = '0x06012c8cf97bead5deae237070f9587f8e7a266d'; // Crypt
3232
const ERC1155_TOKEN_MOCK = '0x60e4d786628fea6478f785a6d7e704777c86a7c6'; // MAYC
3333

3434
const preloadedEngineState = {
35-
settings: { useBlockieIcon: false },
35+
settings: { useBlockieIcon: false, showFiatInTestnets: true },
3636
engine: {
3737
backgroundState: {
3838
PreferencesController: {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import { selectChainId } from '../../../selectors/networkController';
3+
import { TEST_NETWORK_IDS } from '../../../constants/network';
4+
import selectShowFiatInTestnets from '../../../selectors/settings';
5+
import useHideFiatForTestnet from './index';
6+
7+
jest.mock('react-redux', () => ({
8+
useSelector: jest.fn().mockImplementation((selector) => selector()),
9+
}));
10+
11+
jest.mock('../../../selectors/networkController', () => ({
12+
selectChainId: jest.fn(),
13+
}));
14+
15+
jest.mock('../../../selectors/settings', () => ({
16+
__esModule: true,
17+
default: jest.fn(),
18+
}));
19+
20+
describe('useHideFiatForTestnet', () => {
21+
const mockSelectShowFiatInTestnets = jest.mocked(selectShowFiatInTestnets);
22+
const mockSelectChainId = jest.mocked(selectChainId);
23+
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
28+
it('utilizes the specified chain id', () => {
29+
mockSelectShowFiatInTestnets.mockReturnValue(false);
30+
mockSelectChainId.mockReturnValue(TEST_NETWORK_IDS[0]);
31+
32+
const { result } = renderHook(() => useHideFiatForTestnet('0x1'));
33+
34+
expect(result.current).toBe(false);
35+
});
36+
37+
it('returns true if current network is a testnet and showFiatInTestnets is false', () => {
38+
mockSelectShowFiatInTestnets.mockReturnValue(false);
39+
mockSelectChainId.mockReturnValue(TEST_NETWORK_IDS[0]);
40+
41+
const { result } = renderHook(() => useHideFiatForTestnet());
42+
43+
expect(result.current).toBe(true);
44+
});
45+
46+
it('returns false if current network is a testnet and showFiatInTestnets is true', () => {
47+
mockSelectShowFiatInTestnets.mockReturnValue(true);
48+
mockSelectChainId.mockReturnValue(TEST_NETWORK_IDS[0]);
49+
50+
const { result } = renderHook(() => useHideFiatForTestnet());
51+
52+
expect(result.current).toBe(false);
53+
});
54+
55+
it('returns false if current network is not a testnet', () => {
56+
mockSelectShowFiatInTestnets.mockReturnValue(false);
57+
mockSelectChainId.mockReturnValue('0x1');
58+
59+
const { result } = renderHook(() => useHideFiatForTestnet());
60+
61+
expect(result.current).toBe(false);
62+
});
63+
64+
it('returns false if current network is not a testnet but showFiatInTestnets is true', () => {
65+
mockSelectShowFiatInTestnets.mockReturnValue(true);
66+
mockSelectChainId.mockReturnValue('0x1');
67+
68+
const { result } = renderHook(() => useHideFiatForTestnet());
69+
70+
expect(result.current).toBe(false);
71+
});
72+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useSelector } from 'react-redux';
2+
import type { Hex } from '@metamask/utils';
3+
import { selectChainId } from '../../../selectors/networkController';
4+
import selectShowFiatInTestnets from '../../../selectors/settings';
5+
import { TEST_NETWORK_IDS } from '../../../constants/network';
6+
7+
/**
8+
* Returns true if the fiat value should be hidden for testnet networks.
9+
*
10+
* @param providedChainId - Optional chainId to use for the check
11+
* @returns boolean
12+
*/
13+
export default function useHideFiatForTestnet(providedChainId?: Hex): boolean {
14+
const showFiatInTestnets = useSelector(selectShowFiatInTestnets);
15+
const selectedChainId = useSelector(selectChainId);
16+
const chainId = providedChainId ?? selectedChainId;
17+
return TEST_NETWORK_IDS.includes(chainId) && !showFiatInTestnets;
18+
}

app/constants/network.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,10 @@ export const CURRENCY_SYMBOL_BY_CHAIN_ID = {
8686
CHAINLIST_CURRENCY_SYMBOLS_MAP.LINEA_MAINNET,
8787
[NETWORKS_CHAIN_ID.ZKSYNC_ERA]: CHAINLIST_CURRENCY_SYMBOLS_MAP.ZKSYNC_ERA,
8888
};
89+
90+
export const TEST_NETWORK_IDS = [
91+
NETWORKS_CHAIN_ID.GOERLI,
92+
NETWORKS_CHAIN_ID.SEPOLIA,
93+
NETWORKS_CHAIN_ID.LINEA_GOERLI,
94+
NETWORKS_CHAIN_ID.LINEA_SEPOLIA,
95+
];

app/selectors/settings.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { RootState } from '../reducers';
2+
3+
import selectShowFiatInTestnets from './settings';
4+
5+
describe('selectShowFiatInTestnets', () => {
6+
it('returns showFiatOnTestnets from state', () => {
7+
const mockState = {
8+
settings: {
9+
showFiatOnTestnets: true,
10+
},
11+
};
12+
13+
expect(selectShowFiatInTestnets(mockState as RootState)).toBe(true);
14+
});
15+
});

app/selectors/settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { RootState } from '../reducers';
2+
3+
export default function (state: RootState) {
4+
return state.settings.showFiatOnTestnets;
5+
}

0 commit comments

Comments
 (0)