Skip to content

Commit dceef76

Browse files
committed
feat: added test coverage
1 parent 32187e4 commit dceef76

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

app/components/UI/Earn/selectors/featureFlags/index.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
selectMusdConversionBlockedCountries,
1515
parseBlockedCountriesEnv,
1616
selectMusdConversionMinAssetBalanceRequired,
17+
selectMusdTokenRegistrationChainIds,
18+
MUSD_TOKEN_REGISTRATION_CHAIN_IDS_FALLBACK,
1719
} from '.';
1820
import mockedEngine from '../../../../../core/__mocks__/MockedEngine';
1921
import type { Json } from '@metamask/utils';
@@ -2146,4 +2148,58 @@ describe('Earn Feature Flag Selectors', () => {
21462148
expect(result).toBe(0.01);
21472149
});
21482150
});
2151+
2152+
describe('selectMusdTokenRegistrationChainIds', () => {
2153+
it('returns chain IDs from remote flag when the flag is a non-empty array', () => {
2154+
const stateWithRemote = createStateWithRemoteFlags({
2155+
earnMusdTokenRegistrationChainIds: { chainIds: ['0x1', '0xe708'] },
2156+
});
2157+
2158+
const result = selectMusdTokenRegistrationChainIds(stateWithRemote);
2159+
2160+
expect(result).toEqual(['0x1', '0xe708']);
2161+
});
2162+
2163+
it('returns fallback chain IDs when remote flag is absent', () => {
2164+
const stateWithoutRemote = createStateWithRemoteFlags({});
2165+
2166+
const result = selectMusdTokenRegistrationChainIds(stateWithoutRemote);
2167+
2168+
expect(result).toEqual([...MUSD_TOKEN_REGISTRATION_CHAIN_IDS_FALLBACK]);
2169+
});
2170+
2171+
it('returns fallback chain IDs when remote flag chainIds is an empty array', () => {
2172+
const stateWithEmptyRemote = createStateWithRemoteFlags({
2173+
earnMusdTokenRegistrationChainIds: { chainIds: [] },
2174+
});
2175+
2176+
const result = selectMusdTokenRegistrationChainIds(stateWithEmptyRemote);
2177+
2178+
expect(result).toEqual([...MUSD_TOKEN_REGISTRATION_CHAIN_IDS_FALLBACK]);
2179+
});
2180+
2181+
it('returns fallback chain IDs when remote flag chainIds is not an array', () => {
2182+
const stateWithInvalidRemote = createStateWithRemoteFlags({
2183+
earnMusdTokenRegistrationChainIds: { chainIds: 'not-an-array' },
2184+
});
2185+
2186+
const result = selectMusdTokenRegistrationChainIds(
2187+
stateWithInvalidRemote,
2188+
);
2189+
2190+
expect(result).toEqual([...MUSD_TOKEN_REGISTRATION_CHAIN_IDS_FALLBACK]);
2191+
});
2192+
2193+
it('returns fallback chain IDs when remote flag is present but has no chainIds property', () => {
2194+
const stateWithMissingChainIds = createStateWithRemoteFlags({
2195+
earnMusdTokenRegistrationChainIds: {},
2196+
});
2197+
2198+
const result = selectMusdTokenRegistrationChainIds(
2199+
stateWithMissingChainIds,
2200+
);
2201+
2202+
expect(result).toEqual([...MUSD_TOKEN_REGISTRATION_CHAIN_IDS_FALLBACK]);
2203+
});
2204+
});
21492205
});

app/components/UI/Earn/selectors/featureFlags/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ export const MUSD_TOKEN_REGISTRATION_CHAIN_IDS_FALLBACK = [
367367
* Remote flag takes precedence over the local fallback.
368368
* If both are unavailable or invalid, defaults to mainnet and Linea.
369369
*/
370-
// TODO: Test me
371370
export const selectMusdTokenRegistrationChainIds = createSelector(
372371
selectRemoteFeatureFlags,
373372
(remoteFeatureFlags): string[] => {

app/components/UI/Earn/utils/musdConversionTransaction.test.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { parseStandardTokenTransactionData } from '../../../Views/confirmations/
1414
import { MUSD_TOKEN_ADDRESS_BY_CHAIN } from '../constants/musd';
1515
import {
1616
createMusdConversionTransaction,
17+
ensureMusdTokenRegistered,
1718
replaceMusdConversionTransactionForPayToken,
1819
} from './musdConversionTransaction';
1920

@@ -54,6 +55,11 @@ jest.mock('../constants/musd', () => ({
5455
* Mutable mapping so tests can override per-case.
5556
*/
5657
MUSD_TOKEN_ADDRESS_BY_CHAIN: {},
58+
MUSD_TOKEN: {
59+
symbol: 'mUSD',
60+
name: 'MetaMask USD',
61+
decimals: 6,
62+
},
5763
}));
5864

5965
type MockedFindNetworkClientIdByChainId = (chainId: Hex) => string | undefined;
@@ -91,6 +97,23 @@ interface MockedEngineContext {
9197
ApprovalController?: {
9298
rejectRequest: jest.Mock<void, [string, unknown]>;
9399
};
100+
TokensController?: {
101+
state: {
102+
allTokens: Record<string, Record<string, { address: string }[]>>;
103+
};
104+
addToken: jest.Mock<
105+
Promise<void>,
106+
[
107+
{
108+
address: string;
109+
decimals: number;
110+
name: string;
111+
symbol: string;
112+
networkClientId: string;
113+
},
114+
]
115+
>;
116+
};
94117
}
95118

96119
const mockedProviderErrors = providerErrors as jest.Mocked<
@@ -162,6 +185,19 @@ describe('musdConversionTransaction', () => {
162185

163186
const approvalControllerReject = jest.fn<void, [string, unknown]>();
164187

188+
const tokensControllerAddToken = jest.fn<
189+
Promise<void>,
190+
[
191+
{
192+
address: string;
193+
decimals: number;
194+
name: string;
195+
symbol: string;
196+
networkClientId: string;
197+
},
198+
]
199+
>();
200+
165201
beforeEach(() => {
166202
jest.clearAllMocks();
167203

@@ -182,6 +218,10 @@ describe('musdConversionTransaction', () => {
182218
ApprovalController: {
183219
rejectRequest: approvalControllerReject,
184220
},
221+
TokensController: {
222+
state: { allTokens: {} },
223+
addToken: tokensControllerAddToken,
224+
},
185225
};
186226

187227
(MUSD_TOKEN_ADDRESS_BY_CHAIN as Record<string, Hex>)['0x1'] =
@@ -721,4 +761,117 @@ describe('musdConversionTransaction', () => {
721761
expect(newTransactionId).toBeUndefined();
722762
});
723763
});
764+
765+
describe('ensureMusdTokenRegistered', () => {
766+
const MUSD_ADDRESS = '0xmusdAddress' as Hex;
767+
const CHAIN_ID = '0x1' as Hex;
768+
const NETWORK_CLIENT_ID = 'mainnet';
769+
770+
beforeEach(() => {
771+
(MUSD_TOKEN_ADDRESS_BY_CHAIN as Record<string, Hex>)[CHAIN_ID] =
772+
MUSD_ADDRESS;
773+
});
774+
775+
describe('when mUSD token address is not configured for the chain', () => {
776+
it('returns early without calling addToken', async () => {
777+
const unknownChainId = '0xdead' as Hex;
778+
779+
await ensureMusdTokenRegistered({
780+
chainId: unknownChainId,
781+
networkClientId: NETWORK_CLIENT_ID,
782+
});
783+
784+
expect(tokensControllerAddToken).not.toHaveBeenCalled();
785+
});
786+
});
787+
788+
describe('when mUSD token is already registered for the chain', () => {
789+
it('does not call addToken when the token exists for one account', async () => {
790+
mockedEngine.context.TokensController = {
791+
state: {
792+
allTokens: {
793+
[CHAIN_ID]: {
794+
'0xaccountAddress': [{ address: MUSD_ADDRESS }],
795+
},
796+
},
797+
},
798+
addToken: tokensControllerAddToken,
799+
};
800+
801+
await ensureMusdTokenRegistered({
802+
chainId: CHAIN_ID,
803+
networkClientId: NETWORK_CLIENT_ID,
804+
});
805+
806+
expect(tokensControllerAddToken).not.toHaveBeenCalled();
807+
});
808+
});
809+
810+
describe('when mUSD token is not yet registered', () => {
811+
it('calls addToken with the correct token metadata and networkClientId', async () => {
812+
mockedEngine.context.TokensController = {
813+
state: { allTokens: {} },
814+
addToken: tokensControllerAddToken,
815+
};
816+
tokensControllerAddToken.mockResolvedValue(undefined);
817+
818+
await ensureMusdTokenRegistered({
819+
chainId: CHAIN_ID,
820+
networkClientId: NETWORK_CLIENT_ID,
821+
});
822+
823+
expect(tokensControllerAddToken).toHaveBeenCalledTimes(1);
824+
expect(tokensControllerAddToken).toHaveBeenCalledWith({
825+
address: MUSD_ADDRESS,
826+
decimals: 6,
827+
name: 'MetaMask USD',
828+
symbol: 'mUSD',
829+
networkClientId: NETWORK_CLIENT_ID,
830+
});
831+
});
832+
833+
it('calls addToken when the chain entry exists but no accounts hold mUSD', async () => {
834+
mockedEngine.context.TokensController = {
835+
state: {
836+
allTokens: {
837+
[CHAIN_ID]: {
838+
'0xaccountAddress': [{ address: '0xdifferentTokenAddress' }],
839+
},
840+
},
841+
},
842+
addToken: tokensControllerAddToken,
843+
};
844+
tokensControllerAddToken.mockResolvedValue(undefined);
845+
846+
await ensureMusdTokenRegistered({
847+
chainId: CHAIN_ID,
848+
networkClientId: NETWORK_CLIENT_ID,
849+
});
850+
851+
expect(tokensControllerAddToken).toHaveBeenCalledTimes(1);
852+
expect(tokensControllerAddToken).toHaveBeenCalledWith({
853+
address: MUSD_ADDRESS,
854+
decimals: 6,
855+
name: 'MetaMask USD',
856+
symbol: 'mUSD',
857+
networkClientId: NETWORK_CLIENT_ID,
858+
});
859+
});
860+
861+
it('calls addToken when allTokens has no entry for the chain', async () => {
862+
mockedEngine.context.TokensController = {
863+
state: { allTokens: { '0xe708': {} } },
864+
addToken: tokensControllerAddToken,
865+
};
866+
tokensControllerAddToken.mockResolvedValue(undefined);
867+
868+
await ensureMusdTokenRegistered({
869+
chainId: CHAIN_ID,
870+
networkClientId: NETWORK_CLIENT_ID,
871+
});
872+
873+
expect(tokensControllerAddToken).toHaveBeenCalledTimes(1);
874+
});
875+
});
876+
});
724877
});

0 commit comments

Comments
 (0)