Identificação
- Duração: 2 horas
- Tipo: oficina integradora
- Entrega: painel de tarefas final do Módulo 2
- Laboratório:
../exemplos/aula-2.8/index.html
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.
localStorage, sessionStorage, cookie e IndexedDB são iguais?
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:
- explicar o que
localStoragefaz e o que não faz; - diferenciar mecanismos de armazenamento no navegador;
- salvar apenas preferências não sensíveis;
- serializar e validar preferências;
- explicar teste unitário, integração e ponta a ponta;
- estruturar testes com Arrange, Act e Assert;
- testar casos normais, limites e erros;
- manter regras puras separadas do DOM;
- integrar os conteúdos do Módulo 2;
- 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:
- é sensível?
- pertence ao servidor?
- precisa sincronizar?
- pode ser apagado?
- existe requisito legal de retenção?
- 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:
- interpretar com
JSON.parse; - confirmar que é array;
- validar cada tarefa;
- rejeitar a operação inteira ou informar itens inválidos;
- 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:
- problema e público;
- demonstração do fluxo principal;
- arquitetura modular;
- decisão de não armazenar tarefas no navegador;
- testes;
- 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
localStoragenã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.