Skip to content

Commit 1a02740

Browse files
authored
refactor(card): adapt card feature to use new bip-44 selectors (MetaMask#18362)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> This PR refactors the Card feature to use the new BIP-44 selectors, ensuring account and network handling are aligned with the updated account management logic. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.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-mobile/blob/main/.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 fd24251 commit 1a02740

25 files changed

Lines changed: 706 additions & 1249 deletions

app/components/UI/Bridge/components/DestinationAccountSelector.tsx/DestinationAccountSelector.test.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -155,25 +155,32 @@ jest.mock('../../../../../selectors/bridge', () => ({
155155
// Mock the account tree controller selectors
156156
jest.mock(
157157
'../../../../../selectors/multichainAccounts/accountTreeController',
158-
() => ({
159-
selectAccountGroups: (state: MockState) =>
160-
state.engine?.backgroundState?.AccountTreeController?.accountTree
161-
?.accountGroups
162-
? Object.values(
163-
state.engine.backgroundState.AccountTreeController.accountTree
164-
.accountGroups,
165-
)
166-
: [],
167-
selectSelectedAccountGroup: (state: MockState) => {
168-
const selectedId =
169-
state.engine?.backgroundState?.AccountTreeController?.accountTree
170-
?.selectedAccountGroupId;
171-
const accountGroups =
158+
() => {
159+
const actual = jest.requireActual(
160+
'../../../../../selectors/multichainAccounts/accountTreeController',
161+
);
162+
163+
return {
164+
...actual,
165+
selectAccountGroups: (state: MockState) =>
172166
state.engine?.backgroundState?.AccountTreeController?.accountTree
173-
?.accountGroups;
174-
return selectedId && accountGroups ? accountGroups[selectedId] : null;
175-
},
176-
}),
167+
?.accountGroups
168+
? Object.values(
169+
state.engine.backgroundState.AccountTreeController.accountTree
170+
.accountGroups,
171+
)
172+
: [],
173+
selectSelectedAccountGroup: (state: MockState) => {
174+
const selectedId =
175+
state.engine?.backgroundState?.AccountTreeController?.accountTree
176+
?.selectedAccountGroupId;
177+
const accountGroups =
178+
state.engine?.backgroundState?.AccountTreeController?.accountTree
179+
?.accountGroups;
180+
return selectedId && accountGroups ? accountGroups[selectedId] : null;
181+
},
182+
};
183+
},
177184
);
178185

179186
// Mock the feature flag selector

app/components/UI/Card/Views/CardHome/CardHome.test.tsx

Lines changed: 8 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
} from '../../../../../selectors/featureFlagController/deposit';
2020
import { selectChainId } from '../../../../../selectors/networkController';
2121
import { selectCardholderAccounts } from '../../../../../core/redux/slices/card';
22-
import { selectSelectedInternalAccount } from '../../../../../selectors/accountsController';
2322
const mockNavigate = jest.fn();
2423
const mockGoBack = jest.fn();
2524
const mockSetNavigationOptions = jest.fn();
@@ -52,6 +51,10 @@ const mockPriorityToken = {
5251

5352
const mockCurrentAddress = '0x789';
5453

54+
const mockSelectedInternalAccount = {
55+
address: mockCurrentAddress,
56+
};
57+
5558
// Mock hooks
5659
const mockFetchPriorityToken = jest.fn().mockResolvedValue(mockPriorityToken);
5760
const mockNavigateToCardPage = jest.fn();
@@ -355,27 +358,8 @@ describe('CardHome Component', () => {
355358
if (selector === selectCardholderAccounts) {
356359
return [mockCurrentAddress];
357360
}
358-
if (selector === selectSelectedInternalAccount) {
359-
return {
360-
address: mockCurrentAddress,
361-
id: 'account-id',
362-
type: 'eip155:eoa',
363-
options: {},
364-
metadata: {
365-
name: 'Test Account',
366-
importTime: Date.now(),
367-
keyring: { type: 'HD Key Tree' },
368-
},
369-
scopes: [],
370-
methods: [],
371-
};
372-
}
373-
if (
374-
selector
375-
.toString()
376-
.includes('selectSelectedInternalAccountFormattedAddress')
377-
) {
378-
return mockCurrentAddress;
361+
if (selector.toString().includes('selectSelectedInternalAccount')) {
362+
return mockSelectedInternalAccount;
379363
}
380364
if (selector.toString().includes('selectChainId')) {
381365
return '0xe708'; // Linea chain ID - fallback for string matching
@@ -422,27 +406,8 @@ describe('CardHome Component', () => {
422406
if (selector === selectCardholderAccounts) {
423407
return [mockCurrentAddress];
424408
}
425-
if (selector === selectSelectedInternalAccount) {
426-
return {
427-
address: mockCurrentAddress,
428-
id: 'account-id',
429-
type: 'eip155:eoa',
430-
options: {},
431-
metadata: {
432-
name: 'Test Account',
433-
importTime: Date.now(),
434-
keyring: { type: 'HD Key Tree' },
435-
},
436-
scopes: [],
437-
methods: [],
438-
};
439-
}
440-
if (
441-
selector
442-
.toString()
443-
.includes('selectSelectedInternalAccountFormattedAddress')
444-
) {
445-
return mockCurrentAddress;
409+
if (selector.toString().includes('selectSelectedInternalAccount')) {
410+
return mockSelectedInternalAccount;
446411
}
447412
if (selector.toString().includes('selectChainId')) {
448413
return '0xe708'; // Linea chain ID - fallback
@@ -547,7 +512,6 @@ describe('CardHome Component', () => {
547512
expect(mockTrackEvent).toHaveBeenCalled();
548513
expect(mockOpenSwaps).toHaveBeenCalledWith({
549514
chainId: '0xe708',
550-
cardholderAddress: mockCurrentAddress,
551515
});
552516
});
553517
});
@@ -719,156 +683,6 @@ describe('CardHome Component', () => {
719683
expect(navigationOptions.headerTitle).toBeDefined();
720684
});
721685

722-
it('switches to Linea network on focus if not already on Linea', async () => {
723-
// Override the mock to allow network switching for this test
724-
mockFindNetworkClientIdByChainId.mockReturnValue('linea-network-id');
725-
726-
// Mock being on a different chain initially
727-
mockUseSelector.mockImplementation((selector) => {
728-
if (selector === selectChainId) {
729-
return '0x1'; // Ethereum mainnet
730-
}
731-
if (selector === selectPrivacyMode) {
732-
return false;
733-
}
734-
if (selector === selectDepositActiveFlag) {
735-
return true;
736-
}
737-
if (selector === selectDepositMinimumVersionFlag) {
738-
return '0.9.0';
739-
}
740-
if (selector === selectCardholderAccounts) {
741-
return [mockCurrentAddress];
742-
}
743-
if (selector === selectSelectedInternalAccount) {
744-
return {
745-
address: mockCurrentAddress,
746-
id: 'account-id',
747-
type: 'eip155:eoa',
748-
options: {},
749-
metadata: {
750-
name: 'Test Account',
751-
importTime: Date.now(),
752-
keyring: { type: 'HD Key Tree' },
753-
},
754-
scopes: [],
755-
methods: [],
756-
};
757-
}
758-
if (selector.toString().includes('selectChainId')) {
759-
return '0x1'; // Ethereum mainnet - fallback
760-
}
761-
if (selector.toString().includes('selectPrivacyMode')) {
762-
return false;
763-
}
764-
if (selector.toString().includes('selectCardholderAccounts')) {
765-
return [mockCurrentAddress];
766-
}
767-
if (selector.toString().includes('selectEvmTokens')) {
768-
return [mockPriorityToken];
769-
}
770-
if (selector.toString().includes('selectEvmTokenFiatBalances')) {
771-
return ['1000.00'];
772-
}
773-
return [];
774-
});
775-
776-
// Mock useFocusEffect to call the callbacks when they're registered
777-
const focusCallbacks: (() => void)[] = [];
778-
jest.mocked(useFocusEffect).mockImplementation((callback: () => void) => {
779-
focusCallbacks.push(callback);
780-
});
781-
782-
render();
783-
784-
// Execute all focus effect callbacks (network change first, then account change)
785-
await waitFor(async () => {
786-
for (const callback of focusCallbacks) {
787-
callback();
788-
}
789-
});
790-
791-
await waitFor(() => {
792-
expect(mockFindNetworkClientIdByChainId).toHaveBeenCalledWith('0xe708');
793-
expect(mockSetActiveNetwork).toHaveBeenCalledWith('linea-network-id');
794-
});
795-
});
796-
797-
it('handles network switching errors gracefully', async () => {
798-
// Override the mock to allow network switching for this test
799-
mockFindNetworkClientIdByChainId.mockReturnValue('linea-network-id');
800-
mockSetActiveNetwork.mockRejectedValueOnce(new Error('Network error'));
801-
802-
// Mock being on a different chain initially
803-
mockUseSelector.mockImplementation((selector) => {
804-
if (selector === selectChainId) {
805-
return '0x1'; // Ethereum mainnet
806-
}
807-
if (selector === selectPrivacyMode) {
808-
return false;
809-
}
810-
if (selector === selectDepositActiveFlag) {
811-
return true;
812-
}
813-
if (selector === selectDepositMinimumVersionFlag) {
814-
return '0.9.0';
815-
}
816-
if (selector === selectCardholderAccounts) {
817-
return [mockCurrentAddress];
818-
}
819-
if (selector === selectSelectedInternalAccount) {
820-
return {
821-
address: mockCurrentAddress,
822-
id: 'account-id',
823-
type: 'eip155:eoa',
824-
options: {},
825-
metadata: {
826-
name: 'Test Account',
827-
importTime: Date.now(),
828-
keyring: { type: 'HD Key Tree' },
829-
},
830-
scopes: [],
831-
methods: [],
832-
};
833-
}
834-
if (selector.toString().includes('selectChainId')) {
835-
return '0x1'; // Ethereum mainnet - fallback
836-
}
837-
if (selector.toString().includes('selectPrivacyMode')) {
838-
return false;
839-
}
840-
if (selector.toString().includes('selectCardholderAccounts')) {
841-
return [mockCurrentAddress];
842-
}
843-
if (selector.toString().includes('selectEvmTokens')) {
844-
return [mockPriorityToken];
845-
}
846-
if (selector.toString().includes('selectEvmTokenFiatBalances')) {
847-
return ['1000.00'];
848-
}
849-
return [];
850-
});
851-
852-
// Mock useFocusEffect to call the callbacks when they're registered
853-
const focusCallbacks: (() => void)[] = [];
854-
jest.mocked(useFocusEffect).mockImplementation((callback: () => void) => {
855-
focusCallbacks.push(callback);
856-
});
857-
858-
render();
859-
860-
// Execute all focus effect callbacks (network change first, then account change)
861-
await waitFor(async () => {
862-
for (const callback of focusCallbacks) {
863-
callback();
864-
}
865-
});
866-
867-
await waitFor(() => {
868-
expect(mockSetActiveNetwork).toHaveBeenCalled();
869-
});
870-
});
871-
872686
it('dispatches bridge tokens when opening swaps with non-USDC token', async () => {
873687
// Reset useFocusEffect to default mock for this test
874688
jest.mocked(useFocusEffect).mockImplementation(jest.fn());
@@ -902,7 +716,6 @@ describe('CardHome Component', () => {
902716
expect(mockTrackEvent).toHaveBeenCalled();
903717
expect(mockOpenSwaps).toHaveBeenCalledWith({
904718
chainId: '0xe708',
905-
cardholderAddress: mockCurrentAddress,
906719
});
907720
});
908721
});

0 commit comments

Comments
 (0)