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.
97 lines
4.0 KiB
JavaScript
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 });
|
|
});
|
|
});
|