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.
135 lines
4.1 KiB
JavaScript
135 lines
4.1 KiB
JavaScript
/**
|
|
* Helpers de test partagés (chargement de l'asset PK, fabriques de fixtures).
|
|
*
|
|
* Les tests tournent en Node pur (`node --test tests/`) — aucun DOM ni
|
|
* localStorage : la couche pure (pk/, backup) est testée directement, le
|
|
* store est testé avec un backend Map injectable.
|
|
*/
|
|
|
|
import { readFileSync, existsSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { initWithJson } from '../js/pk/pk-profile-store.js';
|
|
|
|
/** Racine du dépôt web. */
|
|
export const WEB_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
/**
|
|
* Dossier des données de test RÉELLES (exports Android de l'utilisatrice) —
|
|
* HORS dépôt (données de santé, cf docs/DEVELOPPEMENT.md §8).
|
|
*
|
|
* Depuis la séparation des dépôts (v1.4.10), ce dossier vit dans le dépôt
|
|
* ANDROID voisin (`../HormoneTrack/local-test-data/`) : le web ne duplique
|
|
* rien, il ne fait que le CONSOMMER en lecture (skip des tests s'il est
|
|
* absent — clone neuf, CI, autre machine). Candidats par ordre de priorité :
|
|
* 1. le dépôt Android voisin (layout standard ~/projects/HormoneTrack*)
|
|
* 2. un sibling direct `../local-test-data` (ancien layout repo unique)
|
|
* 3. un dossier local-test-data/ dans le dépôt web lui-même (si l'utilisatrice
|
|
* préfère y déposer ses exports — gitigné)
|
|
*/
|
|
export function localTestDataDir() {
|
|
const candidates = [
|
|
join(WEB_ROOT, '..', 'HormoneTrack', 'local-test-data'),
|
|
join(WEB_ROOT, '..', 'local-test-data'),
|
|
join(WEB_ROOT, 'local-test-data'),
|
|
];
|
|
for (const dir of candidates) {
|
|
if (existsSync(dir)) return dir;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Candidats de fichiers de backup réels pour les régressions data-driven. */
|
|
export function localBackupPath(name) {
|
|
const dir = localTestDataDir();
|
|
return dir ? join(dir, name) : null;
|
|
}
|
|
|
|
/**
|
|
* Charge l'asset PK réel (le MÊME fichier que l'app Android embarque) et
|
|
* initialise PKProfileStore. À appeler dans les suites qui touchent aux
|
|
* profils ODS / au dispatch moteur.
|
|
*/
|
|
export function initProfiles() {
|
|
const json = readFileSync(join(WEB_ROOT, 'assets/pk_profiles.json'), 'utf8');
|
|
initWithJson(json);
|
|
return JSON.parse(json);
|
|
}
|
|
|
|
/** Contenu brut parsé de l'asset (sans initialiser le store). */
|
|
export function rawProfiles() {
|
|
return JSON.parse(readFileSync(join(WEB_ROOT, 'assets/pk_profiles.json'), 'utf8'));
|
|
}
|
|
|
|
/** Heure en ms. */
|
|
export const HOUR_MS = 3600000;
|
|
/** Jour en ms. */
|
|
export const DAY_MS = 86400000;
|
|
|
|
/**
|
|
* Traitement injectable E2 par défaut (EEn TFS 5 mg) — valeurs des presets
|
|
* `preset_een_tfs`. id=0 = « pas encore en base » (insertion côté store).
|
|
*/
|
|
export function makeTreatment(overrides = {}) {
|
|
return {
|
|
id: 0,
|
|
name: 'EEn test',
|
|
type: 'ESTRADIOL',
|
|
route: 'INJECTION_SUBCUT',
|
|
doseAmount: 5,
|
|
doseUnit: 'mg',
|
|
isActive: true,
|
|
notes: null,
|
|
esterType: 'EEN',
|
|
pkModel: 'TFS',
|
|
absorptionHours: 156,
|
|
eliminationHalfLifeHours: 110,
|
|
bioavailabilityFraction: 1,
|
|
scaleFactor: 1.0,
|
|
forecastIntervalDays: null,
|
|
reminderHour: null,
|
|
reminderMinute: null,
|
|
reminderEnabled: false,
|
|
calendarEventId: null,
|
|
createdAt: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/** Dose simple rattachée au traitement par défaut (id 0 — celui de makeTreatment). */
|
|
export function makeDose(timestamp, doseAmount = 5, overrides = {}) {
|
|
return {
|
|
id: 0,
|
|
treatmentId: 0,
|
|
timestamp,
|
|
doseAmount,
|
|
notes: null,
|
|
esterType: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/** Lab simple. */
|
|
export function makeLab(marker, value, unit, timestamp, overrides = {}) {
|
|
return {
|
|
id: 0,
|
|
marker,
|
|
value,
|
|
unit,
|
|
timestamp,
|
|
notes: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/** assert proche (tolérance absolue ou relative) — les tests PK pin des valeurs d'article. */
|
|
export function assertClose(actual, expected, tolerance, message = '') {
|
|
const diff = Math.abs(actual - expected);
|
|
const tol = typeof tolerance === 'number' && tolerance <= 1 && tolerance > 0 && expected !== 0
|
|
? Math.abs(expected) * tolerance
|
|
: tolerance;
|
|
if (diff > tol) {
|
|
throw new Error(`${message}\n attendu ≈ ${expected} (±${tol}) — obtenu ${actual}`);
|
|
}
|
|
}
|