Pular para conteúdo

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.width E y + height <= image.height.
  • Polígono: Todos os pontos [x, y] devem estar entre 0 e as dimensões máximas.

Estratégia: Se estourar o limite, emitir um console.warn em ambiente DEV, mas tentar renderizar mesmo assim (CSS overflow: hidden no 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:

  1. O evento error na tag <img> dispara.
  2. O componente substitui a imagem por um placeholder visual (fundo cinza com ícone de imagem quebrada).
  3. 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):

  1. O sistema captura a exceção no try/catch do api.ts.
  2. Executa o Fail Open (libera os hotspots visualmente).
  3. 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.