Validação de Dados e Testes Unitários¶
Como o sistema depende inteiramente de arquivos JSON externos para renderizar a interface, a validação rigorosa desses dados é a primeira linha de defesa contra erros de execução (runtime errors).
Esta seção define as regras de integridade que o src/core/validator.ts deve aplicar e como garantimos isso através de testes automatizados.
1. Regras de Schema (JSON Parsing)¶
O validador deve garantir que o JSON baixado adira estritamente à interface CenarioConfig.
1.1 Campos da Raiz (Críticos)¶
Erros nestes campos impedem a renderização total do componente.
| Campo | Regra de Validação | Ação em caso de Erro |
|---|---|---|
id |
String não vazia. | CRITICAL: Aborta e exibe mensagem de erro genérica. |
image |
URL válida (absoluta ou relativa). | CRITICAL: Aborta. |
width / height |
Inteiro > 0. | CRITICAL: Aborta (impossível calcular proporção). |
hotspots |
Array (mesmo que vazio). | CRITICAL: Aborta se for null ou undefined. |
1.2 Regras de Hotspot (Individuais)¶
Erros aqui causam o descarte apenas do hotspot problemático (Fail Partial).
| Contexto | Regra Lógica | Ação |
|---|---|---|
| Geometria | Deve possuir points (Array com length >= 3) OU (x, y, width, height). |
SKIP: Pula o hotspot (impossível desenhar). |
| Modal | Se action === 'modal', o objeto modalData deve existir e ser válido. |
SKIP: Pula para evitar clique morto. |
| Moodle | Se moodle.type === 'activity', activityId deve ser número positivo. |
FALLBACK: Tenta usar link manual. Se não houver, oculta. |
| Acessibilidade | ariaLabel deve ser preenchido. |
WARN: Usa o title ou o id como fallback e loga aviso. |
2. Validação Cruzada (Cross-Validation)¶
Verificações que dependem da relação entre campos.
2.1 Bounds Check (Limites da Imagem)¶
O validador deve checar se as coordenadas estão dentro da área da imagem original.
- Retângulo:
x + width <= image.widthEy + height <= image.height. - Polígono: Todos os pontos
[x, y]devem estar entre0e as dimensões máximas.
Estratégia: Se estourar o limite, emitir um
console.warnem ambiente DEV, mas tentar renderizar mesmo assim (CSSoverflow: hiddenno container cuidará do corte visual).
2.2 Prevenção de Loop em Modais¶
Se um modal (modalData) contiver outro hotspot com ação modal, o sistema deve ter um limite de profundidade (ex: máx
1 nível de aninhamento) para evitar complexidade excessiva de Z-Index e memória.
3. Estratégia de Testes Unitários¶
Utilizaremos Vitest (nativo do ecossistema Vite) para validar a lógica do src/core/validator.ts.
3.1 Setup do Teste¶
Arquivo: src/core/__tests__/validator.test.ts
import {describe, it, expect} from 'vitest';
import {validateConfig} from '../validator';
import {CenarioConfig} from '../types';
describe('Validator Core', () => {
// Mock de um cenário válido básico
const baseConfig: CenarioConfig = {
id: 'test-scenario',
image: 'img.jpg',
width: 1920,
height: 1080,
hotspots: []
};
it('should accept a valid configuration', () => {
const result = validateConfig(baseConfig);
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should reject missing dimensions', () => {
const invalid = {...baseConfig, width: -50};
const result = validateConfig(invalid);
expect(result.isValid).toBe(false);
expect(result.errors).toContain('Dimension width must be positive');
});
});
3.2 Testando Regras de Negócio (Geometria)¶
describe('Geometry Validation', () => {
it('should validate polygon integrity', () => {
const config = {
...baseConfig,
hotspots: [{
id: 'poly-1',
title: 'Porta',
ariaLabel: 'Porta',
// Polígono inválido (apenas 2 pontos não formam área)
points: [[0, 0], [10, 10]]
}]
};
const result = validateConfig(config);
// Não falha o cenário todo, mas marca hotspot como inválido
expect(result.validHotspots).toHaveLength(0);
expect(result.warnings).toContain('Hotspot poly-1 has insufficient points for polygon');
});
it('should accept mixed geometry types', () => {
const config = {
...baseConfig,
hotspots: [
{id: 'h1', x: 10, y: 10, width: 100, height: 100, ariaLabel: 'Box'}, // Rect
{id: 'h2', points: [[0, 0], [10, 0], [5, 10]], ariaLabel: 'Poly'} // Poly
]
};
expect(validateConfig(config).validHotspots).toHaveLength(2);
});
});
3.3 Testando Lógica de Modal¶
describe('Modal Integrity', () => {
it('should require modalData when action is modal', () => {
const config = {
...baseConfig,
hotspots: [{
id: 'tv-fail',
ariaLabel: 'TV',
action: 'modal',
x: 0, y: 0, width: 10, height: 10
// Falta modalData
}]
} as CenarioConfig;
const result = validateConfig(config);
expect(result.validHotspots).toHaveLength(0);
});
});
4. Tratamento de Erros em Runtime¶
Além da validação estática inicial, o sistema deve tratar erros assíncronos.
4.1 Falha de Rede (Imagem)¶
Se a imagem definida no JSON válido retornar 404:
- O evento
errorna tag<img>dispara. - O componente substitui a imagem por um placeholder visual (fundo cinza com ícone de imagem quebrada).
- Um
console.erroré emitido para alertar o desenvolvedor.
4.2 Falha de Schema do Moodle¶
Se a API do Moodle retornar um formato inesperado (ex: o administrador alterou o plugin de webservice):
- O sistema captura a exceção no
try/catchdoapi.ts. - Executa o Fail Open (libera os hotspots visualmente).
- Registra o erro silenciosamente para não assustar o aluno.
Próximo passo na leitura: Consulte o Guia de Integração para colocar o projeto em produção.