Skip to content
Open
Show file tree
Hide file tree
Changes from all 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

This file was deleted.

17 changes: 17 additions & 0 deletions js/packages/mobile-wallet-adapter-walletlib/test/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';

import { SolanaMWAWalletLibError, SolanaMWAWalletLibErrorCode } from '../src/errors.js';

describe('SolanaMWAWalletLibError', () => {
it('sets the expected name, message, and code', () => {
const error = new SolanaMWAWalletLibError(
SolanaMWAWalletLibErrorCode.ERROR_INTENT_DATA_NOT_FOUND,
'Intent data not found',
);

expect(error).toBeInstanceOf(Error);
expect(error.code).toBe(SolanaMWAWalletLibErrorCode.ERROR_INTENT_DATA_NOT_FOUND);
expect(error.message).toBe('Intent data not found');
expect(error.name).toBe('SolanaMWAWalletLibError');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

const { mockAddListener, mockRemove, nativeModules, platform } = vi.hoisted(() => ({
mockAddListener: vi.fn(),
mockRemove: vi.fn(),
nativeModules: {
SolanaMobileWalletAdapterWalletLib: {
resolve: vi.fn(),
},
},
platform: { OS: 'android' },
}));

vi.mock('react-native', () => ({
NativeModules: nativeModules,
NativeEventEmitter: class {
addListener = mockAddListener;
},
Platform: platform,
}));

import { initializeMWAEventListener } from '../src/initializeMWAEventListener.js';
import { MWASessionEventType } from '../src/mwaSessionEvents.js';
import { MWARequestType } from '../src/resolve.js';

afterEach(() => {
mockAddListener.mockReset();
mockRemove.mockReset();
platform.OS = 'android';
vi.restoreAllMocks();
});

describe('initializeMWAEventListener', () => {
it('routes native requests to the request handler', () => {
const handleRequest = vi.fn();
const handleSessionEvent = vi.fn();
const request = {
__type: MWARequestType.AuthorizeDappRequest,
chain: 'solana:mainnet',
requestId: 'request-1',
sessionId: 'session-1',
};

mockAddListener.mockImplementation((_eventName, listener) => {
listener(request);

return { remove: mockRemove };
});

const listener = initializeMWAEventListener(handleRequest, handleSessionEvent);

expect(mockAddListener).toHaveBeenCalledWith('MobileWalletAdapterServiceRequestBridge', expect.any(Function));
expect(handleRequest).toHaveBeenCalledWith(request);
expect(handleSessionEvent).not.toHaveBeenCalled();
expect(listener.remove).toBe(mockRemove);
});

it('routes native session events to the session-event handler', () => {
const handleRequest = vi.fn();
const handleSessionEvent = vi.fn();
const sessionEvent = {
__type: MWASessionEventType.SessionReadyEvent,
sessionId: 'session-1',
};

mockAddListener.mockImplementation((_eventName, listener) => {
listener(sessionEvent);

return { remove: mockRemove };
});

initializeMWAEventListener(handleRequest, handleSessionEvent);

expect(handleRequest).not.toHaveBeenCalled();
expect(handleSessionEvent).toHaveBeenCalledWith(sessionEvent);
});

it('warns when the native event type is unknown', () => {
const handleRequest = vi.fn();
const handleSessionEvent = vi.fn();
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

mockAddListener.mockImplementation((_eventName, listener) => {
listener({ __type: 'UNKNOWN_EVENT' });

return { remove: mockRemove };
});

initializeMWAEventListener(handleRequest, handleSessionEvent);

expect(handleRequest).not.toHaveBeenCalled();
expect(handleSessionEvent).not.toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalledWith('Unexpected native event type');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

const { mockCreateScenario, nativeModules, platform } = vi.hoisted(() => ({
mockCreateScenario: vi.fn(),
nativeModules: {
SolanaMobileWalletAdapterWalletLib: {
createScenario: vi.fn(),
},
},
platform: { OS: 'android' },
}));

nativeModules.SolanaMobileWalletAdapterWalletLib.createScenario = mockCreateScenario;

vi.mock('react-native', () => ({
NativeModules: nativeModules,
Platform: platform,
}));

import { SolanaMWAWalletLibError, SolanaMWAWalletLibErrorCode } from '../src/errors.js';
import {
initializeMobileWalletAdapterSession,
type MobileWalletAdapterConfig,
} from '../src/initializeMobileWalletAdapterSession.js';

const CONFIG: MobileWalletAdapterConfig = {
maxMessagesPerSigningRequest: 2,
maxTransactionsPerSigningRequest: 3,
noConnectionWarningTimeoutMs: 5000,
optionalFeatures: [],
supportedTransactionVersions: ['legacy'],
};

afterEach(() => {
mockCreateScenario.mockReset();
platform.OS = 'android';
});

describe('initializeMobileWalletAdapterSession', () => {
it('creates a scenario with the serialized config', async () => {
mockCreateScenario.mockResolvedValue('session-1');

await expect(initializeMobileWalletAdapterSession('Example Wallet', CONFIG)).resolves.toBe('session-1');
expect(mockCreateScenario).toHaveBeenCalledWith('Example Wallet', JSON.stringify(CONFIG));
});

it('wraps native walletlib errors with the package error class', async () => {
mockCreateScenario.mockRejectedValue(
Object.assign(new Error('Session already created'), {
code: SolanaMWAWalletLibErrorCode.ERROR_SESSION_ALREADY_CREATED,
}),
);

await expect(initializeMobileWalletAdapterSession('Example Wallet', CONFIG)).rejects.toBeInstanceOf(
SolanaMWAWalletLibError,
);
await expect(initializeMobileWalletAdapterSession('Example Wallet', CONFIG)).rejects.toEqual(
expect.objectContaining({
code: SolanaMWAWalletLibErrorCode.ERROR_SESSION_ALREADY_CREATED,
message: 'Session already created',
name: 'SolanaMWAWalletLibError',
}),
);
});

it('rethrows unknown native errors unchanged', async () => {
const error = new Error('Unexpected failure');

mockCreateScenario.mockRejectedValue(error);

await expect(initializeMobileWalletAdapterSession('Example Wallet', CONFIG)).rejects.toBe(error);
});
});
43 changes: 43 additions & 0 deletions js/packages/mobile-wallet-adapter-walletlib/test/resolve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

const { mockResolve, nativeModules, platform } = vi.hoisted(() => ({
mockResolve: vi.fn(),
nativeModules: {
SolanaMobileWalletAdapterWalletLib: {
resolve: vi.fn(),
},
},
platform: { OS: 'android' },
}));

nativeModules.SolanaMobileWalletAdapterWalletLib.resolve = mockResolve;

vi.mock('react-native', () => ({
NativeModules: nativeModules,
Platform: platform,
}));

import { type AuthorizeDappRequest, type AuthorizeDappResponse, MWARequestType, resolve } from '../src/resolve.js';

afterEach(() => {
mockResolve.mockReset();
platform.OS = 'android';
});

describe('resolve', () => {
it('serializes the request and response before forwarding them to the native module', () => {
const request: AuthorizeDappRequest = {
__type: MWARequestType.AuthorizeDappRequest,
chain: 'solana:mainnet',
requestId: 'request-1',
sessionId: 'session-1',
};
const response: AuthorizeDappResponse = {
accounts: [],
};

resolve(request, response);

expect(mockResolve).toHaveBeenCalledWith(JSON.stringify(request), JSON.stringify(response));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

const {
mockGetCallingPackage,
mockGetCallingPackageUid,
mockGetUidForPackage,
mockVerifyCallingPackage,
nativeModules,
platform,
} = vi.hoisted(() => ({
mockGetCallingPackage: vi.fn(),
mockGetCallingPackageUid: vi.fn(),
mockGetUidForPackage: vi.fn(),
mockVerifyCallingPackage: vi.fn(),
nativeModules: {
SolanaMobileDigitalAssetLinks: {
getCallingPackage: vi.fn(),
getCallingPackageUid: vi.fn(),
getUidForPackage: vi.fn(),
verifyCallingPackage: vi.fn(),
},
},
platform: { OS: 'android' },
}));

nativeModules.SolanaMobileDigitalAssetLinks.getCallingPackage = mockGetCallingPackage;
nativeModules.SolanaMobileDigitalAssetLinks.getCallingPackageUid = mockGetCallingPackageUid;
nativeModules.SolanaMobileDigitalAssetLinks.getUidForPackage = mockGetUidForPackage;
nativeModules.SolanaMobileDigitalAssetLinks.verifyCallingPackage = mockVerifyCallingPackage;

vi.mock('react-native', () => ({
NativeModules: nativeModules,
Platform: platform,
}));

import {
getCallingPackage,
getCallingPackageUid,
getUidForPackage,
verifyCallingPackage,
} from '../src/useDigitalAssetLinks.js';

afterEach(() => {
mockGetCallingPackage.mockReset();
mockGetCallingPackageUid.mockReset();
mockGetUidForPackage.mockReset();
mockVerifyCallingPackage.mockReset();
platform.OS = 'android';
});

describe('useDigitalAssetLinks helpers', () => {
it('forwards getCallingPackage to the native module', async () => {
mockGetCallingPackage.mockResolvedValue('com.example.wallet');

await expect(getCallingPackage()).resolves.toBe('com.example.wallet');
expect(mockGetCallingPackage).toHaveBeenCalledWith();
});

it('forwards getCallingPackageUid to the native module', async () => {
mockGetCallingPackageUid.mockResolvedValue(42);

await expect(getCallingPackageUid()).resolves.toBe(42);
expect(mockGetCallingPackageUid).toHaveBeenCalledWith();
});

it('forwards getUidForPackage to the native module', async () => {
mockGetUidForPackage.mockResolvedValue(42);

await expect(getUidForPackage('com.example.wallet')).resolves.toBe(42);
expect(mockGetUidForPackage).toHaveBeenCalledWith('com.example.wallet');
});

it('forwards verifyCallingPackage to the native module', async () => {
mockVerifyCallingPackage.mockResolvedValue(true);

await expect(verifyCallingPackage('https://example.com')).resolves.toBe(true);
expect(mockVerifyCallingPackage).toHaveBeenCalledWith('https://example.com');
});
});
2 changes: 1 addition & 1 deletion js/packages/mobile-wallet-adapter-walletlib/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
// }
{
"extends": "../../tsconfig.json",
"include": ["src"],
"include": ["src", "test"],
"compilerOptions": {
"declarationDir": "./lib/types",
"outDir": "lib/esm"
Expand Down
Loading