Skip to content

Commit 87ebc4b

Browse files
ccharlymontelaidev
andauthored
fix(accounts-controller): do not fire events during update blocks (#5555)
## Explanation Events should be fired after making a state update. Otherwise we could end up with race conditions like some other component react to `AccountsController:accountAdded` and try to call `getAccount(account.id)`, it might not the same value compared to the `account` being passed during the event (since the state update has not been persisted yet). This PR now moves all events firing after the state update. It also includes a small refactor of the `KeyringController:stateChange` handler (which made the new events firing logic, a bit easier). Test E2E PR: - MetaMask/metamask-extension#31393 (CI is passing) ## References N/A ## Changelog TODO ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate - [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes --------- Co-authored-by: Monte Lai <[email protected]>
1 parent 38b134a commit 87ebc4b

File tree

2 files changed

+334
-284
lines changed

2 files changed

+334
-284
lines changed

packages/accounts-controller/src/AccountsController.test.ts

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ describe('AccountsController', () => {
464464
expect(listMultichainAccountsSpy).toHaveBeenCalled();
465465
});
466466

467-
it('not update state when only keyring is unlocked without any keyrings', async () => {
467+
it('does not update state when only keyring is unlocked without any keyrings', async () => {
468468
const messenger = buildMessenger();
469469
const { accountsController } = setupAccountsController({
470470
initialState: {
@@ -652,7 +652,7 @@ describe('AccountsController', () => {
652652
]);
653653
});
654654

655-
it('handle the event when a Snap deleted the account before the it was added', async () => {
655+
it('handles the event when a Snap deleted the account before it was added', async () => {
656656
mockUUIDWithNormalAccounts([mockAccount]);
657657

658658
const messenger = buildMessenger();
@@ -720,6 +720,66 @@ describe('AccountsController', () => {
720720
]);
721721
});
722722

723+
it('handles the event when a Snap keyring has been deleted', async () => {
724+
mockUUIDWithNormalAccounts([mockAccount]);
725+
726+
const messenger = buildMessenger();
727+
messenger.registerActionHandler(
728+
'KeyringController:getKeyringsByType',
729+
// The Snap keyring will be treated as undefined
730+
mockGetKeyringByType.mockReturnValue([]),
731+
);
732+
733+
const mockNewKeyringState = {
734+
isUnlocked: true,
735+
keyrings: [
736+
{
737+
type: KeyringTypes.hd,
738+
accounts: [mockAccount.address],
739+
},
740+
{
741+
type: KeyringTypes.snap,
742+
// Since the Snap keyring will be mocked as "unavailable", this account won't be added
743+
// to the state (like if the Snap did remove it right before the keyring controller
744+
// state change got triggered).
745+
accounts: [mockAccount3.address],
746+
},
747+
],
748+
keyringsMetadata: [
749+
{
750+
id: 'mock-id',
751+
name: 'mock-name',
752+
},
753+
{
754+
id: 'mock-id2',
755+
name: 'mock-name2',
756+
},
757+
],
758+
};
759+
760+
const { accountsController } = setupAccountsController({
761+
initialState: {
762+
internalAccounts: {
763+
accounts: {
764+
[mockAccount.id]: mockAccount,
765+
},
766+
selectedAccount: mockAccount.id,
767+
},
768+
},
769+
messenger,
770+
});
771+
772+
messenger.publish(
773+
'KeyringController:stateChange',
774+
mockNewKeyringState,
775+
[],
776+
);
777+
778+
const accounts = accountsController.listMultichainAccounts();
779+
780+
expect(accounts).toStrictEqual([setLastSelectedAsAny(mockAccount)]);
781+
});
782+
723783
it('increment the default account number when adding an account', async () => {
724784
const messenger = buildMessenger();
725785

@@ -994,7 +1054,9 @@ describe('AccountsController', () => {
9941054

9951055
// First call is 'KeyringController:stateChange'
9961056
expect(messengerSpy).toHaveBeenNthCalledWith(
997-
2,
1057+
// 1. KeyringController:stateChange
1058+
// 2. AccountsController:stateChange
1059+
3,
9981060
'AccountsController:accountAdded',
9991061
setLastSelectedAsAny(mockAccount2),
10001062
);
@@ -1298,7 +1360,9 @@ describe('AccountsController', () => {
12981360

12991361
// First call is 'KeyringController:stateChange'
13001362
expect(messengerSpy).toHaveBeenNthCalledWith(
1301-
2,
1363+
// 1. KeyringController:stateChange
1364+
// 2. AccountsController:stateChange
1365+
3,
13021366
'AccountsController:accountRemoved',
13031367
mockAccount3.id,
13041368
);
@@ -2165,6 +2229,10 @@ describe('AccountsController', () => {
21652229
expect(selectedAccount.id).toStrictEqual(expectedSelectedId);
21662230
},
21672231
);
2232+
2233+
it.todo(
2234+
'does not re-fire a accountChanged event if the account is still the same',
2235+
);
21682236
});
21692237

21702238
describe('loadBackup', () => {
@@ -2603,7 +2671,7 @@ describe('AccountsController', () => {
26032671
).toStrictEqual(mockAccount2.id);
26042672
});
26052673

2606-
it('not emit setSelectedEvmAccountChange if the account is non-EVM', () => {
2674+
it('does not emit setSelectedEvmAccountChange if the account is non-EVM', () => {
26072675
const mockNonEvmAccount = createMockInternalAccount({
26082676
id: 'mock-non-evm',
26092677
name: 'non-evm',
@@ -2635,17 +2703,18 @@ describe('AccountsController', () => {
26352703
expect(
26362704
accountsController.state.internalAccounts.selectedAccount,
26372705
).toStrictEqual(mockNonEvmAccount.id);
2706+
console.log(accountsController.state.internalAccounts.selectedAccount);
26382707

26392708
expect(messengerSpy.mock.calls).toHaveLength(2); // state change and then selectedAccountChange
26402709

2641-
expect(messengerSpy).not.toHaveBeenCalledWith(
2710+
expect(messengerSpy).not.toHaveBeenLastCalledWith(
26422711
'AccountsController:selectedEvmAccountChange',
26432712
mockNonEvmAccount,
26442713
);
26452714

2646-
expect(messengerSpy).toHaveBeenCalledWith(
2715+
expect(messengerSpy).toHaveBeenLastCalledWith(
26472716
'AccountsController:selectedAccountChange',
2648-
mockNonEvmAccount,
2717+
setLastSelectedAsAny(mockNonEvmAccount),
26492718
);
26502719
});
26512720
});

0 commit comments

Comments
 (0)