HormoneTrack-web/tests/store.test.js
Siphonight 491efef8eb v1.4.10 — portage initial de l'app Android v1.4.10
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.
2026-09-08 14:34:58 +02:00

97 lines
4.0 KiB
JavaScript

/**
* Tests du store (localStorage) avec backend Map injectable : CRUD, IDs
* auto-incrémentés, CASCADE, pub/sub, réglages fusionnés.
* (Le module est identique en production — seul le backend change.)
*/
import { test, describe, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { store } from '../js/data/store.js';
import { makeTreatment, makeDose, makeLab } from './helpers.js';
describe('store (localStorage → backend Map en test)', () => {
beforeEach(() => {
store.setBackend(new Map());
});
test('upsert : id 0 = insertion (id auto), id > 0 = mise à jour', () => {
const id = store.upsertTreatment(makeTreatment({ id: 0, name: 'A' }));
assert.equal(id, 1);
store.upsertTreatment(makeTreatment({ id, name: 'A modifié' }));
assert.equal(store.getTreatments().length, 1);
assert.equal(store.getTreatments()[0].name, 'A modifié');
const id2 = store.upsertTreatment(makeTreatment({ id: 0, name: 'B' }));
assert.equal(id2, 2, 'auto-increment');
});
test('IDs auto-incrémentés par table (miroir Room autoGenerate)', () => {
const d1 = store.upsertDoseLog(makeDose(0, 1));
const d2 = store.upsertDoseLog(makeDose(1, 1));
const l1 = store.upsertLabResult(makeLab('E2', 100, 'pg/mL', 0));
assert.equal(d1, 1);
assert.equal(d2, 2);
assert.equal(l1, 1, 'compteurs indépendants par table');
});
test('deleteTreatment : CASCADE sur ses doses (miroir Room FK)', () => {
const trId = store.upsertTreatment(makeTreatment({}));
store.upsertDoseLog(makeDose(0, 1, { treatmentId: trId }));
store.upsertDoseLog(makeDose(1, 1, { treatmentId: trId }));
const other = store.upsertTreatment(makeTreatment({ name: 'autre' }));
store.upsertDoseLog(makeDose(2, 1, { treatmentId: other }));
store.deleteTreatment(trId);
assert.equal(store.getTreatments().length, 1);
assert.equal(store.getDoseLogs().length, 1, 'les doses du traitement supprimé sont parties');
assert.equal(store.getDoseLogs()[0].treatmentId, other);
});
test('pub/sub : les écrans sont notifiés à chaque mutation', () => {
let calls = 0;
const unsub = store.subscribe(() => calls++);
store.upsertTreatment(makeTreatment({}));
store.upsertDoseLog(makeDose(0, 1));
store.deleteDoseLog(1);
assert.equal(calls, 3);
unsub();
store.upsertLabResult(makeLab('T', 0.4, 'ng/mL', 0));
assert.equal(calls, 3, 'désabonné');
});
test('un listener qui plante ne bloque pas les autres ni la donnée', () => {
let ok = 0;
store.subscribe(() => {
throw new Error('listener cassé');
});
store.subscribe(() => ok++);
store.upsertTreatment(makeTreatment({}));
assert.equal(ok, 1, 'le second listener a bien tourné');
assert.equal(store.getTreatments().length, 1, 'la donnée est écrite');
});
test('setSettings fusionne partiellement (pas d\'écrasement des autres clés)', () => {
store.setSettings({ language: 'fr' });
store.setSettings({ autoCalibrate: true });
const s = store.getSettings();
assert.equal(s.language, 'fr', 'la langue est conservée');
assert.equal(s.autoCalibrate, true);
assert.equal(s.alertE2High, null, 'défaut opt-in');
});
test('donnée corrompue dans le storage → valeurs par défaut (jamais de crash au boot)', () => {
store.setBackend(new Map([['hormonetrack.treatments', '}}}pas du json']]));
assert.deepEqual(store.getTreatments(), []);
store.setBackend(new Map([['hormonetrack.tConfig', '{base:']]));
assert.deepEqual(store.getTConfig(), { base: 6.0, floor: 0.2, k: 0.19 }, 'défauts TConfig.kt');
});
test('clearAll remet tout à zéro', () => {
store.upsertTreatment(makeTreatment({}));
store.upsertLabResult(makeLab('E2', 1, 'pg/mL', 0));
store.setTConfig({ base: 5, floor: 0.1, k: 0.2 });
store.clearAll();
assert.deepEqual(store.getTreatments(), []);
assert.deepEqual(store.getLabResults(), []);
assert.deepEqual(store.getTConfig(), { base: 6.0, floor: 0.2, k: 0.19 });
});
});