Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions packages/snaps-jest/src/helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ describe('installSnap', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -473,6 +474,7 @@ describe('installSnap', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -535,6 +537,7 @@ describe('installSnap', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
});

Expand Down
1 change: 1 addition & 0 deletions packages/snaps-simulation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@metamask/superstruct": "^3.1.0",
"@metamask/utils": "^10.0.0",
"@reduxjs/toolkit": "^1.9.5",
"fast-deep-equal": "^3.1.3",
"mime": "^3.0.0",
"readable-stream": "^3.6.2",
"redux-saga": "^1.2.3"
Expand Down
6 changes: 5 additions & 1 deletion packages/snaps-simulation/src/controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ExecutionServiceActions,
SnapInterfaceControllerActions,
SnapInterfaceControllerAllowedActions,
SnapInterfaceControllerStateChangeEvent,
} from '@metamask/snaps-controllers';
import {
caveatSpecifications as snapsCaveatsSpecifications,
Expand All @@ -38,9 +39,12 @@ export type RootControllerAllowedActions =
| ExecutionServiceActions
| SubjectMetadataControllerActions;

export type RootControllerAllowedEvents =
SnapInterfaceControllerStateChangeEvent;

export type RootControllerMessenger = ControllerMessenger<
RootControllerAllowedActions,
any
RootControllerAllowedEvents
>;

export type GetControllersOptions = {
Expand Down
3 changes: 3 additions & 0 deletions packages/snaps-simulation/src/helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ describe('helpers', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -207,6 +208,7 @@ describe('helpers', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -269,6 +271,7 @@ describe('helpers', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
});

Expand Down
85 changes: 85 additions & 0 deletions packages/snaps-simulation/src/interface.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
typeInField,
uploadFile,
selectFromSelector,
waitForUpdate,
} from './interface';
import type { RunSagaFunction } from './store';
import { createStore, resolveInterface, setInterface } from './store';
Expand Down Expand Up @@ -90,6 +91,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: jest.fn(),
selectFromSelector: jest.fn(),
uploadFile: jest.fn(),
waitForUpdate: jest.fn(),
};

it('returns an `ok` function that resolves the user interface with `null` for alert dialogs', async () => {
Expand All @@ -111,6 +113,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
});

Expand Down Expand Up @@ -138,6 +141,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -166,6 +170,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -194,6 +199,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -222,6 +228,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -250,6 +257,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
cancel: expect.any(Function),
});
Expand Down Expand Up @@ -296,6 +304,7 @@ describe('getInterfaceResponse', () => {
selectInDropdown: expect.any(Function),
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
waitForUpdate: expect.any(Function),
uploadFile: expect.any(Function),
});
});
Expand Down Expand Up @@ -336,6 +345,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
cancel: expect.any(Function),
});
});
Expand Down Expand Up @@ -370,6 +380,7 @@ describe('getInterfaceResponse', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
cancel: expect.any(Function),
ok: expect.any(Function),
});
Expand Down Expand Up @@ -1250,6 +1261,7 @@ describe('getInterface', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
});
});
Expand Down Expand Up @@ -1280,6 +1292,7 @@ describe('getInterface', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
ok: expect.any(Function),
});
});
Expand Down Expand Up @@ -1468,6 +1481,41 @@ describe('getInterface', () => {
},
);
});

it('waits for the interface content to update when `waitForUpdate` is called', async () => {
jest.spyOn(rootControllerMessenger, 'call');
const { store, runSaga } = createStore(getMockOptions());

const content = (
<Box>
<Input name="foo" />
</Box>
);
const id = await interfaceController.createInterface(MOCK_SNAP_ID, content);
const type = DialogType.Alert;
const ui = { type: DIALOG_APPROVAL_TYPES[type], id };

store.dispatch(setInterface(ui));

const result = await runSaga(
getInterface,
runSaga,
MOCK_SNAP_ID,
rootControllerMessenger,
).toPromise();

const promise = result.waitForUpdate();

await interfaceController.updateInterface(
MOCK_SNAP_ID,
id,
<Text>Hello world!</Text>,
);

const newInterface = await promise;

expect(newInterface.content.type).toBe('Text');
});
});

describe('selectFromRadioGroup', () => {
Expand Down Expand Up @@ -1761,3 +1809,40 @@ describe('selectFromSelector', () => {
);
});
});

describe('waitForUpdate', () => {
const rootControllerMessenger = getRootControllerMessenger();
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
rootControllerMessenger,
);

const interfaceController = new SnapInterfaceController({
messenger: controllerMessenger,
});

it('waits for the interface content to update', async () => {
const content = <Input name="foo" />;

const interfaceId = await interfaceController.createInterface(
MOCK_SNAP_ID,
content,
);

const promise = waitForUpdate(
rootControllerMessenger,
MOCK_SNAP_ID,
interfaceId,
content,
);

await interfaceController.updateInterface(
MOCK_SNAP_ID,
interfaceId,
<Text>Hello world!</Text>,
);

const newInterface = await promise;

expect(newInterface.content.type).toBe('Text');
});
});
52 changes: 51 additions & 1 deletion packages/snaps-simulation/src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SnapInterfaceControllerState } from '@metamask/snaps-controllers';
import type { DialogApprovalTypes } from '@metamask/snaps-rpc-methods';
import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods';
import type {
Expand All @@ -19,14 +20,20 @@ import {
} from '@metamask/snaps-utils';
import { assertExhaustive, hasProperty } from '@metamask/utils';
import type { PayloadAction } from '@reduxjs/toolkit';
import deepEqual from 'fast-deep-equal';
import { type SagaIterator } from 'redux-saga';
import { call, put, select, take } from 'redux-saga/effects';

import type { RootControllerMessenger } from './controllers';
import { getFileSize, getFileToUpload } from './files';
import type { Interface, RunSagaFunction } from './store';
import { getCurrentInterface, resolveInterface, setInterface } from './store';
import type { FileOptions, SnapInterface, SnapInterfaceActions } from './types';
import type {
FileOptions,
SnapHandlerInterface,
SnapInterface,
SnapInterfaceActions,
} from './types';

/**
* The maximum file size that can be uploaded.
Expand Down Expand Up @@ -752,6 +759,46 @@ export async function selectFromSelector(
});
}

/**
* Wait for an interface to be updated.
*
* @param controllerMessenger - The controller messenger used to call actions.
* @param snapId - The Snap ID.
* @param id - The interface ID.
* @param originalContent - The original interface content.
* @returns A promise that resolves to the updated interface.
*/
export async function waitForUpdate(
controllerMessenger: RootControllerMessenger,
snapId: SnapId,
id: string,
originalContent: JSXElement,
) {
return new Promise<SnapHandlerInterface>((resolve) => {
const listener = (state: SnapInterfaceControllerState) => {
const currentInterface = state.interfaces[id];
const newContent = currentInterface?.content;

if (!deepEqual(originalContent, newContent)) {
// Unsubscribe
controllerMessenger.unsubscribe(
'SnapInterfaceController:stateChange',
listener,
);
const actions = getInterfaceActions(snapId, controllerMessenger, {
content: newContent,
id,
});
resolve({ ...actions, content: newContent });
}
};
controllerMessenger.subscribe(
'SnapInterfaceController:stateChange',
listener,
);
});
}

/**
* Get a formatted file size.
*
Expand Down Expand Up @@ -923,6 +970,9 @@ export function getInterfaceActions(
options,
);
},

waitForUpdate: async () =>
waitForUpdate(controllerMessenger, snapId, id, content),
};
}

Expand Down
3 changes: 3 additions & 0 deletions packages/snaps-simulation/src/request.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ describe('handleRequest', () => {
selectInDropdown: expect.any(Function),
typeInField: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
});

await closeServer();
Expand Down Expand Up @@ -346,6 +347,7 @@ describe('getInterfaceApi', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
});
});

Expand Down Expand Up @@ -379,6 +381,7 @@ describe('getInterfaceApi', () => {
selectFromRadioGroup: expect.any(Function),
selectFromSelector: expect.any(Function),
uploadFile: expect.any(Function),
waitForUpdate: expect.any(Function),
});
});

Expand Down
5 changes: 5 additions & 0 deletions packages/snaps-simulation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ export type SnapInterfaceActions = {
file: string | Uint8Array,
options?: FileOptions,
): Promise<void>;

/**
* Wait for the interface to be updated.
*/
waitForUpdate: () => Promise<SnapHandlerInterface>;
};

/**
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6145,6 +6145,7 @@ __metadata:
eslint-plugin-prettier: "npm:^4.2.1"
eslint-plugin-promise: "npm:^6.1.1"
express: "npm:^4.18.2"
fast-deep-equal: "npm:^3.1.3"
jest: "npm:^29.0.2"
jest-it-up: "npm:^2.0.0"
jest-silent-reporter: "npm:^0.6.0"
Expand Down
Loading