Skip to content

Commit 1a588d5

Browse files
authored
feat(card): add selectIsUserInSupportedCardCountry (#27695)
<!-- 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** Extracts the Card supported-country check into a dedicated `selectIsUserInSupportedCardCountry` selector and refactors `selectDisplayCardButton` to use it. **Why**: The geo-location vs supported-countries logic was inlined in `selectDisplayCardButton`. Extracting it into a named selector improves readability, enables reuse, and simplifies testing. The display logic becomes clearer: show the Card button when the user is in a supported country and the feature flag is on. **What changed**: - **`selectIsUserInSupportedCardCountry`**: New selector that returns whether the user's `geoLocation` is in the feature-flag `cardSupportedCountries` map with value `true`. Uses `selectCardGeoLocation` and `selectCardSupportedCountries`. - **`selectDisplayCardButton`**: Refactored to use `selectIsUserInSupportedCardCountry` instead of inline geo/countries logic. The condition `(cardSupportedCountries)?.[geoLocation] === true && displayCardButtonFeatureFlag` is now `isUserInSupportedCardCountry && displayCardButtonFeatureFlag`. - **Tests**: Added unit tests for `selectIsUserInSupportedCardCountry` covering supported country, unsupported country, UNKNOWN geoLocation, explicitly false country, and empty/missing supported countries. ## **Changelog** CHANGELOG entry: Extracted Card supported-country check into `selectIsUserInSupportedCardCountry` selector. ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Card button display in supported vs unsupported countries Scenario: Card button shows in supported country with feature flag Given I am in a supported country (e.g. US, GB) And the Card display feature flag is enabled When I view the app home/tab bar Then the Card button should be visible Scenario: Card button hidden in unsupported country Given I am in an unsupported country (or geoLocation is UNKNOWN) And the Card display feature flag is enabled When I view the app home/tab bar Then the Card button should not be visible (unless cardholder/alwaysShow) ``` ## **Screenshots/Recordings** No visual design changes — Card button visibility logic is unchanged; only the selector structure was refactored. ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk refactor that extracts existing geoLocation/supported-country logic into a dedicated selector and wires it into `selectDisplayCardButton`, with added unit coverage; behavior should remain the same aside from potential edge cases around missing/empty country maps. > > **Overview** > Introduces a new selector, `selectIsUserInSupportedCardCountry`, to centralize the "geoLocation is in `cardSupportedCountries` with value `true`" check. > > Refactors `selectDisplayCardButton` to depend on this selector instead of inlining the supported-country lookup, and adds focused unit tests covering supported/unsupported/`UNKNOWN` geoLocation, explicitly disabled countries, and empty supported-country maps. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit d09eb3c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 623cfc0 commit 1a588d5

2 files changed

Lines changed: 89 additions & 7 deletions

File tree

app/core/redux/slices/card/index.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import cardReducer, {
2525
selectContactVerificationId,
2626
selectConsentSetId,
2727
resetAuthenticatedData,
28+
selectIsUserInSupportedCardCountry,
2829
} from '.';
2930

3031
// Mock the multichain selectors
@@ -720,6 +721,84 @@ describe('Card Button Display Selectors', () => {
720721
jest.clearAllMocks();
721722
});
722723

724+
describe('selectIsUserInSupportedCardCountry', () => {
725+
it('returns true when geoLocation is in supported countries', () => {
726+
mockSelectCardSupportedCountries.mockReturnValue({
727+
US: true,
728+
GB: true,
729+
});
730+
731+
const stateWithUs: CardSliceState = {
732+
...initialState,
733+
geoLocation: 'US',
734+
};
735+
const mockRootState = {
736+
card: stateWithUs,
737+
} as unknown as RootState;
738+
739+
expect(selectIsUserInSupportedCardCountry(mockRootState)).toBe(true);
740+
});
741+
742+
it('returns false when geoLocation is not in supported countries', () => {
743+
mockSelectCardSupportedCountries.mockReturnValue({ US: true });
744+
745+
const stateWithUnsupported: CardSliceState = {
746+
...initialState,
747+
geoLocation: 'CN',
748+
};
749+
const mockRootState = {
750+
card: stateWithUnsupported,
751+
} as unknown as RootState;
752+
753+
expect(selectIsUserInSupportedCardCountry(mockRootState)).toBe(false);
754+
});
755+
756+
it('returns false when geoLocation is UNKNOWN', () => {
757+
mockSelectCardSupportedCountries.mockReturnValue({ US: true });
758+
759+
const stateWithUnknown: CardSliceState = {
760+
...initialState,
761+
geoLocation: 'UNKNOWN',
762+
};
763+
const mockRootState = {
764+
card: stateWithUnknown,
765+
} as unknown as RootState;
766+
767+
expect(selectIsUserInSupportedCardCountry(mockRootState)).toBe(false);
768+
});
769+
770+
it('returns false when country is explicitly false in supported countries', () => {
771+
mockSelectCardSupportedCountries.mockReturnValue({
772+
US: true,
773+
DE: false,
774+
});
775+
776+
const stateWithDe: CardSliceState = {
777+
...initialState,
778+
geoLocation: 'DE',
779+
};
780+
const mockRootState = {
781+
card: stateWithDe,
782+
} as unknown as RootState;
783+
784+
expect(selectIsUserInSupportedCardCountry(mockRootState)).toBe(false);
785+
});
786+
787+
it('returns false when cardSupportedCountries is empty or missing', () => {
788+
mockSelectCardSupportedCountries.mockReturnValue({});
789+
790+
const stateWithUs: CardSliceState = {
791+
...initialState,
792+
geoLocation: 'US',
793+
};
794+
const mockRootState = {
795+
card: stateWithUs,
796+
} as unknown as RootState;
797+
798+
expect(selectIsUserInSupportedCardCountry(mockRootState)).toBe(false);
799+
});
800+
});
801+
723802
describe('selectAlwaysShowCardButton', () => {
724803
it('should return false when experimental switch is disabled', () => {
725804
mockSelectCardExperimentalSwitch.mockReturnValue(false);

app/core/redux/slices/card/index.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,28 +210,31 @@ export const selectIsDaimoDemo = createSelector(
210210
(card) => card.isDaimoDemo,
211211
);
212212

213+
export const selectIsUserInSupportedCardCountry = createSelector(
214+
selectCardGeoLocation,
215+
selectCardSupportedCountries,
216+
(geoLocation, cardSupportedCountries) =>
217+
(cardSupportedCountries as Record<string, boolean>)?.[geoLocation] === true,
218+
);
219+
213220
export const selectDisplayCardButton = createSelector(
214221
selectIsCardholder,
215222
selectAlwaysShowCardButton,
216-
selectCardGeoLocation,
217-
selectCardSupportedCountries,
218223
selectDisplayCardButtonFeatureFlag,
219224
selectIsAuthenticatedCard,
225+
selectIsUserInSupportedCardCountry,
220226
(
221227
isCardholder,
222228
alwaysShowCardButton,
223-
geoLocation,
224-
cardSupportedCountries,
225229
displayCardButtonFeatureFlag,
226230
isAuthenticated,
231+
isUserInSupportedCardCountry,
227232
) => {
228233
if (
229234
alwaysShowCardButton ||
230235
isCardholder ||
231236
isAuthenticated ||
232-
((cardSupportedCountries as Record<string, boolean>)?.[geoLocation] ===
233-
true &&
234-
displayCardButtonFeatureFlag)
237+
(isUserInSupportedCardCountry && displayCardButtonFeatureFlag)
235238
) {
236239
return true;
237240
}

0 commit comments

Comments
 (0)