testes/.agent/agents/UIAdaptationAgent.js

211 lines
6.4 KiB
JavaScript

/**
* 🤖 AGENTE DE ADAPTAÇÃO DE INTERFACE
*
* Implementação programática do agente de validação de responsividade
*/
export class UIAdaptationAgent {
constructor() {
this.name = 'UIAdaptationAgent';
this.description = 'Valida responsividade e adaptação de interface';
// Personalidade e Background
this.personality = {
name: 'Maya "The Responsive"',
traits: [
'Perfeccionista visual',
'Empática com usuários de diferentes dispositivos',
'Obsessiva com breakpoints',
'Criativa em soluções de layout',
'Paciente e meticulosa'
],
quirks: [
'Testa em dispositivos reais sempre que possível',
'Tem uma coleção mental de todos os tamanhos de tela conhecidos',
'Fica incomodada quando vê um layout quebrando',
'Prefere mobile-first em tudo'
],
catchphrases: [
'Isso vai funcionar no mobile?',
'E no tablet?',
'Vamos pensar mobile-first!',
'Esse breakpoint está correto?'
]
};
this.background = {
origin: 'Ex-designer que migrou para desenvolvimento após frustrações com layouts quebrados',
motivation: 'Garantir que todos tenham a mesma experiência, independente do dispositivo',
experience: '7 anos trabalhando com design responsivo e CSS moderno',
turningPoint: 'Viu um cliente perder uma venda porque o checkout não funcionava no celular',
philosophy: 'Design é sobre pessoas, não sobre pixels',
relationships: {
withFontQuality: 'Trabalham juntos - ela cuida do layout, eu cuido da tipografia',
withPerformance: 'Respeita muito, mas às vezes prioriza UX sobre performance',
withBrowserValidation: 'Aprecia os testes reais que ele faz'
}
};
}
/**
* 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 responsivas
const hasResponsiveClasses = this.hasResponsiveTailwindClasses(componentCode);
if (!hasResponsiveClasses && this.isLayoutComponent(componentCode)) {
warnings.push('Componente de layout pode não ser responsivo');
}
// 2. Verificar uso de unidades fixas (px) vs relativas
const fixedUnits = this.countFixedUnits(componentCode);
if (fixedUnits > 5) {
warnings.push(`Muitas unidades fixas (${fixedUnits}) encontradas. Considere usar unidades relativas`);
}
// 3. Verificar truncamento de texto
if (this.hasLongTextContent(componentCode) && !componentCode.includes('truncate') && !componentCode.includes('line-clamp')) {
warnings.push('Textos longos podem não estar sendo truncados adequadamente');
}
// 4. Verificar breakpoints do Tailwind
const breakpoints = this.extractBreakpoints(componentCode);
metadata.breakpoints = breakpoints;
// 5. Verificar uso de mobile-first
const hasMobileFirst = this.hasMobileFirstApproach(componentCode);
if (!hasMobileFirst && this.isLayoutComponent(componentCode)) {
warnings.push('Componente pode não seguir abordagem mobile-first');
}
// 6. Verificar overflow handling
if (this.isScrollableComponent(componentCode) && !componentCode.includes('overflow')) {
warnings.push('Componente scrollável pode não ter tratamento de overflow adequado');
}
const passed = errors.length === 0;
return {
passed,
errors,
warnings,
metadata: {
hasResponsiveClasses,
breakpoints,
fixedUnitsCount: fixedUnits,
...metadata
}
};
}
/**
* Verifica se tem classes Tailwind responsivas
* @param {string} code - Código
* @returns {boolean}
*/
hasResponsiveTailwindClasses(code) {
const responsivePatterns = [
'sm:', 'md:', 'lg:', 'xl:', '2xl:',
'max-sm:', 'max-md:', 'max-lg:', 'max-xl:'
];
return responsivePatterns.some(pattern => code.includes(pattern));
}
/**
* Conta unidades fixas (px)
* @param {string} code - Código
* @returns {number}
*/
countFixedUnits(code) {
const pxMatches = code.match(/\d+px/g);
return pxMatches ? pxMatches.length : 0;
}
/**
* Verifica se tem conteúdo de texto longo
* @param {string} code - Código
* @returns {boolean}
*/
hasLongTextContent(code) {
return code.includes('text') || code.includes('p>') || code.includes('span');
}
/**
* Extrai breakpoints usados
* @param {string} code - Código
* @returns {string[]}
*/
extractBreakpoints(code) {
const breakpoints = [];
const patterns = ['sm:', 'md:', 'lg:', 'xl:', '2xl:'];
patterns.forEach(pattern => {
if (code.includes(pattern)) {
breakpoints.push(pattern.replace(':', ''));
}
});
return breakpoints;
}
/**
* Verifica se segue mobile-first
* @param {string} code - Código
* @returns {boolean}
*/
hasMobileFirstApproach(code) {
// Se tem classes base (sem prefixo) e classes com breakpoints maiores, provavelmente é mobile-first
return code.includes('className') && this.hasResponsiveTailwindClasses(code);
}
/**
* Verifica se é componente de layout
* @param {string} code - Código
* @returns {boolean}
*/
isLayoutComponent(code) {
return code.includes('div') || code.includes('section') || code.includes('main') ||
code.includes('Layout') || code.includes('Container');
}
/**
* Verifica se é componente scrollável
* @param {string} code - Código
* @returns {boolean}
*/
isScrollableComponent(code) {
return code.includes('scroll') || code.includes('ScrollArea') || code.includes('overflow');
}
/**
* 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;
}
}
}