Portage navigateur complet de l'app Android HormoneTrack v1.4.10 : - moteur PK fidèle (Estrannaise tables ODS, Transfem Science V3C, WHSAH fit Mona, Bateman) — paramètres identiques, invariants Android préservés (fixes #19-#23, #35, #52-#62), 121 tests Node épinglés - calibration par période d'ester et par modèle affiché (#60/#61) - UI 6 écrans + éditeur, CurveChart Canvas (zoom/pan fractionnaire, prévision, pics/creux, fuseau configurable), rappels web (Notification API), seuils d'alerte, i18n FR/EN - sauvegarde JSON v2 compatible Android bidirectionnelle (rétrocompat v1) - 100 % local : localStorage, aucun serveur applicatif, aucune télémétrie - processus : scripts/check.sh (syntaxe, i18n, tests, E2E navigateur, smoke HTTP), docs séparées (README + DEVELOPPEMENT + CHANGELOG) - dépôt GIT SÉPARÉ de l'Android : historique 100 % propre, versions alignées sur l'Android porté, changelogs indépendants Non porté (impossible dans un navigateur, documenté §12) : agenda récurrent, notifications onglet fermé, montre.
80 lines
3.4 KiB
JavaScript
80 lines
3.4 KiB
JavaScript
/**
|
|
* Tests des seuils d'alerte (miroir de AlertsTest.kt + AlertsEngineTest.kt) :
|
|
* comparaison stricte, cohérence, codec d'état, décision de notification.
|
|
*/
|
|
|
|
import { test, describe } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import * as Alerts from '../js/pk/alerts.js';
|
|
|
|
describe('évaluation des seuils', () => {
|
|
const t = new Alerts.Thresholds(400, 50, 1.2, 0.1);
|
|
|
|
test('strict : la valeur EXACTEMENT à la limite ne déclenche rien', () => {
|
|
assert.equal(Alerts.evaluate(400, 50, 400), null, '== high → rien');
|
|
assert.equal(Alerts.evaluate(50, 50, 400), null, '== low → rien');
|
|
assert.equal(Alerts.evaluate(400.01, 50, 400)[0], 'HIGH');
|
|
assert.equal(Alerts.evaluate(49.99, 50, 400)[0], 'LOW');
|
|
});
|
|
|
|
test('null = jamais d\'alerte sur ce côté', () => {
|
|
assert.equal(Alerts.evaluate(1000, null, null), null);
|
|
assert.equal(Alerts.evaluate(1, null, null), null);
|
|
assert.equal(Alerts.evaluate(500, null, 400)[0], 'HIGH');
|
|
});
|
|
|
|
test('HIGH prime LOW en configuration incohérente (défensif)', () => {
|
|
const [level, limit] = Alerts.evaluate(50, 100, 40); // haut < bas (interdit à la saisie)
|
|
assert.equal(level, 'HIGH');
|
|
assert.equal(limit, 40);
|
|
});
|
|
|
|
test('evaluateAll : ordre stable E2 puis T, unités correctes', () => {
|
|
const alerts = Alerts.evaluateAll(500, 0.05, t);
|
|
assert.equal(alerts.length, 2);
|
|
assert.deepEqual(alerts.map((a) => a.marker), ['E2', 'T']);
|
|
assert.deepEqual(alerts.map((a) => a.unit), ['pg/mL', 'ng/mL']);
|
|
assert.equal(alerts[0].level, 'HIGH');
|
|
assert.equal(alerts[1].level, 'LOW');
|
|
});
|
|
|
|
test('isCoherent : haut > bas requis (les deux marqueurs indépendants)', () => {
|
|
assert.equal(new Alerts.Thresholds(400, 50, 1.2, 0.1).isCoherent(), true);
|
|
assert.equal(new Alerts.Thresholds(50, 400, null, null).isCoherent(), false);
|
|
assert.equal(new Alerts.Thresholds(null, null, 0.1, 1.2).isCoherent(), false);
|
|
// un seul côté défini → cohérent
|
|
assert.equal(new Alerts.Thresholds(400, null, null, 0.1).isCoherent(), true);
|
|
});
|
|
});
|
|
|
|
describe('codec d\'état (anti-spam) — rétro-résistant aux entrées malformées', () => {
|
|
test('encode → parse round-trip', () => {
|
|
const alerts = [
|
|
{ marker: 'E2', level: 'HIGH' },
|
|
{ marker: 'T', level: 'LOW' },
|
|
];
|
|
const encoded = Alerts.encodeState(alerts);
|
|
assert.equal(encoded, 'E2:HIGH;T:LOW');
|
|
assert.deepEqual(Alerts.parseState(encoded), { E2: 'HIGH', T: 'LOW' });
|
|
});
|
|
|
|
test('parse : résistant aux segments cassés', () => {
|
|
assert.deepEqual(Alerts.parseState('E2:HIGH;garbage;T:BOGUS;:LOW'), { E2: 'HIGH' });
|
|
assert.deepEqual(Alerts.parseState(''), {});
|
|
assert.deepEqual(Alerts.parseState(null), {});
|
|
assert.deepEqual(Alerts.parseState(undefined), {});
|
|
});
|
|
|
|
test('shouldNotify : nouveau franchissement → true, même état → false', () => {
|
|
const current = { E2: 'HIGH' };
|
|
assert.equal(Alerts.shouldNotify(current, null), true, 'première notification');
|
|
assert.equal(Alerts.shouldNotify(current, { E2: 'HIGH' }), false, 'pas de spam');
|
|
assert.equal(Alerts.shouldNotify(current, { E2: 'LOW' }), true, 'changement H↔L');
|
|
assert.equal(Alerts.shouldNotify({ E2: 'HIGH', T: 'LOW' }, { E2: 'HIGH' }), true, 'nouveau marqueur');
|
|
});
|
|
|
|
test('retour à la normale → false (pas de notif) — l\'appelant efface l\'état', () => {
|
|
assert.equal(Alerts.shouldNotify({}, { E2: 'HIGH' }), false);
|
|
});
|
|
});
|