testes/.agent/agents/FontQualityAgent.js

236 lines
7.1 KiB
JavaScript

/**
* 🤖 AGENTE DE QUALIDADE DE FONTES
*
* Implementação programática do agente de validação de tipografia
*/
export class FontQualityAgent {
constructor() {
this.name = 'FontQualityAgent';
this.description = 'Valida qualidade e consistência de tipografia';
// Personalidade e Background
this.personality = {
name: 'Lucas "The Typographer"',
traits: [
'Apaixonado por tipografia',
'Detalhista com hierarquia visual',
'Artístico e criativo',
'Valoriza legibilidade acima de tudo',
'Conhece todas as fontes do projeto de cor'
],
quirks: [
'Consegue identificar fontes só de olhar',
'Fica incomodado quando vê texto sem hierarquia clara',
'Tem uma paleta mental de tamanhos de fonte ideais',
'Prefere tipografia consistente a designs "criativos"'
],
catchphrases: [
'Essa hierarquia está clara?',
'O contraste está adequado?',
'Esse texto é legível?',
'Precisamos de mais peso aqui'
]
};
this.background = {
origin: 'Ex-designer gráfico especializado em tipografia que descobriu o mundo web',
motivation: 'Garantir que todo texto seja legível, acessível e visualmente agradável',
experience: '10 anos trabalhando com tipografia em design gráfico e web',
turningPoint: 'Viu um projeto ser rejeitado por um cliente porque o texto era ilegível',
philosophy: 'Tipografia é a voz visual do conteúdo - deve ser clara e respeitosa',
relationships: {
withUIAdaptation: 'Trabalham juntos - ela cuida do layout, eu cuido da tipografia',
withPerformance: 'Respeita, mas às vezes prioriza qualidade visual sobre performance',
withDocumentation: 'Aprecia quando a documentação inclui guias de tipografia'
}
};
}
/**
* Valida um componente
* @param {string|Object} component - Componente ou caminho
* @param {Object} context - Contexto
* @returns {Promise<Object>}
*/
async validate(component, context = {}) {
const errors = [];
const warnings = [];
const metadata = {};
const componentPath = typeof component === 'string' ? component : component.path;
const componentCode = context.code || await this.readComponent(componentPath);
if (!componentCode) {
return {
passed: false,
errors: ['Não foi possível ler o componente'],
warnings: [],
metadata: {}
};
}
// 1. Verificar uso de classes Tailwind de texto
const hasTextClasses = this.hasTextClasses(componentCode);
if (!hasTextClasses && this.hasTextContent(componentCode)) {
warnings.push('Texto pode não estar usando classes Tailwind consistentes');
}
// 2. Verificar hierarquia visual (h1, h2, h3)
const hasHierarchy = this.hasTextHierarchy(componentCode);
if (!hasHierarchy && this.hasMultipleTextElements(componentCode)) {
warnings.push('Pode não haver hierarquia visual clara entre títulos e corpo de texto');
}
// 3. Verificar tamanhos de fonte
const fontSizes = this.extractFontSizes(componentCode);
const hasSmallFonts = fontSizes.some(size => this.isTooSmall(size));
if (hasSmallFonts) {
warnings.push('Alguns textos podem estar muito pequenos para leitura confortável (mínimo recomendado: 12-14px)');
}
// 4. Verificar contraste (classes de cor)
const hasColorClasses = this.hasColorClasses(componentCode);
if (!hasColorClasses && this.hasTextContent(componentCode)) {
warnings.push('Textos podem não ter contraste adequado definido');
}
// 5. Verificar font-weight
const hasFontWeights = this.hasFontWeights(componentCode);
if (!hasFontWeights && this.hasMultipleTextElements(componentCode)) {
warnings.push('Pode não haver distinção de peso de fonte para destacar informações importantes');
}
metadata.fontSizes = fontSizes;
metadata.hasHierarchy = hasHierarchy;
const passed = errors.length === 0;
return {
passed,
errors,
warnings,
metadata
};
}
/**
* Verifica se tem classes de texto Tailwind
* @param {string} code - Código
* @returns {boolean}
*/
hasTextClasses(code) {
const textClasses = [
'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl',
'text-xs', 'text-3xl', 'text-4xl', 'text-5xl', 'text-6xl'
];
return textClasses.some(className => code.includes(className));
}
/**
* Verifica se tem conteúdo de texto
* @param {string} code - Código
* @returns {boolean}
*/
hasTextContent(code) {
return code.includes('text') || code.includes('p>') ||
code.includes('span') || code.includes('h1') ||
code.includes('h2') || code.includes('h3');
}
/**
* Verifica se tem hierarquia de texto
* @param {string} code - Código
* @returns {boolean}
*/
hasTextHierarchy(code) {
return code.includes('h1') || code.includes('h2') ||
code.includes('h3') || code.includes('h4') ||
code.includes('text-2xl') || code.includes('text-3xl') ||
code.includes('text-4xl');
}
/**
* Verifica se tem múltiplos elementos de texto
* @param {string} code - Código
* @returns {boolean}
*/
hasMultipleTextElements(code) {
const textElements = ['p', 'span', 'div', 'h1', 'h2', 'h3'];
let count = 0;
textElements.forEach(el => {
if (code.includes(`<${el}`) || code.includes(`</${el}`)) {
count++;
}
});
return count > 2;
}
/**
* Extrai tamanhos de fonte usados
* @param {string} code - Código
* @returns {string[]}
*/
extractFontSizes(code) {
const sizes = [];
const sizePatterns = [
'text-xs', 'text-sm', 'text-base', 'text-lg',
'text-xl', 'text-2xl', 'text-3xl', 'text-4xl'
];
sizePatterns.forEach(size => {
if (code.includes(size)) {
sizes.push(size);
}
});
return sizes;
}
/**
* Verifica se o tamanho é muito pequeno
* @param {string} size - Tamanho (ex: 'text-xs', 'text-sm')
* @returns {boolean}
*/
isTooSmall(size) {
const smallSizes = ['text-xs'];
return smallSizes.includes(size);
}
/**
* Verifica se tem classes de cor
* @param {string} code - Código
* @returns {boolean}
*/
hasColorClasses(code) {
const colorPatterns = [
'text-', 'text-white', 'text-black', 'text-gray',
'text-blue', 'text-green', 'text-red', 'text-yellow'
];
return colorPatterns.some(pattern => code.includes(pattern));
}
/**
* Verifica se tem font-weights
* @param {string} code - Código
* @returns {boolean}
*/
hasFontWeights(code) {
return code.includes('font-') || code.includes('font-bold') ||
code.includes('font-semibold') || code.includes('font-medium') ||
code.includes('font-light') || code.includes('font-normal');
}
/**
* Lê o conteúdo do componente
* @param {string} path - Caminho do arquivo
* @returns {Promise<string|null>}
*/
async readComponent(path) {
try {
const fs = await import('fs/promises');
return await fs.readFile(path, 'utf-8');
} catch (error) {
return null;
}
}
}