Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/ramps-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Refetch the countries catalog on every app startup via `init()` so region preset amounts (e.g. default amounts) stay current ([#9261](https://github.com/MetaMask/core/pull/9261))
- Re-sync `userRegion` preset amounts from the countries catalog after each `getCountries()` call ([#9261](https://github.com/MetaMask/core/pull/9261))
- On startup, `init()` now preserves an already-persisted `userRegion` when the refreshed countries catalog is momentarily empty or no longer lists that region, instead of clearing it ([#9261](https://github.com/MetaMask/core/pull/9261))

## [15.0.0]

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ export type RampsControllerSetSelectedProviderAction = {
* This should be called once at app startup to set up the initial region.
*
* Idempotent: subsequent calls return the same promise unless forceRefresh is set.
* Skips getCountries when countries are already loaded; skips geolocation when
* userRegion already exists.
* Force-refetches the countries catalog on startup (bypassing the in-session
* request cache) so region preset amounts stay current. The catalog is not
* persisted, so a cold start always re-fetches it regardless. Skips
* geolocation when userRegion already exists.
*
* @param options - Options for cache behavior. forceRefresh bypasses idempotency and re-runs the full flow.
* @returns Promise that resolves when initialization is complete.
Expand Down
245 changes: 237 additions & 8 deletions packages/ramps-controller/src/RampsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,6 @@ describe('RampsController', () => {
),
).toMatchInlineSnapshot(`
{
"countries": {
"data": [],
"error": null,
"isLoading": false,
"selected": null,
},
"orders": [],
"providerAutoSelected": false,
"userRegion": null,
Expand Down Expand Up @@ -1880,6 +1874,166 @@ describe('RampsController', () => {
});
});

it('re-syncs userRegion preset amounts after getCountries', async () => {
const staleCountries: Country[] = [
{
isoCode: 'CR',
id: '/regions/cr',
flag: '🇨🇷',
name: 'Costa Rica',
phone: {
prefix: '+506',
placeholder: '8312 3456',
template: 'XXXX XXXX',
},
currency: 'CRC',
supported: { buy: true, sell: true },
defaultAmount: 100,
quickAmounts: [20, 50, 100],
},
];
const freshCountries: Country[] = [
{
...staleCountries[0],
defaultAmount: 25000,
quickAmounts: [10000, 25000, 50000],
},
];

await withController(
{
options: {
state: {
userRegion: {
country: staleCountries[0],
state: null,
regionCode: 'cr',
},
},
},
},
async ({ controller, rootMessenger }) => {
rootMessenger.registerActionHandler(
'RampsService:getCountries',
async () => freshCountries,
);

await rootMessenger.call('RampsController:getCountries');

expect(controller.state.userRegion?.country.defaultAmount).toBe(
25000,
);
expect(
controller.state.userRegion?.country.quickAmounts,
).toStrictEqual([10000, 25000, 50000]);
},
);
});

it('leaves userRegion unchanged when the refreshed catalog is empty', async () => {
const userRegionCountry: Country = {
isoCode: 'CR',
id: '/regions/cr',
flag: '🇨🇷',
name: 'Costa Rica',
phone: {
prefix: '+506',
placeholder: '8312 3456',
template: 'XXXX XXXX',
},
currency: 'CRC',
supported: { buy: true, sell: true },
defaultAmount: 100,
quickAmounts: [20, 50, 100],
};

await withController(
{
options: {
state: {
userRegion: {
country: userRegionCountry,
state: null,
regionCode: 'cr',
},
},
},
},
async ({ controller, rootMessenger }) => {
rootMessenger.registerActionHandler(
'RampsService:getCountries',
async () => [],
);

await rootMessenger.call('RampsController:getCountries');

expect(controller.state.countries.data).toStrictEqual([]);
expect(controller.state.userRegion?.country.defaultAmount).toBe(100);
},
);
});

it('leaves userRegion unchanged when its region is absent from the refreshed catalog', async () => {
const userRegionCountry: Country = {
isoCode: 'CR',
id: '/regions/cr',
flag: '🇨🇷',
name: 'Costa Rica',
phone: {
prefix: '+506',
placeholder: '8312 3456',
template: 'XXXX XXXX',
},
currency: 'CRC',
supported: { buy: true, sell: true },
defaultAmount: 100,
quickAmounts: [20, 50, 100],
};
const otherCountries: Country[] = [
{
isoCode: 'US',
id: '/regions/us',
flag: '🇺🇸',
name: 'United States',
phone: {
prefix: '+1',
placeholder: '201 555 0123',
template: 'XXX XXX XXXX',
},
currency: 'USD',
supported: { buy: true, sell: true },
defaultAmount: 100,
quickAmounts: [100, 300, 1000],
},
];

await withController(
{
options: {
state: {
userRegion: {
country: userRegionCountry,
state: null,
regionCode: 'cr',
},
},
},
},
async ({ controller, rootMessenger }) => {
rootMessenger.registerActionHandler(
'RampsService:getCountries',
async () => otherCountries,
);

await rootMessenger.call('RampsController:getCountries');

expect(controller.state.countries.data).toStrictEqual(otherCountries);
expect(controller.state.userRegion?.country.isoCode).toBe('CR');
expect(controller.state.userRegion?.country.defaultAmount).toBe(100);
},
);
});

it('throws when updating resource field and resource is null', async () => {
const stateWithNullCountries = {
...getDefaultRampsControllerState(),
Expand Down Expand Up @@ -2110,7 +2264,7 @@ describe('RampsController', () => {
});
});

it('skips getCountries and geolocation when userRegion and countries exist', async () => {
it('refetches countries on init but skips geolocation when userRegion exists', async () => {
let getCountriesCalled = false;
let getGeolocationCalled = false;
await withController(
Expand Down Expand Up @@ -2148,7 +2302,7 @@ describe('RampsController', () => {

await rootMessenger.call('RampsController:init');

expect(getCountriesCalled).toBe(false);
expect(getCountriesCalled).toBe(true);
expect(getGeolocationCalled).toBe(false);
expect(controller.state.userRegion?.regionCode).toBe('us-ca');
},
Expand Down Expand Up @@ -2229,6 +2383,81 @@ describe('RampsController', () => {
},
);
});

it('preserves a persisted userRegion when the startup catalog refresh is empty', async () => {
let getGeolocationCalled = false;
await withController(
{
options: {
state: {
userRegion: createMockUserRegion('us-ca'),
},
},
},
async ({ controller, rootMessenger }) => {
rootMessenger.registerActionHandler(
'RampsService:getCountries',
async () => [],
);
rootMessenger.registerActionHandler(
'RampsService:getGeolocation',
async () => {
getGeolocationCalled = true;
return 'us-ca';
},
);

expect(
await rootMessenger.call('RampsController:init'),
).toBeUndefined();

// A transient empty catalog must not fall back to geolocation nor
// wipe the persisted region via setUserRegion's cleanup.
expect(getGeolocationCalled).toBe(false);
expect(controller.state.countries.data).toStrictEqual([]);
expect(controller.state.userRegion?.regionCode).toBe('us-ca');
},
);
});

it('preserves a persisted userRegion when the startup catalog no longer lists the region', async () => {
const catalogWithoutUs: Country[] = [
{
isoCode: 'FR',
name: 'France',
flag: '🇫🇷',
currency: 'EUR',
phone: { prefix: '+33', placeholder: '', template: '' },
supported: { buy: true, sell: true },
},
];
await withController(
{
options: {
state: {
userRegion: createMockUserRegion('us-ca'),
},
},
},
async ({ controller, rootMessenger }) => {
rootMessenger.registerActionHandler(
'RampsService:getCountries',
async () => catalogWithoutUs,
);

expect(
await rootMessenger.call('RampsController:init'),
).toBeUndefined();

// The region is absent from the refreshed catalog, but the previously
// valid region must be preserved rather than wiped.
expect(controller.state.countries.data).toStrictEqual(
catalogWithoutUs,
);
expect(controller.state.userRegion?.regionCode).toBe('us-ca');
},
);
});
});

describe('setUserRegion', () => {
Expand Down
Loading
Loading