v1.12.0 (web) : fix #70 (inactifs sans projection) + page Doses (glyphes de forme, marqueurs prise de sang) — miroir Android
- FIX #70 : generateForecastDoses garde !isActive — un traitement archivé à Posologie ne projette plus de créneaux ; l'horizon du chip Prévision (chart.js) ne considère que les Posologies ACTIVES. §6.bis inchangé : l'historique reste simulé, seul le FUTUR s'arrête. 3 tests miroirs. - Page Doses : routeGlyph (💊💉💧🩹🧴, miroir routeIcon) + encarts « prise de sang le même jour » 🧪↑/🧪↓ (labMarkersForDose, miroir) ; i18n FR/EN ; 10 tests doses-extras.test.js (⚠️ makeDose(ts, amount, overrides)). 194 tests + E2E verts (check.sh). WEB_VERSION alignée sur 1.12.0.
This commit is contained in:
parent
0dd5fa7dfd
commit
92d207819d
15
README.md
15
README.md
@ -17,14 +17,13 @@
|
||||
> Fie-toi toujours à tes analyses de sang et aux consignes de ton
|
||||
> endocrinologue.
|
||||
|
||||
- **Statut** : web v1.11.0 — parité fonctionnelle avec l'Android v1.11.0
|
||||
sur la logique miroirée (page Traitements : inactifs regroupés tout en
|
||||
bas sous un en-tête dédié, cartes atténuées — helper pur
|
||||
`treatmentsForDisplay` miroir du Kotlin ; le fix #68 de la reco de prise
|
||||
de sang reste couvert, cf changelog) ;
|
||||
l'auto-backup journalier Android reste structurellement non porté,
|
||||
cf §12 — l'export manuel couvre la donnée ·
|
||||
**184 tests verts** · E2E navigateur ✅ · conteneur testé en
|
||||
- **Statut** : web v1.12.0 — parité fonctionnelle avec l'Android v1.12.0
|
||||
sur la logique miroirée (fix #70 : un traitement INACTIF ne projette plus
|
||||
de créneaux — graphique Prévision ; page Doses : glyphes de forme de
|
||||
prise 💊💉💧🩹🧴 et encarts « prise de sang le même jour » 🧪↑/🧪↓) ;
|
||||
l'auto-backup journalier et les notifications Android restent
|
||||
structurellement non portés, cf §12 ·
|
||||
**194 tests verts** · E2E navigateur ✅ · conteneur testé en
|
||||
configuration durcie ✅ · lint/i18n ✅
|
||||
- **Journal des versions web** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
|
||||
- **Releases** : [gitea.cloudyfy.fr/Siphonight/HormoneTrack-web/releases](https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web/releases)
|
||||
|
||||
@ -5,6 +5,32 @@
|
||||
> section la plus récente EN PREMIER ; ce fichier est lu par le dialog
|
||||
> « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`).
|
||||
|
||||
## [1.12.0] — 2026-09-29
|
||||
|
||||
### Corrigé — un traitement inactif à Posologie projetait encore ses injections (#70)
|
||||
|
||||
- Miroir de l'Android v1.12.0 (remontée : « un traitement mis en inactif ne
|
||||
devrait plus être pris en compte pour les futures prédictions ») :
|
||||
`generateForecastDoses` (js/pk/pk-engine.js) garde `!isActive` — et
|
||||
l'horizon du chip Prévision du graphique (js/ui/chart.js) ne considère
|
||||
que les Posologies ACTIVES.
|
||||
- §6.bis inchangé : l'historique d'un inactif reste simulé — seul le FUTUR
|
||||
s'arrête. 3 tests miroirs (inactif vide, actif inchangé, avant/après
|
||||
archivage).
|
||||
|
||||
### Ajouté — page Doses : glyphes de forme de prise + marqueurs « prise de sang le même jour »
|
||||
|
||||
- Chaque ligne de dose porte le glyphe de sa forme (💊 oral, 💉 injection
|
||||
IM/SC, 💧 gel, 🩹 patch, 🧴 autre — helper pur `routeGlyph` dans
|
||||
js/data/models.js, miroir de `routeIcon`).
|
||||
- Une analyse de sang LE MÊME JOUR qu'une dose affiche un encart latéral
|
||||
🧪↑ (avant la dose) et/ou 🧪↓ (après) — helper pur `labMarkersForDose`
|
||||
(js/util/format.js, jour calendaire LOCAL ; lab à l'heure exacte =
|
||||
APRÈS, convention documentée).
|
||||
- 10 tests (`tests/doses-extras.test.js`, miroirs de DosesExtrasTest.kt).
|
||||
|
||||
Vérifié : 194 tests verts + E2E (`check.sh`).
|
||||
|
||||
## [1.11.0] — 2026-09-29
|
||||
|
||||
### Ajouté — les traitements inactifs regroupés en bas de la page Traitements
|
||||
|
||||
@ -60,7 +60,7 @@ Règles du portage, fixées au départ :
|
||||
|
||||
| Choix | Décision | Pourquoi |
|
||||
|---|---|---|
|
||||
| Langage | JavaScript ES modules natifs (pas de TS, pas de build) | zéro chaîne de compilation → le déploiement = copier le dossier ; les types critiques sont documentés en JSDoc et épinglés par 184 tests |
|
||||
| Langage | JavaScript ES modules natifs (pas de TS, pas de build) | zéro chaîne de compilation → le déploiement = copier le dossier ; les types critiques sont documentés en JSDoc et épinglés par 194 tests |
|
||||
| Framework UI | Aucun — DOM via un helper `el()` (`ui/components.js`) | même philosophie que la v1 Android (pas de ViewModel/DI) ; une seule abstraction à connaître |
|
||||
| Rendu graphique | Canvas 2D natif (portage de `CurveChart.kt`) | pas de lib de charts : fidélité du rendu, zéro dépendance |
|
||||
| Persistance | `localStorage` (clés préfixées `hormonetrack.`) | données petites (JSON) ; backend INJECTABLE → testable en Node (`store.setBackend(new Map())`) |
|
||||
@ -263,6 +263,8 @@ HormoneTrack-web/ (dépôt séparé)
|
||||
│ ├── treatments-display.test.js ← treatmentsForDisplay (v1.11.0 : actifs
|
||||
│ │ d'abord, inactifs regroupés en bas — tri
|
||||
│ │ stable)
|
||||
│ ├── doses-extras.test.js ← fix #70 garde prévision inactifs +
|
||||
│ │ routeGlyph + labMarkersForDose (v1.12.0)
|
||||
│ ├── lab-trajectory-model.test.js ← LabTrajectoryModelTest.kt (v1.5.0
|
||||
│ │ « Tracé labs » + v1.6.0 prolongation —
|
||||
│ │ passage exact, garde #61, horizon cutoff)
|
||||
@ -408,7 +410,7 @@ Pièges rencontrés :
|
||||
|---|---|---|---|
|
||||
| Syntaxe | `node --check` | tous les modules ES | `bash scripts/check.sh` |
|
||||
| i18n | `scripts/i18n-check.mjs` | clés FR/EN synchronisées + clés utilisées | (dans check.sh) |
|
||||
| Unitaires | `node --test` | **184 tests** : noyau PK (ESE analytique/TFS/WHS), calibration, backup, store, helpers, alertes, rappels, changelog, tracé labs, estrannaise-models/cloud, rounding, presets (v1.9.4), ordre d'affichage des traitements (v1.11.0) | `npm test` |
|
||||
| Unitaires | `node --test` | **194 tests** : noyau PK (ESE analytique/TFS/WHS), calibration, backup, store, helpers, alertes, rappels, changelog, tracé labs, estrannaise-models/cloud, rounding, presets (v1.9.4), ordre d'affichage des traitements (v1.11.0), garde prévision inactifs + extras Doses (v1.12.0) | `npm test` |
|
||||
| E2E navigateur | playwright-core + build Firefox | app réelle : rendu, canvas peint (pixels), navigation, dialog changelog, langue | `npm run e2e` |
|
||||
| Smoke HTTP | curl | ressources clés en 200 | `check.sh --with-serve` |
|
||||
|
||||
|
||||
@ -229,3 +229,25 @@ export function newLabResult(init = {}) {
|
||||
export function treatmentsForDisplay(treatments) {
|
||||
return [...treatments].sort((a, b) => Number(b.isActive) - Number(a.isActive));
|
||||
}
|
||||
|
||||
/**
|
||||
* GLYPHE de FORME DE PRISE (v1.12.0, miroir de `routeIcon` —
|
||||
* `ui/screens/DosesScreen.kt`, même KDoc) : l'app web n'a pas d'icônes
|
||||
* Material → émojis, même mapping : ORAL = pilule 💊 ; IM/SC = seringue 💉
|
||||
* (glyphe partagé, la page Doses différencie par le texte de la ligne) ;
|
||||
* GEL = goutte 💧 ; PATCH = sparadrap 🩹 ; OTHER/inconnu = compte-gouttes
|
||||
* 🧴. PUR → testable en node (tests/doses-extras.test.js).
|
||||
*
|
||||
* @param {string|null} route
|
||||
* @returns {string} émoji
|
||||
*/
|
||||
export function routeGlyph(route) {
|
||||
switch (route) {
|
||||
case 'ORAL': return '💊';
|
||||
case 'INJECTION_IM':
|
||||
case 'INJECTION_SUBCUT': return '💉';
|
||||
case 'TRANSDERMAL_GEL': return '💧';
|
||||
case 'TRANSDERMAL_PATCH': return '🩹';
|
||||
default: return '🧴';
|
||||
}
|
||||
}
|
||||
|
||||
@ -512,6 +512,14 @@ export function computeCurve(treatments, doseLogs, startMs, endMs, stepMs, tConf
|
||||
export function generateForecastDoses(treatment, allDoseLogs, toMs, nowMs = Date.now()) {
|
||||
const intervalDays = treatment.forecastIntervalDays;
|
||||
if (intervalDays === null || intervalDays === undefined || intervalDays <= 0.0) return [];
|
||||
// ⚠️ FIX #70 (v1.12.0, miroir du Kotlin) : un traitement INACTIF ne
|
||||
// projette PLUS de créneaux — l'archivage arrête les prédictions
|
||||
// d'injections futures (graphique Prévision, extension de fenêtre,
|
||||
// horizon LabTiming). Cohérent avec les rappels (nextReminderFireFor
|
||||
// garde déjà !isActive). ⚠️ §6.bis inchangé : l'INACTIF reste simulé
|
||||
// pour son HISTORIQUE (computeCurve ne passe pas ici) — seul le FUTUR
|
||||
// s'arrête.
|
||||
if (!treatment.isActive) return [];
|
||||
const intervalMs = Math.trunc(intervalDays * 24.0 * HOUR_MS);
|
||||
if (intervalMs <= 0) return [];
|
||||
|
||||
|
||||
@ -171,7 +171,10 @@ export function renderChart(container, ctx, { preserveState = false } = {}) {
|
||||
}
|
||||
|
||||
// ── Horizon de prévision (12 × plus grande Posologie, borné 30 j–1 an) ────
|
||||
// ⚠️ FIX #70 (v1.12.0, miroir du Kotlin) : SEULS les traitements ACTIFS
|
||||
// comptent — la Posologie d'un archivé n'active plus le chip Prévision.
|
||||
const maxIntervalDays = treatments
|
||||
.filter((tr) => tr.isActive)
|
||||
.map((tr) => tr.forecastIntervalDays)
|
||||
.filter((x) => x !== null && x !== undefined)
|
||||
.reduce((m, x) => Math.max(m, x), 0) || null;
|
||||
|
||||
@ -15,8 +15,9 @@
|
||||
*/
|
||||
|
||||
import { store } from '../data/store.js';
|
||||
import { routeGlyph, routeLabelKey } from '../data/models.js';
|
||||
import { t } from '../util/i18n.js';
|
||||
import { formatDose, formatTime, formatLongDate, formatISODate, HrtDuration } from '../util/format.js';
|
||||
import { formatDose, formatTime, formatLongDate, formatISODate, HrtDuration, labMarkersForDose } from '../util/format.js';
|
||||
import { el, assistChip, confirmDialog, fab } from './components.js';
|
||||
import { openDoseDialog } from './dialogs.js';
|
||||
|
||||
@ -91,7 +92,17 @@ export function renderDoses(container) {
|
||||
col.appendChild(el('div', { class: 'group-header' }, dateKey));
|
||||
for (const log of logs) {
|
||||
const tr = treatmentMap.get(log.treatmentId);
|
||||
// v1.12.0 : marqueurs « prise de sang le même jour » (↑ avant / ↓
|
||||
// après) — miroir labMarkersForDose de DosesScreen.kt
|
||||
const markers = labMarkersForDose(store.getLabResults(), log.timestamp);
|
||||
const item = el('div', { class: 'list-item', onclick: () => openDoseDialog({ treatments, preselected: null, existing: log }) });
|
||||
// v1.12.0 : GLYPHE de forme de prise à gauche (💊 oral, 💉 injection
|
||||
// IM/SC, 💧 gel, 🩹 patch, 🧴 autre — cf routeGlyph dans models.js)
|
||||
item.appendChild(el('span', {
|
||||
class: 'sub',
|
||||
style: 'align-self:center;font-size:1.1em;',
|
||||
title: tr ? t(routeLabelKey(tr.route)) : '',
|
||||
}, routeGlyph(tr ? tr.route : null)));
|
||||
const main = el('div', { class: 'main' });
|
||||
main.appendChild(el('div', { class: 'title' },
|
||||
`${tr ? tr.name : '?'} · ${formatDose(log.doseAmount)} ${tr ? tr.doseUnit : ''}`));
|
||||
@ -107,12 +118,28 @@ export function renderDoses(container) {
|
||||
if (log.notes && log.notes.trim() !== '') sub.appendChild(el('span', {}, log.notes));
|
||||
main.appendChild(sub);
|
||||
item.appendChild(main);
|
||||
const side = el('div', { class: 'row', style: 'align-items:center;gap:2px;' });
|
||||
// Encart latéral « prise de sang le même jour » (title = phrase
|
||||
// complète pour l'accessibilité, miroir des contentDescription)
|
||||
if (markers.before) {
|
||||
side.appendChild(el('span', {
|
||||
class: 'sub', style: 'color:var(--accent,#d6589e);',
|
||||
title: t('lab_before_dose'),
|
||||
}, '🧪↑'));
|
||||
}
|
||||
if (markers.after) {
|
||||
side.appendChild(el('span', {
|
||||
class: 'sub', style: 'color:var(--accent,#d6589e);',
|
||||
title: t('lab_after_dose'),
|
||||
}, '🧪↓'));
|
||||
}
|
||||
const del = el('button', { class: 'icon-btn', 'aria-label': t('delete') }, '🗑');
|
||||
del.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // ne pas déclencher l'édition
|
||||
confirmDialog(t('delete'), t('confirm_delete'), () => store.deleteDoseLog(log.id));
|
||||
});
|
||||
item.appendChild(del);
|
||||
side.appendChild(del);
|
||||
item.appendChild(side);
|
||||
col.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ import { el, showDialog, field, switchRow, filterChip, toast } from './component
|
||||
* (APK + web au MÊME numéro, tags appariés `vX.Y.Z` + `vX.Y.Z-web`).
|
||||
* v1.4.10 = portage initial de l'Android v1.4.10.
|
||||
*/
|
||||
export const WEB_VERSION = '1.11.0';
|
||||
export const WEB_VERSION = '1.12.0';
|
||||
|
||||
/** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */
|
||||
function parseNumOrNull(text) {
|
||||
|
||||
@ -240,3 +240,32 @@ export function labNotesForDisplay(group) {
|
||||
if (distinctPairs.length === 1) return [distinctPairs[0][1]];
|
||||
return distinctPairs.map(([marker, text]) => `${marker} : ${text}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marqueurs d'ANALYSE DE SANG pour UNE dose (v1.12.0, miroir fidèle de
|
||||
* `ui/screens/DosesScreen.kt` — même KDoc). Une analyse compte si elle
|
||||
* tombe le MÊME JOUR calendaire LOCAL que la dose ; une analyse exactement
|
||||
* À l'heure de la dose compte comme APRÈS (convention documentée).
|
||||
*
|
||||
* PUR → testable en node (tests/doses-extras.test.js).
|
||||
*
|
||||
* @param {Array<object>} labs LabResult[]
|
||||
* @param {number} doseMs
|
||||
* @param {string} [timeZone=null] IANA (défaut : locale du navigateur)
|
||||
* @returns {{before: boolean, after: boolean}}
|
||||
*/
|
||||
export function labMarkersForDose(labs, doseMs, timeZone = null) {
|
||||
// Comparaison de JOURS CALENDAIRES : 'fr-CA' rend YYYY-MM-DD (CLDR/ISO) —
|
||||
// honore `timeZone` quand fourni, sinon la locale du navigateur/node.
|
||||
const opts = timeZone ? { timeZone } : {};
|
||||
const day = new Date(doseMs).toLocaleDateString('fr-CA', opts);
|
||||
let before = false;
|
||||
let after = false;
|
||||
for (const lab of labs) {
|
||||
if (new Date(lab.timestamp).toLocaleDateString('fr-CA', opts) === day) {
|
||||
if (lab.timestamp < doseMs) before = true;
|
||||
else after = true;
|
||||
}
|
||||
}
|
||||
return { before, after };
|
||||
}
|
||||
|
||||
@ -64,6 +64,8 @@ const EN = {
|
||||
// Labs
|
||||
edit_which_lab: 'Which entry to edit?',
|
||||
confirm_delete_blood_draw: 'Delete the whole blood draw ({0})?',
|
||||
lab_before_dose: 'Blood test earlier the same day, before this dose',
|
||||
lab_after_dose: 'Blood test later the same day, after this dose',
|
||||
// Traitements
|
||||
inactive: 'inactive',
|
||||
inactive_section: 'Inactive treatments',
|
||||
@ -273,6 +275,8 @@ const FR = {
|
||||
confirm_delete_treatment: 'Supprimer ce traitement ? Son historique de doses sera supprimé aussi (les analyses sont conservées).',
|
||||
edit_which_lab: 'Quelle entrée modifier ?',
|
||||
confirm_delete_blood_draw: 'Supprimer la prise de sang entière ({0}) ?',
|
||||
lab_before_dose: 'Prise de sang plus tôt le même jour, avant cette dose',
|
||||
lab_after_dose: 'Prise de sang plus tard le même jour, après cette dose',
|
||||
inactive: 'inactive',
|
||||
inactive_section: 'Traitements inactifs',
|
||||
pk_absorption: 'Tmax',
|
||||
|
||||
116
tests/doses-extras.test.js
Normal file
116
tests/doses-extras.test.js
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Tests des ajouts v1.12.0 à la page Doses (miroir de DosesExtrasTest.kt +
|
||||
* ForecastDosesGuardTest.kt) :
|
||||
* - FIX #70 : un traitement INACTIF ne projette PLUS de créneaux
|
||||
* (generateForecastDoses) — l'historique reste simulé (§6.bis) ;
|
||||
* - routeGlyph : un émoji PAR forme de prise (IM/SC partagent la seringue
|
||||
* par design — différenciées par le texte de la ligne côté web) ;
|
||||
* - labMarkersForDose : marqueurs « prise de sang le même jour » —
|
||||
* avant/après la dose, jour calendaire LOCAL, frontière minuit.
|
||||
*
|
||||
* Déterministes (instants construits via Date locale, hors changement
|
||||
* d'heure — même discipline que misc.test.js).
|
||||
*/
|
||||
|
||||
import { test, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { generateForecastDoses } from '../js/pk/pk-engine.js';
|
||||
import { routeGlyph, treatmentsForDisplay } from '../js/data/models.js';
|
||||
import { labMarkersForDose } from '../js/util/format.js';
|
||||
import { makeTreatment, makeDose, DAY_MS, HOUR_MS } from './helpers.js';
|
||||
|
||||
const NOW = 1_790_000_000_000;
|
||||
|
||||
describe('forecast guard for inactive treatments (fix #70, v1.12.0)', () => {
|
||||
test('un inactif à Posologie + historique ne projette plus rien', () => {
|
||||
const archived = makeTreatment({ id: 1, isActive: false, forecastIntervalDays: 7 });
|
||||
const doses = [
|
||||
makeDose(NOW - 8 * DAY_MS, 5, { treatmentId: 1 }),
|
||||
makeDose(NOW - DAY_MS, 5, { treatmentId: 1 }),
|
||||
];
|
||||
assert.equal(generateForecastDoses(archived, doses, NOW + 30 * DAY_MS, NOW).length, 0);
|
||||
});
|
||||
|
||||
test('un actif jumeau projette toujours (comportement inchangé)', () => {
|
||||
const active = makeTreatment({ id: 2, isActive: true, forecastIntervalDays: 7 });
|
||||
const doses = [
|
||||
makeDose(NOW - 8 * DAY_MS, 5, { treatmentId: 2 }),
|
||||
makeDose(NOW - DAY_MS, 5, { treatmentId: 2 }),
|
||||
];
|
||||
const slots = generateForecastDoses(active, doses, NOW + 30 * DAY_MS, NOW);
|
||||
assert.ok(slots.length > 0, 'un actif à Posologie projette');
|
||||
assert.equal(slots[0].timestamp, NOW - DAY_MS + 7 * DAY_MS);
|
||||
});
|
||||
|
||||
test('archiver un traitement actif arrête ses créneaux futurs', () => {
|
||||
const treatment = makeTreatment({ id: 3, isActive: true, forecastIntervalDays: 7 });
|
||||
const doses = [makeDose(NOW - DAY_MS, 5, { treatmentId: 3 })];
|
||||
const before = generateForecastDoses(treatment, doses, NOW + 30 * DAY_MS, NOW);
|
||||
const after = generateForecastDoses(
|
||||
{ ...treatment, isActive: false }, doses, NOW + 30 * DAY_MS, NOW,
|
||||
);
|
||||
assert.ok(before.length > 0);
|
||||
assert.equal(after.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('route glyphs (v1.12.0)', () => {
|
||||
test('un glyphe par forme (IM/SC partagent la seringue par design)', () => {
|
||||
const oral = routeGlyph('ORAL');
|
||||
const im = routeGlyph('INJECTION_IM');
|
||||
const sc = routeGlyph('INJECTION_SUBCUT');
|
||||
const gel = routeGlyph('TRANSDERMAL_GEL');
|
||||
const patch = routeGlyph('TRANSDERMAL_PATCH');
|
||||
const other = routeGlyph('OTHER');
|
||||
assert.notEqual(oral, im);
|
||||
assert.notEqual(oral, gel);
|
||||
assert.notEqual(im, gel);
|
||||
assert.notEqual(gel, patch);
|
||||
assert.notEqual(patch, other);
|
||||
assert.equal(im, sc, 'IM/SC : même seringue (texte de ligne distinct côté web)');
|
||||
});
|
||||
|
||||
test('route inconnue/null → glyphe OTHER', () => {
|
||||
assert.equal(routeGlyph(null), routeGlyph('OTHER'));
|
||||
assert.equal(routeGlyph(undefined), routeGlyph('OTHER'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('lab markers per dose (v1.12.0)', () => {
|
||||
// Jour calendaire LOCAL de node (hors changement d'heure)
|
||||
const noon = new Date(2026, 8, 27, 12, 0).getTime(); // 27 sept 2026 12:00 local
|
||||
|
||||
const lab = (atMs) => ({ marker: 'E2', value: 100, unit: 'pg/mL', timestamp: atMs });
|
||||
|
||||
test('labs avant ET après la dose le même jour → les deux marqueurs', () => {
|
||||
const m = labMarkersForDose([lab(noon - HOUR_MS), lab(noon + HOUR_MS)], noon);
|
||||
assert.equal(m.before, true);
|
||||
assert.equal(m.after, true);
|
||||
});
|
||||
|
||||
test('lab d\'un autre jour ne marque jamais', () => {
|
||||
const m = labMarkersForDose([lab(noon - 24 * HOUR_MS)], noon);
|
||||
assert.equal(m.before, false);
|
||||
assert.equal(m.after, false);
|
||||
});
|
||||
|
||||
test('frontière minuit : 23:50 la veille ≠ jour suivant', () => {
|
||||
const lateEvening = new Date(2026, 8, 26, 23, 50).getTime();
|
||||
const earlyMorning = new Date(2026, 8, 27, 0, 10).getTime();
|
||||
const m = labMarkersForDose([lab(lateEvening)], earlyMorning);
|
||||
assert.equal(m.before, false);
|
||||
assert.equal(m.after, false);
|
||||
});
|
||||
|
||||
test('lab exactement à l\'heure de la dose compte comme APRÈS (convention)', () => {
|
||||
const m = labMarkersForDose([lab(noon)], noon);
|
||||
assert.equal(m.before, false);
|
||||
assert.equal(m.after, true);
|
||||
});
|
||||
|
||||
test('aucun lab → aucun marqueur', () => {
|
||||
const m = labMarkersForDose([], noon);
|
||||
assert.equal(m.before, false);
|
||||
assert.equal(m.after, false);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user