Engenharia de IA Aplicada à Programação Web
Aula 8 de 8
Navegar por módulos e aulas
  1. 01 Fundamentos da Web
  2. 02 JavaScript moderno
  3. 03 TypeScript, Git e qualidade
  4. 04 Angular
  5. 05 Node.js, NestJS e APIs
  1. 2.1 Valores, variáveis e tipos
  2. 2.2 Condições e repetições
  3. 2.3 Funções, escopo e closures
  4. 2.4 Arrays e transformação de dados
  5. 2.5 Objetos, JSON e modelagem
  6. 2.6 Módulos e tratamento de erros
  7. 2.7 Promises, async/await e Fetch
  8. 2.8 Armazenamento, testes e projeto

Módulo 2 · Aula 2.8

Armazenamento, testes e projeto final

2 horas Oficina integradora JavaScript moderno
Ver fonte Markdown

Identificação

Introdução

O que é armazenamento no navegador?

O navegador oferece mecanismos para preservar pequenas informações entre interações ou visitas. Um deles é localStorage:

localStorage.setItem("course-theme", "dark");

const theme = localStorage.getItem("course-theme");

Ele guarda pares de chave e valor como strings para uma origem específica.

localStorage é banco de dados?

Não. localStorage:

  • armazena apenas strings;
  • possui espaço limitado;
  • não oferece consultas relacionais;
  • não possui transações como um banco relacional;
  • é acessível pelo JavaScript da página;
  • pode ser apagado pelo usuário;
  • não sincroniza automaticamente entre dispositivos.

No curso, dados de negócio serão persistidos no MySQL pela API. Nesta aula, localStorage guardará apenas preferências não sensíveis, como tema, filtro e ordenação.

Posso guardar senha ou token em localStorage?

Não é uma escolha segura para segredos. Qualquer JavaScript executado na mesma origem pode acessar o conteúdo. Uma vulnerabilidade XSS pode expor os valores.

Não armazene:

  • senha;
  • chave de API;
  • dados financeiros;
  • informações pessoais sensíveis;
  • token de autenticação apenas por conveniência.

A estratégia de autenticação será definida posteriormente no back-end, considerando cookies seguros, duração, CSRF e modelo de ameaça.

Não:

  • localStorage: persiste até ser removido;
  • sessionStorage: normalmente dura enquanto a aba estiver aberta;
  • cookie: pode ser enviado automaticamente ao servidor e possui atributos de segurança;
  • IndexedDB: banco de dados no navegador para dados estruturados e maiores.

Escolha pela necessidade, não pela familiaridade.

O que é um teste automatizado?

Um teste automatizado executa uma regra, compara o resultado real ao esperado e informa sucesso ou falha:

const result = classifyPriority(true, true);

if (result !== "critical") {
  throw new Error("Esperava prioridade critical");
}

Ele pode ser repetido após cada alteração.

Teste automatizado substitui teste manual?

Não. Eles se complementam:

  • testes unitários verificam regras isoladas;
  • testes de integração verificam partes trabalhando juntas;
  • testes ponta a ponta verificam fluxos pela interface;
  • testes manuais ajudam a explorar usabilidade e situações não previstas.

Onde isso aparece no projeto?

O painel final reunirá:

  • carregamento assíncrono de tarefas;
  • filtros e ordenação com arrays;
  • métricas;
  • criação de tarefas;
  • preferências locais;
  • regras puras;
  • testes executados no navegador;
  • documentação para entrega.

Objetivos

Ao final da aula, o aluno deverá conseguir:

  1. explicar o que localStorage faz e o que não faz;
  2. diferenciar mecanismos de armazenamento no navegador;
  3. salvar apenas preferências não sensíveis;
  4. serializar e validar preferências;
  5. explicar teste unitário, integração e ponta a ponta;
  6. estruturar testes com Arrange, Act e Assert;
  7. testar casos normais, limites e erros;
  8. manter regras puras separadas do DOM;
  9. integrar os conteúdos do Módulo 2;
  10. documentar e apresentar o projeto.

Pré-requisitos

  • Aulas 2.1 a 2.7 concluídas;
  • navegador, DevTools e servidor local;
  • domínio básico de módulos, JSON, arrays e Fetch.

Pergunta orientadora

Como concluir uma aplicação previsível, preservar somente preferências adequadas e provar que suas regras continuam funcionando?

Roteiro sugerido

Etapa Duração
Armazenamento no navegador 25 min
Fundamentos de testes 30 min
Integração do painel 25 min
Testes e correções 20 min
Documentação e apresentação 15 min
Encerramento 5 min

1. Origem e persistência

Armazenamento web é separado por origem, formada por protocolo, host e porta:

http://localhost
https://localhost
http://localhost:8080

Essas origens são diferentes. Uma página não acessa livremente o armazenamento de outra origem.

2. API do localStorage

localStorage.setItem("key", "value");
localStorage.getItem("key");
localStorage.removeItem("key");
localStorage.clear();

clear() apaga todas as chaves daquela origem e pode remover dados de outras partes do site. Prefira remover as chaves pertencentes à aplicação.

3. Valores são strings

localStorage.setItem("compact", true);

const value = localStorage.getItem("compact");
typeof value; // "string"

Para objetos:

const preferences = {
  theme: "dark",
  filter: "open",
};

localStorage.setItem(
  "task-panel:preferences",
  JSON.stringify(preferences),
);

Leitura:

const text = localStorage.getItem("task-panel:preferences");
const preferences = text
  ? JSON.parse(text)
  : defaultPreferences;

4. Validar preferências

O conteúdo pode estar ausente, corrompido ou ter sido alterado:

function isValidPreferences(value) {
  return (
    typeof value === "object" &&
    value !== null &&
    ["light", "dark"].includes(value.theme) &&
    ["all", "open", "done"].includes(value.filter)
  );
}

Se for inválido, volte ao padrão.

5. Tratar falhas de armazenamento

O acesso pode falhar por política, limite ou modo do navegador:

try {
  localStorage.setItem(key, value);
} catch {
  showMessage(
    "A preferência será usada apenas nesta sessão.",
  );
}

A funcionalidade principal não deve depender silenciosamente de uma preferência.

6. Nomes de chave

Evite chaves genéricas:

localStorage.setItem("theme", "dark");

Use namespace:

localStorage.setItem(
  "posia:task-panel:preferences:v1",
  json,
);

O sufixo de versão ajuda futuras migrações.

7. O que não armazenar

Mesmo que um valor caiba, não significa que deve ser salvo. Pergunte:

  1. é sensível?
  2. pertence ao servidor?
  3. precisa sincronizar?
  4. pode ser apagado?
  5. existe requisito legal de retenção?
  6. um script injetado poderia expô-lo?

No laboratório, tarefas permanecem em memória. Somente a preferência visual é persistida.

8. O que é testar?

Testar é comparar comportamento observado com expectativa definida.

Teste manual:

1. marcar urgente e importante;
2. executar classificação;
3. confirmar prioridade crítica.

Teste automatizado:

test("classifica urgente e importante", () => {
  expect(
    classifyPriority(true, true),
  ).toBe("critical");
});

9. Pirâmide de testes

Uma estratégia comum possui:

  • muitos testes unitários rápidos;
  • alguns testes de integração;
  • poucos testes ponta a ponta essenciais.

Não é uma regra matemática. O equilíbrio depende do risco, arquitetura e custo de manutenção.

10. Teste unitário

Verifica uma unidade pequena e isolada:

const result = normalizeTitle("  Estudar   testes ");
assertEqual(result, "Estudar testes");

Funções puras são boas candidatas porque não dependem do DOM, rede ou relógio.

11. Teste de integração

Verifica componentes juntos:

formulário → regra de criação → lista renderizada

Pode revelar contratos incompatíveis que testes unitários isolados não encontram.

12. Teste ponta a ponta

Simula o fluxo próximo ao usuário:

abrir página → preencher tarefa → enviar → confirmar cartão

É mais abrangente, mas normalmente mais lento e sensível ao ambiente.

13. Arrange, Act, Assert

Organize o teste:

// Arrange
const input = "  Estudar testes  ";

// Act
const result = normalizeTitle(input);

// Assert
assertEqual(result, "Estudar testes");

Essa separação torna intenção e falha mais fáceis de compreender.

14. Nome do teste

Um bom nome descreve comportamento:

retorna critical quando a tarefa é urgente e importante

Evite:

teste 1
funciona
classifyPriority

15. Casos normais, limites e inválidos

Para validateTitle:

  • título comum;
  • exatamente 3 caracteres;
  • 2 caracteres;
  • string vazia;
  • somente espaços;
  • tipo inesperado, se fizer parte do contrato público.

Limites frequentemente revelam erros como > no lugar de >=.

16. Uma expectativa principal

Um teste pode conter mais de uma verificação, mas deve ter um motivo claro para falhar. Se o teste valida comportamentos independentes, divida-o.

17. Testes não devem depender de ordem

Cada teste deve preparar seu próprio estado. Um teste não deveria funcionar apenas porque outro executou antes.

Evite estado global mutável entre testes.

18. Testar implementação ou comportamento?

Prefira testar resultados observáveis:

assertEqual(
  classifyPriority(true, false),
  "high",
);

Evite testes que quebrem somente porque a implementação interna foi reorganizada, sem mudança de comportamento.

19. Cobertura

Cobertura mede quais partes do código foram executadas pelos testes. Ela não garante qualidade das expectativas.

Um teste pode executar uma linha sem verificar o resultado correto. Use cobertura como indicador, não como objetivo isolado.

20. Test runner do laboratório

O laboratório possui um executor pequeno para fins didáticos:

test("normaliza espaços", () => {
  assertEqual(
    normalizeTitle(" A   B "),
    "A B",
  );
});

Projetos profissionais utilizarão ferramentas como Vitest, Jest ou o executor nativo apropriado ao ambiente. Nosso executor mostra os princípios sem exigir instalação.

21. Arquitetura do projeto final

assets/
  api.js
  main.js
  storage.js
  task-rules.js
  tests.js
  styles.css
data/
  tasks.json

Responsabilidades:

  • api.js: carregar dados;
  • storage.js: preferências;
  • task-rules.js: regras puras;
  • tests.js: testes;
  • main.js: coordenar DOM e estado.

22. Fluxo do painel

Fetch → tarefas em memória
               ↓
          regras puras
               ↓
 filtro → ordenação → métricas → DOM
               ↑
     preferência local não sensível

23. Importação e exportação

Exportar:

const json = JSON.stringify(tasks, null, 2);

Importar exige:

  1. interpretar com JSON.parse;
  2. confirmar que é array;
  3. validar cada tarefa;
  4. rejeitar a operação inteira ou informar itens inválidos;
  5. atualizar o estado apenas após sucesso.

Não execute JSON e não insira texto com innerHTML.

24. Documentação da entrega

O README deve incluir:

  • objetivo;
  • como abrir;
  • arquitetura;
  • funcionalidades;
  • decisões de armazenamento;
  • como executar os testes;
  • limitações;
  • próximos passos.

Documentação registra decisões que não são evidentes no código.

25. Apresentação

Roteiro de 5 minutos:

  1. problema e público;
  2. demonstração do fluxo principal;
  3. arquitetura modular;
  4. decisão de não armazenar tarefas no navegador;
  5. testes;
  6. limitações e evolução com API/MySQL.

Evite narrar cada arquivo. Mostre decisões e resultados.

26. Laboratório guiado

Abra o painel final.

Etapa 1 — Carregue os dados

O painel busca tarefas locais por Fetch e representa carregamento, sucesso e erro.

Etapa 2 — Transforme

Altere filtro e ordenação. A preferência será persistida, mas as tarefas continuarão somente em memória.

Etapa 3 — Crie uma tarefa

Envie título, prioridade e estimativa. Observe normalização, validação, métricas e renderização segura.

Etapa 4 — Execute testes

Abra a seção de qualidade e clique em Executar testes. Analise cada resultado.

Etapa 5 — Reinicie preferências

Remova somente a chave do painel. Não use localStorage.clear().

27. Erros comuns

Salvar tudo no navegador

Preferência local não substitui persistência de negócio no servidor.

Não validar o JSON armazenado

Dados locais também podem estar ausentes ou corrompidos.

Guardar segredo

Não armazene senha, chave ou dados sensíveis no front-end.

Testar somente caminho feliz

Inclua limites, vazio e erro.

Escrever teste depois e adaptar a expectativa ao resultado

A expectativa deve vir do requisito, não do comportamento acidental.

Misturar DOM e regra

Regras acopladas à página ficam difíceis de testar.

28. Boas práticas

  • persista somente o necessário;
  • use chave com namespace e versão;
  • trate falha de armazenamento;
  • mantenha regra pura;
  • dê nomes comportamentais aos testes;
  • isole o estado de cada teste;
  • teste limites e falhas;
  • preserve acessibilidade;
  • documente decisões de segurança;
  • apresente limitações honestamente.

29. Exercícios

Exercício 1 — Preferências

Salve tema e filtro em uma chave versionada.

Exercício 2 — Validação

Corrompa o JSON salvo e confirme o retorno ao padrão.

Exercício 3 — Teste de limite

Teste título com 2 e com 3 caracteres.

Exercício 4 — Matriz de prioridade

Teste as quatro combinações de urgência e importância.

Exercício 5 — Integração

Crie uma tarefa pela interface e confirme lista e métrica.

30. Desafio

Amplie o projeto:

  • importação e exportação de tarefas;
  • testes para JSON inválido;
  • estado de API indisponível;
  • tema escuro persistido;
  • relatório de testes;
  • documentação de ameaças;
  • preparação para substituir a fonte local por uma API NestJS e MySQL.

31. Lista de verificação de conclusão

  • Sei explicar por que localStorage não é banco de dados.
  • Não armazeno informações sensíveis.
  • Valido preferências carregadas.
  • Sei diferenciar tipos de teste.
  • Uso Arrange, Act e Assert.
  • Testei limites e falhas.
  • Regras estão separadas do DOM.
  • O painel representa estados assíncronos.
  • A documentação está atualizada.
  • Consigo apresentar decisões e limitações.

32. Critérios de avaliação

Critério Pontos
Integração das regras e dados 20
Filtros, métricas e interface 15
Preferências locais seguras 15
Tratamento assíncrono 15
Testes das regras essenciais 20
Acessibilidade 5
Documentação e apresentação 10
Total 100

Resumo

Nesta aula, aprendemos que:

  • armazenamento no navegador não substitui banco de dados;
  • somente preferências não sensíveis devem ser persistidas neste projeto;
  • testes automatizados tornam expectativas repetíveis;
  • testes unitários, integração e ponta a ponta possuem escopos diferentes;
  • regras puras facilitam testes;
  • uma entrega inclui código, estados, segurança, documentação e apresentação.

Encerramento do módulo

O Módulo 2 está concluído. No próximo módulo, iniciaremos TypeScript para adicionar contratos estáticos às estruturas e funções JavaScript construídas até aqui.