Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a134df
update notification example snap and test-snaps
hmalik88 Dec 14, 2024
eb9a0aa
update struct
hmalik88 Dec 16, 2024
c2d9d06
update notification validator
hmalik88 Dec 16, 2024
0297ea9
add snaps jest support for expanded notifications, update example snap
hmalik88 Dec 17, 2024
f694dfe
Merge branch 'main' into hm/update-notification-example
hmalik88 Dec 17, 2024
2320d5c
update matchers & test
hmalik88 Dec 17, 2024
5038962
fix test
hmalik88 Dec 17, 2024
7b415bc
update manifest
hmalik88 Dec 17, 2024
2cb571f
fix matchers to also include param for content
hmalik88 Dec 18, 2024
b19d893
clean up notification matcher
hmalik88 Dec 19, 2024
18b7ec1
Merge branch 'main' into hm/update-notification-example
hmalik88 Dec 19, 2024
2e483dc
more fixes
hmalik88 Dec 23, 2024
692bb09
fix type
hmalik88 Dec 23, 2024
3251730
fix another type
hmalik88 Dec 23, 2024
da600f0
Merge branch 'main' into hm/update-notification-example
hmalik88 Dec 23, 2024
30a2a51
fix type once more
hmalik88 Dec 23, 2024
d6ecb1e
update formatting and types
hmalik88 Dec 23, 2024
8a4f74a
use correct struct
hmalik88 Dec 23, 2024
b5c8a6c
Merge branch 'main' into hm/update-notification-example
hmalik88 Jan 6, 2025
3314fcd
Merge branch 'main' into hm/update-notification-example
hmalik88 Jan 8, 2025
3c366a7
apply code review
hmalik88 Jan 8, 2025
e01fe8d
Merge branch 'main' into hm/update-notification-example
hmalik88 Jan 10, 2025
4886c48
update per comments
hmalik88 Jan 10, 2025
9ccf7b6
Merge branch 'main' into hm/update-notification-example
hmalik88 Jan 10, 2025
ca6b3dc
fix type
hmalik88 Jan 10, 2025
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
2 changes: 1 addition & 1 deletion packages/examples/packages/notifications/snap.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SnapConfig } from '@metamask/snaps-cli';

const config: SnapConfig = {
input: './src/index.ts',
input: './src/index.tsx',
server: {
port: 8016,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "BCoBVWLL9kEMS4mzBTMcdS5vPQvj8L9ghKBJLng9mg0=",
"shasum": "Eb+BA76BVAepPArIg/rG5eUOOHDm4mbiHE/GzpFOeHo=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ describe('onRpcRequest', () => {
});
});

describe('inApp-expanded', () => {
it('sends an expanded view notification', async () => {
const { request } = await installSnap();

const response = await request({
method: 'inApp-expanded',
origin: 'Jest',
});

expect(response).toRespondWith(null);
expect(response).toSendNotification(
'Hello from MetaMask, click here for an expanded view!',
NotificationType.InApp,
'Hello World!',
{ text: 'Go home', href: 'metamask://client/' },
);
});
});

describe('native', () => {
it('sends a native notification', async () => {
const { request } = await installSnap();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { MethodNotFoundError, NotificationType } from '@metamask/snaps-sdk';
import type { OnRpcRequestHandler } from '@metamask/snaps-sdk';
import { Box, Row, Address } from '@metamask/snaps-sdk/jsx';

/**
* Handle incoming JSON-RPC requests from the dapp, sent through the
Expand Down Expand Up @@ -39,6 +40,28 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
},
});

case 'inApp-expanded':
return await snap.request({
method: 'snap_notify',
params: {
type: NotificationType.InApp,
message: 'Hello from MetaMask, click here for an expanded view!',
title: 'Hello World!',
content: (
<Box>
<Row
label="From"
variant="warning"
tooltip="This address has been deemed dangerous."
>
<Address address="0x1234567890123456789012345678901234567890" />
</Row>
</Box>
),
footerLink: { text: 'Go home', href: 'metamask://client/' },
},
});

default:
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw new MethodNotFoundError({ method: request.method });
Expand Down
2 changes: 2 additions & 0 deletions packages/snaps-jest/src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ interface SnapsMatchers {
toSendNotification(
message: string,
type?: EnumToUnion<NotificationType>,
title?: string,
footerLink?: { text: string; href: string },
): void;

/**
Expand Down
107 changes: 102 additions & 5 deletions packages/snaps-jest/src/matchers.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@jest/globals';
import { panel, text } from '@metamask/snaps-sdk';
import { NotificationType, panel, text } from '@metamask/snaps-sdk';
import { Box, Text } from '@metamask/snaps-sdk/jsx';

import {
Expand Down Expand Up @@ -160,35 +160,71 @@ describe('toRespondWithError', () => {
});

describe('toSendNotification', () => {
it('passes when the notification is correct', () => {
it('passes when a notification is correct', () => {
expect(
getMockResponse({
notifications: [
{
id: '1',
type: 'native',
type: NotificationType.Native,
message: 'foo',
},
],
}),
).toSendNotification('foo');
});

it('passes when an expanded view notification is correct', () => {
expect(
getMockResponse({
notifications: [
{
id: '1',
type: NotificationType.InApp,
message: 'foo',
title: 'bar',
content: 'abcd',
footerLink: { text: 'foo', href: 'https://metamask.io' },
},
],
}),
).toSendNotification('foo', 'inApp', 'bar', {
text: 'foo',
href: 'https://metamask.io',
});
});

it('passes when an expanded view notification without footer is correct', () => {
expect(
getMockResponse({
notifications: [
{
id: '1',
type: NotificationType.InApp,
message: 'foo',
title: 'bar',
content: 'abcd',
},
],
}),
).toSendNotification('foo', 'inApp', 'bar');
});

it('passes when the notification is correct with a type', () => {
expect(
getMockResponse({
notifications: [
{
id: '1',
type: 'native',
type: NotificationType.Native,
message: 'foo',
},
],
}),
).toSendNotification('foo', 'native');
});

it('fails when the notification is incorrect', () => {
it('fails when a notification is incorrect', () => {
expect(() =>
expect(
getMockResponse({
Expand All @@ -204,6 +240,67 @@ describe('toSendNotification', () => {
).toThrow('Received:');
});

it("fails when an expanded view notification's title is incorrect", () => {
expect(() =>
expect(
getMockResponse({
notifications: [
{
id: '1',
type: 'inApp',
message: 'foo',
title: 'bar',
content: 'abcd',
},
],
}),
).toSendNotification('foo', 'inApp', 'baz'),
).toThrow('Received:');
});

it("fails when an expanded view notification's footerLink is incorrect", () => {
expect(() =>
expect(
getMockResponse({
notifications: [
{
id: '1',
type: 'inApp',
message: 'foo',
title: 'bar',
content: 'abcd',
footerLink: { text: 'Leave site', href: 'https://metamask.io' },
},
],
}),
).toSendNotification('foo', 'inApp', 'bar', {
text: 'Go back',
href: 'metamask://client/',
}),
).toThrow('Received:');
});

it("fails when an expanded view notification's content is missing", () => {
expect(() =>
expect(
getMockResponse({
notifications: [
{
id: '1',
type: 'inApp',
message: 'foo',
title: 'bar',
footerLink: { text: 'Leave site', href: 'https://metamask.io' },
},
],
}),
).toSendNotification('foo', 'inApp', 'bar', {
text: 'Leave site',
href: 'https://metamask.io',
}),
).toThrow('Received:');
});

it('fails when the notification is incorrect with a type', () => {
expect(() =>
expect(
Expand Down
83 changes: 69 additions & 14 deletions packages/snaps-jest/src/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import type { MatcherFunction } from '@jest/expect';
import { expect } from '@jest/globals';
import type {
NotificationType,
EnumToUnion,
ComponentOrElement,
Component,
} from '@metamask/snaps-sdk';
import { NotificationType } from '@metamask/snaps-sdk';
import type { JSXElement } from '@metamask/snaps-sdk/jsx';
import { isJSXElementUnsafe } from '@metamask/snaps-sdk/jsx';
import type { SnapResponse } from '@metamask/snaps-simulation';
Expand Down Expand Up @@ -158,32 +158,87 @@ export const toRespondWithError: MatcherFunction<[expected: Json]> = function (
* is intended to be used with the `expect` global.
*
* @param actual - The actual response.
* @param expected - The expected notification message.
* @param type - The expected notification type.
* @param expectedMessage - The expected notification message.
* @param expectedType - The expected notification type.
* @param expectedTitle - The expected notification title.
* @param expectedFooterLink - The expected footer link object.
* @returns The status and message.
*/
export const toSendNotification: MatcherFunction<
[expected: string, type?: EnumToUnion<NotificationType> | undefined]
> = function (actual, expected, type) {
[
expectedMessage: string,
expectedType?: EnumToUnion<NotificationType> | undefined,
expectedTitle?: string | undefined,
expectedFooterLink?: { text: string; href: string } | undefined,
]
> = function (
actual,
expectedMessage,
expectedType,
expectedTitle,
expectedFooterLink,
) {
assertActualIsSnapResponse(actual, 'toSendNotification');

const notificationValidator = (
notification: SnapResponse['notifications'][number],
) => {
const { type, message, title, content, footerLink } = notification;
const hasExpectedType = expectedType !== undefined;
const hasExpectedTitle = expectedTitle !== undefined;
const hasExpectedFooterLink = expectedFooterLink !== undefined;

if (!this.equals(message, expectedMessage)) {
return false;
}

if (hasExpectedType && type !== expectedType) {
return false;
}

if (type === NotificationType.InApp) {
if (hasExpectedTitle && !this.equals(title, expectedTitle)) {
return false;
}

if (title && !content) {
return false;
}

if (
hasExpectedFooterLink &&
!this.equals(footerLink, expectedFooterLink)
) {
return false;
}
}

return true;
};

const { notifications } = actual;
const pass = notifications.some(
(notification) =>
this.equals(notification.message, expected) &&
(type === undefined || notification.type === type),
);
const pass = notifications.some(notificationValidator);

const message = pass
? () =>
`${this.utils.matcherHint('.not.toSendNotification')}\n\n` +
`Expected: ${this.utils.printExpected(expected)}\n` +
`Expected type: ${this.utils.printExpected(type)}\n` +
`Expected message: ${this.utils.printExpected(expectedMessage)}\n` +
`Expected type: ${this.utils.printExpected(expectedType)}\n` +
`Expected title: ${this.utils.printExpected(expectedTitle)}\n` +
`Expected footer link: ${this.utils.printExpected(
expectedFooterLink,
)}\n` +
`Expected content id type: ${this.utils.printExpected('String')}\n` +
`Received: ${this.utils.printReceived(notifications)}`
: () =>
`${this.utils.matcherHint('.toSendNotification')}\n\n` +
`Expected: ${this.utils.printExpected(expected)}\n` +
`Expected type: ${this.utils.printExpected(type)}\n` +
`Expected message: ${this.utils.printExpected(expectedMessage)}\n` +
`Expected type: ${this.utils.printExpected(expectedType)}\n` +
`Expected title: ${this.utils.printExpected(expectedTitle)}\n` +
`Expected footer link: ${this.utils.printExpected(
expectedFooterLink,
)}\n` +
`Expected content id type: ${this.utils.printExpected('String')}\n` +
`Received: ${this.utils.printReceived(notifications)}`;

return { message, pass };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ describe('getShowInAppNotificationImplementation', () => {
id: expect.any(String),
type: NotificationType.InApp,
message: 'message',
content: undefined,
title: undefined,
footerLink: undefined,
},
]);
});
Expand Down
Loading
Loading