O botão de cancelar que não cancelava
O trabalho desta semana no Greenhouse foi um pacote pequeno de UX: renomear um projeto inline, um chip de “negligenciado há Nd” em cards parados na lista de trabalho, preview de imagem ao lado do preview de áudio já existente, e um link “por que essas regras?” de volta para a tela de regras do onboarding. Nada disso parecia arriscado.
O bug que eu quase lancei
A interface de renomear é simples assim: clica num ícone de lápis, o título vira um campo de texto, Enter ou clicar fora salva, Escape cancela. Escrevi a versão óbvia primeiro:
function cancel() {
editing = false;
}
Parecia certo. Aí sentei para escrever o teste de regressão especificamente para o Escape, e para o teste realmente significar alguma coisa simulei a sequência real: apertar Escape, depois disparar o blur que acontece logo depois (remover um input focado do DOM dispara um evento de blur nele, antes do navegador terminar de desmontá-lo).
Foi aí que quebrou. Minha função save() roda no blur, viu o texto ainda digitado e não confirmado do usuário sentado no valor vinculado (porque cancel() nunca tocava nele, só fechava o editor), e salvou mesmo assim. Escape parecia funcionar num clique manual normal. Não cancelava nada de verdade, só teve sorte de que geralmente nada dá blur num input que você está no meio de remover.
A correção
Uma linha: resetar o valor antes de fechar.
function cancel() {
value = original;
editing = false;
}
save() já tinha uma proteção de “não faz nada se nada mudou” (necessária para o caso comum de abrir o campo e dar blur sem digitar nada). Depois que cancel() reseta o valor primeiro, essa proteção torna toda a corrida inofensiva, não importa em que ordem os eventos caiam.
Por que clicar manualmente não teria pego isso
Essa é a parte que ficou comigo. Se eu tivesse só clicado na funcionalidade manualmente, Escape parece completamente correto, nada salva visivelmente. O bug só existe no intervalo entre “o input fecha” e “algum evento perdido do navegador dispara durante esse fechamento”, e navegadores não disparam esse blur de forma confiável em todo caminho de remoção, então até testes manuais repetidos talvez não mostrassem isso. Só encontrei porque escrever um teste de regressão de verdade me forçou a simular a ordem exata dos eventos, não porque eu estava caçando uma corrida.
Também nesta sessão
Verifiquei as quatro partes do pacote contra o app de verdade rodando, via IPC real, não testes mockados, usando o harness WebDriver construído ao longo de sessões anteriores. Mais uma coisinha que vale uma nota de rodapé: você não pode passar um elemento DOM encontrado como argumento de script para o endpoint de execução de script desse harness e esperar que ele continue sendo um elemento real do outro lado, ele é deserializado como um objeto simples. A correção é só reconsultar ele com document.querySelector dentro do próprio script.
Leitura relacionada
O bug que meus testes unitários jamais poderiam ter encontrado
Um quadro kanban renderizava uma prop que ficava obsoleta com dados novos, e todo teste mockado passava porque um mock não consegue expressar isso.
Um gate de acessibilidade de dois segundos que roda a cada push
axe-core sobre o harness de testes de componente já existente, restrito a regras WCAG de verdade, e o que um gate em jsdom não consegue detectar.
Adicionando espanhol e português a um app desktop, e caindo na minha própria armadilha
Uma troca de idioma baseada em reload testada contra o app real, um arquivo de config que acabou importando, e uma palavra em espanhol do hemisfério errado.
Você também pode achar útil
Proton VPN
VPN comercial com filtragem NetShield e interruptor de desligamento automático.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisAdGuard para iOS
Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.
Como afiliado da AdGuard, ganho com compras qualificadas.
Saiba maiseSIM Airalo
eSIM de dados local para viagens - sem necessidade de trocar um SIM físico.
Este é meu link de indicação da Airalo. Você recebe um desconto no seu primeiro eSIM e eu ganho crédito da Airalo para o meu.
Saiba mais