Files
mcp/test/ui/ui-tools.test.ts
zeekay c724ae360a Let the tests run again
Every suite had stopped executing. ts-jest peers on `typescript >=4.3 <7`
and this repo builds against 7, so all twenty files died inside ConfigSet
with "Cannot read properties of undefined (reading 'fileExists')" — twenty
failures, zero tests, and nothing to read them by because the push gate
deliberately runs typecheck and build rather than jest.

swc transforms instead: it strips types rather than asking the compiler
to, so it has no TypeScript version to disagree with, and `tsc --noEmit`
already answers the question ts-jest was answering twice.

Running as real ESM then exposed what the CommonJS fallback had hidden.
jest.mock does not hoist there, so a static import binds the real module
and the suite fails on `mockReturnValue is not a function` — an absent
mock reported as a broken one. The search, autogui and ui suites now
register through unstable_mockModule and import after it. One of them had
never mocked fs at all: it cast fs.promises to a mocked type and called
mockImplementation on the real module.

384 tests, twenty suites, all passing.
2026-09-01 09:42:54 -07:00

194 lines
6.5 KiB
TypeScript

import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
// Mock external dependencies
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
// hoisting for jest.mock to rely on, so a static import binds the REAL module and
// the suite dies on `mockReturnValue is not a function`.
jest.unstable_mockModule('../../src/ui/registry-api.js', () => ({
fetchRegistry: jest.fn(),
getRegistryItem: jest.fn(),
getRegistryItemUrl: jest.fn(),
}));
const registry = await import('../../src/ui/registry-api.js');
const { uiTools } = await import('../../src/ui/ui-tools.js');
describe('UI Tools', () => {
beforeEach(() => {
// Reset environment variables
delete process.env.REGISTRY_URL;
// Clear all mocks
jest.clearAllMocks();
});
describe('uiTools array', () => {
test('should export UI tools array', () => {
expect(Array.isArray(uiTools)).toBe(true);
expect(uiTools.length).toBeGreaterThan(0);
});
test('should have valid tool structures', () => {
uiTools.forEach(tool => {
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('inputSchema');
expect(tool).toHaveProperty('handler');
expect(typeof tool.name).toBe('string');
expect(typeof tool.description).toBe('string');
expect(typeof tool.handler).toBe('function');
expect(tool.inputSchema).toHaveProperty('type');
expect(tool.inputSchema.type).toBe('object');
});
});
test('should have unique tool names', () => {
const names = uiTools.map(tool => tool.name);
const uniqueNames = new Set(names);
expect(uniqueNames.size).toBe(uiTools.length);
});
});
describe('Tool functionality', () => {
test('should handle registry errors gracefully', async () => {
// Test with a tool that likely interacts with registry
const tool = uiTools.find(t => t.name.includes('list') || t.name.includes('registry'));
if (tool) {
const { fetchRegistry } = registry;
fetchRegistry.mockRejectedValue(new Error('Network error'));
const result = await tool.handler({});
// Should not throw and should handle error gracefully
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);
}
});
test('should validate input schemas', async () => {
// Test each tool's input schema structure
uiTools.forEach(tool => {
expect(tool.inputSchema).toHaveProperty('type');
expect(tool.inputSchema).toHaveProperty('properties');
if (tool.inputSchema.required) {
expect(Array.isArray(tool.inputSchema.required)).toBe(true);
}
});
});
test('should handle empty/minimal inputs', async () => {
// Test tools that don't require parameters
const toolsWithoutRequired = uiTools.filter(tool =>
!tool.inputSchema.required || tool.inputSchema.required.length === 0
);
for (const tool of toolsWithoutRequired) {
try {
const result = await tool.handler({});
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);
} catch (error) {
// Tool might require external dependencies that aren't available in test
// This is acceptable for UI tools that depend on external registries
expect(error).toBeDefined();
}
}
});
});
describe('Registry caching', () => {
test('should handle registry URL from environment', () => {
const originalUrl = process.env.REGISTRY_URL;
process.env.REGISTRY_URL = 'https://test.example.com/registry.json';
// Test that environment variable is respected
expect(process.env.REGISTRY_URL).toBe('https://test.example.com/registry.json');
// Restore original value
if (originalUrl) {
process.env.REGISTRY_URL = originalUrl;
} else {
delete process.env.REGISTRY_URL;
}
});
});
describe('Error handling', () => {
test('should handle network failures', async () => {
const { fetchRegistry } = registry;
fetchRegistry.mockRejectedValue(new Error('Network timeout'));
// Try to execute tools that might depend on network
const networkDependentTools = uiTools.filter(tool =>
tool.description.toLowerCase().includes('registry') ||
tool.description.toLowerCase().includes('component')
);
for (const tool of networkDependentTools) {
try {
const result = await tool.handler({});
// Should handle network errors gracefully
expect(result).toHaveProperty('content');
} catch (error) {
// Acceptable for tools that require network access
expect(error).toBeDefined();
}
}
});
test('should handle malformed registry data', async () => {
const { fetchRegistry } = registry;
fetchRegistry.mockResolvedValue([{ invalid: 'data' }]);
const registryTools = uiTools.filter(tool =>
tool.name.includes('registry') || tool.name.includes('list')
);
for (const tool of registryTools) {
try {
const result = await tool.handler({});
expect(result).toHaveProperty('content');
} catch (error) {
// Should handle malformed data gracefully
expect(error).toBeDefined();
}
}
});
});
describe('Integration capabilities', () => {
test('should provide component-related functionality', () => {
const componentTools = uiTools.filter(tool =>
tool.description.toLowerCase().includes('component') ||
tool.name.includes('component')
);
expect(componentTools.length).toBeGreaterThan(0);
componentTools.forEach(tool => {
expect(tool.description).toBeTruthy();
expect(tool.inputSchema).toBeTruthy();
});
});
test('should provide registry interaction capabilities', () => {
const registryTools = uiTools.filter(tool =>
tool.description.toLowerCase().includes('registry') ||
tool.name.includes('registry') ||
tool.name.includes('list')
);
expect(registryTools.length).toBeGreaterThan(0);
registryTools.forEach(tool => {
expect(tool.description).toBeTruthy();
expect(tool.inputSchema).toBeTruthy();
});
});
});
});