Skip to content

Commit de4f506

Browse files
committed
feat(snap-account-service): handle :accountGroup{Created,Updated,Removed} events
1 parent ff1e9aa commit de4f506

3 files changed

Lines changed: 300 additions & 31 deletions

File tree

packages/snap-account-service/src/SnapAccountService.test.ts

Lines changed: 162 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ function getMessenger(
105105
'KeyringController:stateChange',
106106
'KeyringController:unlock',
107107
'AccountTreeController:selectedAccountGroupChange',
108+
'AccountTreeController:accountGroupCreated',
109+
'AccountTreeController:accountGroupUpdated',
110+
'AccountTreeController:accountGroupRemoved',
108111
],
109112
});
110113
return messenger;
@@ -202,11 +205,57 @@ function buildSnap(id: string, hasKeyring: boolean): TruncatedSnap {
202205
/**
203206
* Builds a minimal `AccountGroupObject` for tests.
204207
*
208+
* @param id - The group ID.
205209
* @param accounts - The list of account IDs in the group.
206210
* @returns A minimal `AccountGroupObject`.
207211
*/
208-
function buildGroup(accounts: string[]): AccountGroupObject {
209-
return { accounts } as unknown as AccountGroupObject;
212+
function buildGroup(
213+
id: AccountGroupId,
214+
accounts: string[],
215+
): AccountGroupObject {
216+
return { id, accounts } as unknown as AccountGroupObject;
217+
}
218+
219+
/**
220+
* Publishes an AccountTreeController accountGroupCreated event on the root
221+
* messenger.
222+
*
223+
* @param rootMessenger - The root messenger.
224+
* @param group - The created account group.
225+
*/
226+
function publishAccountGroupCreated(
227+
rootMessenger: RootMessenger,
228+
group: AccountGroupObject,
229+
): void {
230+
rootMessenger.publish('AccountTreeController:accountGroupCreated', group);
231+
}
232+
233+
/**
234+
* Publishes an AccountTreeController accountGroupUpdated event on the root
235+
* messenger.
236+
*
237+
* @param rootMessenger - The root messenger.
238+
* @param group - The updated account group.
239+
*/
240+
function publishAccountGroupUpdated(
241+
rootMessenger: RootMessenger,
242+
group: AccountGroupObject,
243+
): void {
244+
rootMessenger.publish('AccountTreeController:accountGroupUpdated', group);
245+
}
246+
247+
/**
248+
* Publishes an AccountTreeController accountGroupRemoved event on the root
249+
* messenger.
250+
*
251+
* @param rootMessenger - The root messenger.
252+
* @param groupId - The removed account group ID.
253+
*/
254+
function publishAccountGroupRemoved(
255+
rootMessenger: RootMessenger,
256+
groupId: AccountGroupId,
257+
): void {
258+
rootMessenger.publish('AccountTreeController:accountGroupRemoved', groupId);
210259
}
211260

212261
/**
@@ -642,7 +691,7 @@ describe('SnapAccountService', () => {
642691
const setSelectedAccounts = jest.fn().mockResolvedValue(undefined);
643692
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
644693
mocks.AccountTreeController.getAccountGroupObject.mockReturnValue(
645-
buildGroup(MOCK_ACCOUNTS),
694+
buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS),
646695
);
647696
expect(service).toBeDefined();
648697

@@ -694,7 +743,7 @@ describe('SnapAccountService', () => {
694743
const setSelectedAccounts = jest.fn().mockRejectedValue(error);
695744
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
696745
mocks.AccountTreeController.getAccountGroupObject.mockReturnValue(
697-
buildGroup(MOCK_ACCOUNTS),
746+
buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS),
698747
);
699748
const consoleErrorSpy = jest
700749
.spyOn(console, 'error')
@@ -706,7 +755,7 @@ describe('SnapAccountService', () => {
706755

707756
expect(setSelectedAccounts).toHaveBeenCalledWith(MOCK_ACCOUNTS);
708757
expect(consoleErrorSpy).toHaveBeenCalledWith(
709-
'Error handling selected account group change:',
758+
'Error forwarding selected accounts:',
710759
error,
711760
);
712761

@@ -729,7 +778,7 @@ describe('SnapAccountService', () => {
729778
MOCK_GROUP_ID,
730779
);
731780
mocks.AccountTreeController.getAccountGroupObject.mockReturnValue(
732-
buildGroup(MOCK_ACCOUNTS),
781+
buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS),
733782
);
734783
expect(service).toBeDefined();
735784

@@ -770,7 +819,7 @@ describe('SnapAccountService', () => {
770819
MOCK_GROUP_ID,
771820
);
772821
mocks.AccountTreeController.getAccountGroupObject.mockReturnValue(
773-
buildGroup(MOCK_ACCOUNTS),
822+
buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS),
774823
);
775824
const consoleErrorSpy = jest
776825
.spyOn(console, 'error')
@@ -782,11 +831,116 @@ describe('SnapAccountService', () => {
782831

783832
expect(setSelectedAccounts).toHaveBeenCalledWith(MOCK_ACCOUNTS);
784833
expect(consoleErrorSpy).toHaveBeenCalledWith(
785-
'Error forwarding selected account group on unlock:',
834+
'Error forwarding selected accounts:',
786835
error,
787836
);
788837

789838
consoleErrorSpy.mockRestore();
790839
});
791840
});
841+
842+
describe.each([
843+
['accountGroupCreated', publishAccountGroupCreated] as const,
844+
['accountGroupUpdated', publishAccountGroupUpdated] as const,
845+
])('on AccountTreeController:%s', (_eventName, publishEvent) => {
846+
const MOCK_GROUP_ID = 'keyring:01JABC/group-1' as AccountGroupId;
847+
const OTHER_GROUP_ID = 'keyring:01JABC/group-2' as AccountGroupId;
848+
const MOCK_ACCOUNTS = [
849+
'00000000-0000-0000-0000-000000000001',
850+
'00000000-0000-0000-0000-000000000002',
851+
];
852+
853+
it('forwards the accounts from the event payload when the affected group is the selected one', async () => {
854+
const { service, rootMessenger, mocks } = setup();
855+
const setSelectedAccounts = jest.fn().mockResolvedValue(undefined);
856+
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
857+
mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue(
858+
MOCK_GROUP_ID,
859+
);
860+
expect(service).toBeDefined();
861+
862+
publishEvent(rootMessenger, buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS));
863+
await flushMicrotasks();
864+
865+
expect(
866+
mocks.AccountTreeController.getAccountGroupObject,
867+
).not.toHaveBeenCalled();
868+
expect(setSelectedAccounts).toHaveBeenCalledWith(MOCK_ACCOUNTS);
869+
});
870+
871+
it('does nothing when the affected group is not the selected one', async () => {
872+
const { service, rootMessenger, mocks } = setup();
873+
const setSelectedAccounts = jest.fn().mockResolvedValue(undefined);
874+
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
875+
mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue(
876+
OTHER_GROUP_ID,
877+
);
878+
expect(service).toBeDefined();
879+
880+
publishEvent(rootMessenger, buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS));
881+
await flushMicrotasks();
882+
883+
expect(
884+
mocks.AccountTreeController.getAccountGroupObject,
885+
).not.toHaveBeenCalled();
886+
expect(setSelectedAccounts).not.toHaveBeenCalled();
887+
});
888+
889+
it('does nothing when no account group is selected', async () => {
890+
const { service, rootMessenger, mocks } = setup();
891+
const setSelectedAccounts = jest.fn().mockResolvedValue(undefined);
892+
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
893+
mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue('');
894+
expect(service).toBeDefined();
895+
896+
publishEvent(rootMessenger, buildGroup(MOCK_GROUP_ID, MOCK_ACCOUNTS));
897+
await flushMicrotasks();
898+
899+
expect(
900+
mocks.AccountTreeController.getAccountGroupObject,
901+
).not.toHaveBeenCalled();
902+
expect(setSelectedAccounts).not.toHaveBeenCalled();
903+
});
904+
});
905+
906+
describe('on AccountTreeController:accountGroupRemoved', () => {
907+
const MOCK_GROUP_ID = 'keyring:01JABC/group-1' as AccountGroupId;
908+
const OTHER_GROUP_ID = 'keyring:01JABC/group-2' as AccountGroupId;
909+
910+
it('clears the selected accounts when the removed group is the selected one', async () => {
911+
const { service, rootMessenger, mocks } = setup();
912+
const setSelectedAccounts = jest.fn().mockResolvedValue(undefined);
913+
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
914+
mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue(
915+
MOCK_GROUP_ID,
916+
);
917+
expect(service).toBeDefined();
918+
919+
publishAccountGroupRemoved(rootMessenger, MOCK_GROUP_ID);
920+
await flushMicrotasks();
921+
922+
expect(
923+
mocks.AccountTreeController.getAccountGroupObject,
924+
).not.toHaveBeenCalled();
925+
expect(setSelectedAccounts).toHaveBeenCalledWith([]);
926+
});
927+
928+
it('does nothing when the removed group is not the selected one', async () => {
929+
const { service, rootMessenger, mocks } = setup();
930+
const setSelectedAccounts = jest.fn().mockResolvedValue(undefined);
931+
mockLegacySnapKeyring(mocks, { setSelectedAccounts });
932+
mocks.AccountTreeController.getSelectedAccountGroup.mockReturnValue(
933+
OTHER_GROUP_ID,
934+
);
935+
expect(service).toBeDefined();
936+
937+
publishAccountGroupRemoved(rootMessenger, MOCK_GROUP_ID);
938+
await flushMicrotasks();
939+
940+
expect(
941+
mocks.AccountTreeController.getAccountGroupObject,
942+
).not.toHaveBeenCalled();
943+
expect(setSelectedAccounts).not.toHaveBeenCalled();
944+
});
945+
});
792946
});

0 commit comments

Comments
 (0)