Skip to content

Commit 8fb823b

Browse files
committed
fix(assets-controller): consume account activity
1 parent dce810b commit 8fb823b

11 files changed

Lines changed: 1042 additions & 87 deletions

packages/assets-controller/src/AssetsController.test.ts

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ type AllEvents = MessengerEvents<AssetsControllerMessenger>;
8585

8686
type RootMessenger = Messenger<MockAnyNamespace, AllActions, AllEvents>;
8787

88+
/** Mirrors the private `RESUBSCRIBE_DEBOUNCE_MS` in AssetsController.ts. */
89+
const RESUBSCRIBE_DEBOUNCE_MS = 250;
90+
91+
/** Mirrors the private `RESUBSCRIBE_JITTER_MS` in AssetsController.ts. */
92+
const RESUBSCRIBE_JITTER_MS = 5000;
93+
8894
const MOCK_ACCOUNT_ID = 'mock-account-id-1';
8995
const MOCK_ASSET_ID =
9096
'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' as Caip19AssetId;
@@ -1392,14 +1398,81 @@ describe('AssetsController', () => {
13921398
});
13931399

13941400
describe('handleActiveChainsUpdate', () => {
1395-
it('re-subscribes assets when chains are added', async () => {
1401+
it('re-subscribes assets when chains are added, debounced and jittered', async () => {
13961402
await withController(async ({ controller }) => {
1397-
const subscribeSpy = jest.spyOn(controller, 'subscribeAssetsPrice');
1403+
jest.useFakeTimers();
1404+
const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.5);
1405+
try {
1406+
const subscribeSpy = jest.spyOn(controller, 'subscribeAssetsPrice');
13981407

1399-
const onActiveChainsUpdated = controller.getOnActiveChainsUpdated();
1400-
onActiveChainsUpdated('TestDataSource', ['eip155:1'], []);
1408+
const onActiveChainsUpdated = controller.getOnActiveChainsUpdated();
1409+
onActiveChainsUpdated('TestDataSource', ['eip155:1'], []);
1410+
1411+
// Re-subscribe is debounced, so it does not run synchronously.
1412+
expect(subscribeSpy).not.toHaveBeenCalled();
14011413

1402-
expect(subscribeSpy).toHaveBeenCalledTimes(1);
1414+
// Additions are jittered on top of the base debounce, so nothing runs
1415+
// at the base debounce window.
1416+
jest.advanceTimersByTime(RESUBSCRIBE_DEBOUNCE_MS);
1417+
expect(subscribeSpy).not.toHaveBeenCalled();
1418+
1419+
// Once the jitter window elapses it runs exactly once.
1420+
jest.advanceTimersByTime(RESUBSCRIBE_JITTER_MS);
1421+
expect(subscribeSpy).toHaveBeenCalledTimes(1);
1422+
} finally {
1423+
randomSpy.mockRestore();
1424+
jest.useRealTimers();
1425+
}
1426+
});
1427+
});
1428+
1429+
it('re-subscribes promptly (no jitter) when a chain removal pre-empts a pending jittered addition', async () => {
1430+
await withController(async ({ controller }) => {
1431+
jest.useFakeTimers();
1432+
const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.9);
1433+
try {
1434+
const subscribeSpy = jest.spyOn(controller, 'subscribeAssetsPrice');
1435+
1436+
const onActiveChainsUpdated = controller.getOnActiveChainsUpdated();
1437+
// Addition schedules a jittered re-subscribe...
1438+
onActiveChainsUpdated('TestDataSource', ['eip155:1'], []);
1439+
// ...then a removal arrives; it must pre-empt to the prompt window.
1440+
onActiveChainsUpdated('TestDataSource', [], ['eip155:137']);
1441+
1442+
jest.advanceTimersByTime(RESUBSCRIBE_DEBOUNCE_MS);
1443+
expect(subscribeSpy).toHaveBeenCalledTimes(1);
1444+
} finally {
1445+
randomSpy.mockRestore();
1446+
jest.useRealTimers();
1447+
}
1448+
});
1449+
});
1450+
1451+
it('coalesces a burst of active-chain updates into a single re-subscribe', async () => {
1452+
await withController(async ({ controller }) => {
1453+
jest.useFakeTimers();
1454+
try {
1455+
const subscribeSpy = jest.spyOn(controller, 'subscribeAssetsPrice');
1456+
1457+
const onActiveChainsUpdated = controller.getOnActiveChainsUpdated();
1458+
// Simulate a burst of chain up/down notifications within the window.
1459+
onActiveChainsUpdated('TestDataSource', ['eip155:1'], []);
1460+
onActiveChainsUpdated(
1461+
'TestDataSource',
1462+
['eip155:1', 'eip155:137'],
1463+
['eip155:1'],
1464+
);
1465+
onActiveChainsUpdated(
1466+
'TestDataSource',
1467+
['eip155:137'],
1468+
['eip155:1', 'eip155:137'],
1469+
);
1470+
1471+
jest.advanceTimersByTime(RESUBSCRIBE_DEBOUNCE_MS);
1472+
expect(subscribeSpy).toHaveBeenCalledTimes(1);
1473+
} finally {
1474+
jest.useRealTimers();
1475+
}
14031476
});
14041477
});
14051478

@@ -1428,12 +1501,18 @@ describe('AssetsController', () => {
14281501

14291502
it('re-subscribes assets when chains are removed', async () => {
14301503
await withController(async ({ controller }) => {
1431-
const subscribeSpy = jest.spyOn(controller, 'subscribeAssetsPrice');
1504+
jest.useFakeTimers();
1505+
try {
1506+
const subscribeSpy = jest.spyOn(controller, 'subscribeAssetsPrice');
14321507

1433-
const onActiveChainsUpdated = controller.getOnActiveChainsUpdated();
1434-
onActiveChainsUpdated('TestDataSource', [], ['eip155:1']);
1508+
const onActiveChainsUpdated = controller.getOnActiveChainsUpdated();
1509+
onActiveChainsUpdated('TestDataSource', [], ['eip155:1']);
14351510

1436-
expect(subscribeSpy).toHaveBeenCalledTimes(1);
1511+
jest.advanceTimersByTime(RESUBSCRIBE_DEBOUNCE_MS);
1512+
expect(subscribeSpy).toHaveBeenCalledTimes(1);
1513+
} finally {
1514+
jest.useRealTimers();
1515+
}
14371516
});
14381517
});
14391518

@@ -2019,8 +2098,19 @@ describe('AssetsController', () => {
20192098
};
20202099

20212100
await withController(
2022-
{ state: initialState },
2101+
{ state: initialState, clientControllerState: { isUiOpen: true } },
20232102
async ({ controller, messenger }) => {
2103+
// UI must be open and keyring unlocked so AccountActivityDataSource is
2104+
// subscribed and can route balanceUpdated events to the account.
2105+
(
2106+
messenger as unknown as {
2107+
publish: (topic: string, payload?: unknown) => void;
2108+
}
2109+
).publish('ClientController:stateChange', { isUiOpen: true });
2110+
messenger.publish('KeyringController:unlock');
2111+
2112+
await flushPromises();
2113+
20242114
messenger.publish('AccountActivityService:balanceUpdated', {
20252115
address: '0x1234567890123456789012345678901234567890',
20262116
chain: 'eip155:42161',

0 commit comments

Comments
 (0)