Skip to main content
Glama
analyze-mobile-app-tool.test.ts15.6 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { registerAnalyzeMobileAppTool } from '../../src/tools/analyze-mobile-app.js'; import { Config } from '../../src/config/index.js'; import { OpenRouterClient } from '../../src/utils/openrouter-client.js'; import { ImageProcessor } from '../../src/utils/image-processor.js'; import { Logger } from '../../src/utils/logger.js'; // Mock dependencies vi.mock('../../src/config/index.js'); vi.mock('../../src/utils/openrouter-client.js'); vi.mock('../../src/utils/image-processor.js'); vi.mock('../../src/utils/logger.js'); const MockedConfig = vi.mocked(Config); const MockedOpenRouterClient = vi.mocked(OpenRouterClient); const MockedImageProcessor = vi.mocked(ImageProcessor); const MockedLogger = vi.mocked(Logger); describe('registerAnalyzeMobileAppTool', () => { let mockServer: any; let mockConfig: any; let mockOpenRouterClient: any; let mockImageProcessor: any; let mockLogger: any; beforeEach(() => { vi.clearAllMocks(); // Create mocks mockServer = { setRequestHandler: vi.fn(), }; mockConfig = { getOpenRouterConfig: vi.fn(() => ({ apiKey: 'test-api-key', model: 'test-model', })), getServerConfig: vi.fn(() => ({ maxImageSize: 10485760, })), }; mockOpenRouterClient = { analyzeImage: vi.fn(), }; mockImageProcessor = { processImage: vi.fn(), isValidImageType: vi.fn(() => true), }; mockLogger = { getInstance: vi.fn(() => ({ info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn(), })), }; // Setup mock returns MockedConfig.getInstance = vi.fn(() => mockConfig); MockedOpenRouterClient.getInstance = vi.fn(() => mockOpenRouterClient); MockedImageProcessor.getInstance = vi.fn(() => mockImageProcessor); MockedLogger.getInstance = mockLogger.getInstance; // Register the tool registerAnalyzeMobileAppTool(mockServer); }); afterEach(() => { vi.resetAllMocks(); }); describe('tool registration', () => { it('should register the tool handler', () => { expect(mockServer.setRequestHandler).toHaveBeenCalledWith( expect.anything(), expect.any(Function) ); }); }); describe('tool handler', () => { let toolHandler: any; beforeEach(() => { // Get the registered handler const handlerCall = mockServer.setRequestHandler.mock.calls[0]; toolHandler = handlerCall[1]; }); it('should successfully analyze mobile app screenshot with default settings', async () => { const mockProcessedImage = { data: 'base64mobile', mimeType: 'image/png', size: 3000, }; const mockAnalysisResult = { success: true, analysis: '{"platform": "ios", "ui_patterns": ["navigation_bar", "tab_bar"]}', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/mobile-screenshot.png', }, }, }; const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64mobile', 'image/png', expect.stringContaining('mobile app screenshot'), { format: 'json', maxTokens: 4000, } ); expect(result).toEqual({ content: [ { type: 'text', text: '{"platform": "ios", "ui_patterns": ["navigation_bar", "tab_bar"]}', }, ], }); }); it('should analyze mobile app with iOS platform specified', async () => { const mockProcessedImage = { data: 'base64ios', mimeType: 'image/jpeg', size: 2500, }; const mockAnalysisResult = { success: true, analysis: '{"platform": "ios", "ios_elements": ["navigation_bar", "safe_area"], "design_system": "human_interface_guidelines"}', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/ios-screenshot.jpg', platform: 'ios', focusArea: 'ui-design', format: 'json', }, }, }; const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64ios', 'image/jpeg', expect.stringContaining('ios platform conventions and design patterns'), expect.stringContaining('UI design elements'), { format: 'json', maxTokens: 4000, } ); expect(result).toEqual({ content: [ { type: 'text', text: '{"platform": "ios", "ios_elements": ["navigation_bar", "safe_area"], "design_system": "human_interface_guidelines"}', }, ], }); }); it('should analyze mobile app with Android platform specified', async () => { const mockProcessedImage = { data: 'base64android', mimeType: 'image/webp', size: 3500, }; const mockAnalysisResult = { success: true, analysis: '{"platform": "android", "material_design": true, "components": ["floating_action_button", "app_bar"]}', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'base64', data: 'base64data', mimeType: 'image/webp', platform: 'android', focusArea: 'navigation', }, }, }; const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64android', 'image/webp', expect.stringContaining('android platform conventions and design patterns'), expect.stringContaining('navigation patterns'), { format: 'json', maxTokens: 4000, } ); expect(result).toEqual({ content: [ { type: 'text', text: '{"platform": "android", "material_design": true, "components": ["floating_action_button", "app_bar"]}', }, ], }); }); it('should analyze mobile app with auto-detect platform', async () => { const mockProcessedImage = { data: 'base64auto', mimeType: 'image/png', size: 2800, }; const mockAnalysisResult = { success: true, analysis: '{"platform": "android", "ui_heuristics": {"gestures": ["swipe", "tap"], "accessibility": "good"}}', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/auto-detect.png', platform: 'auto-detect', includeUXHeuristics: true, focusArea: 'user-experience', }, }, }; const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64auto', 'image/png', expect.stringContaining('mobile app screenshot'), expect.stringContaining('user experience'), { format: 'json', maxTokens: 4000, } ); expect(result).toEqual({ content: [ { type: 'text', text: '{"platform": "android", "ui_heuristics": {"gestures": ["swipe", "tap"], "accessibility": "good"}}', }, ], }); }); it('should analyze mobile app with different focus areas', async () => { const focusAreas = ['ui-design', 'user-experience', 'navigation', 'accessibility', 'performance', 'onboarding']; const mockProcessedImage = { data: 'base64mobile', mimeType: 'image/png', size: 3000, }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: true, analysis: 'Analysis complete', model: 'test-model', }); for (const focusArea of focusAreas) { const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/mobile-screenshot.png', platform: 'ios', focusArea, }, }, }; await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64mobile', 'image/png', expect.stringContaining('iOS mobile app'), expect.stringContaining(focusArea), { format: 'json', maxTokens: 4000, } ); } }); it('should handle UX heuristics disabled', async () => { const mockProcessedImage = { data: 'base64mobile', mimeType: 'image/png', size: 3000, }; const mockAnalysisResult = { success: true, analysis: '{"platform": "android", "basic_ui": ["buttons", "text_fields"]}', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/mobile-screenshot.png', platform: 'android', includeUXHeuristics: false, focusArea: 'ui-design', }, }, }; const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64mobile', 'image/png', expect.stringContaining('mobile app screenshot'), expect.stringContaining('UI design'), { format: 'json', maxTokens: 4000, } ); expect(result).toEqual({ content: [ { type: 'text', text: '{"platform": "android", "basic_ui": ["buttons", "text_fields"]}', }, ], }); }); it('should handle text format output', async () => { const mockProcessedImage = { data: 'base64mobile', mimeType: 'image/png', size: 3000, }; const mockAnalysisResult = { success: true, analysis: 'The mobile app follows Material Design principles with a clean layout and intuitive navigation.', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/mobile-screenshot.png', platform: 'android', format: 'text', focusArea: 'accessibility', }, }, }; const result = await toolHandler(request); expect(mockOpenRouterClient.analyzeImage).toHaveBeenCalledWith( 'base64mobile', 'image/png', expect.stringContaining('mobile app screenshot'), expect.stringContaining('accessibility'), { format: 'text', maxTokens: 4000, } ); expect(result).toEqual({ content: [ { type: 'text', text: 'The mobile app follows Material Design principles with a clean layout and intuitive navigation.', }, ], }); }); it('should handle URL input for mobile app screenshots', async () => { const mockProcessedImage = { data: 'base64fromurl', mimeType: 'image/jpeg', size: 3200, }; const mockAnalysisResult = { success: true, analysis: '{"platform": "ios", "app_type": "social_media", "engagement_features": ["likes", "shares"]}', model: 'test-model', }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue(mockAnalysisResult); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'url', data: 'https://example.com/mobile-app-screenshot.jpg', platform: 'ios', focusArea: 'onboarding', }, }, }; const result = await toolHandler(request); expect(mockImageProcessor.processImage).toHaveBeenCalledWith({ type: 'url', data: 'https://example.com/mobile-app-screenshot.jpg', mimeType: undefined, }); expect(result).toEqual({ content: [ { type: 'text', text: '{"platform": "ios", "app_type": "social_media", "engagement_features": ["likes", "shares"]}', }, ], }); }); it('should reject unknown tool names', async () => { const request = { params: { name: 'unknown_mobile_tool', arguments: {}, }, }; await expect(toolHandler(request)).rejects.toThrow('Unknown tool: unknown_mobile_tool'); }); it('should reject requests without arguments', async () => { const request = { params: { name: 'analyze_mobile_app_screenshot', }, }; await expect(toolHandler(request)).rejects.toThrow('Arguments are required'); }); it('should handle analysis errors gracefully', async () => { const mockProcessedImage = { data: 'base64mobile', mimeType: 'image/png', size: 3000, }; mockImageProcessor.processImage.mockResolvedValue(mockProcessedImage); mockOpenRouterClient.analyzeImage.mockResolvedValue({ success: false, error: 'Mobile app analysis failed', }); const request = { params: { name: 'analyze_mobile_app_screenshot', arguments: { type: 'file', data: '/path/to/mobile-screenshot.png', }, }, }; const result = await toolHandler(request); expect(result).toEqual({ content: [ { type: 'text', text: 'Error: Mobile app analysis failed', }, ], isError: true, }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JonathanJude/openrouter-image-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server