@open3cl/engine 1.11.1 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/11_ecs.spec.js CHANGED
@@ -13,7 +13,8 @@ vi.mock('./enums.js', () => ({
13
13
  type_installation: {
14
14
  1: 'installation individuelle',
15
15
  2: 'installation collective',
16
- 3: 'installation collective multi-bâtiment'
16
+ 3: 'installation collective multi-bâtiment',
17
+ 4: 'installation de type inconnu'
17
18
  }
18
19
  }
19
20
  }));
@@ -566,6 +567,21 @@ describe('calc_ecs - rendement de distribution', () => {
566
567
  });
567
568
  });
568
569
 
570
+ test('type d’installation ni individuelle ni collective : aucun critère de configuration', () => {
571
+ const de = { enum_type_installation_id: '4' };
572
+ const ecs = makeEcs(de, [
573
+ {
574
+ donnee_entree: { position_volume_chauffe: 1 },
575
+ donnee_intermediaire: { conso_ecs: 0, conso_ecs_depensier: 0 }
576
+ }
577
+ ]);
578
+ calc_ecs({}, ecs, 100, 200, 1, 'ca1', 'h1a', 'maison', false, null, null, false);
579
+
580
+ // Le matcher reste vide : ni configuration_logement ni type_reseau_collectif
581
+ expect(tv).toHaveBeenCalledWith('rendement_distribution_ecs', {});
582
+ expect(requestInput).not.toHaveBeenCalled();
583
+ });
584
+
569
585
  test('aucune ligne forfaitaire trouvée : rendement de distribution non renseigné', () => {
570
586
  const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
571
587
  vi.mocked(tv).mockReturnValue(undefined);
@@ -28,7 +28,8 @@ vi.mock('./enums.js', () => ({
28
28
  type_generateur_ch: {
29
29
  89: 'chaudière gaz standard 2001-2015',
30
30
  X: 'chaudière fioul',
31
- Y: 'chaudière gaz'
31
+ Y: 'chaudière gaz',
32
+ Z: 'générateur sans équivalent ECS'
32
33
  },
33
34
  type_generateur_ecs: {
34
35
  50: 'chaudière gaz standard',
@@ -563,6 +564,28 @@ describe('tv_generateur_combustion - génération mixte (checkEcsVsChauffageForM
563
564
  errSpy.mockRestore();
564
565
  });
565
566
 
567
+ test('type CH sans équivalent ECS : type de générateur ECS conservé sans message d’alignement', () => {
568
+ state.bug = true;
569
+ const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
570
+ tv.mockReturnValue({ ...ROW_DEFAUT });
571
+ const di = { pn: 20000 };
572
+ // CH mixte 'Z' : aucun type ECS portant le même libellé -> pas d'alignement possible
573
+ const de = {
574
+ enum_type_generateur_ecs_id: 'A',
575
+ enum_usage_generateur_id: '3',
576
+ tv_generateur_combustion_id: '42',
577
+ ratio_virtualisation: 1,
578
+ presence_ventouse: 0
579
+ };
580
+ tv_generateur_combustion(dpeAvecChMixte('Z'), di, de, 'ecs', 200, -9, 1);
581
+ expect(tv).toHaveBeenCalledWith(
582
+ 'generateur_combustion',
583
+ expect.objectContaining({ enum_type_generateur_ecs_id: 'A' })
584
+ );
585
+ expect(errSpy).not.toHaveBeenCalled();
586
+ errSpy.mockRestore();
587
+ });
588
+
566
589
  test('types ECS et CH identiques : type de générateur ECS conservé', () => {
567
590
  state.bug = true;
568
591
  tv.mockReturnValue({ ...ROW_DEFAUT });
@@ -58,6 +58,14 @@ describe('updateGenerateurBouilleurs - substitution par une chaudière bois équ
58
58
  expect(de.enum_type_generateur_ch_id).toBe(attendu);
59
59
  });
60
60
 
61
+ test('année numérique antérieure à 1948 : aucune période correspondante, valeur par défaut conservée', () => {
62
+ getFicheTechnique.mockReturnValue({ valeur: '1900' });
63
+ const de = { enum_type_generateur_ch_id: '48' };
64
+ updateGenerateurBouilleurs({}, de, 'ch');
65
+ // Aucun seuil atteint : on garde la chaudière bois par défaut 2004-2012 (values[2004])
66
+ expect(de.enum_type_generateur_ch_id).toBe('58');
67
+ });
68
+
61
69
  test('type ECS (granulés 115) : utilise la table ECS des chaudières bois', () => {
62
70
  getFicheTechnique.mockReturnValue({ valeur: '2016' });
63
71
  const de = { enum_type_generateur_ecs_id: '115' };
@@ -173,6 +173,27 @@ describe('tv_temp_fonc_30_100 - températures de fonctionnement', () => {
173
173
  expect(di.temp_fonc_100).toBe(75);
174
174
  });
175
175
 
176
+ test('une température à 100 % plus basse issue d’un second émetteur ne remplace pas la valeur retenue', () => {
177
+ tv.mockImplementation((table, matcher) => {
178
+ if (table !== 'temp_fonc_100') return null;
179
+ // Le premier émetteur renvoie 75, le second 60 (plus basse)
180
+ return matcher.enum_temp_distribution_ch_id === '3'
181
+ ? { tv_temp_fonc_100_id: 'a', temp_fonc_100: '75' }
182
+ : { tv_temp_fonc_100_id: 'b', temp_fonc_100: '60' };
183
+ });
184
+ const di = {};
185
+ const de = { enum_type_generateur_ch_id: '89' };
186
+ tv_temp_fonc_30_100(
187
+ di,
188
+ de,
189
+ {},
190
+ [emetteur(), emetteur({ enum_temp_distribution_ch_id: '4' })],
191
+ 1975
192
+ );
193
+ expect(di.temp_fonc_100).toBe(75);
194
+ expect(de.tv_temp_fonc_100_id).toBe('a');
195
+ });
196
+
176
197
  test('aucune ligne forfaitaire trouvée : erreurs signalées pour temp_fonc_30 et temp_fonc_100', () => {
177
198
  const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
178
199
  tv.mockReturnValue(null);
@@ -709,6 +730,26 @@ describe('calc_generateur_combustion_ch - rendement de génération', () => {
709
730
  warnSpy.mockRestore();
710
731
  });
711
732
 
733
+ test('bug_for_bug_compat : qp0 déjà exprimé en W (>= 1) conservé sans correction', () => {
734
+ state.bug = true;
735
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
736
+ const di = { pn: 20000, rpn: 0.9, rpint: 0.85, qp0: 200, temp_fonc_30: 40, temp_fonc_100: 70 };
737
+ const de = {
738
+ enum_type_generateur_ch_id: '89',
739
+ enum_type_energie_id: '2',
740
+ type_energie: 'gaz naturel',
741
+ presence_regulation_combustion: false,
742
+ description: 't'
743
+ };
744
+ calc_generateur_combustion_ch({}, di, de, { cdimref: 0.8, cdimrefDep: 0.6 });
745
+ // qp0 >= 1 -> pas de multiplication par 1000, pas d'avertissement
746
+ expect(di.qp0).toBe(200);
747
+ expect(warnSpy).not.toHaveBeenCalled();
748
+ // même résultat que la correction d'un qp0 de 0,2 kW (référence de régression ci-dessus)
749
+ expect(di.rg).toBeCloseTo(0.8557877790761849, 9);
750
+ warnSpy.mockRestore();
751
+ });
752
+
712
753
  test('la présence de régulation modifie le rendement (tf30 vs tf100)', () => {
713
754
  const base = () => ({
714
755
  pn: 25000,
@@ -559,6 +559,23 @@ describe('calc_gen_ecs - consommation par générateur', () => {
559
559
  expect(g.donnee_intermediaire.Qgw).toBe(0);
560
560
  });
561
561
 
562
+ test('installation collective : volume de la fiche non numérique ignoré, volume saisi conservé', () => {
563
+ // « ba » (texte avant le premier « l ») n'est pas un nombre -> VsFiche = NaN, rejeté
564
+ vi.mocked(getFicheTechnique).mockReturnValue({ valeur: 'ballon' });
565
+ ecs_de.enum_type_installation_id = '2';
566
+ ecs_de.ratio_virtualisation = 1;
567
+ const g = gen({
568
+ type_energie: 'gaz',
569
+ enum_type_generateur_ecs_id: 'autre',
570
+ type_stockage_ecs: 'ballon',
571
+ volume_stockage: 150
572
+ });
573
+ calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'immeuble');
574
+
575
+ // Vs = volume saisi (150), identique au cas d'une fiche « 150 L »
576
+ expect(g.donnee_intermediaire.Qgw).toBeCloseTo(1064620.987190969, 9);
577
+ });
578
+
562
579
  test('bug_for_bug_compat : volume proratisé recalé sur la description', () => {
563
580
  state.bug = true;
564
581
  vi.mocked(getVolumeStockageFromDescription).mockReturnValue(100);
@@ -852,6 +869,61 @@ describe('calc_gen_ecs - consommation par générateur', () => {
852
869
  expect(g.donnee_entree.enum_type_generateur_ecs_id).toBe('84');
853
870
  });
854
871
 
872
+ test('type 84 combustion : ligne combustion sans type ECS, type 84 conservé', () => {
873
+ vi.mocked(getChaudiereFioulDefautId).mockReturnValue(undefined);
874
+ vi.mocked(tv).mockReturnValue({ tv_generateur_combustion_id: '42' });
875
+ const g = gen({
876
+ type_energie: 'gaz',
877
+ enum_type_generateur_ecs_id: '84',
878
+ tv_generateur_combustion_id: '42',
879
+ enum_methode_saisie_carac_sys_id: '1'
880
+ });
881
+ calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
882
+
883
+ // Aucune liste de types ECS exploitable dans la ligne : pas de redressement
884
+ expect(g.donnee_entree.enum_type_generateur_ecs_id).toBe('84');
885
+ });
886
+
887
+ test('type 84 sans combustion ni pompe à chaleur : ni calcul SCOP ni redressement du type', () => {
888
+ const g = gen({
889
+ type_energie: 'gaz',
890
+ enum_type_generateur_ecs_id: '84',
891
+ enum_methode_saisie_carac_sys_id: '1'
892
+ });
893
+ calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
894
+
895
+ expect(scopOrCop).not.toHaveBeenCalled();
896
+ expect(tv_generateur_combustion).not.toHaveBeenCalled();
897
+ expect(g.donnee_entree.enum_type_generateur_ecs_id).toBe('84');
898
+ });
899
+
900
+ test('bug_for_bug_compat : qp0 déjà en W (>= 1) conservé sans correction', () => {
901
+ state.bug = true;
902
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
903
+ vi.mocked(tvColumnIDs).mockImplementation((table) =>
904
+ table === 'generateur_combustion' ? ['chaud'] : []
905
+ );
906
+ vi.mocked(tv_generateur_combustion).mockImplementation((dpe, di) => {
907
+ di.rpn = 0.9;
908
+ di.qp0 = 100;
909
+ di.pn = 20000;
910
+ di.pveilleuse = 1;
911
+ di.pveil = 0;
912
+ });
913
+ const g = gen({
914
+ type_energie: 'gaz',
915
+ enum_type_generateur_ecs_id: 'chaud',
916
+ enum_methode_saisie_carac_sys_id: '1'
917
+ });
918
+ calc_gen_ecs({}, g, ecs_di, ecs_de, 0, '1', '1', 'maison');
919
+
920
+ expect(g.donnee_intermediaire.qp0).toBe(100);
921
+ expect(warnSpy).not.toHaveBeenCalled();
922
+ // même rendement que pour un qp0 de 0,1 kW corrigé en 100 W (cf. test de correction)
923
+ expect(g.donnee_intermediaire.rendement_generation).toBeCloseTo(0.3446955189582535, 9);
924
+ warnSpy.mockRestore();
925
+ });
926
+
855
927
  test('type 84 pompe à chaleur : déduit via tv_scop_id', () => {
856
928
  vi.mocked(scopOrCop).mockImplementation((di) => {
857
929
  di.scop = 3;
@@ -355,6 +355,26 @@ describe('conso_aux_distribution_ch - auxiliaires de distribution', () => {
355
355
  expect(di.conso_auxiliaire_distribution_ch).toBeCloseTo(372.082418731648, 9);
356
356
  });
357
357
 
358
+ test('émetteur hors listes (type 1, « autre ») : perte de charge par défaut deltaPem=35, Fcot=0,802', () => {
359
+ const conso = (type) => {
360
+ const em = [
361
+ {
362
+ donnee_entree: {
363
+ enum_type_emission_distribution_id: type,
364
+ enum_temp_distribution_ch_id: '3'
365
+ }
366
+ }
367
+ ];
368
+ const di = {};
369
+ conso_aux_distribution_ch(em, {}, di, {}, 100, 1, 1, '0', 100000);
370
+ return di.conso_auxiliaire_distribution_ch;
371
+ };
372
+ // Type 24 (monotube) : deltaPem=30, même Fcot (0,802) -> seul deltaPem diffère
373
+ expect(conso('1')).toBeGreaterThan(conso('24'));
374
+ // Type 10 (radiateurs autres) : deltaPem=10
375
+ expect(conso('24')).toBeGreaterThan(conso('10'));
376
+ });
377
+
358
378
  /**
359
379
  * Seuls les émetteurs reliés au générateur (même enum_lien_generateur_emetteur_id) dimensionnent
360
380
  * son circulateur. Autotest CSTB IC5-0-3 : chaudière (lien 1) sur plancher chauffant + convecteurs
package/3.1_b.spec.js CHANGED
@@ -365,4 +365,27 @@ describe('b - calcul du coefficient de réduction des déperditions', () => {
365
365
  });
366
366
  expect(di.b).toBe(0.7);
367
367
  });
368
+
369
+ test('adjacence hors liste aiu/aue (type 10 absent, type inconnu) : seul le type est apparié', () => {
370
+ tv.mockReturnValue({ b: '0.5', tv_coef_reduction_deperdition_id: '7' });
371
+ const di = {};
372
+ const de = { enum_type_adjacence_id: '1' };
373
+ b(di, de, {}, 1);
374
+ expect(tv).toHaveBeenCalledWith(TABLE, { enum_type_adjacence_id: '1' });
375
+ expect(di.b).toBe(0.5);
376
+ });
377
+
378
+ test('rapport inférieur à la plus petite borne : seule la borne max est renseignée', () => {
379
+ tv.mockReturnValue({ b: '0.9', tv_coef_reduction_deperdition_id: '3' });
380
+ const de = {
381
+ enum_type_adjacence_id: '8',
382
+ enum_cfg_isolation_lnc_id: '2',
383
+ surface_aiu: 1,
384
+ surface_aue: 10
385
+ };
386
+ b({}, de, {}, 1);
387
+ const matcher = tv.mock.calls[0][1];
388
+ expect(matcher.aiu_aue_max).toBe('≤ 0,25');
389
+ expect(matcher.aiu_aue_min).toBeUndefined();
390
+ });
368
391
  });
package/3.2.1_mur.spec.js CHANGED
@@ -226,6 +226,63 @@ describe('calc_mur - calcul de Umur0 par table forfaitaire (tv_umur0)', () => {
226
226
  error.mockRestore();
227
227
  });
228
228
 
229
+ test('bug_for_bug : épaisseur absente et ligne tv_umur0_id sans épaisseur : matcher inchangé', () => {
230
+ bugForBugCompat = true;
231
+ const error = vi.spyOn(console, 'error').mockImplementation(() => {});
232
+ tv.mockImplementation((table, matcher) => {
233
+ if (table === 'umur0' && matcher.tv_umur0_id) return { tv_umur0_id: '1' };
234
+ if (table === 'umur0') return { ...ROW_UMUR0 };
235
+ return null;
236
+ });
237
+ const mur = {
238
+ donnee_entree: {
239
+ methode_saisie_u: 'non isolé',
240
+ methode_saisie_u0:
241
+ 'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
242
+ enum_materiaux_structure_mur_id: '5',
243
+ description: 'mur sans épaisseur',
244
+ tv_umur0_id: 1,
245
+ enum_type_doublage_id: '2'
246
+ }
247
+ };
248
+
249
+ calc_mur(mur, 'h1a', '1', '0');
250
+
251
+ // Aucune épaisseur à reprendre : pas d'avertissement et épaisseur nulle conservée
252
+ expect(error).not.toHaveBeenCalled();
253
+ expect(tv).toHaveBeenLastCalledWith('umur0', {
254
+ enum_materiaux_structure_mur_id: '5',
255
+ epaisseur_structure: 0
256
+ });
257
+ error.mockRestore();
258
+ });
259
+
260
+ test('bug_for_bug : ligne tv_umur0_id introuvable : matcher inchangé', () => {
261
+ bugForBugCompat = true;
262
+ tv.mockImplementation((table, matcher) => {
263
+ if (table === 'umur0' && matcher.tv_umur0_id) return undefined;
264
+ if (table === 'umur0') return { ...ROW_UMUR0 };
265
+ return null;
266
+ });
267
+ const mur = {
268
+ donnee_entree: {
269
+ methode_saisie_u: 'non isolé',
270
+ methode_saisie_u0:
271
+ 'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
272
+ enum_materiaux_structure_mur_id: '5',
273
+ tv_umur0_id: 1,
274
+ enum_type_doublage_id: '2'
275
+ }
276
+ };
277
+
278
+ calc_mur(mur, 'h1a', '1', '0');
279
+
280
+ expect(tv).toHaveBeenLastCalledWith('umur0', {
281
+ enum_materiaux_structure_mur_id: '5',
282
+ epaisseur_structure: 0
283
+ });
284
+ });
285
+
229
286
  test('bug_for_bug : épaisseur > 80 recalculée en la divisant par 10 (valeur en millimètres)', () => {
230
287
  bugForBugCompat = true;
231
288
  const error = vi.spyOn(console, 'error').mockImplementation(() => {});
@@ -423,6 +480,48 @@ describe('calc_mur - prise en compte du doublage', () => {
423
480
  }
424
481
  );
425
482
 
483
+ test('bug_for_bug : doublage de type 1 sans description : type de doublage inchangé', () => {
484
+ bugForBugCompat = true;
485
+ const mur = {
486
+ donnee_entree: {
487
+ methode_saisie_u: 'non isolé',
488
+ methode_saisie_u0:
489
+ 'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
490
+ enum_materiaux_structure_mur_id: '5',
491
+ epaisseur_structure: 30,
492
+ enum_type_doublage_id: '1'
493
+ }
494
+ };
495
+
496
+ calc_mur(mur, 'h1a', '1', '0');
497
+
498
+ // Type 1 (pas de doublage) : Umur0 de la table non modifié
499
+ expect(mur.donnee_intermediaire.umur0).toBe(2);
500
+ });
501
+
502
+ test('bug_for_bug : type de doublage > 2 inconnu (6) : aucune résistance de doublage ajoutée', () => {
503
+ bugForBugCompat = true;
504
+ const error = vi.spyOn(console, 'error').mockImplementation(() => {});
505
+ const mur = {
506
+ donnee_entree: {
507
+ methode_saisie_u: 'non isolé',
508
+ methode_saisie_u0:
509
+ 'déterminé selon le matériau et épaisseur à partir de la table de valeur forfaitaire',
510
+ enum_materiaux_structure_mur_id: '5',
511
+ epaisseur_structure: 30,
512
+ enum_type_doublage_id: '6'
513
+ }
514
+ };
515
+
516
+ calc_mur(mur, 'h1a', '1', '0');
517
+
518
+ // Le type 6 est hors énumération type_doublage (1 à 5) : aucune résistance de doublage n'est
519
+ // définie, ni dans le calcul ni dans la vérification de minoration. Comportement actuel figé :
520
+ // Umur0 devient NaN (donnée d'entrée invalide, non protégée par le moteur).
521
+ expect(mur.donnee_intermediaire.umur0).toBeNaN();
522
+ error.mockRestore();
523
+ });
524
+
426
525
  test('bug_for_bug : DPE minorant Umur0 à 2,5 avant doublage : avertissement (Umur0 avant)', () => {
427
526
  bugForBugCompat = true;
428
527
  const error = vi.spyOn(console, 'error').mockImplementation(() => {});
@@ -342,6 +342,35 @@ describe('calc_ph - compatibilité bug for bug (récupération depuis tv_uph_id)
342
342
  error.mockRestore();
343
343
  });
344
344
 
345
+ test('ligne tv_uph_id introuvable : type_toiture et effet_joule calculés conservés', () => {
346
+ const error = vi.spyOn(console, 'error').mockImplementation(() => {});
347
+ tv.mockImplementation((table, matcher) => {
348
+ if (table === 'uph0') return { ...ROW_UPH0 };
349
+ if (table === 'uph' && matcher.tv_uph_id) return undefined;
350
+ if (table === 'uph') return { ...ROW_UPH };
351
+ return null;
352
+ });
353
+
354
+ const ph = {
355
+ donnee_entree: {
356
+ methode_saisie_u: 'isolation inconnue (table forfaitaire)',
357
+ methode_saisie_u0: 'type de paroi inconnu (valeur par défaut)',
358
+ type_adjacence: 'extérieur',
359
+ type_plancher_haut: 'terrasse',
360
+ tv_uph_id: 7,
361
+ description: 'PH test'
362
+ }
363
+ };
364
+
365
+ calc_ph(ph, 'h1a', '1', '0');
366
+
367
+ // Aucune correction issue du DPE : pas d'avertissement, matcher final en 'terrasse'
368
+ expect(error).not.toHaveBeenCalled();
369
+ expect(tv).toHaveBeenCalledWith('uph', expect.objectContaining({ type_toiture: 'terrasse' }));
370
+ expect(ph.donnee_intermediaire.uph).toBe(0.3);
371
+ error.mockRestore();
372
+ });
373
+
345
374
  test('effet_joule du DPE différent : la valeur du DPE est conservée dans le matcher', () => {
346
375
  const error = vi.spyOn(console, 'error').mockImplementation(() => {});
347
376
  tv.mockImplementation((table, matcher) => {
@@ -234,6 +234,16 @@ describe('tv_uw - coefficient de transmission de la fenêtre', () => {
234
234
  expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Pas de valeur forfaitaire uw'));
235
235
  });
236
236
 
237
+ test('Ug exactement tabulé sans ligne uw : uw indéfini et erreur émise', () => {
238
+ // delta_ug = 0 (bornes identiques) mais aucune ligne uw pour ce Ug
239
+ rows.uw = null;
240
+ const bv = baseBv();
241
+ calc_bv(bv, 1);
242
+
243
+ expect(bv.donnee_intermediaire.uw).toBeUndefined();
244
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Pas de valeur forfaitaire uw'));
245
+ });
246
+
237
247
  test('interpolation entre deux Ug tabulés (delta_ug ≠ 0)', () => {
238
248
  rows.ug = { ug: '3', tv_ug_id: '5' };
239
249
  getRange.mockReturnValue([2.8, 4]);
@@ -810,6 +810,33 @@ describe('calc_pont_thermique - récupération du mur via la baie vitrée (compa
810
810
  expect(errorSpy).toHaveBeenCalled();
811
811
  });
812
812
 
813
+ test('hors compatibilité bug : la baie vitrée n’est pas consultée pour retrouver le mur', () => {
814
+ state.bug = false;
815
+ tv.mockReturnValue({ k: '0.5', tv_pont_thermique_id: '42' });
816
+ const mur = [
817
+ {
818
+ donnee_entree: { reference: 'M1', enum_type_adjacence_id: '1', type_isolation: 'iti' },
819
+ donnee_utilisateur: {}
820
+ }
821
+ ];
822
+ // Même configuration que le cas bug_for_bug : reference_paroi = index du mur (0)
823
+ const bv = [{ donnee_entree: { reference: 'BV1', reference_paroi: '0' } }];
824
+ const pt = pontThermique({
825
+ type_liaison: 'refend / mur',
826
+ reference_1: 'BV1',
827
+ description: 'refend'
828
+ });
829
+
830
+ calc_pont_thermique(pt, '1', logement({ mur, bv }));
831
+
832
+ // Mur non retrouvé via la baie : isolation ITI par défaut, aucun message de récupération
833
+ expect(tv).toHaveBeenCalledWith('pont_thermique', {
834
+ enum_type_liaison_id: '12',
835
+ isolation_mur: '^iti'
836
+ });
837
+ expect(errorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Aucun mur trouvé'));
838
+ });
839
+
813
840
  test('reference_paroi ne correspondant pas à l’index du mur : aucun mur retrouvé (isolation ITI par défaut)', () => {
814
841
  state.bug = true;
815
842
  tv.mockReturnValue({ k: '0.5', tv_pont_thermique_id: '42' });
@@ -146,6 +146,18 @@ describe('calc_ventilation - orchestration', () => {
146
146
  );
147
147
  });
148
148
 
149
+ test('surface ventilée égale à Sh : conservée sans application de clé de répartition', () => {
150
+ const vt = ventilation({
151
+ surface_ventile: 100,
152
+ cle_repartition_ventilation: 2,
153
+ q4pa_conv_saisi: 1.3
154
+ });
155
+ calc_ventilation(vt, CG, 'th', 80, 100, [], [], [], []);
156
+
157
+ // surface_ventile = Sh = 100 (clé ignorée) => Hvent = 0,34 * 1,2 * 100
158
+ expect(vt.donnee_intermediaire.hvent).toBeCloseTo(40.8, 9);
159
+ });
160
+
149
161
  test('surface ventilée différente de Sh sans clé de répartition : divisée par 1', () => {
150
162
  // Branche `de.cle_repartition_ventilation || 1` : surface_ventile conservée telle quelle
151
163
  const vt = ventilation({ surface_ventile: 50, q4pa_conv_saisi: 1.3 });
package/7_inertie.spec.js CHANGED
@@ -54,6 +54,16 @@ describe('Inertie unit tests', () => {
54
54
  }
55
55
  );
56
56
 
57
+ test('plancher haut non lourd (type 9) isolé par ITE ou non isolé : inertie 0', () => {
58
+ expect(
59
+ inertie.calculateInertiePhLourd({ enum_type_isolation_id: 2, enum_type_plancher_haut_id: 9 })
60
+ ).toBe(0);
61
+ });
62
+
63
+ test('type de plancher haut inconnu avec isolation ITE : considéré lourd, inertie 1', () => {
64
+ expect(inertie.calculateInertiePhLourd({ enum_type_isolation_id: 4 })).toBe(1);
65
+ });
66
+
57
67
  test.each([
58
68
  [1, 1, 0, 2, 0],
59
69
  [1, 1, 0, 3, 0],
@@ -93,6 +93,10 @@ describe('calc_Fj - fraction des besoins de chauffage couverte par les apports',
93
93
  expect(lourde).not.toBeCloseTo(legere, 9);
94
94
  });
95
95
 
96
+ test('une inertie inconnue ne définit pas alpha : le résultat n’est pas un nombre', () => {
97
+ expect(calc_Fj(100, 2000, 5000, 1000, 'inconnue')).toBeNaN();
98
+ });
99
+
96
100
  test('les inerties "très lourde" et "lourde" partagent le même alpha', () => {
97
101
  expect(calc_Fj(100, 2000, 5000, 1000, 'très lourde')).toBeCloseTo(
98
102
  calc_Fj(100, 2000, 5000, 1000, 'lourde'),
package/9_conso_ch.js CHANGED
@@ -289,7 +289,7 @@ function calc_ch_base_appoint(
289
289
 
290
290
  di.conso_ch = 0;
291
291
  for (const mois of mois_liste) {
292
- let bch_base_j = 0;
292
+ let bch_base_j;
293
293
 
294
294
  if (pe > 0) {
295
295
  // Nombre d’heures de chauffage sur le mois j
@@ -23,7 +23,9 @@ vi.mock('./utils.js', () => ({
23
23
  }));
24
24
 
25
25
  vi.mock('./core/tv/infrastructure/tvs.store.js', () => ({
26
- TvsStore: vi.fn(() => mockTvsStore)
26
+ TvsStore: vi.fn(function () {
27
+ return mockTvsStore;
28
+ })
27
29
  }));
28
30
 
29
31
  const { rendement_emission, calc_emetteur_ch, isIntermittenceCollectiveSaisie } =
@@ -342,6 +342,17 @@ describe('checkForGeneratorType - classification du générateur', () => {
342
342
  expect(de.enum_type_generateur_ch_id).toBe('119');
343
343
  });
344
344
 
345
+ test('cas 119 par défaut avec ligne de rendement sans type de générateur : aucune réécriture', () => {
346
+ vi.mocked(requestInputID).mockReturnValue('119');
347
+ vi.mocked(tv).mockReturnValue({ enum_type_generateur_ch_id: undefined });
348
+ const de = { tv_rendement_generation_id: 3, enum_type_generateur_ch_id: '119' };
349
+ const du = {};
350
+ checkForGeneratorType({}, de, {}, du);
351
+ expect(de.enum_type_generateur_ch_id).toBe('119');
352
+ expect(du.isPacGenerator).toBe(false);
353
+ expect(du.isCombustionGenerator).toBe(false);
354
+ });
355
+
345
356
  /**
346
357
  * Chaudière fioul par défaut : chaudière standard datant de la construction du bâtiment.
347
358
  * @see : Methode_de_calcul_3CL_DPE_2021-338.pdf - §17.2.1.1
@@ -22,7 +22,11 @@ vi.mock('./6.2_surface_sud_equivalente.js', () => ({ calc_sse: vi.fn() }));
22
22
  vi.mock('./10_besoin_fr.js', () => ({ default: vi.fn() }));
23
23
  vi.mock('./11_besoin_ecs.js', () => ({ default: vi.fn() }));
24
24
  vi.mock('./6.1_apport_gratuit.js', () => ({ calc_ai: vi.fn(), calc_as: vi.fn() }));
25
- vi.mock('./11_nadeq.js', () => ({ Nadeq: vi.fn(() => ({ calculateNadeq })) }));
25
+ vi.mock('./11_nadeq.js', () => ({
26
+ Nadeq: vi.fn(function () {
27
+ return { calculateNadeq };
28
+ })
29
+ }));
26
30
 
27
31
  const { default: calc_apport_et_besoin } = await import('./apport_et_besoin.js');
28
32
  const { calc_sse } = await import('./6.2_surface_sud_equivalente.js');
@@ -4,25 +4,6 @@ import { UPB_ADDITIONAL_VALUES } from '../../tv/infrastructure/assets/additional
4
4
  * Add additional values for ue
5
5
  */
6
6
  export class AddAdditionnalUeValuesTables {
7
- /**
8
- * @type {FileStore}
9
- */
10
- #fileStore;
11
-
12
- /**
13
- * @type {ApplicationConfig}
14
- */
15
- #appConfig;
16
-
17
- /**
18
- * @param fileStore {FileStore}
19
- * @param appConfig {ApplicationConfig}
20
- */
21
- constructor(fileStore, appConfig) {
22
- this.#fileStore = fileStore;
23
- this.#appConfig = appConfig;
24
- }
25
-
26
7
  /**
27
8
  * Ajout de valeurs supplémentaires pour le calcul du facteur Ue pour les déperditions plancher_bas
28
9
  * @param tableValues
@@ -3,12 +3,11 @@ import { AddAdditionnalUeValuesTables } from './add-additionnal-ue-values-tables
3
3
  import { UPB_ADDITIONAL_VALUES } from '../../tv/infrastructure/assets/additional-ue-values.js';
4
4
 
5
5
  /**
6
- * `execute` n'utilise ni le fileStore ni l'appConfig : on les passe en doubles
7
- * vides pour isoler la logique d'aplatissement des valeurs Ue additionnelles
8
- * dans la table `ue`.
6
+ * Le service n'a aucune dépendance : on vérifie la logique d'aplatissement des
7
+ * valeurs Ue additionnelles dans la table `ue`.
9
8
  */
10
9
  describe('AddAdditionnalUeValuesTables', () => {
11
- const buildService = () => new AddAdditionnalUeValuesTables({}, {});
10
+ const buildService = () => new AddAdditionnalUeValuesTables();
12
11
 
13
12
  test('ajoute toutes les valeurs additionnelles dans la table ue', () => {
14
13
  const nbAttendu = UPB_ADDITIONAL_VALUES.reduce((acc, entree) => acc + entree.values.length, 0);
@@ -69,4 +69,29 @@ describe('SynchronizeDpeGesLimitValuesTables unit tests', () => {
69
69
  });
70
70
  });
71
71
  });
72
+ test('regroupe plusieurs surfaces sous une même classe d’altitude', () => {
73
+ const fileStore = new FileStore();
74
+ const appConfig = new ApplicationConfig();
75
+ const synchronizeDpeGesLimitValuesTables = new SynchronizeDpeGesLimitValuesTables(
76
+ fileStore,
77
+ appConfig
78
+ );
79
+
80
+ vi.spyOn(fileStore, 'readLocalOdsFileAndConvertToJson').mockResolvedValue({
81
+ dpe_class_limit: [
82
+ { classe_altitude: '400-800m', surface: '10', A: '124' },
83
+ { classe_altitude: '400-800m', surface: '20', A: '100' }
84
+ ]
85
+ });
86
+ vi.spyOn(ApplicationConfig.prototype, 'solicitationsExtFilePath', 'get').mockReturnValue(
87
+ 'src/file.ods'
88
+ );
89
+
90
+ return synchronizeDpeGesLimitValuesTables.execute().then((output) => {
91
+ // Le groupe d'altitude n'est créé qu'une fois : la 1ʳᵉ surface n'est pas écrasée
92
+ expect(output).toStrictEqual({
93
+ dpe_class_limit: { '400-800m': { 10: { A: 124 }, 20: { A: 100 } } }
94
+ });
95
+ });
96
+ });
72
97
  });
@@ -99,4 +99,47 @@ describe('SynchronizeValeurTables unit tests', () => {
99
99
  expect(contenu).toContain('valeurs-ue-completees');
100
100
  });
101
101
  });
102
+ test('force isolation_surfaces à "0" uniquement lorsque la valeur est absente dans q4pa_conv', () => {
103
+ const fileStore = new FileStore();
104
+ const appConfig = new ApplicationConfig();
105
+ const synchronizeC1Tables = new SynchronizeC1Tables(fileStore, appConfig);
106
+ const synchronizeSolicitationTables = new SynchronizeSolicitationsTables(fileStore, appConfig);
107
+ const synchronizeDpeGesLimitValuesTables = new SynchronizeDpeGesLimitValuesTables(
108
+ fileStore,
109
+ appConfig
110
+ );
111
+ const addAdditionnalUeValuesTables = new AddAdditionnalUeValuesTables(fileStore, appConfig);
112
+ const synchronizeValeurTables = new SynchronizeValeurTables(
113
+ fileStore,
114
+ appConfig,
115
+ synchronizeSolicitationTables,
116
+ synchronizeDpeGesLimitValuesTables,
117
+ addAdditionnalUeValuesTables,
118
+ synchronizeC1Tables
119
+ );
120
+
121
+ vi.spyOn(ApplicationConfig.prototype, 'ademeValeurTablesFileUrl', 'get').mockReturnValue(
122
+ 'http://localhost/file.xlsx'
123
+ );
124
+ vi.spyOn(ApplicationConfig.prototype, 'assetsOutputFolder', 'get').mockReturnValue(
125
+ 'src/assets'
126
+ );
127
+
128
+ // Le parsing Excel ignore les valeurs à 0 : la 1ʳᵉ ligne n'a pas de isolation_surfaces,
129
+ // la 2ᵉ en possède déjà une qui doit être conservée.
130
+ vi.spyOn(fileStore, 'downloadXlsxFileAndConvertToJson').mockResolvedValue({
131
+ q4pa_conv: [{ q4pa_conv: 'a' }, { q4pa_conv: 'b', isolation_surfaces: 'oui' }]
132
+ });
133
+ vi.spyOn(fileStore, 'writeFileToLocalSystem').mockResolvedValue(null);
134
+ vi.spyOn(synchronizeC1Tables, 'execute').mockResolvedValue({});
135
+ vi.spyOn(synchronizeSolicitationTables, 'execute').mockResolvedValue({});
136
+ vi.spyOn(synchronizeDpeGesLimitValuesTables, 'execute').mockResolvedValue({});
137
+ vi.spyOn(addAdditionnalUeValuesTables, 'execute').mockResolvedValue({});
138
+
139
+ return synchronizeValeurTables.execute().then(() => {
140
+ const contenu = vi.mocked(fileStore.writeFileToLocalSystem).mock.calls[0][1];
141
+ const tvs = JSON.parse(contenu.match(/tvs = (\{[\s\S]*\});\n export/)[1]);
142
+ expect(tvs.q4pa_conv.map((l) => l.isolation_surfaces)).toEqual(['0', 'oui']);
143
+ });
144
+ });
102
145
  });
@@ -58,6 +58,17 @@ describe('Service de logs (log-service)', () => {
58
58
  expect(console.error).toBe(consoleOrigine.error);
59
59
  });
60
60
 
61
+ test('setLoggerOn sans option conserve console.error neutralisé', () => {
62
+ setLoggerOff(true);
63
+ const erreurNeutralisee = console.error;
64
+ setLoggerOn();
65
+
66
+ expect(logger.silent).toBe(false);
67
+ expect(console.log).toBe(consoleOrigine.log);
68
+ expect(console.error).toBe(erreurNeutralisee);
69
+ expect(console.error).not.toBe(consoleOrigine.error);
70
+ });
71
+
61
72
  test('les méthodes console neutralisées sont des fonctions no-op', () => {
62
73
  setLoggerOff();
63
74
 
@@ -77,6 +77,15 @@ describe('DpeSanitizerService.execute - collections manquantes', () => {
77
77
  });
78
78
  });
79
79
 
80
+ describe('DpeSanitizerService - transformation des clés', () => {
81
+ test('conserve les clés inchangées (transformation identité)', () => {
82
+ vi.mocked(has).mockReturnValue(true);
83
+ service.execute({});
84
+ const keyTransform = vi.mocked(ObjectUtil.deepObjectTransform).mock.calls[0][1];
85
+ expect(keyTransform('generateur_ecs')).toBe('generateur_ecs');
86
+ });
87
+ });
88
+
80
89
  /**
81
90
  * Récupère la fonction de transformation des valeurs passée en 3e argument de
82
91
  * `deepObjectTransform` après un appel à `execute`.
package/engine.js CHANGED
@@ -36,7 +36,7 @@ import DpeSanitizerService from './dpe-sanitizer.service.js';
36
36
  import { reconstituerRdimDpeGenereImmeuble } from './17.2_rdim_dpe_genere_immeuble.js';
37
37
  import { reconstituerRdimEcsIndividuelleUnique } from './11_rdim_ecs_individuelle_unique.js';
38
38
 
39
- const LIB_VERSION = '1.11.1';
39
+ const LIB_VERSION = '1.11.2';
40
40
 
41
41
  const dpeSanitizerService = new DpeSanitizerService();
42
42
 
package/engine.spec.js CHANGED
@@ -83,9 +83,15 @@ vi.mock('./11_rdim_ecs_individuelle_unique.js', () => ({
83
83
  const calculateInertie = vi.fn();
84
84
  const calculateEnr = vi.fn();
85
85
  const ratioProrataPvLogement = vi.fn();
86
- vi.mock('./7_inertie.js', () => ({ Inertie: vi.fn(() => ({ calculateInertie })) }));
86
+ vi.mock('./7_inertie.js', () => ({
87
+ Inertie: vi.fn(function () {
88
+ return { calculateInertie };
89
+ })
90
+ }));
87
91
  vi.mock('./16.2_production_enr.js', () => ({
88
- ProductionENR: vi.fn(() => ({ calculateEnr })),
92
+ ProductionENR: vi.fn(function () {
93
+ return { calculateEnr };
94
+ }),
89
95
  ratioProrataPvLogement: (...args) => ratioProrataPvLogement(...args)
90
96
  }));
91
97
 
@@ -93,7 +99,9 @@ vi.mock('./16.2_production_enr.js', () => ({
93
99
  // renvoie l'entrée telle quelle.
94
100
  const sanitizeExecute = vi.fn((dpe) => dpe);
95
101
  vi.mock('./dpe-sanitizer.service.js', () => ({
96
- default: vi.fn(() => ({ execute: sanitizeExecute }))
102
+ default: vi.fn(function () {
103
+ return { execute: sanitizeExecute };
104
+ })
97
105
  }));
98
106
 
99
107
  const { getVersion, get_conso_coeff_1_7_2027, get_classe_ges_dpe, calcul_3cl, calcul_3cl_xml } =
@@ -650,6 +658,20 @@ describe('calcul_3cl - collections vidables (poursuite du calcul)', () => {
650
658
  });
651
659
 
652
660
  describe('calcul_3cl - surfaces immeuble / appartement et prorata', () => {
661
+ test('méthode application inconnue : surface habitable indéterminée transmise à la déperdition', () => {
662
+ enums.methode_application_dpe_log = { 1: 'méthode farfelue' };
663
+ const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
664
+ const dpe = makeDpe();
665
+
666
+ calcul_3cl(dpe, { sanitize: false });
667
+
668
+ // th null => aucune surface retenue (ni logement ni immeuble)
669
+ expect(calc_deperdition.mock.calls[0][2]).toBeNull();
670
+ expect(calc_deperdition.mock.calls[0][5]).toBeUndefined();
671
+ expect(errSpy).toHaveBeenCalled();
672
+ errSpy.mockRestore();
673
+ });
674
+
653
675
  test('immeuble : Sh = surface immeuble ; ECS collective individualisée non divisée', () => {
654
676
  enums.methode_application_dpe_log = { 1: 'dpe immeuble collectif' };
655
677
  const dpe = makeDpe();
@@ -1340,4 +1362,22 @@ describe('get_conso_coeff_1_7_2027', () => {
1340
1362
  expect(res.ep_conso_5_usages_2027).toBeCloseTo(ep, 6);
1341
1363
  expect(classe_bilan_dpe).toHaveBeenCalledWith(Math.floor(ep / 200), 'zc1', 'ca1', 200);
1342
1364
  });
1365
+
1366
+ test('méthode application inconnue : surface indéterminée, aucune surface ne lui est substituée', () => {
1367
+ enums.methode_application_dpe_log = { 1: 'méthode farfelue' };
1368
+ const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
1369
+ vi.mocked(classe_bilan_dpe).mockReturnValue('G');
1370
+ const dpe = makeDpe();
1371
+ dpe.logement.sortie = {
1372
+ ep_conso: { ep_conso_5_usages: 10000 },
1373
+ ef_conso: { conso_5_usages: 6000 }
1374
+ };
1375
+
1376
+ get_conso_coeff_1_7_2027(dpe);
1377
+
1378
+ // th null => ni surface logement ni surface immeuble
1379
+ expect(classe_bilan_dpe.mock.calls[0][3]).toBeUndefined();
1380
+ expect(errSpy).toHaveBeenCalled();
1381
+ errSpy.mockRestore();
1382
+ });
1343
1383
  });
@@ -5,9 +5,11 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
5
5
  // du comportement réel de `DpePreProcessor` (testé dans son propre spec).
6
6
  const preprocessMock = vi.fn();
7
7
  vi.mock('../../../preprocessor/index.js', () => ({
8
- DpePreProcessor: vi.fn().mockImplementation(() => ({
9
- preprocess: preprocessMock
10
- }))
8
+ DpePreProcessor: vi.fn(function () {
9
+ return {
10
+ preprocess: preprocessMock
11
+ };
12
+ })
11
13
  }));
12
14
 
13
15
  const { DpeNormalizerService } = await import('./dpe-normalizer.service.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open3cl/engine",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "description": "Open Source 3CL-DPE engine",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -26,7 +26,7 @@
26
26
  "qa:format": "prettier --plugin-search-dir . --write .",
27
27
  "release": "semantic-release",
28
28
  "release:dry": "semantic-release --dry-run",
29
- "prepare": "husky install",
29
+ "prepare": "husky",
30
30
  "assets:sync": "node scripts/synchronize_assets.js",
31
31
  "version:update": "node scripts/update_version.js",
32
32
  "reports:preview": "npm run http-server dist/reports/corpus -o",
@@ -45,43 +45,40 @@
45
45
  ],
46
46
  "dependencies": {
47
47
  "dioma": "^0.4.6",
48
- "fast-xml-parser": "^5.11.1",
48
+ "fast-xml-parser": "^5.11.2",
49
49
  "lodash-es": "^4.18.1",
50
50
  "winston": "^3.19.0"
51
51
  },
52
52
  "devDependencies": {
53
- "@aws-sdk/client-s3": "^3.1013.0",
54
- "@commitlint/cli": "^19.2.1",
55
- "@commitlint/config-conventional": "^19.1.0",
56
- "@commitlint/cz-commitlint": "^19.2.0",
57
- "@duckdb/node-api": "^1.3.2-alpha.25",
58
- "@semantic-release/changelog": "^6.0.3",
53
+ "@aws-sdk/client-s3": "^3.1160.0",
54
+ "@commitlint/cli": "^21.2.4",
55
+ "@commitlint/config-conventional": "^21.2.4",
56
+ "@commitlint/cz-commitlint": "^21.2.4",
57
+ "@duckdb/node-api": "^2.0.0-r.1",
58
+ "@eslint/js": "^10.0.1",
59
+ "@semantic-release/changelog": "^7.0.0",
59
60
  "@semantic-release/commit-analyzer": "^13.0.1",
60
61
  "@semantic-release/exec": "^7.1.0",
61
- "@semantic-release/git": "^10.0.1",
62
- "@semantic-release/github": "^12.0.2",
63
- "@types/node": "^20.12.4",
64
- "@vitest/coverage-v8": "^3.2.7",
62
+ "@semantic-release/git": "^11.0.1",
63
+ "@semantic-release/github": "^12.0.10",
64
+ "@types/node": "^24.19.2",
65
+ "@vitest/coverage-v8": "^5.0.5",
65
66
  "ansi-colors": "^4.1.3",
66
67
  "cli-progress": "^3.12.0",
67
68
  "deep-object-diff": "^1.1.9",
68
- "eslint": "^8.57.0",
69
- "eslint-config-prettier": "^9.1.0",
70
- "eslint-config-standard": "^17.1.0",
71
- "eslint-plugin-check-file": "^2.7.1",
72
- "eslint-plugin-import": "^2.29.1",
73
- "eslint-plugin-n": "^16.6.2",
74
- "eslint-plugin-promise": "^6.1.1",
75
- "fast-csv": "^5.0.2",
69
+ "eslint": "^10.14.0",
70
+ "eslint-config-prettier": "^10.1.8",
71
+ "fast-csv": "^5.0.8",
72
+ "globals": "^17.13.0",
76
73
  "http-server": "^14.1.1",
77
- "husky": "^9.0.11",
78
- "js-yaml": "^4.3.2",
79
- "jscpd": "^3.5.10",
80
- "piscina": "^5.1.2",
81
- "prettier": "^3.2.5",
74
+ "husky": "^9.1.7",
75
+ "js-yaml": "^5.5.0",
76
+ "jscpd": "^5.4.2",
77
+ "piscina": "^5.3.2",
78
+ "prettier": "^3.9.10",
82
79
  "pretty-quick": "^4.0.0",
83
- "semantic-release": "^25.0.2",
84
- "vitest": "3.2.7",
80
+ "semantic-release": "^25.0.9",
81
+ "vitest": "^5.0.5",
85
82
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
86
83
  },
87
84
  "author": "Open3Cl",
@@ -92,21 +89,5 @@
92
89
  "bugs": {
93
90
  "url": "https://github.com/Open3CL/engine/issues"
94
91
  },
95
- "homepage": "https://github.com/Open3CL/engine#readme",
96
- "jscpd": {
97
- "threshold": 0.1,
98
- "reporters": [
99
- "console",
100
- "html"
101
- ],
102
- "ignore": [
103
- "**/*.spec.js"
104
- ],
105
- "path": [
106
- "src"
107
- ],
108
- "output": "./duplicate",
109
- "absolute": true,
110
- "gitignore": true
111
- }
92
+ "homepage": "https://github.com/Open3CL/engine#readme"
112
93
  }
package/utils.js CHANGED
@@ -459,12 +459,11 @@ export function tv(filePath, matcher) {
459
459
  function tvLookup(filePath, matcher) {
460
460
  const list = tvs[filePath];
461
461
  const matcher_size = Object.keys(matcher).length;
462
- let match_count = 0;
463
462
  let max_match_count = 0;
464
463
  let match = null;
465
464
 
466
465
  for (const row of list) {
467
- match_count = 0;
466
+ let match_count = 0;
468
467
  for (const key in matcher) {
469
468
  if (tvMatchImpl(row, key, matcher)) match_count += 1;
470
469
  }
package/utils.spec.js CHANGED
@@ -215,6 +215,14 @@ describe('Utils unit tests', () => {
215
215
  expect(obj.other).toBe(3);
216
216
  expect(obj.nested.enum_c).toBe('7');
217
217
  });
218
+
219
+ test('ignore les propriétés héritées du prototype', () => {
220
+ const obj = Object.create({ enum_herite: 5 });
221
+ obj.enum_propre = 6;
222
+ useEnumAsString(obj);
223
+ expect(obj.enum_propre).toBe('6');
224
+ expect(obj.enum_herite).toBe(5);
225
+ });
218
226
  });
219
227
 
220
228
  // -------------------------------------------------------------------------------------------
@@ -589,6 +597,14 @@ describe('Utils unit tests', () => {
589
597
  expect(obj.nested.remove_me).toBeUndefined();
590
598
  expect(obj.nested.b).toBe(5);
591
599
  });
600
+
601
+ test('ignore les propriétés héritées du prototype', () => {
602
+ const obj = Object.create({ remove_me: 1, herite: { remove_me: 2 } });
603
+ obj.propre = 1;
604
+ removeKeyFromJSON(obj, 'remove_me', []);
605
+ expect(obj.remove_me).toBe(1);
606
+ expect(obj.herite.remove_me).toBe(2);
607
+ });
592
608
  });
593
609
 
594
610
  // -------------------------------------------------------------------------------------------