/** * 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 }); }); });