Skip to main content
Glama

MCP Xcode

by Stefan-Nitu
BuildXcodePresenter.unit.test.ts14.4 kB
import { describe, it, expect } from '@jest/globals'; import { BuildXcodePresenter } from '../../../../presentation/presenters/BuildXcodePresenter.js'; import { BuildResult } from '../../domain/BuildResult.js'; import { BuildIssue } from '../../domain/BuildIssue.js'; import { Platform } from '../../../../shared/domain/Platform.js'; describe('BuildXcodePresenter', () => { // Factory method for creating SUT - DAMP approach function createSUT(): BuildXcodePresenter { return new BuildXcodePresenter(); } // Test data factories function createTestMetadata(overrides = {}) { return { scheme: 'MyApp', platform: Platform.iOS, configuration: 'Debug', showWarningDetails: false, ...overrides }; } function createSuccessResult(appPath?: string, logPath?: string): BuildResult { return BuildResult.succeeded(appPath, logPath); } function createFailureResult(issues: BuildIssue[] = [], logPath?: string): BuildResult { return BuildResult.failed(issues, 1, logPath); } describe('when presenting successful build', () => { it('should show success message with scheme', () => { const presenter = createSUT(); const result = createSuccessResult(); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].type).toBe('text'); expect(response.content[0].text).toContain('✅ Build succeeded: MyApp'); }); it('should show warning count for successful build with warnings', () => { const presenter = createSUT(); // Create a successful build result with warnings const warnings = [ BuildIssue.warning('Deprecated API', 'api.swift', 5), BuildIssue.warning('Unused variable', 'vars.swift', 15) ]; const result = BuildResult.succeeded('/path/to/app.app', undefined, warnings); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('✅ Build succeeded: MyApp'); expect(response.content[0].text).toContain('Warnings: 2'); expect(response.content[0].text).not.toContain('Deprecated API'); // Details not shown by default }); it('should show warning details for successful build when requested', () => { const presenter = createSUT(); const warnings = [ BuildIssue.warning('Deprecated API', 'api.swift', 5), BuildIssue.warning('Unused variable', 'vars.swift', 15) ]; const result = BuildResult.succeeded('/path/to/app.app', undefined, warnings); const metadata = createTestMetadata({ showWarningDetails: true }); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('✅ Build succeeded: MyApp'); expect(response.content[0].text).toContain('Warnings: 2'); expect(response.content[0].text).toContain('⚠️ Warnings:'); expect(response.content[0].text).toContain('Deprecated API'); expect(response.content[0].text).toContain('Unused variable'); }); it('should include platform and configuration', () => { const presenter = createSUT(); const result = createSuccessResult(); const metadata = { scheme: 'MyApp', platform: Platform.iOS, configuration: 'Release' }; const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('Platform: iOS'); expect(response.content[0].text).toContain('Configuration: Release'); }); it('should show app path when available', () => { const presenter = createSUT(); const appPath = '/path/to/MyApp.app'; const result = createSuccessResult(appPath); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain(`App path: ${appPath}`); }); it('should show N/A when app path is not available', () => { const presenter = createSUT(); const result = createSuccessResult(undefined); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('App path: N/A'); }); it('should show log path when available', () => { const presenter = createSUT(); const logPath = '/var/logs/build.log'; const result = createSuccessResult(undefined, logPath); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('📁 Full logs saved to: /var/logs/build.log'); }); }); describe('when presenting failed build with errors', () => { it('should show failure message with scheme', () => { const presenter = createSUT(); const result = createFailureResult(); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('❌ Build failed: MyApp'); expect(response.content[0].text).toContain('Platform: iOS'); expect(response.content[0].text).toContain('Configuration: Debug'); }); it('should show all errors when there are less than 50', () => { const presenter = createSUT(); const errors = Array.from({ length: 30 }, (_, i) => BuildIssue.error(`Error ${i + 1}`, `file${i}.swift`, (i + 1) * 10) ); const result = createFailureResult(errors); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('❌ Errors (30):'); expect(text).toContain('Error 1'); expect(text).toContain('Error 30'); expect(text).not.toContain('... and'); }); it('should limit to 50 errors when there are more', () => { const presenter = createSUT(); const errors = Array.from({ length: 75 }, (_, i) => BuildIssue.error(`Error ${i + 1}`, `file${i}.swift`, (i + 1) * 10) ); const result = createFailureResult(errors); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('❌ Errors (75):'); expect(text).toContain('Error 1'); expect(text).toContain('Error 50'); expect(text).not.toContain('Error 51'); expect(text).toContain('... and 25 more errors'); }); it('should handle exactly 50 errors without truncation message', () => { const presenter = createSUT(); const errors = Array.from({ length: 50 }, (_, i) => BuildIssue.error(`Error ${i + 1}`, `file${i}.swift`) ); const result = createFailureResult(errors); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('❌ Errors (50):'); expect(text).toContain('Error 50'); expect(text).not.toContain('... and'); }); }); describe('when handling warnings', () => { it('should show warning count but not details by default', () => { const presenter = createSUT(); const warnings = [ BuildIssue.warning('Deprecated API', 'api.swift', 5), BuildIssue.warning('Unused variable', 'vars.swift', 15) ]; const result = createFailureResult(warnings); const metadata = createTestMetadata(); // showWarningDetails defaults to false const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('⚠️ Warnings: 2'); expect(text).not.toContain('Deprecated API'); expect(text).not.toContain('Unused variable'); }); it('should show warning details when showWarningDetails is true', () => { const presenter = createSUT(); const warnings = [ BuildIssue.warning('Deprecated API', 'api.swift', 5), BuildIssue.warning('Unused variable', 'vars.swift', 15) ]; const result = createFailureResult(warnings); const metadata = createTestMetadata({ showWarningDetails: true }); const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('⚠️ Warnings (2):'); expect(text).toContain('Deprecated API'); expect(text).toContain('Unused variable'); }); it('should show only count when showWarningDetails is explicitly false', () => { const presenter = createSUT(); const warnings = [BuildIssue.warning('Deprecated API')]; const result = createFailureResult(warnings); const metadata = { scheme: 'MyApp', platform: Platform.iOS, configuration: 'Debug', showWarningDetails: false }; const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('⚠️ Warnings: 1'); expect(text).not.toContain('Deprecated API'); }); it('should limit warnings to 20 when showing details', () => { const presenter = createSUT(); const warnings = Array.from({ length: 30 }, (_, i) => BuildIssue.warning(`Warning ${i + 1}`, `file${i}.swift`) ); const result = createFailureResult(warnings); const metadata = createTestMetadata({ showWarningDetails: true }); const response = presenter.present(result, metadata); const text = response.content[0].text; expect(text).toContain('⚠️ Warnings (30):'); expect(text).toContain('Warning 1'); expect(text).toContain('Warning 20'); expect(text).not.toContain('Warning 21'); expect(text).toContain('... and 10 more warnings'); }); it('should show errors with warning count or details based on flag', () => { const presenter = createSUT(); const issues = [ BuildIssue.error('Compile error', 'main.swift'), BuildIssue.warning('Unused import', 'imports.swift') ]; const result = createFailureResult(issues); // With warning details const responseWithDetails = presenter.present(result, createTestMetadata({ showWarningDetails: true })); expect(responseWithDetails.content[0].text).toContain('❌ Errors (1):'); expect(responseWithDetails.content[0].text).toContain('Compile error'); expect(responseWithDetails.content[0].text).toContain('⚠️ Warnings (1):'); expect(responseWithDetails.content[0].text).toContain('Unused import'); // Without warning details (just count) const responseWithoutDetails = presenter.present(result, createTestMetadata({ showWarningDetails: false })); expect(responseWithoutDetails.content[0].text).toContain('❌ Errors (1):'); expect(responseWithoutDetails.content[0].text).toContain('Compile error'); expect(responseWithoutDetails.content[0].text).toContain('⚠️ Warnings: 1'); expect(responseWithoutDetails.content[0].text).not.toContain('Unused import'); }); }); describe('when formatting issues', () => { it('should use toString method when available', () => { const presenter = createSUT(); const issue = BuildIssue.error('Test error', 'test.swift', 10, 5); const result = createFailureResult([issue]); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); // BuildIssue.toString() formats as "file:line:column: message" expect(response.content[0].text).toContain('test.swift:10:5: Test error'); }); it('should handle issues without file location', () => { const presenter = createSUT(); const issue = BuildIssue.error('General error'); const result = createFailureResult([issue]); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response.content[0].text).toContain('General error'); }); }); describe('when presenting generic errors', () => { it('should format error with message', () => { const presenter = createSUT(); const error = new Error('Something went wrong'); const response = presenter.presentError(error); expect(response.content[0].type).toBe('text'); expect(response.content[0].text).toBe('❌ Something went wrong'); }); }); describe('MCP response format', () => { it('should always return content array with type and text', () => { const presenter = createSUT(); const result = createSuccessResult(); const metadata = createTestMetadata(); const response = presenter.present(result, metadata); expect(response).toHaveProperty('content'); expect(Array.isArray(response.content)).toBe(true); expect(response.content.length).toBeGreaterThan(0); expect(response.content[0]).toHaveProperty('type'); expect(response.content[0]).toHaveProperty('text'); expect(response.content[0].type).toBe('text'); }); }); describe('when presenting errors', () => { it('should format validation errors in user-friendly way', () => { const presenter = createSUT(); // Use actual domain error from BuildRequest const schemeError = new Error('Scheme is required'); schemeError.name = 'BuildRequest.RequiredSchemeError'; const response = presenter.presentError(schemeError); // We WANT user-friendly messages, not JSON expect(response.content[0].text).toBe('❌ Scheme is required'); expect(response.content[0].text).not.toContain('JSON'); expect(response.content[0].text).not.toContain('[{'); }); it('should handle non-validation errors normally', () => { const presenter = createSUT(); const error = new Error('Project path does not exist'); const response = presenter.presentError(error); expect(response.content[0].text).toBe('❌ Project path does not exist'); }); }); });

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/Stefan-Nitu/mcp-xcode'

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