Skip to content

Commit

Permalink
Add and fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeRx committed Mar 22, 2024
1 parent ef008aa commit ae4c72c
Show file tree
Hide file tree
Showing 9 changed files with 732 additions and 52 deletions.
2 changes: 1 addition & 1 deletion packages/snaps-jest/src/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ describe('installSnap', () => {

expect(response).toStrictEqual(
expect.objectContaining({
content: { type: 'text', value: 'Hello, world!' },
getInterface: expect.any(Function),
}),
);

Expand Down
188 changes: 175 additions & 13 deletions packages/snaps-jest/src/internals/request.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { SnapInterfaceController } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';
import { text } from '@metamask/snaps-sdk';
import { UserInputEventType, button, input, text } from '@metamask/snaps-sdk';
import { HandlerType } from '@metamask/snaps-utils';

import {
getMockServer,
getRestrictedSnapInterfaceControllerMessenger,
getRootControllerMessenger,
} from '../test-utils';
import { handleRequest } from './request';
import {
getInterfaceApi,
getInterfaceFromResult,
handleRequest,
} from './request';
import { handleInstallSnap } from './simulation';

describe('handleRequest', () => {
Expand All @@ -32,7 +36,6 @@ describe('handleRequest', () => {
});

expect(response).toStrictEqual({
content: undefined,
id: expect.any(String),
response: {
result: 'Hello, world!',
Expand Down Expand Up @@ -85,7 +88,7 @@ describe('handleRequest', () => {
},
},
notifications: [],
content,
getInterface: expect.any(Function),
});

await closeServer();
Expand Down Expand Up @@ -127,8 +130,44 @@ describe('handleRequest', () => {
});
});

describe('getContentFromResult', () => {
it('gets the content from the SnapInterfaceController if the result contains an ID', async () => {
describe('getInterfaceFromResult', () => {
const controllerMessenger = getRootControllerMessenger();
// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger:
getRestrictedSnapInterfaceControllerMessenger(controllerMessenger),
});
it('returns the interface ID if the result includes it', async () => {
const result = await getInterfaceFromResult(
{ id: 'foo' },
'bar' as SnapId,
controllerMessenger,
);

expect(result).toBe('foo');
});

it('creates a new interface and returns its ID if the result contains content', async () => {
jest.spyOn(controllerMessenger, 'call');

const result = await getInterfaceFromResult(
{ content: text('foo') },
'bar' as SnapId,
controllerMessenger,
);

expect(result).toStrictEqual(expect.any(String));

expect(controllerMessenger.call).toHaveBeenCalledWith(
'SnapInterfaceController:createInterface',
'bar',
text('foo'),
);
});
});

describe('getInterfaceApi', () => {
it('gets the content from the SnapInterfaceController if the result contains an interface ID', async () => {
const controllerMessenger = getRootControllerMessenger();
const interfaceController = new SnapInterfaceController({
messenger:
Expand All @@ -140,33 +179,156 @@ describe('getContentFromResult', () => {

const id = await interfaceController.createInterface(snapId, content);

const result = getContentFromResult({ id }, snapId, controllerMessenger);
const getInterface = await getInterfaceApi(
{ id },
snapId,
controllerMessenger,
);

expect(getInterface).toStrictEqual(expect.any(Function));

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = getInterface!();

expect(result).toStrictEqual(content);
expect(result).toStrictEqual({
content,
clickElement: expect.any(Function),
typeInField: expect.any(Function),
});
});

it('gets the content from the result if the result contains the content', () => {
it('gets the content from the SnapInterfaceController if the result contains content', async () => {
const controllerMessenger = getRootControllerMessenger();

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger:
getRestrictedSnapInterfaceControllerMessenger(controllerMessenger),
});

const snapId = 'foo' as SnapId;
const content = text('foo');

const result = getContentFromResult(
const getInterface = await getInterfaceApi(
{ content },
snapId,
controllerMessenger,
);

expect(result).toStrictEqual(content);
expect(getInterface).toStrictEqual(expect.any(Function));

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = getInterface!();

expect(result).toStrictEqual({
content,
clickElement: expect.any(Function),
typeInField: expect.any(Function),
});
});

it('returns undefined if there is no content associated with the result', () => {
it('returns undefined if there is no interface ID associated with the result', async () => {
const controllerMessenger = getRootControllerMessenger();

const snapId = 'foo' as SnapId;

const result = getContentFromResult({}, snapId, controllerMessenger);
const result = await getInterfaceApi({}, snapId, controllerMessenger);

expect(result).toBeUndefined();
});

it('sends the request to the snap when using `clickElement`', async () => {
const controllerMessenger = getRootControllerMessenger();

jest.spyOn(controllerMessenger, 'call');

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger:
getRestrictedSnapInterfaceControllerMessenger(controllerMessenger),
});

const snapId = 'foo' as SnapId;
const content = button({ value: 'foo', name: 'foo' });

const getInterface = await getInterfaceApi(
{ content },
snapId,
controllerMessenger,
);

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const snapInterface = getInterface!();

await snapInterface.clickElement('foo');

expect(controllerMessenger.call).toHaveBeenNthCalledWith(
4,
'ExecutionService:handleRpcRequest',
snapId,
{
origin: '',
handler: HandlerType.OnUserInput,
request: {
jsonrpc: '2.0',
method: ' ',
params: {
event: {
type: UserInputEventType.ButtonClickEvent,
name: 'foo',
},
id: expect.any(String),
},
},
},
);
});

it('sends the request to the snap when using `typeInField`', async () => {
const controllerMessenger = getRootControllerMessenger();

jest.spyOn(controllerMessenger, 'call');

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger:
getRestrictedSnapInterfaceControllerMessenger(controllerMessenger),
});

const snapId = 'foo' as SnapId;
const content = input('foo');

const getInterface = await getInterfaceApi(
{ content },
snapId,
controllerMessenger,
);

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const snapInterface = getInterface!();

await snapInterface.typeInField('foo', 'bar');

expect(controllerMessenger.call).toHaveBeenNthCalledWith(
6,
'ExecutionService:handleRpcRequest',
snapId,
{
origin: '',
handler: HandlerType.OnUserInput,
request: {
jsonrpc: '2.0',
method: ' ',
params: {
event: {
type: UserInputEventType.InputChangeEvent,
name: 'foo',
value: 'bar',
},
id: expect.any(String),
},
},
},
);
});
});
54 changes: 45 additions & 9 deletions packages/snaps-jest/src/internals/request.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AbstractExecutionService } from '@metamask/snaps-controllers';
import type { SnapId } from '@metamask/snaps-sdk';
import type { SnapId, Component } from '@metamask/snaps-sdk';
import type { HandlerType } from '@metamask/snaps-utils';
import { unwrapError } from '@metamask/snaps-utils';
import { getSafeJson, hasProperty, isPlainObject } from '@metamask/utils';
Expand Down Expand Up @@ -70,11 +70,11 @@ export function handleRequest({
...options,
},
})
.then((result) => {
.then(async (result) => {
const notifications = getNotifications(store.getState());
store.dispatch(clearNotifications());

const getInterfaceFn = getInterfaceFromResult(
const getInterfaceFn = await getInterfaceApi(
result,
snapId,
controllerMessenger,
Expand Down Expand Up @@ -113,6 +113,36 @@ export function handleRequest({
return promise;
}

/**
* Get the interface ID from the result if it's available or create a new interface if the result contains static components.
*
* @param result - The handler result object.
* @param snapId - The Snap ID.
* @param controllerMessenger - The controller messenger.
* @returns The interface ID or undefined if the result doesn't include content.
*/
export async function getInterfaceFromResult(
result: unknown,
snapId: SnapId,
controllerMessenger: RootControllerMessenger,
) {
if (isPlainObject(result) && hasProperty(result, 'id')) {
return result.id as string;
}

if (isPlainObject(result) && hasProperty(result, 'content')) {
const id = await controllerMessenger.call(
'SnapInterfaceController:createInterface',
snapId,
result.content as Component,
);

return id;
}

return undefined;
}

/**
* Get the response content from the SnapInterfaceController and include the interaction methods.
*
Expand All @@ -121,25 +151,31 @@ export function handleRequest({
* @param controllerMessenger - The controller messenger.
* @returns The content components if any.
*/
export function getInterfaceFromResult(
export async function getInterfaceApi(
result: unknown,
snapId: SnapId,
controllerMessenger: RootControllerMessenger,
): (() => SnapHandlerInterface) | undefined {
if (isPlainObject(result) && hasProperty(result, 'id')) {
): Promise<(() => SnapHandlerInterface) | undefined> {
const interfaceId = await getInterfaceFromResult(
result,
snapId,
controllerMessenger,
);

if (interfaceId) {
return () => {
const { content } = controllerMessenger.call(
'SnapInterfaceController:getInterface',
snapId,
result.id as string,
interfaceId,
);

const clickElementFn: SnapInterfaceActions['clickElement'] = async (
name,
) => {
await clickElement(
controllerMessenger,
result.id as string,
interfaceId,
content,
snapId,
name,
Expand All @@ -152,7 +188,7 @@ export function getInterfaceFromResult(
) => {
await typeInField(
controllerMessenger,
result.id as string,
interfaceId,
content,
snapId,
name,
Expand Down
Loading

0 comments on commit ae4c72c

Please sign in to comment.