Skip to content

Repository files navigation

QuizLab

Version Status Stack PWA Zero Runtime Deps

Autor: José Anderson (@DessimA)
LinkedIn: /in/dessim
Website: dessima.pages.dev


Índice

  1. Visão Geral
  2. Arquitetura de Software
  3. Estrutura de Pastas
  4. Módulos e Responsabilidades
  5. Telas e Fluxo de Navegação
  6. Regras de Negócio
  7. Requisitos Funcionais
  8. Requisitos Não Funcionais
  9. Protocolo JSON
  10. Persistência de Dados
  11. Design System
  12. Internacionalização (i18n)
  13. Monitoramento
  14. Qualidade de Código (Lint)
  15. Testes Automatizados
  16. CI/CD
  17. Contribuição

Visão Geral

O QuizLab é uma Single Page Application (SPA) desenvolvida em Vanilla JavaScript (ES6+), construída com Vite e zero dependências em runtime. A aplicação permite criar, importar, gerenciar e responder simulados de múltipla escolha diretamente no navegador.

Toda a lógica de negócio, validação, estado e persistência ocorre no lado do cliente (client-side), sem necessidade de servidor ou backend. A aplicação funciona offline após o primeiro carregamento graças a um Service Worker (PWA).

Pilares do projeto:

  • Zero Runtime Dependencies nenhuma biblioteca externa, framework ou bundler em produção.
  • Client-Side First lógica e persistência vivem inteiramente no navegador.
  • Modularidade cada responsabilidade é isolada em um módulo IIFE próprio, exposto via window.
  • DRY lógica de renderização e validação reutilizável em toda a base de código.
  • i18n Nativo todos os textos da interface centralizados em um repositório único.

Arquitetura de Software

O projeto possui uma arquitetura de duas camadas:

  1. Camada de Build (Vite + ESM): Os arquivos são escritos como módulos ES6 (import/export). O Vite faz o bundle e code-splitting, gerando múltiplos entry points: index.html (app principal), docs.html (documentação) e troubleshooting.html (ajuda).

  2. Camada de Runtime (IIFE Singletons): Em runtime, cada módulo é um objeto Singleton encapsulado em IIFE (Immediately Invoked Function Expression) e exposto no escopo global via window, simulando namespaces. Isso garante zero dependências entre módulos em produção.

O padrão de comunicação central é o Event Delegation: um único listener no document intercepta todos os eventos, roteia pelo atributo data-action do elemento HTML e executa o handler registrado no app.js. Isso elimina addEventListener espalhados pelo código e desacopla completamente o HTML da lógica JS.

graph TD
    DOM[DOM Events] --> Delegator[EventDelegator]

    Delegator -- "data-action route" --> App[app.js / App class]

    App -- "init / reset / confirm" --> Engine[QuizEngine]
    App -- "wizard / export" --> Creator[CreatorManager]
    App -- "CRUD / search / bulk" --> Lib[LibraryManager]
    App -- "change screen" --> Screen[ScreenManager]

    Engine -- "getState()" --> Screen
    Engine -- "getState()" --> Renderer[QuizRenderer]
    Engine -- "saveStatsToLibrary()" --> Storage[StorageManager]

    Creator -- "validateQuiz()" --> Validator[Validator]
    Lib -- "get / set / canStore()" --> Storage

    App -- "show()" --> Toast[ToastSystem]
    App -- "confirm() / alert() / custom()" --> Modal[ModalManager]

    Storage <--> LS[(LocalStorage)]
Loading

Separação de Camadas

Camada Responsabilidade
Core Configurações globais, persistência, validação
Components Componentes de UI reutilizáveis sem estado de negócio
Features Lógica de negócio de cada funcionalidade
UI Renderização de telas e roteamento de eventos
Data Internacionalização e repositório de textos

Fluxo de Inicialização

sequenceDiagram
    participant HTML as index.html
    participant Vite as Vite Bundle
    participant Entry as js/entries/index.js
    participant App as js/app.js (App class)
    participant Modules as Módulos IIFE

    HTML->>Vite: <script type="module" src="js/entries/index.js">
    Vite->>Entry: Carrega entry point
    Entry->>Entry: Importa datadog-rum.js, version.js, config.js
    Entry->>Entry: Importa todos os módulos IIFE
    Entry->>Entry: Importa TEXTS e I18n
    Entry->>App: Importa app.js
    Entry->>window: Expõe TEXTS globalmente
    Entry->>I18n: I18n.init(TEXTS)
    Note over App: DOMContentLoaded
    App->>I18n: I18n.apply()
    App->>Modules: ThemeManager.init()
    App->>Modules: ToastSystem.init()
    App->>Modules: StorageManager.init()
    App->>Modules: ModalManager.init()
    App->>Modules: EventDelegator.init()
    App->>App: _setupEventHandlers()
    App->>Modules: Adiciona quiz demo se biblioteca vazia
Loading

Estrutura de Pastas

quizlab/
├── index.html                     # Entry point principal (SPA)
├── docs.html                      # Documentação do usuário (com i18n)
├── troubleshooting.html           # Página de ajuda e suporte (com i18n)
├── styles.css                     # Estilos globais com variáveis CSS
├── sw.js                          # Service Worker (PWA)
├── vite.config.js                 # Configuração do Vite (3 entry points)
├── package.json
├── CHANGES.md                     # Changelog detalhado
├── .env.example                   # Exemplo de variáveis de ambiente (Datadog)
│
├── js/
│   ├── entries/
│   │   ├── index.js               # Entry point principal (Vite)
│   │   ├── docs.js                # Entry point docs.html (com i18n)
│   │   └── troubleshooting.js     # Entry point troubleshooting.html
│   ├── app.js                     # Classe App orquestradora
│   ├── datadog-rum.js             # Monitoramento Datadog RUM
│   ├── core/
│   │   ├── version.js             # Constante APP_VERSION
│   │   ├── config.js              # CONFIG e Utils (constantes, enums, helpers)
│   │   ├── storage-manager.js     # Facade para o localStorage
│   │   └── validator.js           # Validação de schema JSON
│   ├── components/
│   │   ├── exam-nav-map.js        # Painel de mapa de questões (Modo Exame)
│   │   ├── icon-system.js         # Injeção de SVG inline
│   │   ├── theme-manager.js       # Toggle dark/light com persistência
│   │   ├── modal-manager.js       # Modais de confirmação, alerta e custom
│   │   ├── toast-system.js        # Notificações flutuantes
│   │   └── focus-trap.js          # Acessibilidade: foco dentro de modais
│   ├── features/
│   │   ├── quiz-engine.js         # Estado do quiz, lógica de resposta e timer
│   │   ├── creator-manager.js     # Wizard de criação e drag & drop
│   │   ├── library-manager.js     # Renderização, busca, seleção em massa
│   │   ├── review-manager.js      # Tela de revisão, resultado e rastreamento
│   │   ├── review-quiz-builder.js # Montagem do quiz temporário de revisão
│   │   └── file-handler.js        # Leitura, parse e importação de JSONs
│   └── ui/
│       ├── screen-manager.js      # Troca de telas e retomada de sessão
│       ├── quiz-renderer.js       # Renderização das questões no DOM
│       └── event-delegator.js     # Listener único e roteamento data-action
│
├── data/
│   ├── texts.js                   # Repositório central de textos da UI e docs
│   └── i18n.js                    # Motor de internacionalização
│
├── tests/
│   ├── setup/
│   │   ├── environment.js         # Polyfills de DOM para Node.js
│   │   └── loader.js              # Carregador de módulos IIFE em Node
│   ├── unit/
│   │   ├── quiz-engine.test.js    # Engine + timer + exam mode (49 testes)
│   │   ├── validator.test.js      # Schema validation
│   │   ├── storage-manager.test.js
│   │   ├── file-handler.test.js
│   │   ├── p1-p2-fixes.test.js    # Regressão: sessão órfã, debounce, CSS
│   │   └── security-fixes.test.js # Regressão: XSS, módulos reais
│   └── integration/
│       └── quiz-flow.test.js      # Fluxo completo estudo + exame
│
├── e2e/                           # Testes End-to-End (Playwright)
│   ├── package.json               # "type": "module" para o diretório
│   ├── helpers.js                 # Seed de biblioteca e fluxos reutilizáveis
│   ├── fixtures/                  # JSONs determinísticos para os testes
│   ├── quiz-flow.spec.js          # Modo estudo: iniciar, responder, resultado
│   ├── exam-mode.spec.js          # Modo exame: timer, sem feedback, setas
│   ├── library.spec.js            # Import, busca, ordenação, exclusão
│   ├── creator.spec.js            # Criação de simulado do zero
│   ├── resume.spec.js             # Retomada de sessão salva
│   └── shuffle.spec.js            # Embaralhamento de questões/alternativas
│
├── playwright.config.js          # Configuração do Playwright (webServer Vite)
├── dist/                          # Saída do build (Vite)
├── assets/                        # Imagens e favicon
│
└── .github/
    └── workflows/
        ├── ci.yml                 # Testes + E2E em PRs para main e develop
        └── deploy.yml             # Testes em push para main (produção)

Módulos e Responsabilidades

version.js

Define a constante APP_VERSION como fonte única de verdade da versão da aplicação. É carregado primeiro pelo entry point, garantindo que o nome do cache do Service Worker seja sempre atualizado junto com a versão.

config.js

Centraliza todas as constantes, enums e funções utilitárias puras da aplicação.

CONFIG.STORAGE               // chaves do localStorage
CONFIG.LIMITS                // quotas de armazenamento, histórico
CONFIG.TIMINGS               // autosave (30s), toast (4s), debounce (300ms), timer (120s/questão)
CONFIG.QUESTION_TYPES        // 'unica' | 'multipla'
CONFIG.QUIZ_MODES            // 'study' | 'exam'
CONFIG.ELEMENTS              // IDs das telas no DOM

Utils.formatTime(seconds)    // formata MM:SS
Utils.plural(count, word)    // pluraliza palavra
Utils.truncate(text)         // corta com reticências

Limites de armazenamento relevantes:

Constante Valor Descrição
STORAGE_SAFE_QUOTA_BYTES 4 MB Quota fixa medida no localStorage
STORAGE_WARN_THRESHOLD 0.70 Barra amarela a partir de 70% de uso
STORAGE_BLOCK_THRESHOLD 0.85 Bloqueio de novas importações a partir de 85%
MAX_HISTORY_ENTRIES 10 Partidas mantidas no histórico por simulado
MAX_WRONG_PER_QUIZ 65 Máx. de questões erradas rastreadas por simulado

storage-manager.js

Facade para o localStorage. Todas as operações de leitura e escrita passam por aqui, centralizando a serialização JSON e o tratamento de erros.

Métodos relevantes:

  • getById(id) atalho para getLibrary().find(), evita repetição nos consumidores.
  • replaceInLibrary(id, data) substitui conteúdo e atualiza questionsCount em uma única escrita.
  • updateLibraryMeta(id, updates) merge parcial dos metadados sem tocar nas questões.
  • removeManyFromLibrary(ids) exclui um array de IDs em uma única escrita, limpando sessão órfã se necessário.
  • updateQuizStats(id, stats) registra resultado, acumula histórico (limitado a MAX_HISTORY_ENTRIES) e recalcula média.
  • saveWrongQuestions(id, list) thin wrapper em updateLibraryMeta para persistir wrongQuestions.
  • getAggregatedWrong(quizIds?) agrega wrongQuestions de todos ou de IDs selecionados.
  • getStorageStats() síncrono. Mede o uso real do localStorage via Blob.size por chave. Retorna { usage, quota, percent }.
  • canStore(data) síncrono. Projeta o uso pós-adição e bloqueia se >= STORAGE_BLOCK_THRESHOLD. Retorna { allowed, reason?, stats }.

validator.js

Valida estruturas de dados em dois contextos:

  • validateQuiz(data) valida um objeto JSON completo antes de importar ou exportar. Retorna { valid: Boolean, errors: String[] }.
  • isQuestionCardValid(card) valida um card de questão no DOM do criador em tempo real.

app.js

Classe App que orquestra a inicialização de todos os módulos. Verifica a disponibilidade de cada manager (com retry até 3s), configura handlers de eventos, gerencia auto-save do rascunho, atalhos de teclado (Ctrl+S, Escape), eventos de timer (quizlab:timer-tick, quizlab:timer-expired) e adiciona um simulado demo na primeira visita.

Responsabilidades principais: inicializar módulos na ordem correta, registrar todos os handlers data-action via EventDelegator.registerMultiple(), gerenciar modal de opções do quiz, e coordenar o fluxo de exportação.

quiz-engine.js

O "cérebro" da aplicação. Não toca no DOM. Mantém e expõe o estado completo do quiz em andamento.

Responsabilidades: inicializar e restaurar sessão, selecionar alternativas (única/múltipla), confirmar respostas, navegar entre questões, marcar questões com flag, controlar o timer via setInterval e emitir eventos customizados (quizlab:timer-tick, quizlab:timer-expired, quizlab:state-changed, quizlab:answer-changed).

Modo Exame: gerencia o estado answersFinalized que só é ativado ao chamar finalize() (manual ou por expiração do timer). Antes da finalização, select() permite alterações livres na resposta da questão atual. getPendingReview() retorna questões não respondidas e/ou marcadas com flag. getExamProgress() retorna contagens de progresso para a UI. finalize() calcula o score total e marca questionAnswered[] para todas as questões.

creator-manager.js

Gerencia o wizard de criação e edição de simulados: construir cards de questão no DOM, reordenar por drag & drop, validar cada card em tempo real, serializar o objeto final e acionar preview/exportação.

library-manager.js

Renderiza e gerencia a tela de biblioteca com CRUD, busca, seleção em massa, abas (Simulados/Revisão), indicador de armazenamento circular SVG, painel de revisão de erros e badge de erros.

review-manager.js

Gerencia a tela de revisão pré-finalização e a tela de resultado. Renderiza gabarito completo, seção de questões marcadas, histórico de tentativas e persiste o banco de erros.

review-quiz-builder.js

Monta o objeto de quiz temporário para a sessão de revisão de erros a partir do array agregado de wrongQuestions.

file-handler.js

Pipeline de importação de arquivos JSON. Suporta importação única e em massa (até 50 arquivos, 2MB cada), com classificação em salvos, ignorados (idênticos), conflitos e falhas.

screen-manager.js

Controla a troca de telas, inicialização de quiz, restauração de sessão, barra de timer do Modo Exame e overlay de carregamento.

quiz-renderer.js

Constrói o HTML da questão atual a partir do estado do QuizEngine e injeta no DOM.

event-delegator.js

Registra um único listener por tipo de evento no document. Ao capturar um evento, sobe a árvore DOM buscando o atributo data-action, data-oninput ou data-onchange e executa o handler correspondente registrado via register() ou registerMultiple().

exam-nav-map.js

Componente IIFE que gerencia o painel de mapa de questões do Modo Exame. Cria um overlay modal com grade de células numeradas, cada uma com estado visual:

  • Cinza: não visitada / sem resposta
  • Azul/primary: pelo menos uma alternativa selecionada
  • Amarelo/warning: marcada com flag para revisão
  • Borda destacada: questão atual

Fechamento via Escape ou clique fora. Atualização dinâmica via evento quizlab:state-changed.

icon-system.js

Injeção de ícones SVG inline. Usa WeakSet para impedir injeção duplicada. Aguarda document.fonts.load() antes de renderizar.

toast-system.js

Notificações flutuantes com auto-dismiss (4s), tipos info, success e error.

modal-manager.js

Gerencia modais de confirmação (confirm), alerta (alert) e conteúdo dinâmico (custom). Suporta múltiplos modais identificados por ID.

focus-trap.js

Prende o foco dentro de modais abertos para navegação por teclado.

theme-manager.js

Alterna data-theme no <html>, persiste no localStorage e atualiza ícone e aria-label.

Módulos de Dados

texts.js

Repositório centralizado de todos os textos visíveis da interface, organizado por contexto (app, header, landing, library, import, editor, quiz, result, creator, options, feedback, review, modal, confirm, export, toast, stats, footer). Nenhuma string hardcoded deve existir nos módulos.

i18n.js

Motor de internacionalização que carrega os textos de texts.js e os aplica nos elementos HTML via atributos data-i18n, data-i18n-placeholder, data-i18n-value e data-i18n-badge. Métodos públicos: init(), t() (tradução por caminho), apply() (aplica em toda a página), applyTo() (aplica em container específico), format() (interpolação de variáveis).


Telas e Fluxo de Navegação

flowchart TD
    Landing([Landing Page]) --> Upload

    Upload[Upload Screen] -->|Importar JSON| Validate{Validar JSON}
    Validate -->|Inválido| Upload
    Validate -->|Único válido| Options
    Validate -->|Múltiplos válidos| Batch[Batch Report Modal]
    Batch --> Library

    Upload -->|Acessar Biblioteca| Library[Library Screen]
    Upload -->|Criar Simulado| Creator[Creator Screen]

    Library -->|Iniciar| Options[Quiz Options Modal]
    Library -->|Retomar sessão salva| Quiz
    Library -->|Editar| Creator
    Library -->|Excluir em massa| Library

    Creator -->|Exportar / Salvar| Library

    Options -->|Confirmar modo e embaralhamento| Quiz[Quiz Screen]

    Quiz -->|Última questão respondida| Review[Review Screen]
    Review -->|Questões pendentes| Quiz
    Review -->|Finalizar| Result[Result Screen]

    Quiz -->|Timer expirado - Modo Exame| Result

    Result --> Upload
Loading

Telas registradas em CONFIG.ELEMENTS:

ID Descrição
landingPage Apresentação inicial com CTAs e funcionalidades
uploadScreen Hub principal: importar, biblioteca, criar
quizScreen Resolução das questões
reviewScreen Revisão de questões pendentes antes de finalizar
resultScreen Resultado final com estatísticas e histórico
libraryScreen Grade de simulados salvos com seleção em massa
creatorScreen Wizard de criação e edição

Regras de Negócio

Importação de JSON

  • Somente arquivos .json são aceitos (validação de extensão e MIME type).
  • Limite de 50 arquivos por vez e 2MB por arquivo, com timeout de 10s.
  • O arquivo passa pela validação completa de schema antes de qualquer ação.
  • Se o simulado já existe na biblioteca com o mesmo nome e mesmo conteúdo (verificado via hash dos IDs das questões), o arquivo importado é ignorado silenciosamente.
  • Se o nome coincide mas o conteúdo difere, o usuário é consultado para substituir ou não (fluxo de arquivo único). Em importação em massa, conflitos são reportados e devem ser resolvidos individualmente.
  • A importação é bloqueada quando o uso do localStorage atinge 85% da quota segura de 4 MB.

Biblioteca

  • Capacidade determinada dinamicamente pela quota do localStorage (quota segura: 4 MB).
  • Indicador circular SVG no cabeçalho da tela exibe o percentual de uso em tempo real.
  • Modo seleção em massa permite selecionar múltiplos cards e excluí-los em uma única operação.
  • Cada item armazena metadados de desempenho: média de acertos, número de partidas, data do último acesso e histórico das últimas 10 partidas.
  • Ao excluir um simulado, a sessão ativa associada é removida automaticamente do localStorage.

Modo de Jogo

Ao iniciar um simulado, o usuário escolhe:

  • Modo Estudo: feedback visual imediato após confirmar cada resposta. Progresso salvo automaticamente para retomada futura.
  • Modo Exame: sem feedback durante a resolução. Timer decrescente em barra dedicada (#examTimerBar) com animação de pulso nos últimos 60 segundos. Resultado somente ao finalizar ou quando o tempo esgotar (calculado como questões × 120 segundos, ou valor customizado).

Embaralhamento

O usuário pode optar por embaralhar a ordem das questões, a ordem das alternativas, ou ambas, via algoritmo Fisher-Yates.

Timer

Calculado automaticamente (questões × 120 segundos). O QuizEngine gerencia o intervalo e emite eventos customizados sem tocar no DOM. A camada de UI consome esses eventos para atualizar #timerDisplay e #examTimerBar.

Retomada de Sessão

Se o usuário fechar o navegador durante um Modo Estudo, o progresso é detectado ao acessar a biblioteca. O card do simulado exibe um botão "Retomar" ao lado de "Iniciar". Um hash de sessão detecta se o quiz foi modificado desde o salvamento.

Rascunho do Criador

Salvo automaticamente a cada 30 segundos (e também com Ctrl+S). Restaurado na próxima abertura do criador.

Rastreamento de Erros

  • Ocorre apenas para simulados salvos na Biblioteca Local.
  • Ao finalizar uma partida, questões incorretas são persistidas. Questões acertadas são removidas do banco.
  • Limite de 65 questões por simulado. Ao atingir, questões com menor errorCount são descartadas.
  • O quiz de revisão é temporário: não possui libraryId e não é salvo na biblioteca.

Fluxo de Revisão de Erros:

sequenceDiagram
    participant LM as LibraryManager
    participant SM as StorageManager
    participant RQB as ReviewQuizBuilder
    participant SC as ScreenManager
    participant QE as QuizEngine
    participant RM as ReviewManager

    LM->>SM: getAggregatedWrong(selectedIds)
    SM-->>LM: wrongQuestions[]
    LM->>RQB: build(wrongQuestions, qty)
    RQB-->>LM: quizObj com _reviewSources
    LM->>SC: loadQuiz(quizObj, null, options)
    SC->>QE: init(quizObj)
    Note over QE: Sessão temporária (sem libraryId)
    QE-->>SC: state pronto
    SC->>SC: change(quizScreen)

    Note over RM: Ao finalizar...
    RM->>RM: _extractAndSaveWrongQuestions()
    RM->>SM: updateLibraryMeta(sourceId, ...) × N origens
Loading

Requisitos Funcionais

RF01 Importação de JSON único RF02 Importação em massa (até 50 arquivos) RF03 Validação de schema RF04 Biblioteca de simulados com CRUD RF05 Indicador de armazenamento (circular SVG com thresholds 70%/85%) RF06 Criador de simulados com drag & drop e rascunho automático RF07 Modo Estudo (feedback imediato + salvamento automático) RF08 Modo Exame (timer decrescente + barra dedicada) RF09 Questões de única e múltipla escolha RF10 Embaralhamento (questões e/ou alternativas) RF11 Marcação de questões (flag) RF12 Navegação livre entre questões via grade RF13 Revisão antes de finalizar RF14 Retomada de sessão salva (com hash de integridade) RF15 Histórico de desempenho (últimas 10 partidas) RF16 Tema claro e escuro (com persistência) RF17 Onboarding de primeira visita RF18 Rascunho automático do criador (30s + Ctrl+S) RF19 Rastreamento de erros (com limite e deduplicação) RF20 Aba Revisão na Biblioteca com badge numérico RF21 Listagem de fontes de revisão com seleção individual RF22 Controle de quantidade via slider RF23 Quiz de revisão temporário RF24 Atualização pós-revisão (remove acertos do banco) RF25 Internacionalização da interface (i18n) RF26 Relatório de desempenho por tema (pontos fortes/fracos) RF27 Sugestão de tags no criador (24 tags padrão) RF28 Exportação com opção de salvar na biblioteca RF29 Edição de respostas durante o Modo Exame (alteração livre até finalização) RF30 Mapa de questões (ExamNavMap) com grade visual e estados (respondida/marcada/atual) RF31 Revisão pré-finalização obrigatória no Modo Exame RF32 Proteção contra saída acidental (beforeunload) durante exame ativo


Requisitos Não Funcionais

RNF01 Zero dependências em runtime (Vanilla JS) RNF02 Funciona Offline (PWA com Service Worker, Cache First para fontes, Network First para assets) RNF03 Persistência Client-Side (localStorage) RNF04 Guard Dinâmico de Cota (medição real via Blob.size, quota segura de 4 MB) RNF05 Responsividade (mobile < 480px, tablet, desktop ≥ 1024px) RNF06 Performance de Eventos (Event Delegation único no document) RNF07 Integridade de Estado (SRP: engine não toca no DOM, storage é acesso único ao localStorage) RNF08 Acessibilidade (focus trap em modais, aria-live para feedback/timer, contraste 4.5:1+, suporte a prefers-contrast e prefers-reduced-motion) RNF09 Segurança (textContent substitui innerHTML, Utils.escapeHtml(), validação de tipos, sanitização, timeout em leitura de arquivos) RNF10 Build tooling com Vite (code-splitting, 3 entry points, substituição de import.meta.env) RNF11 Content Security Policy (CSP) via meta tag em todas as páginas RNF12 Sanitização de XSS em todos os pontos de entrada de dados do usuário


Protocolo JSON

Formato esperado para importação e exportação:

{
  "nomeSimulado": "string (obrigatório)",
  "descricao": "string (opcional)",
  "tags": ["string"],
  "tempoLimiteMinutos": 60,
  "questoes": [
    {
      "id": "string | number (obrigatório)",
      "enunciado": "string (obrigatório)",
      "tipo": "unica | multipla (obrigatório)",
      "tags": ["string (obrigatório: 1 a 2 temas predominantes)"],
      "alternativas": [
        { "id": "string (obrigatório)", "texto": "string (obrigatório)" }
      ],
      "respostasCorretas": ["id_da_alternativa"],
      "explicacao": "string (opcional): pode citar alternativas por {{id}}"
    }
  ]
}

Regras de validação:

  • nomeSimulado, questoes são obrigatórios.
  • Cada questão deve ter id, enunciado, tipo, alternativas (mínimo 2) e respostasCorretas (mínimo 1).
  • tags por questão é obrigatório: array com 1 a 2 strings (temas predominantes), sem itens vazios nem duplicadas. São usadas no resultado final para o relatório de desempenho por tema (matriz, lacunas críticas, gaps e plano de estudo).
  • Para tipo: "unica", respostasCorretas deve conter exatamente 1 elemento.
  • Todos os IDs em respostasCorretas devem existir no array alternativas da questão.
  • explicacao é opcional e pode citar as alternativas por referências dinâmicas {{id}} (ex.: A resposta é '{{c}}'). O QuizLab substitui o placeholder pela letra exibida na ordem atual, então a explicação acompanha o embaralhamento de questões e alternativas (padrão usado nos bancos "Revisão de Véspera"). Referências a IDs inexistentes são preservadas intactas para facilitar a detecção de erros, e o validator rejeita refs a IDs que não existem.
  • Todos os campos string são validados contra tipos primitivos.
  • tempoLimiteMinutos é opcional; se ausente, o timer do Modo Exame é calculado automaticamente (questões × 120 segundos).

Persistência de Dados

Todas as chaves do localStorage são centralizadas em CONFIG.STORAGE:

Chave Conteúdo
quizlab_library Array de simulados com metadados
quizlab_session Estado da sessão ativa (Modo Estudo)
quizlab_draft Rascunho do criador (autosave a cada 30s, restaurado ao abrir)
quizlab_theme Preferência de tema (dark / light)

Estrutura de metadados por item da biblioteca:

{
  "id": "uuid",
  "data": { ... },
  "meta": {
    "timesPlayed": 5,
    "lastPlayed": 1700000000000,
    "averageScore": 72,
    "history": [
      { "playedAt": 1700000000000, "score": 80, "correct": 8, "total": 10 }
    ],
    "wrongQuestions": [
      { "sourceQuizId": "uuid", "sourceQuizName": "string", "questao": { ... }, "errorCount": 1 }
    ]
  }
}

Design System

O design system é definido inteiramente em variáveis CSS no :root de styles.css, organizadas em três camadas (primitivo → semântico → componente), seguindo o Design System Coringa v2. Nenhum valor de cor, espaçamento ou tipografia deve ser hardcoded nos módulos.

Primitivos: --roxo-950/900/800/700/500/300/100, --verde-700/500/300, --cinza-800/600/400/200, --branco, --preto-absoluto, --vermelho-500/700, --amarelo-500, --teal-500/700/900, --azul-500/700.

Semânticos:

  • Ação (verde, racionado a CTAs e destaques): --primary, --primary-dark, --primary-glow, --on-accent (texto sobre fundo verde)
  • Marca (roxo): --brand, --brand-strong, --brand-glow
  • Estado: --success/--success-strong (teal), --error/--error-bg/ --danger-solid (vermelho), --warning (amarelo), --info/--info-glow (azul)
  • Superfícies: --bg, --bg-glass, --surface, --surface-light, --code-bg, --code-text
  • Texto e borda: --text, --text-main, --text-secondary, --text-muted, --border, --border-glass, --overlay

Tipografia: --font (Inter, corpo), --font-display (Fraunces, títulos de destaque com eixo WONK) e --font-mono (JetBrains Mono).

Elevação e movimento: --elevation-1/2/3 (a 3 inclui glow temático e é usada em modais), --shadow-sm/md/lg, --duration-fast/base/slow/dramatic, --ease-standard/dramatic.

Forma e assinatura: --radius-sm/md/lg/pill/full; o recorte assimétrico (assinatura do Coringa) é aplicado ao CTA principal do hero, à carta da landing, ao badge e a selos de canto em modais e na barra do quiz. A personalidade v2.1 adiciona glow ambiente no fundo, grão sutil (SVG noise) e losangos arlequim como divisores. (.btn-hero.btn-primary::before com clip-path), preservando o :focus-visible.

Acessibilidade: :focus-visible global com contorno verde de 2px; estados de erro/sucesso/informação usam o par fundo -700/texto branco verificado; prefers-reduced-motion desliga animações; prefers-contrast: more reforça as cores.


Internacionalização (i18n)

Todos os textos visíveis ao usuário estão centralizados em data/texts.js e aplicados via data/i18n.js.

Como usar

Atributos HTML disponíveis:

Atributo Efeito
data-i18n="caminho.chave" Define o textContent do elemento
data-i18n-placeholder="caminho.chave" Define o placeholder do input
data-i18n-value="caminho.chave" Define o textContent (para options)
data-i18n-badge="caminho.chave" Define o textContent (para badges)
// Exemplo de uso programático
I18n.t('library.title')          // 'Minha Biblioteca'
I18n.format('import.batchReport.saved', { count: 5 })  // '5 importado(s) com sucesso.'

Monitoramento

O QuizLab utiliza Datadog RUM (Real User Monitoring) para métricas de performance e erros em produção. A configuração é feita via variáveis de ambiente injetadas pelo Vite:

DATADOG_APPLICATION_ID=
DATADOG_CLIENT_TOKEN=
DATADOG_SERVICE=quizlab
DATADOG_ENV=production
DATADOG_VERSION=1.6.3

Copie .env.example para .env e preencha os valores para habilitar o monitoramento em desenvolvimento.


Qualidade de Código (Lint)

O projeto usa ESLint (flat config), um verificador de política de texto e um detector de dead code para manter a qualidade e a consistência do código:

npm run lint           # ESLint + política de texto + dead code (falha se houver problemas)
npm run lint:js        # apenas ESLint
npm run lint:fix       # corrige automaticamente o que for possível (eslint --fix)
npm run lint:text      # apenas a política anti-travessão
npm run lint:deadcode  # apenas a verificação de dead code
  • ESLint (eslint.config.mjs): base eslint:recommended, globais do navegador e do app (padrão IIFE que expõe módulos no window), testes em CommonJS e scripts de ferramentas em Node. console.* é permitido por design (debug do projeto).
  • Política de texto (scripts/check-no-em-dash.js): o travessão em-dash (U+2014) e o escape equivalente em JavaScript são proibidos em todo o projeto (código, comentários, testes, documentação, CSS, JSON e workflows). Use hífen simples " - ".
  • Dead code (scripts/check-deadcode.js): detecta métodos de módulos globais (window.X = X) nunca usados, classes CSS definidas em styles.css mas nunca referenciadas (HTML/JS/data) e chaves i18n (folhas do objeto TEXTS em data/texts.js) nunca usadas via TEXTS.caminho.chave, atributos data-i18n* ou I18n.t()/format(). Métodos privados (prefixo _) são ignorados por convenção; DDActions (scaffold de telemetria) é exceção explícita no script.

O lint roda no CI (npm run lint) e deve passar localmente antes de qualquer Pull Request.

Pre-commit hook (husky)

O projeto usa husky para rodar o lint automaticamente antes de cada commit. Após npm install, o hook é instalado sozinho e bloqueia commits com lint sujo ou travessão em-dash. Para pular o hook em um caso pontual: git commit --no-verify.


Testes Automatizados

O projeto usa dois níveis de teste: unitários/integração com o runner nativo do Node.js (node:test, sem dependências) e end-to-end com Playwright (Chromium) rodando contra o app real servido pelo Vite.

Testes unitários e de integração

# Servidor de desenvolvimento (Vite)
npm run dev

# Build de produção
npm run build

# Preview do build
npm run preview

# Testes
npm test                  # todos os testes
npm run test:unit         # apenas unitários
npm run test:integration  # apenas integração

Cobertura:

  • quiz-engine.test.js init, select (única/múltipla), confirm, navigate, flag, reset, shuffling.
  • validator.test.js schema válido, campos obrigatórios, tipos inválidos, alternativas.
  • storage-manager.test.js CRUD, replaceInLibrary, updateLibraryMeta, removeManyFromLibrary, histórico, getStorageStats (quota fixa 4 MB), canStore, saveWrongQuestions, getAggregatedWrong.
  • file-handler.test.js _findDuplicate, _handleSingle, _handleBatch (salvos, skipped, conflito, cheio, redirect), handleMultiple (filtro de extensão).
  • p1-p2-fixes.test.js regressão: sessão órfã após deletar simulado, modal repetido, timer invisível no Modo Exame.
  • security-fixes.test.js regressão: XSS via explicações/alternativas, validação de arquivo (limite 2MB/50 arquivos/timeout 10s), sanitização de descrições.
  • quiz-flow.test.js fluxo completo: adicionar → iniciar → responder → salvar stats → acumulação de média.
  • explanation-utils.test.js referências {{id}} na explicação resolvendo para a letra exibida na ordem atual (incluindo embaralhamento).

Os testes rodam em Node.js >= 21 sem DOM real. O ambiente é simulado via polyfills em tests/setup/environment.js e os módulos IIFE são carregados via tests/setup/loader.js.

Testes End-to-End (Playwright)

Os testes E2E validam os fluxos reais no navegador: landing, biblioteca, quiz (estudo e exame), criador, importação e retomada de sessão. O Playwright inicia o servidor do Vite automaticamente na porta 4173 (configurado em playwright.config.js).

# Instala o browser Chromium (uma vez)
npm run test:e2e:install

# Executa os testes E2E (inicia o Vite sozinho)
npm run test:e2e

# Executa com janela visível (debug)
npm run test:e2e:headed

Detalhes de implementação:

  • Os testes semeiam o localStorage (quizlab_library) antes do app carregar via page.addInitScript (helpers em e2e/helpers.js), garantindo determinismo sem depender do simulado demo.
  • Fixtures JSON determinísticos ficam em e2e/fixtures/.
  • Os seletores usam data-action (e não textos visíveis) para não depender do catálogo TEXTS.
  • O diretório e2e/ tem package.json próprio com "type": "module" para permitir ESM sem afetar o restante do projeto (CommonJS).
  • O teste de embaralhamento (shuffle.spec.js) fixa Math.random via addInitScript (mockDeterministicRandom) para tornar a permutação do Fisher-Yates determinística e livre de flakiness.

Em Linux, se o Chromium falhar com libnspr4.so: cannot open shared object file, instale as dependências de sistema:

npx playwright install-deps chromium

CI/CD

ci.yml executa em Pull Requests para main e develop e pushes para develop:

  1. Checkout do código
  2. Setup Node.js 22
  3. npm ci
  4. npm run lint
  5. npm run test:unit
  6. npm run test:integration
  7. Job separado e2e: npx playwright install --with-deps chromium + npm run test:e2e (relatório em artefato se falhar)

deploy.yml executa em push para main (produção):

  1. Checkout do código
  2. Setup Node.js 22
  3. npm ci
  4. npm run lint
  5. npm test (suite completa)

Contribuição

Leia o CONTRIBUTING.md para entender os padrões de arquitetura (ESM + IIFE, Event Delegation), convenções de commit (Conventional Commits) e o checklist de qualidade antes de enviar um Pull Request.


Documentação gerada com base na versão v1.6.3.

About

Plataforma de simulados de alta performance e privacidade total. Crie e pratique via JSON com design Cyberpunk. 100% Client-Side.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages