Files
carinha-control-panel/web/tests/nucleo.test.ts
julioandClaude Opus 5.5 c5fd54c1d9 feat(celular): rosto e app usam SSE e token; remove telas legadas
- carinha.html assina /eventos (expressao, app_config, conversa, reacao,
  recarregar) e volta ao polling de /fila sozinho se o SSE cair; foto dos
  ajustes a cada (re)conexao; token (?t=) vai em todo fetch ao servidor
- APK 0.8-token: token nas preferencias e no menu de toque longo, enviado
  na pagina, no HTTP do Java e ao llama-server (--api-key); trocar servidor
  por Intent exige o token salvo; camera/microfone so para o servidor
  configurado ou a copia local
- cerebro/conversa mandam a chave ao llama-server do S10
- servidor: evento SSE 'recarregar'; remove /camera/frame e /app/connect;
  enderecos antigos redirecionam para a secao do painel novo
- remove dashboard, painel, conversa, emojis, treinamento, braco, servos,
  api, scripts e CSS antigos, audio-robot.js e servir-local.bat
- logica pura do painel em web/src/lib/nucleo.ts com testes node --test
  (substitui test_movimentos.js e test_rastreamento.js)
- testes de navegador portados para o painel novo (rtc, rosto, rastreamento)
- README, docs/PAINEL.md e docs/SEGURANCA.md reescritos; versao 0.8.0;
  carinha-pc.service le /etc/carinha.env

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:11:58 -03:00

58 lines
2.5 KiB
TypeScript

// node --test (Node 22.6+ remove os tipos sozinho). Substitui tests/test_movimentos.js e test_rastreamento.js.
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { encaixe, escolherAlvo, limites, sequencias } from '../src/lib/nucleo.ts';
test('mão tem prioridade sobre rosto e entradas inválidas são ignoradas', () => {
const faces = [{ x: 0.2, y: 0.1, w: 0.3, h: 0.4, score: 0.9 }];
const maos = [Array.from({ length: 21 }, () => ({ x: 0.8, y: 0.6 }))];
assert.equal(escolherAlvo(faces, maos)?.tipo, 'mao');
const rosto = escolherAlvo(faces, [])!;
assert.equal(rosto.tipo, 'rosto');
assert.ok(Math.abs(rosto.x - 0.35) < 1e-9 && Math.abs(rosto.y - 0.3) < 1e-9);
assert.equal(escolherAlvo([], []), null);
assert.equal(escolherAlvo([{ x: NaN, y: 0, w: 1, h: 1 }], []), null);
});
test('entre rostos, segue o mais perto do alvo anterior', () => {
const faces = [
{ x: 0.0, y: 0.0, w: 0.4, h: 0.4 },
{ x: 0.7, y: 0.7, w: 0.2, h: 0.2 },
];
assert.equal(escolherAlvo(faces, [])?.x, 0.2);
assert.ok(Math.abs(escolherAlvo(faces, [], { tipo: 'rosto', x: 0.8, y: 0.8 })!.x - 0.8) < 1e-9);
});
test('encaixe centraliza sem cortar', () => {
const r = encaixe(1000, 500, 640, 480);
assert.ok(Math.abs(r.x - (500 - (500 * 640) / 480 / 2)) < 1e-9);
assert.equal(r.y, 0);
assert.ok(Math.abs(r.h - 500) < 1e-9);
});
test('sequências respeitam a faixa calibrada e voltam ao neutro', () => {
const faixas = Array.from({ length: 7 }, (): [number, number] => [81, 99]);
const seqs = sequencias(faixas);
assert.equal(seqs.length, 5);
for (const s of seqs) {
assert.ok(s.passos.length >= 3);
for (const passo of s.passos) {
assert.equal(passo.length, 7);
passo.forEach((v) => assert.ok(v >= 81 && v <= 99, `${s.nome}: ${v}`));
}
}
assert.deepEqual(seqs[0].passos.at(-1), Array(7).fill(90));
// Faixa que não contém 90°: o neutro vira o ponto mais próximo dentro dela.
const deslocada = sequencias(Array.from({ length: 7 }, (): [number, number] => [100, 140]));
assert.deepEqual(deslocada[0].passos.at(-1), Array(7).fill(100));
});
test('limites seguem a calibração e o par 5/6 invertido', () => {
const cal = Array.from({ length: 8 }, (_, canal) => ({ canal, minimo: 1400, maximo: 1600, centro: 1500 }));
const art = [0, 1, 2, 3, 4, 5, 7].map((canal) => ({ canal }));
const f = limites(cal, art);
assert.deepEqual(f[0], [81, 99]);
cal[6] = { canal: 6, minimo: 1450, maximo: 1600, centro: 1500 };
assert.deepEqual(limites(cal, art)[5], [81, 94]);
});