/**
* Direct handler tests for Phosphor Icons MCP Server
* Tests the tool handlers directly without needing the full MCP server
*/
import { z } from 'zod';
// Mock the icon metadata
const POPULAR_ICONS = [
{ name: "house", category: "interface", tags: ["home", "main", "dashboard"] },
{ name: "heart", category: "social", tags: ["like", "favorite", "love"] },
{ name: "user", category: "people", tags: ["person", "profile", "account"] },
{ name: "gear", category: "interface", tags: ["settings", "config", "preferences"] },
];
const PHOSPHOR_CORE_RAW_BASE = "https://raw.githubusercontent.com/phosphor-icons/core/main/assets";
console.log('๐งช Testing Phosphor Icons MCP Handlers\n');
console.log('='.repeat(60));
// Test 1: Test fetching an icon
async function testFetchIcon() {
console.log('\n๐ฅ Test 1: Fetching icon from GitHub');
try {
const iconName = 'heart'; // Use heart which we know exists
const weight = 'regular';
const url = `${PHOSPHOR_CORE_RAW_BASE}/${weight}/${iconName}.svg`;
const response = await fetch(url, {
headers: { 'User-Agent': 'PhosphorIconsMCP/1.0.0' }
});
if (response.ok) {
const svg = await response.text();
const isValidSvg = svg.trim().startsWith('<svg');
console.log('โ
Icon fetched successfully');
console.log(' Valid SVG:', isValidSvg ? 'โ
' : 'โ');
console.log(' Size:', svg.length, 'bytes');
console.log(' Preview:', svg.substring(0, 100).replace(/\n/g, ' ') + '...');
return true;
} else {
console.log('โ Failed to fetch icon:', response.status, response.statusText);
return false;
}
} catch (error) {
console.log('โ Error:', error.message);
return false;
}
}
// Test 2: Test icon with color modification
async function testIconColorModification() {
console.log('\n๐จ Test 2: Icon color modification');
try {
const iconName = 'heart';
const weight = 'fill';
// For non-regular weights, filename is {name}-{weight}.svg
const fileName = weight === 'regular' ? `${iconName}.svg` : `${iconName}-${weight}.svg`;
const url = `${PHOSPHOR_CORE_RAW_BASE}/${weight}/${fileName}`;
const response = await fetch(url, {
headers: { 'User-Agent': 'PhosphorIconsMCP/1.0.0' }
});
if (response.ok) {
let svg = await response.text();
const originalSvg = svg;
// Apply color (simulating the handler logic for fill weight)
svg = svg.replace(/fill="[^"]*"/g, `fill="#FF0000"`);
const hasColor = svg.includes('#FF0000') || svg.includes('fill="#FF0000"');
console.log('โ
Color modification test');
console.log(' Color applied:', hasColor ? 'โ
' : 'โ');
console.log(' SVG changed:', originalSvg !== svg ? 'โ
' : 'โ');
return true;
} else {
console.log('โ Failed to fetch icon:', response.status, response.statusText);
return false;
}
} catch (error) {
console.log('โ Error:', error.message);
return false;
}
}
// Test 3: Test icon size modification
async function testIconSizeModification() {
console.log('\n๐ Test 3: Icon size modification');
try {
const iconName = 'heart'; // Use heart which we know exists
const weight = 'regular';
const fileName = weight === 'regular' ? `${iconName}.svg` : `${iconName}-${weight}.svg`;
const url = `${PHOSPHOR_CORE_RAW_BASE}/${weight}/${fileName}`;
const response = await fetch(url, {
headers: { 'User-Agent': 'PhosphorIconsMCP/1.0.0' }
});
if (response.ok) {
let svg = await response.text();
const originalSvg = svg;
// Apply size (simulating the handler logic - check if width exists first)
const size = 32;
if (!svg.includes('width=')) {
svg = svg.replace(/<svg([^>]*)>/, `<svg$1 width="${size}" height="${size}">`);
} else {
svg = svg.replace(/width="[^"]*"/, `width="${size}"`);
svg = svg.replace(/height="[^"]*"/, `height="${size}"`);
}
const hasSize = svg.includes(`width="${size}"`) && svg.includes(`height="${size}"`);
console.log('โ
Size modification test');
console.log(' Size applied:', hasSize ? 'โ
' : 'โ');
console.log(' SVG changed:', originalSvg !== svg ? 'โ
' : 'โ');
return true;
} else {
console.log('โ Failed to fetch icon:', response.status, response.statusText);
return false;
}
} catch (error) {
console.log('โ Error:', error.message);
return false;
}
}
// Test 4: Test search functionality
function testSearchIcons() {
console.log('\n๐ Test 4: Search icons functionality');
try {
const query = 'house';
const searchTerm = query.toLowerCase().trim();
const matches = POPULAR_ICONS.filter((icon) => {
return (
icon.name.toLowerCase().includes(searchTerm) ||
icon.category?.toLowerCase().includes(searchTerm) ||
icon.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))
);
});
const found = matches.length > 0;
console.log('โ
Search test');
console.log(' Query:', query);
console.log(' Results found:', found ? `โ
(${matches.length})` : 'โ');
if (found) {
console.log(' Matches:', matches.map(i => i.name).join(', '));
}
return found;
} catch (error) {
console.log('โ Error:', error.message);
return false;
}
}
// Test 5: Test categories
function testListCategories() {
console.log('\n๐ Test 5: List categories');
try {
const categories = Array.from(
new Set(
POPULAR_ICONS.map((icon) => icon.category).filter(
(cat) => cat !== undefined
)
)
).sort();
console.log('โ
Categories test');
console.log(' Categories found:', categories.length);
console.log(' Categories:', categories.join(', '));
return categories.length > 0;
} catch (error) {
console.log('โ Error:', error.message);
return false;
}
}
// Test 6: Test invalid icon name handling
async function testInvalidIcon() {
console.log('\nโ ๏ธ Test 6: Invalid icon name handling');
try {
const iconName = 'invalid-icon-xyz-123';
const weight = 'regular';
const url = `${PHOSPHOR_CORE_RAW_BASE}/${weight}/${iconName}.svg`;
const response = await fetch(url, {
headers: { 'User-Agent': 'PhosphorIconsMCP/1.0.0' }
});
if (!response.ok) {
console.log('โ
Invalid icon correctly returns error');
console.log(' Status:', response.status);
return true;
} else {
console.log('โ ๏ธ Icon unexpectedly found (might be valid)');
return true; // Not necessarily a failure
}
} catch (error) {
console.log('โ Error:', error.message);
return false;
}
}
// Test 7: Test different weights
async function testDifferentWeights() {
console.log('\nโ๏ธ Test 7: Different icon weights');
const weights = ['thin', 'light', 'regular', 'bold', 'fill', 'duotone'];
const iconName = 'heart';
let successCount = 0;
for (const weight of weights) {
try {
// For non-regular weights, filename is {name}-{weight}.svg
const fileName = weight === 'regular' ? `${iconName}.svg` : `${iconName}-${weight}.svg`;
const url = `${PHOSPHOR_CORE_RAW_BASE}/${weight}/${fileName}`;
const response = await fetch(url, {
headers: { 'User-Agent': 'PhosphorIconsMCP/1.0.0' }
});
if (response.ok) {
successCount++;
console.log(` โ
${weight}`);
} else {
console.log(` โ ${weight} (${response.status})`);
}
} catch (error) {
console.log(` โ ${weight} (${error.message})`);
}
}
console.log(`\n Summary: ${successCount}/${weights.length} weights available`);
return successCount === weights.length;
}
// Run all tests
async function runAllTests() {
const results = [];
results.push(await testFetchIcon());
results.push(await testIconColorModification());
results.push(await testIconSizeModification());
results.push(testSearchIcons());
results.push(testListCategories());
results.push(await testInvalidIcon());
results.push(await testDifferentWeights());
console.log('\n' + '='.repeat(60));
const passed = results.filter(r => r).length;
const total = results.length;
console.log(`\n๐ Test Results: ${passed}/${total} passed`);
if (passed === total) {
console.log('โจ All tests passed!');
process.exit(0);
} else {
console.log('โ ๏ธ Some tests failed');
process.exit(1);
}
}
runAllTests().catch(console.error);