From f879f563084fca85efac424100fc448f83c913a0 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 14 Oct 2019 17:34:58 +0200
Subject: [PATCH 01/11] Progress on Solveur GUI

---
 .../calculators/solveur/solveur.config.json   | 32 +++++++++++
 src/app/calculators/solveur/solveur.en.json   | 10 ++++
 src/app/calculators/solveur/solveur.fr.json   | 10 ++++
 .../definition/concrete/form-solveur.ts       | 53 +++++++++++++++++++
 src/app/formulaire/fieldset.ts                | 21 ++++++++
 src/app/formulaire/select-field.ts            | 25 ++++++++-
 src/app/services/formulaire.service.ts        |  6 +++
 src/locale/messages.en.json                   |  2 +
 src/locale/messages.fr.json                   |  2 +
 9 files changed, 160 insertions(+), 1 deletion(-)
 create mode 100644 src/app/calculators/solveur/solveur.config.json
 create mode 100644 src/app/calculators/solveur/solveur.en.json
 create mode 100644 src/app/calculators/solveur/solveur.fr.json
 create mode 100644 src/app/formulaire/definition/concrete/form-solveur.ts

diff --git a/src/app/calculators/solveur/solveur.config.json b/src/app/calculators/solveur/solveur.config.json
new file mode 100644
index 000000000..7e6ac3bbd
--- /dev/null
+++ b/src/app/calculators/solveur/solveur.config.json
@@ -0,0 +1,32 @@
+[
+    {
+        "id": "fs_target",
+        "type": "fieldset",
+        "fields": [
+            {
+                "id": "select_target_nub",
+                "type": "select",
+                "source": "solveur_target"
+            },
+            "Ytarget"
+        ]
+    },
+    {
+        "id": "fs_searched",
+        "type": "fieldset",
+        "fields": [
+            {
+                "id": "select_searched_param",
+                "type": "select",
+                "source": "solveur_searched"
+            },
+            "Xinit"
+        ]
+    },
+    {
+        "type": "options",
+        "targetNubSelectId": "select_target_nub",
+        "searchedParamSelectId": "select_searched_param",
+        "_help": "solveur.html"
+    }
+]
\ No newline at end of file
diff --git a/src/app/calculators/solveur/solveur.en.json b/src/app/calculators/solveur/solveur.en.json
new file mode 100644
index 000000000..cbf67bcf2
--- /dev/null
+++ b/src/app/calculators/solveur/solveur.en.json
@@ -0,0 +1,10 @@
+{
+    "fs_target": "Target parameter characteristics",
+    "fs_searched": "Searched parameter characteristics",
+
+    "Ytarget": "Value of target parameter",
+    "Xinit": "Initial value for searched parameter",
+
+    "select_target_nub": "Module and parameter to calculate",
+    "select_searched_param": "Searched parameter"
+}
\ No newline at end of file
diff --git a/src/app/calculators/solveur/solveur.fr.json b/src/app/calculators/solveur/solveur.fr.json
new file mode 100644
index 000000000..86899fc0d
--- /dev/null
+++ b/src/app/calculators/solveur/solveur.fr.json
@@ -0,0 +1,10 @@
+{
+    "fs_target": "Caractéristiques du paramètre cible",
+    "fs_searched": "Caractéristiques du paramètre recherché",
+
+    "Ytarget": "Valeur du paramètre cible",
+    "Xinit": "Valeur initiale du paramètre recherché",
+
+    "select_target_nub": "Module et paramètre à calculer",
+    "select_searched_param": "Paramètre recherché"
+}
\ No newline at end of file
diff --git a/src/app/formulaire/definition/concrete/form-solveur.ts b/src/app/formulaire/definition/concrete/form-solveur.ts
new file mode 100644
index 000000000..09d2fe6ab
--- /dev/null
+++ b/src/app/formulaire/definition/concrete/form-solveur.ts
@@ -0,0 +1,53 @@
+import { IObservable } from "jalhyd";
+
+import { FormulaireBase } from "./form-base";
+import { FieldSet } from "../../fieldset";
+
+/**
+ * Formulaire pour les Solveurs
+ */
+export class FormulaireSolveur extends FormulaireBase {
+
+    /** id of select configuring target Nub */
+    private _targetNubSelectId: string;
+
+    /** id of select configuring searched param */
+    private _searchedParamSelectId: string;
+
+    protected parseOptions(json: {}) {
+        super.parseOptions(json);
+        this._targetNubSelectId = this.getOption(json, "targetNubSelectId");
+        this._searchedParamSelectId = this.getOption(json, "searchedParamSelectId");
+    }
+
+    public afterParseFieldset(fs: FieldSet) {
+        if (this._searchedParamSelectId) {
+            const sel = fs.getFormulaireNodeById(this._searchedParamSelectId);
+            if (sel) {
+                fs.properties.addObserver(this);
+            }
+        }
+        if (this._targetNubSelectId) {
+            const sel = fs.getFormulaireNodeById(this._targetNubSelectId);
+            if (sel) {
+                fs.properties.addObserver(this);
+            }
+        }
+    }
+
+    // interface Observer
+
+    public update(sender: IObservable, data: any) {
+        super.update(sender, data);
+        console.log("FormulaireSolveur().update", sender.constructor.name, data);
+        if (data.action === "propertyChange") {
+            if (data.name === "gridType") {
+                this.reset();
+                // Inclined grids have more input fields (OEntH and cIncl)
+                this.getFieldsetById("fs_grille").updateFields();
+                // Alpha and Beta are not always shown
+                this.getFieldsetById("fs_plan").updateFields();
+            }
+        }
+    }
+}
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index b5d4978c5..07c6d3be1 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -10,6 +10,7 @@ import {
     GrilleType,
     GrilleProfile,
     BiefRegime,
+    Solveur,
 } from "jalhyd";
 
 import { FormulaireElement } from "./formulaire-element";
@@ -258,6 +259,26 @@ export class FieldSet extends FormulaireElement implements Observer {
             case "fs_water_line": // Bief
                 this.setSelectValueFromProperty("select_regime", "regime");
                 break;
+
+            case "fs_target": // Solveur
+                this.setSelectValueFromProperty("select_target_nub", "nubToCalculate");
+                break;
+
+            case "fs_searched": // Solveur
+                const selectField: SelectField = this.getFormulaireNodeById("select_searched_param") as SelectField;
+                if (selectField) {
+                    const nub = this.parentForm.currentNub as Solveur;
+                    const X: ParamDefinition = nub.searchedParameter;
+                    if (X !== undefined) {
+                        const selectElement = selectField.getSelectedEntryFromValue(X);
+                        try {
+                            selectField.setValue(selectElement);
+                        } catch (e) {
+                            console.error(`fieldset.updateFields(): cannot set ${X.parentNub.uid}/${X.symbol} on <select> "select_searched_param"`);
+                        }
+                    }
+                }
+                break;
         }
     }
 
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index 308b319b0..065b09633 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -8,7 +8,10 @@ import {
     StructureType,
     LoiDebit,
     GrilleType,
-    GrilleProfile
+    GrilleProfile,
+    Solveur,
+    ParamValueMode,
+    Session
  } from "jalhyd";
 
 import { Field } from "./field";
@@ -188,6 +191,26 @@ export class SelectField extends Field {
                 this.addEntry(new SelectEntry(this._entriesBaseId + BiefRegime.Fluvial, BiefRegime.Fluvial));
                 this.addEntry(new SelectEntry(this._entriesBaseId + BiefRegime.Torrentiel, BiefRegime.Torrentiel));
                 break;
+
+            case "solveur_target": // Solveur, paramètre cible (à calculer)
+                // find all Nubs having at least one link to another Nub's result
+                console.log(">> update solveur targets");
+                const downstreamNubs = Session.getInstance().getDownstreamNubs();
+                for (const dn of downstreamNubs) {
+                    this.addEntry(new SelectEntry(this._entriesBaseId + dn.uid, dn.uid));
+                }
+                break;
+
+            case "solveur_searched": // Solveur, paramètre recherché (à faire varier)
+                // find all non-calculated, non-linked parameters of all Nubs that
+                // the current "target" Nub depends on (if any)
+                console.log(">> update solveur searched");
+                const ntc: Nub = (nub as Solveur).nubToCalculate;
+                const searchableParams = Solveur.getDependingNubsSearchableParams(ntc);
+                for (const p of searchableParams) {
+                    this.addEntry(new SelectEntry(this._entriesBaseId + p.nubUid + "_" + p.symbol, p));
+                }
+                break;
         }
     }
 }
diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts
index 65a60d25b..025930609 100644
--- a/src/app/services/formulaire.service.ts
+++ b/src/app/services/formulaire.service.ts
@@ -39,6 +39,7 @@ import { FormulaireMacrorugoCompound } from "../formulaire/definition/concrete/f
 import { FormulaireLechaptCalmon } from "../formulaire/definition/concrete/form-lechapt-calmon";
 import { FormulaireGrille } from "../formulaire/definition/concrete/form-grille";
 import { FormulaireBief } from "../formulaire/definition/concrete/form-bief";
+import { FormulaireSolveur } from "../formulaire/definition/concrete/form-solveur";
 
 @Injectable()
 export class FormulaireService extends Observable {
@@ -84,6 +85,7 @@ export class FormulaireService extends Observable {
         this.calculatorPaths[CalculatorType.Grille] = "grille";
         this.calculatorPaths[CalculatorType.Pente] = "pente";
         this.calculatorPaths[CalculatorType.Bief] = "bief";
+        this.calculatorPaths[CalculatorType.Solveur] = "solveur";
     }
 
     private get _intlService(): I18nService {
@@ -329,6 +331,10 @@ export class FormulaireService extends Observable {
                 f = new FormulaireBief();
                 break;
 
+            case CalculatorType.Solveur:
+                f = new FormulaireSolveur();
+                break;
+
             default:
                 f = new FormulaireBase();
         }
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 5a3e63c64..ad74567f5 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -431,6 +431,8 @@
     "INFO_SNACKBAR_RESULTS_CALCULATED": "Results calculated for",
     "INFO_SNACKBAR_RESULTS_INVALIDATED": "Results invalidated for",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Settings saved on this device",
+    "INFO_SOLVEUR_TITRE": "Multimodule solver",
+    "INFO_SOLVEUR_TITRE_COURT": "Solver",
     "INFO_THEME_CREDITS": "Credit",
     "INFO_THEME_DEVALAISON_TITRE": "Downstream migration",
     "INFO_THEME_DEVALAISON_DESCRIPTION": "Tools for dimensioning the structures present on the water intakes of hydroelectric power plants known as \"ichthyocompatible\" and consisting of fine grid planes associated with one or more outlets.",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 523d6cd37..e93d09b95 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -430,6 +430,8 @@
     "INFO_SNACKBAR_RESULTS_CALCULATED": "Résultats calculés pour",
     "INFO_SNACKBAR_RESULTS_INVALIDATED": "Résultats invalidés pour",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Paramètres enregistrés sur cet appareil",
+    "INFO_SOLVEUR_TITRE": "Solveur multimodule",
+    "INFO_SOLVEUR_TITRE_COURT": "Solveur",
     "INFO_THEME_CREDITS": "Crédit",
     "INFO_THEME_DEVALAISON_TITRE": "Dévalaison",
     "INFO_THEME_DEVALAISON_DESCRIPTION": "Outils de dimensionnements des ouvrages présents sur les prises d'eau des centrales hydroélectriques dites \"ichtyocompatibles\" et constituées de plans de grilles fines associés à un ou plusieurs exutoires.",
-- 
GitLab


From 0bb707f3115680a2c1238dec3a8a18cd8f826080 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 15 Oct 2019 17:11:43 +0200
Subject: [PATCH 02/11] Input: render undefined values as ""

---
 src/app/components/generic-input/generic-input.component.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 2e076ed1c..f49b62c0e 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -220,7 +220,11 @@ export abstract class GenericInputComponent implements OnChanges {
      * MAJ et validation de l'UI
      */
     protected updateAndValidateUI() {
-        this._uiValue = String(this.getModelValue());
+        if (this.getModelValue() !== undefined) {
+            this._uiValue = String(this.getModelValue());
+        } else {
+            this._uiValue = "";
+        }
         this.validateUI();
     }
 
-- 
GitLab


From 08bc15aef4eed9415d6a082c1bf492044ca3585e Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 16 Oct 2019 09:22:37 +0200
Subject: [PATCH 03/11] Update jalhyd_branch

---
 jalhyd_branch | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/jalhyd_branch b/jalhyd_branch
index 0c6550147..4517376e6 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-55-ajout-d-un-module-de-calcul-des-cotes-amont-aval-d-un-bief
+152-solveur-multi-modules
-- 
GitLab


From 917fb69990d3fc0241b11b0ed66fe3e4f6afba95 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 16 Oct 2019 16:04:36 +0200
Subject: [PATCH 04/11] Solveur GUI

interface for Solveur module
draw Solveur relations in modules diagram view
---
 .../calculators/solveur/solveur.config.json   |  6 +-
 src/app/calculators/solveur/solveur.en.json   |  1 +
 src/app/calculators/solveur/solveur.fr.json   |  1 +
 .../calculator.component.ts                   |  7 ++
 .../modules-diagram.component.ts              | 20 +++-
 .../select-field-line.component.ts            | 12 ++-
 .../definition/concrete/form-solveur.ts       | 76 +++++++++++----
 .../formulaire/definition/form-definition.ts  |  6 ++
 src/app/formulaire/fieldset.ts                | 37 +++++++-
 src/app/formulaire/ngparam.ts                 |  5 +-
 src/app/formulaire/select-field-nub.ts        | 37 ++++++++
 src/app/formulaire/select-field-parameter.ts  | 36 ++++++++
 src/app/formulaire/select-field-reference.ts  | 92 +++++++++++++++++++
 src/app/formulaire/select-field.ts            | 24 +----
 src/app/util.ts                               | 11 +++
 src/locale/messages.en.json                   | 11 ++-
 src/locale/messages.fr.json                   | 11 ++-
 17 files changed, 336 insertions(+), 57 deletions(-)
 create mode 100644 src/app/formulaire/select-field-nub.ts
 create mode 100644 src/app/formulaire/select-field-parameter.ts
 create mode 100644 src/app/formulaire/select-field-reference.ts

diff --git a/src/app/calculators/solveur/solveur.config.json b/src/app/calculators/solveur/solveur.config.json
index 7e6ac3bbd..40b0bbb8c 100644
--- a/src/app/calculators/solveur/solveur.config.json
+++ b/src/app/calculators/solveur/solveur.config.json
@@ -5,7 +5,8 @@
         "fields": [
             {
                 "id": "select_target_nub",
-                "type": "select",
+                "type": "select_reference",
+                "reference": "nub",
                 "source": "solveur_target"
             },
             "Ytarget"
@@ -17,7 +18,8 @@
         "fields": [
             {
                 "id": "select_searched_param",
-                "type": "select",
+                "type": "select_reference",
+                "reference": "parameter",
                 "source": "solveur_searched"
             },
             "Xinit"
diff --git a/src/app/calculators/solveur/solveur.en.json b/src/app/calculators/solveur/solveur.en.json
index cbf67bcf2..df14b3764 100644
--- a/src/app/calculators/solveur/solveur.en.json
+++ b/src/app/calculators/solveur/solveur.en.json
@@ -4,6 +4,7 @@
 
     "Ytarget": "Value of target parameter",
     "Xinit": "Initial value for searched parameter",
+    "X": "Value for searched parameter",
 
     "select_target_nub": "Module and parameter to calculate",
     "select_searched_param": "Searched parameter"
diff --git a/src/app/calculators/solveur/solveur.fr.json b/src/app/calculators/solveur/solveur.fr.json
index 86899fc0d..1439bd8da 100644
--- a/src/app/calculators/solveur/solveur.fr.json
+++ b/src/app/calculators/solveur/solveur.fr.json
@@ -4,6 +4,7 @@
 
     "Ytarget": "Valeur du paramètre cible",
     "Xinit": "Valeur initiale du paramètre recherché",
+    "X": "Valeur du paramètre recherché",
 
     "select_target_nub": "Module et paramètre à calculer",
     "select_searched_param": "Paramètre recherché"
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 82dc8d735..365fa61e7 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -376,6 +376,8 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
                     this._calculatorNameComponent.model = this._formulaire;
                     // reload localisation in all cases
                     this.formulaireService.loadUpdateFormulaireLocalisation(this._formulaire);
+                    // call Form init hook
+                    this._formulaire.onCalculatorInit();
                     break;
             }
         } else if (sender instanceof FormulaireDefinition) {
@@ -530,6 +532,11 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
         return (this.isPAB || this.isMRC);
     }
 
+    // true if current Nub is Solveur
+    public get isSolveur() {
+        return this.is(CalculatorType.Solveur);
+    }
+
     // true if current Nub is PAB
     public get isPAB() {
         return this.is(CalculatorType.Pab);
diff --git a/src/app/components/modules-diagram/modules-diagram.component.ts b/src/app/components/modules-diagram/modules-diagram.component.ts
index 68297ad6c..d84bfdac7 100644
--- a/src/app/components/modules-diagram/modules-diagram.component.ts
+++ b/src/app/components/modules-diagram/modules-diagram.component.ts
@@ -16,7 +16,8 @@ import {
     LoiDebit,
     Nub,
     MacrorugoCompound,
-    Pab
+    Pab,
+    Solveur
 } from "jalhyd";
 
 import { I18nService } from "../../services/internationalisation.service";
@@ -43,7 +44,7 @@ export class ModulesDiagramComponent implements AfterContentInit, AfterViewCheck
     private nativeElement: any;
 
     @ViewChild("diagram", { static: true })
-    public diagram;
+    public diagram: any;
 
     public error: boolean;
 
@@ -179,7 +180,7 @@ export class ModulesDiagramComponent implements AfterContentInit, AfterViewCheck
                 // simple Nub (no children)
                 def.push(f.uid + "(\"" + f.calculatorName + "\")");
             }
-            // fnid all linked parameters
+            // find all linked parameters
             for (const p of nub.parameterIterator) {
                 if (p.valueMode === ParamValueMode.LINK && p.isReferenceDefined()) {
                     const target = p.referencedValue.nub;
@@ -190,6 +191,19 @@ export class ModulesDiagramComponent implements AfterContentInit, AfterViewCheck
                     def.push(nub.uid + "-->|" + symb + "|" + target.uid);
                 }
             }
+            // add Solveur links
+            if (nub instanceof Solveur) {
+                const ntc = nub.nubToCalculate;
+                const sp = nub.searchedParameter;
+                const reads = this.intlService.localizeText("INFO_DIAGRAM_SOLVEUR_READS");
+                const finds = this.intlService.localizeText("INFO_DIAGRAM_SOLVEUR_FINDS");
+                if (ntc !== undefined) {
+                    def.push(nub.uid + "-->|" + reads + ":" + ntc.calculatedParam.symbol + "|" + ntc.uid);
+                }
+                if (sp !== undefined) {
+                    def.push(sp.nubUid + "-->|" + finds + ":" + sp.symbol + "|" + nub.uid);
+                }
+            }
         }
 
         return def.join("\n");
diff --git a/src/app/components/select-field-line/select-field-line.component.ts b/src/app/components/select-field-line/select-field-line.component.ts
index 04a69146b..e9615dbed 100644
--- a/src/app/components/select-field-line/select-field-line.component.ts
+++ b/src/app/components/select-field-line/select-field-line.component.ts
@@ -1,8 +1,9 @@
-import { Component, Input } from "@angular/core";
+import { Component, Input, OnInit } from "@angular/core";
 
 import { SelectField } from "../../formulaire/select-field";
 import { SelectEntry } from "../../formulaire/select-entry";
 import { I18nService } from "../../services/internationalisation.service";
+import { SelectFieldReference } from "../../formulaire/select-field-reference";
 
 @Component({
     selector: "select-field-line",
@@ -11,7 +12,7 @@ import { I18nService } from "../../services/internationalisation.service";
         "./select-field-line.component.scss"
     ]
 })
-export class SelectFieldLineComponent {
+export class SelectFieldLineComponent implements OnInit {
 
     /** aide en ligne */
     protected helpLink: string | { [key: string]: string };
@@ -83,4 +84,11 @@ export class SelectFieldLineComponent {
     public get uitextOpenHelp() {
         return this.i18nService.localizeText("INFO_CALCULATOR_OPEN_HELP");
     }
+
+    // called every time we navigate to the module
+    ngOnInit(): void {
+        if (this._select instanceof SelectFieldReference) {
+            this._select.updateEntries();
+        }
+    }
 }
diff --git a/src/app/formulaire/definition/concrete/form-solveur.ts b/src/app/formulaire/definition/concrete/form-solveur.ts
index 09d2fe6ab..e04fc1665 100644
--- a/src/app/formulaire/definition/concrete/form-solveur.ts
+++ b/src/app/formulaire/definition/concrete/form-solveur.ts
@@ -1,7 +1,9 @@
-import { IObservable } from "jalhyd";
+import { IObservable, ParamDefinition, Solveur } from "jalhyd";
 
 import { FormulaireBase } from "./form-base";
-import { FieldSet } from "../../fieldset";
+import { SelectFieldNub } from "../../select-field-nub";
+import { SelectFieldParameter } from "../../select-field-parameter";
+import { NgParameter } from "../../ngparam";
 
 /**
  * Formulaire pour les Solveurs
@@ -20,33 +22,73 @@ export class FormulaireSolveur extends FormulaireBase {
         this._searchedParamSelectId = this.getOption(json, "searchedParamSelectId");
     }
 
-    public afterParseFieldset(fs: FieldSet) {
-        if (this._searchedParamSelectId) {
-            const sel = fs.getFormulaireNodeById(this._searchedParamSelectId);
+    protected completeParse(json: {}) {
+        super.completeParse(json);
+        if (this._targetNubSelectId) {
+            const sel = this.getFormulaireNodeById(this._targetNubSelectId);
             if (sel) {
-                fs.properties.addObserver(this);
+                sel.addObserver(this);
             }
         }
-        if (this._targetNubSelectId) {
-            const sel = fs.getFormulaireNodeById(this._targetNubSelectId);
+        if (this._searchedParamSelectId) {
+            const sel = this.getFormulaireNodeById(this._searchedParamSelectId);
             if (sel) {
-                fs.properties.addObserver(this);
+                sel.addObserver(this);
             }
         }
+
+    }
+
+    private debugState() {
+        const sol = this._currentNub as Solveur;
+        const ntc = sol.nubToCalculate;
+        const spm = sol.searchedParameter;
+        console.log(
+            `ETAT:\n X.singleValue=${sol.prms.X.singleValue}\n Y.singleValue=${sol.prms.Y.singleValue}`
+            + `\n Xinit.singleValue=${sol.prms.Xinit.singleValue}\n Ytarget.singleValue=${sol.prms.Ytarget.singleValue}`
+            + `\n searchedParam.singleValue=${spm.singleValue}`
+        );
     }
 
     // interface Observer
 
     public update(sender: IObservable, data: any) {
         super.update(sender, data);
-        console.log("FormulaireSolveur().update", sender.constructor.name, data);
-        if (data.action === "propertyChange") {
-            if (data.name === "gridType") {
-                this.reset();
-                // Inclined grids have more input fields (OEntH and cIncl)
-                this.getFieldsetById("fs_grille").updateFields();
-                // Alpha and Beta are not always shown
-                this.getFieldsetById("fs_plan").updateFields();
+        if (sender instanceof SelectFieldNub) {
+            if (data.action === "select") {
+                // update Solveur property: Nub to calculate
+                console.log("(i) update Nub to calculate");
+                try {
+                    // if searchedParam is set to a value that won't be available anymore
+                    // once nubToCalculate is updated, setPropValue throws an error, but
+                    // nubToCalculate is updated anyway; here, just inhibit the error
+                    this._currentNub.properties.setPropValue("nubToCalculate", data.value.value);
+                } catch (e) { }
+                // refresh parameters selector
+                const sel = this.getFormulaireNodeById(this._searchedParamSelectId) as SelectFieldParameter;
+                if (sel) {
+                    console.log("(ii) update Parameters entries");
+                    sel.updateEntries();
+                    this.debugState();
+                    // reflect changes in GUI
+                    const inputYtarget = this.getFormulaireNodeById("Ytarget") as NgParameter;
+                    inputYtarget.notifyValueModified(this);
+                }
+            }
+        }
+        if (sender instanceof SelectFieldParameter) {
+            if (data.action === "select") {
+                // update Solveur property: searched Parameter
+                console.log("(i) update searched Parameter");
+                const p: ParamDefinition = data.value.value;
+                this._currentNub.properties.setPropValue(
+                    "searchedParameter",
+                    p.nubUid + "/" + p.symbol
+                );
+                this.debugState();
+                // reflect changes in GUI
+                const inputXinit = this.getFormulaireNodeById("Xinit") as NgParameter;
+                inputXinit.notifyValueModified(this);
             }
         }
     }
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index b178d3a3d..5aa007c8a 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -452,6 +452,12 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return new TopFormulaireElementIterator(this);
     }
 
+    /**
+     * Appelé par CalculatorComponent lrosque le Formulaire est chargé dans la vue,
+     * c'est à dire lorsqu'on affiche un module de calcul à l'écran
+     */
+    public onCalculatorInit() {}
+
     //  interface Observer
 
     public update(sender: any, data: any) {
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 07c6d3be1..e892c0c2c 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -21,6 +21,8 @@ import { FormulaireDefinition } from "./definition/form-definition";
 import { StringMap } from "../stringmap";
 import { FormulaireNode } from "./formulaire-node";
 import { FieldsetContainer } from "./fieldset-container";
+import { SelectFieldNub } from "./select-field-nub";
+import { SelectFieldParameter } from "./select-field-parameter";
 
 export class FieldSet extends FormulaireElement implements Observer {
     /**
@@ -97,6 +99,28 @@ export class FieldSet extends FormulaireElement implements Observer {
         return res;
     }
 
+    private parse_select_reference(json: {}): SelectField {
+        const refType = json["reference"];
+        const source = json["source"];
+        let res: SelectField;
+        if (source === undefined || source === "") {
+            throw new Error(`Fieldset.parse_select_reference(): "source" must not be empty`);
+        }
+        switch (refType) {
+            case "nub": // @TODO upstreamNub / downstreamNub ?
+                res = new SelectFieldNub(this, source);
+                break;
+            case "parameter":
+                res = new SelectFieldParameter(this, source);
+                break;
+            default:
+                throw new Error(`Fieldset.parse_select_reference(): unknown reference type ${refType}`);
+        }
+        res.parseConfig(json);
+        res.addObserver(this);
+        return res;
+    }
+
     public get properties(): Props {
         return this.nub.properties;
     }
@@ -186,6 +210,11 @@ export class FieldSet extends FormulaireElement implements Observer {
                     this.addField(param);
                     break;
 
+                case "select_reference":
+                    param = this.parse_select_reference(field);
+                    this.addField(param);
+                    break;
+
             }
         }
     }
@@ -260,7 +289,7 @@ export class FieldSet extends FormulaireElement implements Observer {
                 this.setSelectValueFromProperty("select_regime", "regime");
                 break;
 
-            case "fs_target": // Solveur
+            /* case "fs_target": // Solveur
                 this.setSelectValueFromProperty("select_target_nub", "nubToCalculate");
                 break;
 
@@ -274,11 +303,13 @@ export class FieldSet extends FormulaireElement implements Observer {
                         try {
                             selectField.setValue(selectElement);
                         } catch (e) {
-                            console.error(`fieldset.updateFields(): cannot set ${X.parentNub.uid}/${X.symbol} on <select> "select_searched_param"`);
+                            console.error(
+                                `fieldset.updateFields(): cannot set ${X.parentNub.uid}/${X.symbol} on <select> "select_searched_param"`
+                            );
                         }
                     }
                 }
-                break;
+                break; */
         }
     }
 
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 5387c058d..b5d15d216 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -113,9 +113,8 @@ export class NgParameter extends InputField implements Observer {
                         const cVal = ref.nub.result.getCalculatedValues();
                         valuePreview = fv(cVal[0]) + " … " + fv(cVal[cVal.length - 1]);
                     } else {
-                        const vCalc = ref.nub.result.vCalc;
-                        if (vCalc) {
-                            valuePreview = fv(vCalc);
+                        if (ref.nub.result.resultElements.length > 0 && ref.nub.result.vCalc) {
+                            valuePreview = fv(ref.nub.result.vCalc);
                         } else {
                             // computation has been run but has failed
                             valuePreview = i18n.localizeText("INFO_PARAMFIELD_CALCULATION_FAILED");
diff --git a/src/app/formulaire/select-field-nub.ts b/src/app/formulaire/select-field-nub.ts
new file mode 100644
index 000000000..345699fc1
--- /dev/null
+++ b/src/app/formulaire/select-field-nub.ts
@@ -0,0 +1,37 @@
+import { SelectFieldReference } from "./select-field-reference";
+import { SelectEntry } from "./select-entry";
+import { ServiceFactory } from "../services/service-factory";
+import { decodeHtml } from "../util";
+
+import { Session } from "jalhyd";
+
+/**
+ * A select field that populates itself with references to Nubs
+ */
+export class SelectFieldNub extends SelectFieldReference {
+
+    protected initSelectedValue() {}
+
+    /**
+     * Populates entries with available references
+     */
+    protected populate() {
+        switch (this._source) {
+            case "solveur_target": // Solveur, paramètre cible (à calculer)
+                // find all Nubs having at least one link to another Nub's result
+                const fs = ServiceFactory.instance.formulaireService;
+                const downstreamNubs = Session.getInstance().getDownstreamNubs();
+                for (const dn of downstreamNubs) {
+                    const calc = fs.getFormulaireFromId(dn.uid).calculatorName;
+                    let label = calc;
+                    if (dn.calculatedParam !== undefined) {
+                        const varName = fs.expandVariableName(dn.calcType, dn.calculatedParam.symbol);
+                        label += ` / ${varName} (${dn.calculatedParam.symbol})`;
+                    }
+                    this.addEntry(new SelectEntry(this._entriesBaseId + dn.uid, dn.uid, decodeHtml(label)));
+                }
+                break;
+        }
+    }
+
+}
diff --git a/src/app/formulaire/select-field-parameter.ts b/src/app/formulaire/select-field-parameter.ts
new file mode 100644
index 000000000..d84ad50ac
--- /dev/null
+++ b/src/app/formulaire/select-field-parameter.ts
@@ -0,0 +1,36 @@
+import { SelectFieldReference } from "./select-field-reference";
+import { SelectEntry } from "./select-entry";
+import { decodeHtml } from "../util";
+import { ServiceFactory } from "../services/service-factory";
+
+import { Nub, Solveur } from "jalhyd";
+
+/**
+ * A select field that populates itself with references to ParamDefinitions
+ */
+export class SelectFieldParameter extends SelectFieldReference {
+
+    protected initSelectedValue() {}
+
+    /**
+     * Populates entries with available references
+     */
+    protected populate() {
+        switch (this._source) {
+            case "solveur_searched": // Solveur, paramètre recherché (à faire varier)
+                // find all non-calculated, non-linked parameters of all Nubs that
+                // the current "target" Nub depends on (if any)
+                const fs = ServiceFactory.instance.formulaireService;
+                const ntc: Nub = (this.parentForm.currentNub as Solveur).nubToCalculate;
+                const searchableParams = Solveur.getDependingNubsSearchableParams(ntc);
+                for (const p of searchableParams) {
+                    const calc = fs.getFormulaireFromId(p.parentNub.uid).calculatorName;
+                    const varName = fs.expandVariableName(p.parentNub.calcType, p.symbol);
+                    const label = `${p.symbol} - ${varName} (${calc})`;
+                    this.addEntry(new SelectEntry(this._entriesBaseId + p.nubUid + "_" + p.symbol, p, decodeHtml(label)));
+                }
+                break;
+        }
+    }
+
+}
diff --git a/src/app/formulaire/select-field-reference.ts b/src/app/formulaire/select-field-reference.ts
new file mode 100644
index 000000000..a84b272ea
--- /dev/null
+++ b/src/app/formulaire/select-field-reference.ts
@@ -0,0 +1,92 @@
+import { SelectField } from "./select-field";
+import { SelectEntry } from "./select-entry";
+import { FormulaireNode } from "./formulaire-node";
+
+/**
+ * A select field that populates itself with references to
+ * available objects (for ex. Nub or ParamDefinition)
+ */
+export abstract class SelectFieldReference extends SelectField {
+
+    /** source identifier for populate() method */
+    protected _source: string;
+
+    constructor(parent: FormulaireNode, source: string) {
+        super(parent);
+        this._source = source;
+        this.initSelectedValue();
+    }
+
+    protected abstract initSelectedValue();
+
+    /**
+     * Populates entries with available references
+     */
+    protected abstract populate();
+
+    /**
+     * Reloads available entries, trying to keep the current selected
+     * value; does not notify observers if value did not change
+     */
+    public updateEntries() {
+        // store previous selected entry
+        const pse = this._selectedEntry;
+        // empty
+        this.clearEntries();
+        // populate
+        this.populate();
+        // keep previously selected entry if possible
+        if (pse && pse.id) {
+            this.setValueFromId(pse.id);
+        }
+        // if no entry is available anymore, unset value
+        if (this.entries.length === 0) {
+            super.setValue(undefined);
+        }
+    }
+
+    /**
+     * Updates selectedValue; notifies observers only if
+     * value.id has changed
+     */
+    public setValue(v: SelectEntry) {
+        const previousSelectedEntry = this._selectedEntry;
+        this._selectedEntry = v;
+        if (
+            ! previousSelectedEntry
+            || (previousSelectedEntry.id !== v.id)
+        ) {
+            console.log(`--> select, setValue: ${v.value}`);
+            this.notifyObservers({
+                "action": "select",
+                "value": v
+            }, this);
+        }
+    }
+
+    /**
+     * Sets value from given ID; if it was not found, sets the
+     * first available entry as selectedValue
+     */
+    public setValueFromId(id: string) {
+        let found = false;
+        for (const e of this._entries) {
+            if (e.id === id) {
+                found = true;
+                this.setValue(e);
+            }
+        }
+        if (! found) {
+            // default to first available entry if any
+            if (this._entries.length > 0) {
+                this.setValue(this._entries[0]);
+            } else {
+                // notify observers that no value is selected anymore
+                this.notifyObservers({
+                    "action": "select",
+                    "value": undefined
+                }, this);
+            }
+        }
+    }
+}
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index 065b09633..66545a058 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -108,7 +108,9 @@ export class SelectField extends Field {
     public updateLocalisation(loc: StringMap) {
         super.updateLocalisation(loc);
         for (const e of this._entries) {
-            e.label = loc[e.id];
+            if (loc[e.id] !== undefined) {
+                e.label = loc[e.id];
+            }
         }
     }
 
@@ -191,26 +193,6 @@ export class SelectField extends Field {
                 this.addEntry(new SelectEntry(this._entriesBaseId + BiefRegime.Fluvial, BiefRegime.Fluvial));
                 this.addEntry(new SelectEntry(this._entriesBaseId + BiefRegime.Torrentiel, BiefRegime.Torrentiel));
                 break;
-
-            case "solveur_target": // Solveur, paramètre cible (à calculer)
-                // find all Nubs having at least one link to another Nub's result
-                console.log(">> update solveur targets");
-                const downstreamNubs = Session.getInstance().getDownstreamNubs();
-                for (const dn of downstreamNubs) {
-                    this.addEntry(new SelectEntry(this._entriesBaseId + dn.uid, dn.uid));
-                }
-                break;
-
-            case "solveur_searched": // Solveur, paramètre recherché (à faire varier)
-                // find all non-calculated, non-linked parameters of all Nubs that
-                // the current "target" Nub depends on (if any)
-                console.log(">> update solveur searched");
-                const ntc: Nub = (nub as Solveur).nubToCalculate;
-                const searchableParams = Solveur.getDependingNubsSearchableParams(ntc);
-                for (const p of searchableParams) {
-                    this.addEntry(new SelectEntry(this._entriesBaseId + p.nubUid + "_" + p.symbol, p));
-                }
-                break;
         }
     }
 }
diff --git a/src/app/util.ts b/src/app/util.ts
index 6d6a10bb1..e4d3e1f1b 100644
--- a/src/app/util.ts
+++ b/src/app/util.ts
@@ -30,3 +30,14 @@ export function fv(p: NgParameter | number): string {
 
     return formattedValue(value, nDigits);
 }
+
+/**
+ * Trick to decode HTML entities in a string
+ * https://stackoverflow.com/a/7394787/5986614
+ * @param html string containing HTML entities, like &nbsp;
+ */
+export function decodeHtml(html: string): string {
+    const txt = document.createElement("textarea");
+    txt.innerHTML = html;
+    return txt.value;
+}
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index ad74567f5..270145ca4 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -4,7 +4,7 @@
     "WARNING_DOWNSTREAM_ELEVATION_POSSIBLE_SUBMERSION": "Downstream elevation is higher than weir elevation (possible submersion)",
     "WARNING_NOTCH_SUBMERSION_GREATER_THAN_07": "Notch formula is discouraged when submersion is greater than 0.7",
     "WARNING_SLOT_SUBMERSION_NOT_BETWEEN_07_AND_09": "Slot formula is discouraged when submersion is lower than 0.7 or greater than 0.9",
-    "ERROR_ABSTRACT": "%nb% errors occurred during calculation",
+    "WARNING_ERRORS_ABSTRACT": "%nb% errors occurred during calculation",
     "ERROR_BIEF_Z1_CALC_FAILED": "Unable to calculate upstream elevation (calculation interrupted before upstream)",
     "ERROR_BIEF_Z2_CALC_FAILED": "Unable to calculate downstream elevation (calculation interrupted before downstream)",
     "ERROR_DICHO_CONVERGE": "Dichotomy could not converge",
@@ -15,6 +15,8 @@
     "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomy: the solution %targetSymbol%=%targetValue% is greater than the maximum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomy: the solution %targetSymbol%=%targetValue%  is lower than the minimum computable value %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "Upstream elevation is lower than downstream elevation",
+    "ERROR_IN_CALC_CHAIN": "An error occurred in calculation chain",
+    "WARNING_ERROR_IN_CALC_CHAIN_STEPS": "Errors occurred during chain calculation",
     "ERROR_INTERVAL_OUTSIDE": "Interval: value %value% is outside of %interval%",
     "ERROR_INTERVAL_UNDEF": "Interval: invalid 'undefined' value",
     "ERROR_INVALID_AT_POSITION": "Position %s:",
@@ -80,6 +82,8 @@
     "INFO_COURBEREMOUS_TITRE": "Backwater curves",
     "INFO_DEVER_TITRE_COURT": "Free weir",
     "INFO_DEVER_TITRE": "Free flow weir stage-discharge laws",
+    "INFO_DIAGRAM_SOLVEUR_FINDS": "finds",
+    "INFO_DIAGRAM_SOLVEUR_READS": "reads",
     "INFO_DIAGRAM_TITLE": "Calculation modules diagram",
     "INFO_DIAGRAM_DRAWING_ERROR": "Error while drawing diagram",
     "INFO_DIAGRAM_CALCULATED_PARAM": "calculated parameter",
@@ -455,7 +459,7 @@
     "INFO_EXAMPLE_LABEL_PAB_COMPLETE": "Standard fish ladder",
     "INFO_EXAMPLES_TITLE": "Examples",
     "INFO_EXAMPLES_SUBTITLE": "Load standard examples",
-    "WARNING_ABSTRACT": "%nb% warnings occurred during calculation",
+    "WARNING_WARNINGS_ABSTRACT": "%nb% warnings occurred during calculation",
     "WARNING_REMOUS_ARRET_CRITIQUE": "Calculation stopped: critical elevation reached at abscissa %x%",
     "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p must not be greater than 2.5. h/p is forced to 2.5",
     "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "Threshold height should be greater than 0.1 m. Beta coefficient is forced to 0",
@@ -468,5 +472,6 @@
     "WARNING_DOWNSTREAM_BOTTOM_HIGHER_THAN_WATER": "Downstream water elevation is lower or equal to bottom elevation",
     "WARNING_YN_SECTION_PENTE_NEG_NULLE_HNORMALE_INF": "Normal depth: slope is negative or zero, normal depth is infinite",
     "WARNING_YN_SECTION_NON_CONVERGENCE_NEWTON_HNORMALE": "Normal depth: non convergence of the calculation (Newton's method)",
-    "WARNING_SESSION_LOAD_NOTES_MERGED": "Notes have been merged"
+    "WARNING_SESSION_LOAD_NOTES_MERGED": "Notes have been merged",
+    "WARNING_VALUE_ROUNDED_TO_INTEGER": "Value of %symbol% was rounded to %rounded%"
 }
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index e93d09b95..e04f71c07 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -4,7 +4,7 @@
     "WARNING_DOWNSTREAM_ELEVATION_POSSIBLE_SUBMERSION": "La cote de l'eau aval est plus élevée que la cote du seuil (ennoiement possible)",
     "WARNING_NOTCH_SUBMERSION_GREATER_THAN_07": "La formule de l'échancrure n'est pas conseillée pour un ennoiement supérieur à 0.7",
     "WARNING_SLOT_SUBMERSION_NOT_BETWEEN_07_AND_09": "La formule de la fente n'est pas conseillée pour un ennoiement inférieur à 0.7 et supérieur à 0.9",
-    "ERROR_ABSTRACT": "%nb% erreurs rencontrées lors du calcul",
+    "WARNING_ERRORS_ABSTRACT": "%nb% erreurs rencontrées lors du calcul",
     "ERROR_BIEF_Z1_CALC_FAILED": "Impossible de calculer la cote amont (calcul interrompu avant l'amont)",
     "ERROR_BIEF_Z2_CALC_FAILED": "Impossible de calculer la cote aval (calcul interrompu avant l'aval)",
     "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger",
@@ -15,6 +15,8 @@
     "ERROR_DICHO_TARGET_TOO_HIGH": "Dichotomie&nbsp;: la solution %targetSymbol%=%targetValue% est supérieure à la valeur maximale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_DICHO_TARGET_TOO_LOW": "Dichotomie&nbsp;: la solution %targetSymbol%=%targetValue% est inférieure à la valeur minimale calculable %targetSymbol%(%variableSymbol%=%variableExtremeValue%)=%extremeTarget%)",
     "ERROR_ELEVATION_ZI_LOWER_THAN_Z2": "La cote amont est plus basse que la cote aval",
+    "ERROR_IN_CALC_CHAIN": "Une erreur est survenue dans la chaîne de calcul",
+    "WARNING_ERROR_IN_CALC_CHAIN_STEPS": "Des erreurs sont survenues durant le calcul en chaîne",
     "ERROR_INTERVAL_OUTSIDE": "Intervalle&nbsp;: la valeur %value% est hors de l'intervalle %interval%",
     "ERROR_INTERVAL_UNDEF": "Interval&nbsp;: valeur 'undefined' incorrecte",
     "ERROR_INVALID_AT_POSITION": "Position %s :",
@@ -80,6 +82,8 @@
     "INFO_COURBEREMOUS_TITRE": "Courbes de remous",
     "INFO_DEVER_TITRE_COURT": "Déver. dénoyés",
     "INFO_DEVER_TITRE": "Lois de déversoirs dénoyés",
+    "INFO_DIAGRAM_SOLVEUR_FINDS": "trouve",
+    "INFO_DIAGRAM_SOLVEUR_READS": "lit",
     "INFO_DIAGRAM_TITLE": "Diagramme des modules de calcul",
     "INFO_DIAGRAM_DRAWING_ERROR": "Erreur lors du dessin du diagramme",
     "INFO_DIAGRAM_CALCULATED_PARAM": "paramètre calculé",
@@ -454,7 +458,7 @@
     "INFO_EXAMPLE_LABEL_PAB_COMPLETE": "Passe à bassins type",
     "INFO_EXAMPLES_TITLE": "Exemples",
     "INFO_EXAMPLES_SUBTITLE": "Charger des exemples types",
-    "WARNING_ABSTRACT": "%nb% avertissements rencontrés lors du calcul",
+    "WARNING_WARNINGS_ABSTRACT": "%nb% avertissements rencontrés lors du calcul",
     "WARNING_REMOUS_ARRET_CRITIQUE": "Arrêt du calcul&nbsp;: hauteur critique atteinte à l'abscisse %x%",
     "WARNING_STRUCTUREKIVI_HP_TROP_ELEVE": "h/p ne doit pas être supérieur à 2,5. h/p est forcé à 2,5",
     "WARNING_STRUCTUREKIVI_PELLE_TROP_FAIBLE": "La pelle du seuil doit mesurer au moins 0,1 m. Le coefficient béta est forcé à 0",
@@ -467,5 +471,6 @@
     "WARNING_DOWNSTREAM_BOTTOM_HIGHER_THAN_WATER": "La cote de l'eau à l'aval est plus basse ou égale à la cote de fond",
     "WARNING_YN_SECTION_PENTE_NEG_NULLE_HNORMALE_INF": "Hauteur normale: pente négative ou nulle, hauteur normale infinie",
     "WARNING_YN_SECTION_NON_CONVERGENCE_NEWTON_HNORMALE": "Hauteur normale: non convergence du calcul (méthode de Newton)",
-    "WARNING_SESSION_LOAD_NOTES_MERGED": "Les notes ont été fusionnées"
+    "WARNING_SESSION_LOAD_NOTES_MERGED": "Les notes ont été fusionnées",
+    "WARNING_VALUE_ROUNDED_TO_INTEGER": "La valeur de %symbol% a été arrondie à %rounded%"
 }
-- 
GitLab


From fd05a5a0c97baf3d31fd086939dc27bbfc83d1f1 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 21 Oct 2019 17:06:51 +0200
Subject: [PATCH 05/11] Matomo: do not track "calculator" page views anymore

---
 src/app/components/generic-calculator/calculator.component.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 365fa61e7..18b6f57aa 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -122,8 +122,6 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
         this.intlService = ServiceFactory.instance.i18nService;
         this.formulaireService = ServiceFactory.instance.formulaireService;
 
-        this.matomoTracker.trackPageView("calculator");
-
         // hotkeys listeners
         this.hotkeysService.add(new Hotkey("alt+w", AppComponent.onHotkey(this.closeCalculator, this)));
         this.hotkeysService.add(new Hotkey("alt+d", AppComponent.onHotkey(this.cloneCalculator, this)));
-- 
GitLab


From 8ae2133bfbb9d75cc64accc1f2cfda1c1ddda46b Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 21 Oct 2019 17:25:34 +0200
Subject: [PATCH 06/11] Update translations

---
 src/locale/messages.en.json | 2 +-
 src/locale/messages.fr.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 270145ca4..b6db3ee32 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -7,7 +7,7 @@
     "WARNING_ERRORS_ABSTRACT": "%nb% errors occurred during calculation",
     "ERROR_BIEF_Z1_CALC_FAILED": "Unable to calculate upstream elevation (calculation interrupted before upstream)",
     "ERROR_BIEF_Z2_CALC_FAILED": "Unable to calculate downstream elevation (calculation interrupted before downstream)",
-    "ERROR_DICHO_CONVERGE": "Dichotomy could not converge",
+    "ERROR_DICHO_CONVERGE": "Dichotomy could not converge. Last approximation: %lastApproximation%",
     "ERROR_DICHO_FUNCTION_VARIATION": "unable to determinate function direction of variation",
     "ERROR_DICHO_INIT_DOMAIN": "Dichotomy: target %targetSymbol%=%targetValue% does not exist for variable %variableSymbol% valued in interval %variableInterval%",
     "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomy (initial interval search): invalid null step growth",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index e04f71c07..f7dd7975f 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -7,7 +7,7 @@
     "WARNING_ERRORS_ABSTRACT": "%nb% erreurs rencontrées lors du calcul",
     "ERROR_BIEF_Z1_CALC_FAILED": "Impossible de calculer la cote amont (calcul interrompu avant l'amont)",
     "ERROR_BIEF_Z2_CALC_FAILED": "Impossible de calculer la cote aval (calcul interrompu avant l'aval)",
-    "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger",
+    "ERROR_DICHO_CONVERGE": "La dichotomie n'a pas pu converger. Dernière approximation: %lastApproximation%",
     "ERROR_DICHO_FUNCTION_VARIATION": "Dichotomie&nbsp;: impossible de determiner le sens de  variation de la fonction",
     "ERROR_DICHO_INIT_DOMAIN": "Dichotomie&nbsp;: la valeur cible %targetSymbol%=%targetValue% n'existe pas pour la variable %variableSymbol% prise dans l'intervalle %variableInterval%",
     "ERROR_DICHO_INVALID_STEP_GROWTH": "Dichotomie&nbsp;: l'augmentation du pas pour la recherche de l'intervalle de départ est incorrecte (=0)",
-- 
GitLab


From 895a29f79266994f54f0d83aff169d24a39f47b2 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 22 Oct 2019 10:19:45 +0200
Subject: [PATCH 07/11] Solveur: properly set targets initial values on session
 loading

---
 src/app/formulaire/fieldset.ts               | 22 --------------------
 src/app/formulaire/select-field-nub.ts       | 10 +++++++--
 src/app/formulaire/select-field-parameter.ts |  8 ++++++-
 src/app/formulaire/select-field-reference.ts | 10 ++++++++-
 src/app/formulaire/select-field.ts           |  7 +++++++
 5 files changed, 31 insertions(+), 26 deletions(-)

diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index e892c0c2c..068116458 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -288,28 +288,6 @@ export class FieldSet extends FormulaireElement implements Observer {
             case "fs_water_line": // Bief
                 this.setSelectValueFromProperty("select_regime", "regime");
                 break;
-
-            /* case "fs_target": // Solveur
-                this.setSelectValueFromProperty("select_target_nub", "nubToCalculate");
-                break;
-
-            case "fs_searched": // Solveur
-                const selectField: SelectField = this.getFormulaireNodeById("select_searched_param") as SelectField;
-                if (selectField) {
-                    const nub = this.parentForm.currentNub as Solveur;
-                    const X: ParamDefinition = nub.searchedParameter;
-                    if (X !== undefined) {
-                        const selectElement = selectField.getSelectedEntryFromValue(X);
-                        try {
-                            selectField.setValue(selectElement);
-                        } catch (e) {
-                            console.error(
-                                `fieldset.updateFields(): cannot set ${X.parentNub.uid}/${X.symbol} on <select> "select_searched_param"`
-                            );
-                        }
-                    }
-                }
-                break; */
         }
     }
 
diff --git a/src/app/formulaire/select-field-nub.ts b/src/app/formulaire/select-field-nub.ts
index 345699fc1..359addcd7 100644
--- a/src/app/formulaire/select-field-nub.ts
+++ b/src/app/formulaire/select-field-nub.ts
@@ -3,14 +3,20 @@ import { SelectEntry } from "./select-entry";
 import { ServiceFactory } from "../services/service-factory";
 import { decodeHtml } from "../util";
 
-import { Session } from "jalhyd";
+import { Session, Solveur } from "jalhyd";
 
 /**
  * A select field that populates itself with references to Nubs
  */
 export class SelectFieldNub extends SelectFieldReference {
 
-    protected initSelectedValue() {}
+    protected initSelectedValue() {
+        const nub = this.parentForm.currentNub;
+        if (nub instanceof Solveur) {
+            const ntc = nub.nubToCalculate;
+            this.setValueFromId(this._entriesBaseId + ntc.uid);
+        }
+    }
 
     /**
      * Populates entries with available references
diff --git a/src/app/formulaire/select-field-parameter.ts b/src/app/formulaire/select-field-parameter.ts
index d84ad50ac..7ac211949 100644
--- a/src/app/formulaire/select-field-parameter.ts
+++ b/src/app/formulaire/select-field-parameter.ts
@@ -10,7 +10,13 @@ import { Nub, Solveur } from "jalhyd";
  */
 export class SelectFieldParameter extends SelectFieldReference {
 
-    protected initSelectedValue() {}
+    protected initSelectedValue() {
+        const nub = this.parentForm.currentNub;
+        if (nub instanceof Solveur) {
+            const sp = nub.searchedParameter;
+            this.setValueFromId(this._entriesBaseId + sp.nubUid + "_" + sp.symbol);
+        }
+    }
 
     /**
      * Populates entries with available references
diff --git a/src/app/formulaire/select-field-reference.ts b/src/app/formulaire/select-field-reference.ts
index a84b272ea..2d715850a 100644
--- a/src/app/formulaire/select-field-reference.ts
+++ b/src/app/formulaire/select-field-reference.ts
@@ -14,7 +14,6 @@ export abstract class SelectFieldReference extends SelectField {
     constructor(parent: FormulaireNode, source: string) {
         super(parent);
         this._source = source;
-        this.initSelectedValue();
     }
 
     protected abstract initSelectedValue();
@@ -24,6 +23,15 @@ export abstract class SelectFieldReference extends SelectField {
      */
     protected abstract populate();
 
+    /**
+     * Once config is parsed, init original value from model
+     * (needs config, for this._entriesBaseId to be set)
+     */
+    protected afterParseConfig() {
+        this.populate();
+        this.initSelectedValue();
+    }
+
     /**
      * Reloads available entries, trying to keep the current selected
      * value; does not notify observers if value did not change
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index 66545a058..b2ea54a8c 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -77,6 +77,11 @@ export class SelectField extends Field {
      */
     protected populate() { }
 
+    /**
+     * Triggered at the end of parseConfig()
+     */
+    protected afterParseConfig() { }
+
     public getSelectedEntryFromValue(val: any): SelectEntry {
         for (const se of this._entries) {
             if (se.value === val) {
@@ -194,5 +199,7 @@ export class SelectField extends Field {
                 this.addEntry(new SelectEntry(this._entriesBaseId + BiefRegime.Torrentiel, BiefRegime.Torrentiel));
                 break;
         }
+
+        this.afterParseConfig();
     }
 }
-- 
GitLab


From bc837da2962e3115beaa4327e1b2f55125ba5b82 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 22 Oct 2019 11:50:35 +0200
Subject: [PATCH 08/11] Solveur: fix select update mechanism

---
 .../definition/concrete/form-solveur.ts       | 14 +++---
 src/app/formulaire/select-field-nub.ts        |  4 +-
 src/app/formulaire/select-field-parameter.ts  |  4 +-
 src/app/formulaire/select-field-reference.ts  | 45 +++++++++++--------
 4 files changed, 38 insertions(+), 29 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-solveur.ts b/src/app/formulaire/definition/concrete/form-solveur.ts
index e04fc1665..e472d035f 100644
--- a/src/app/formulaire/definition/concrete/form-solveur.ts
+++ b/src/app/formulaire/definition/concrete/form-solveur.ts
@@ -28,12 +28,16 @@ export class FormulaireSolveur extends FormulaireBase {
             const sel = this.getFormulaireNodeById(this._targetNubSelectId);
             if (sel) {
                 sel.addObserver(this);
+                // force 1st observation
+                (sel as SelectFieldNub).notifySelectValueChanged();
             }
         }
         if (this._searchedParamSelectId) {
             const sel = this.getFormulaireNodeById(this._searchedParamSelectId);
             if (sel) {
                 sel.addObserver(this);
+                // force 1st observation
+                (sel as SelectFieldNub).notifySelectValueChanged();
             }
         }
 
@@ -41,12 +45,11 @@ export class FormulaireSolveur extends FormulaireBase {
 
     private debugState() {
         const sol = this._currentNub as Solveur;
-        const ntc = sol.nubToCalculate;
         const spm = sol.searchedParameter;
         console.log(
-            `ETAT:\n X.singleValue=${sol.prms.X.singleValue}\n Y.singleValue=${sol.prms.Y.singleValue}`
+            `ETAT:\n X.singleValue=${sol.prms.X ? sol.prms.X.singleValue : "UNDEF"}\n Y.singleValue=${sol.prms.Y.singleValue}`
             + `\n Xinit.singleValue=${sol.prms.Xinit.singleValue}\n Ytarget.singleValue=${sol.prms.Ytarget.singleValue}`
-            + `\n searchedParam.singleValue=${spm.singleValue}`
+            + `\n searchedParam.singleValue=${spm ? spm.singleValue : "UNDEF"}`
         );
     }
 
@@ -57,7 +60,6 @@ export class FormulaireSolveur extends FormulaireBase {
         if (sender instanceof SelectFieldNub) {
             if (data.action === "select") {
                 // update Solveur property: Nub to calculate
-                console.log("(i) update Nub to calculate");
                 try {
                     // if searchedParam is set to a value that won't be available anymore
                     // once nubToCalculate is updated, setPropValue throws an error, but
@@ -67,9 +69,7 @@ export class FormulaireSolveur extends FormulaireBase {
                 // refresh parameters selector
                 const sel = this.getFormulaireNodeById(this._searchedParamSelectId) as SelectFieldParameter;
                 if (sel) {
-                    console.log("(ii) update Parameters entries");
                     sel.updateEntries();
-                    this.debugState();
                     // reflect changes in GUI
                     const inputYtarget = this.getFormulaireNodeById("Ytarget") as NgParameter;
                     inputYtarget.notifyValueModified(this);
@@ -79,13 +79,11 @@ export class FormulaireSolveur extends FormulaireBase {
         if (sender instanceof SelectFieldParameter) {
             if (data.action === "select") {
                 // update Solveur property: searched Parameter
-                console.log("(i) update searched Parameter");
                 const p: ParamDefinition = data.value.value;
                 this._currentNub.properties.setPropValue(
                     "searchedParameter",
                     p.nubUid + "/" + p.symbol
                 );
-                this.debugState();
                 // reflect changes in GUI
                 const inputXinit = this.getFormulaireNodeById("Xinit") as NgParameter;
                 inputXinit.notifyValueModified(this);
diff --git a/src/app/formulaire/select-field-nub.ts b/src/app/formulaire/select-field-nub.ts
index 359addcd7..b19492cc0 100644
--- a/src/app/formulaire/select-field-nub.ts
+++ b/src/app/formulaire/select-field-nub.ts
@@ -14,7 +14,9 @@ export class SelectFieldNub extends SelectFieldReference {
         const nub = this.parentForm.currentNub;
         if (nub instanceof Solveur) {
             const ntc = nub.nubToCalculate;
-            this.setValueFromId(this._entriesBaseId + ntc.uid);
+            if (ntc !== undefined) {
+                this.setValueFromId(this._entriesBaseId + ntc.uid);
+            }
         }
     }
 
diff --git a/src/app/formulaire/select-field-parameter.ts b/src/app/formulaire/select-field-parameter.ts
index 7ac211949..b979a98f7 100644
--- a/src/app/formulaire/select-field-parameter.ts
+++ b/src/app/formulaire/select-field-parameter.ts
@@ -14,7 +14,9 @@ export class SelectFieldParameter extends SelectFieldReference {
         const nub = this.parentForm.currentNub;
         if (nub instanceof Solveur) {
             const sp = nub.searchedParameter;
-            this.setValueFromId(this._entriesBaseId + sp.nubUid + "_" + sp.symbol);
+            if (sp !== undefined) {
+                this.setValueFromId(this._entriesBaseId + sp.nubUid + "_" + sp.symbol);
+            }
         }
     }
 
diff --git a/src/app/formulaire/select-field-reference.ts b/src/app/formulaire/select-field-reference.ts
index 2d715850a..3a0d8396c 100644
--- a/src/app/formulaire/select-field-reference.ts
+++ b/src/app/formulaire/select-field-reference.ts
@@ -46,10 +46,13 @@ export abstract class SelectFieldReference extends SelectField {
         // keep previously selected entry if possible
         if (pse && pse.id) {
             this.setValueFromId(pse.id);
-        }
-        // if no entry is available anymore, unset value
-        if (this.entries.length === 0) {
-            super.setValue(undefined);
+        } else {
+            // if no entry is available anymore, unset value
+            if (this.entries.length === 0) {
+                super.setValue(undefined);
+            } else {
+                this.setDefaultValue();
+            }
         }
     }
 
@@ -64,14 +67,17 @@ export abstract class SelectFieldReference extends SelectField {
             ! previousSelectedEntry
             || (previousSelectedEntry.id !== v.id)
         ) {
-            console.log(`--> select, setValue: ${v.value}`);
-            this.notifyObservers({
-                "action": "select",
-                "value": v
-            }, this);
+            this.notifySelectValueChanged();
         }
     }
 
+    public notifySelectValueChanged() {
+        this.notifyObservers({
+            "action": "select",
+            "value": this._selectedEntry
+        }, this);
+    }
+
     /**
      * Sets value from given ID; if it was not found, sets the
      * first available entry as selectedValue
@@ -85,16 +91,17 @@ export abstract class SelectFieldReference extends SelectField {
             }
         }
         if (! found) {
-            // default to first available entry if any
-            if (this._entries.length > 0) {
-                this.setValue(this._entries[0]);
-            } else {
-                // notify observers that no value is selected anymore
-                this.notifyObservers({
-                    "action": "select",
-                    "value": undefined
-                }, this);
-            }
+            this.setDefaultValue();
+        }
+    }
+
+    protected setDefaultValue() {
+        // default to first available entry if any
+        if (this._entries.length > 0) {
+            this.setValue(this._entries[0]);
+        } else {
+            // notify observers that no value is selected anymore
+            this.notifySelectValueChanged();
         }
     }
 }
-- 
GitLab


From 8682553e7bbbba56f9e94f6e7ec565d03228edea Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 22 Oct 2019 12:02:53 +0200
Subject: [PATCH 09/11] Fix bug in Solveur init

---
 .../formulaire/definition/concrete/form-solveur.ts   | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-solveur.ts b/src/app/formulaire/definition/concrete/form-solveur.ts
index e472d035f..231f815b0 100644
--- a/src/app/formulaire/definition/concrete/form-solveur.ts
+++ b/src/app/formulaire/definition/concrete/form-solveur.ts
@@ -79,11 +79,13 @@ export class FormulaireSolveur extends FormulaireBase {
         if (sender instanceof SelectFieldParameter) {
             if (data.action === "select") {
                 // update Solveur property: searched Parameter
-                const p: ParamDefinition = data.value.value;
-                this._currentNub.properties.setPropValue(
-                    "searchedParameter",
-                    p.nubUid + "/" + p.symbol
-                );
+                try {
+                    const p: ParamDefinition = data.value.value;
+                    this._currentNub.properties.setPropValue(
+                        "searchedParameter",
+                        p.nubUid + "/" + p.symbol
+                    );
+                } catch (e) { }
                 // reflect changes in GUI
                 const inputXinit = this.getFormulaireNodeById("Xinit") as NgParameter;
                 inputXinit.notifyValueModified(this);
-- 
GitLab


From 160f906368f403659070c9235359de34ef2d0d9b Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 22 Oct 2019 12:11:15 +0200
Subject: [PATCH 10/11] Updated e2e for Solveur compliance

---
 e2e/calculate-all-params.e2e-spec.ts |  7 ++++++-
 e2e/check-translations.e2e-spec.ts   | 15 ++++++++-------
 e2e/clone-all-calc.e2e-spec.ts       |  7 ++++++-
 3 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts
index 6f5632baf..9533c2fca 100644
--- a/e2e/calculate-all-params.e2e-spec.ts
+++ b/e2e/calculate-all-params.e2e-spec.ts
@@ -18,7 +18,12 @@ describe("ngHyd − calculate all parameters of all calculators", () => {
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21 ];
+  const calcTypes = [
+    0, 1, 2, 3, 4, 5, 6, 8, 9, 10,
+    11, 12, 13, 15, 17, 18, 19, 20,
+    21,
+    // 22 - Solveur is not calculated here because it is not independent
+  ];
 
   // for each calculator
   for (const ct of calcTypes) {
diff --git a/e2e/check-translations.e2e-spec.ts b/e2e/check-translations.e2e-spec.ts
index 2aef4ff34..b0c10f970 100644
--- a/e2e/check-translations.e2e-spec.ts
+++ b/e2e/check-translations.e2e-spec.ts
@@ -25,7 +25,7 @@ describe("ngHyd − check translation of all calculators", () => {
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21 ];
+  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21, 22 ];
 
   // options of "Language" selector on preferences page
   const langs = [ "English", "Français" ];
@@ -67,12 +67,13 @@ describe("ngHyd − check translation of all calculators", () => {
           // check that "compute" button is active
           const calcButton = calcPage.getCalculateButton();
           const disabledState = await calcButton.getAttribute("disabled");
-          expect(disabledState).not.toBe("true");
-          // click "compute" button
-          await calcButton.click();
-          // check that result is not empty
-          const hasResults = await calcPage.hasResults();
-          expect(hasResults).toBe(true);
+          if (! disabledState) {
+            // click "compute" button
+            await calcButton.click();
+            // check that result is not empty
+            const hasResults = await calcPage.hasResults();
+            expect(hasResults).toBe(true);
+          }
 
           // check absence of "*** message not found" in whole DOM
           expect(await browser.getPageSource()).not.toContain("*** message not found");
diff --git a/e2e/clone-all-calc.e2e-spec.ts b/e2e/clone-all-calc.e2e-spec.ts
index e5b77dcd1..8c32c1ff6 100644
--- a/e2e/clone-all-calc.e2e-spec.ts
+++ b/e2e/clone-all-calc.e2e-spec.ts
@@ -18,7 +18,12 @@ describe("ngHyd − clone all calculators with all possible <select> values", ()
   });
 
   // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/
-  const calcTypes = [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 17, 18, 19, 20, 21 ];
+  const calcTypes = [
+    0, 1, 2, 3, 4, 5, 6, 8, 9, 10,
+    11, 12, 13, 15, 17, 18, 19, 20,
+    21,
+    // 22 - Solveur is not cloned here because it is not independent
+  ];
 
   // for each calculator
   for (const ct of calcTypes) {
-- 
GitLab


From dea8594cc81d33396855e90a796b0c1d159a7eec Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 22 Oct 2019 14:56:09 +0200
Subject: [PATCH 11/11] Add e2e for Solveur

---
 e2e/session/session-solveur-chutes.json | 124 ++++++++++++++++++++++
 e2e/solveur.e2e-spec.ts                 | 135 ++++++++++++++++++++++++
 2 files changed, 259 insertions(+)
 create mode 100644 e2e/session/session-solveur-chutes.json
 create mode 100644 e2e/solveur.e2e-spec.ts

diff --git a/e2e/session/session-solveur-chutes.json b/e2e/session/session-solveur-chutes.json
new file mode 100644
index 000000000..3142108ec
--- /dev/null
+++ b/e2e/session/session-solveur-chutes.json
@@ -0,0 +1,124 @@
+{
+    "header": {
+        "source": "jalhyd",
+        "format_version": "1.3",
+        "created": "2019-10-22T08:08:13.816Z"
+    },
+    "settings": {
+        "precision": 0.0001,
+        "maxIterations": 100,
+        "displayPrecision": 3
+    },
+    "documentation": "",
+    "session": [
+        {
+            "uid": "NjRvcG",
+            "props": {
+                "calcType": "PabChute"
+            },
+            "meta": {
+                "title": "PAB : chute"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "Z1",
+                    "mode": "SINGLE",
+                    "value": 2.1513761467889907
+                },
+                {
+                    "symbol": "Z2",
+                    "mode": "SINGLE",
+                    "value": 0.8669724770642202
+                },
+                {
+                    "symbol": "DH",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "dnV4bD",
+            "props": {
+                "calcType": "PabNombre"
+            },
+            "meta": {
+                "title": "PAB : nombre"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "DHT",
+                    "mode": "LINK",
+                    "targetNub": "NjRvcG",
+                    "targetParam": "DH"
+                },
+                {
+                    "symbol": "N",
+                    "mode": "SINGLE",
+                    "value": 10
+                },
+                {
+                    "symbol": "DH",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "OHBpcz",
+            "props": {
+                "calcType": "PabPuissance"
+            },
+            "meta": {
+                "title": "PAB : puissance"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "DH",
+                    "mode": "LINK",
+                    "targetNub": "dnV4bD",
+                    "targetParam": "DH"
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 0.1
+                },
+                {
+                    "symbol": "V",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "PV",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "ODM0Z2",
+            "props": {
+                "calcType": "Solveur",
+                "nubToCalculate": "OHBpcz",
+                "searchedParameter": "NjRvcG/Z2"
+            },
+            "meta": {
+                "title": "Solveur"
+            },
+            "children": [],
+            "parameters": [
+                {
+                    "symbol": "Xinit",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "Ytarget",
+                    "mode": "SINGLE",
+                    "value": 252
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/e2e/solveur.e2e-spec.ts b/e2e/solveur.e2e-spec.ts
new file mode 100644
index 000000000..84b3df0e9
--- /dev/null
+++ b/e2e/solveur.e2e-spec.ts
@@ -0,0 +1,135 @@
+import { AppPage } from "./app.po";
+import { ListPage } from "./list.po";
+import { CalculatorPage } from "./calculator.po";
+import { Navbar } from "./navbar.po";
+import { browser } from "protractor";
+import { SideNav } from "./sidenav.po";
+
+/**
+ * Clone calculators
+ */
+describe("Solveur - ", () => {
+  let startPage: AppPage;
+  let listPage: ListPage;
+  let calcPage: CalculatorPage;
+  let navbar: Navbar;
+  let sidenav: SideNav;
+
+  beforeEach(() => {
+    startPage = new AppPage();
+    listPage = new ListPage();
+    calcPage = new CalculatorPage();
+    navbar = new Navbar();
+    sidenav = new SideNav();
+  });
+
+  it("load > calculate", async () => {
+    await startPage.navigateTo();
+
+    await navbar.clickMenuButton();
+    await browser.sleep(200);
+
+    await sidenav.clickLoadSessionButton();
+    await browser.sleep(200);
+
+    await sidenav.loadSessionFile("./session/session-solveur-chutes.json");
+    await browser.sleep(200);
+
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(4);
+    await navbar.clickCalculatorTab(3); // n°3 should be the latest
+
+    // check input values
+    expect(await calcPage.getInputById("Xinit").getAttribute("value")).toBe("0.5");
+    expect(await calcPage.getInputById("Ytarget").getAttribute("value")).toBe("252");
+    // check Nub to calculate
+    const ntc = calcPage.getSelectById("select_target_nub");
+    const ntcV = await calcPage.getSelectValueText(ntc);
+    expect(ntcV).toContain("PAB : puissance / Puissance dissipée (PV)");
+    // check searched Parameter
+    const sp = calcPage.getSelectById("select_searched_param");
+    const spV = await calcPage.getSelectValueText(sp);
+    expect(spV).toContain("Z2 - Cote aval (PAB : chute)");
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("true");
+    // click "compute" button
+    await calcButton.click();
+    // check that result is not empty
+    const hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(true);
+  });
+
+  it("create > feed > calculate > clone > calculate clone", async () => {
+    await startPage.navigateTo();
+
+    // 1. create empty Solveur
+    await listPage.clickMenuEntryForCalcType(22); // Solveur
+    await browser.sleep(500);
+
+    // 2. create PAB:Chute, PAB:Nombre and PAB:Puissance linked to one another
+    await navbar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(12); // PAB:Chute
+    await browser.sleep(500);
+    await navbar.clickNewCalculatorButton();
+
+    await listPage.clickMenuEntryForCalcType(13); // PAB:Nombre
+    await browser.sleep(500);
+    // link DHT to PAB:Chute.DH
+    const dht = calcPage.getInputById("DHT");
+    await calcPage.setParamMode(dht, "link");
+    // Calculate DH
+    const dh_nombre = calcPage.getInputById("DH");
+    await calcPage.setParamMode(dh_nombre, "cal");
+
+    await navbar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(6); // PAB:Puissance
+    await browser.sleep(500);
+    // link DH to PAB:Nombre.DH
+    const dh_puiss = calcPage.getInputById("DH");
+    await calcPage.setParamMode(dh_puiss, "link");
+
+    // Go back to Solveur
+    await navbar.clickCalculatorTab(0);
+
+    await calcPage.changeSelectValue(calcPage.getSelectById("select_target_nub"), 1); // "Puissance / PV"
+    await browser.sleep(500);
+    await calcPage.changeSelectValue(calcPage.getSelectById("select_searched_param"), 2); // "Chute / Z2"
+    await browser.sleep(500);
+    await calcPage.getInputById("Ytarget").sendKeys("318");
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("true");
+    // click "compute" button
+    await calcButton.click();
+    // check that result is not empty
+    const hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(true);
+
+    // otherwise clickCloneCalcButton() fails with "Element is not clickable at point"
+    await browser.executeScript("window.scrollTo(0, 0);");
+    await calcPage.clickCloneCalcButton();
+    await browser.sleep(500);
+
+    // 4. check existence of the cloned module
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(5);
+    await navbar.clickCalculatorTab(4); // n°4 should be the latest
+
+    // check that result is empty
+    const hasResultsClone1 = await calcPage.hasResults();
+    expect(hasResultsClone1).toBe(false);
+
+    // check that "compute" button is active
+    const calcButtonClone = calcPage.getCalculateButton();
+    const disabledStateClone = await calcButtonClone.getAttribute("disabled");
+    expect(disabledStateClone).not.toBe("true");
+    // click "compute" button
+    await calcButtonClone.click();
+    // check that result is not empty
+    const hasResultsClone2 = await calcPage.hasResults();
+    expect(hasResultsClone2).toBe(true);
+  });
+});
-- 
GitLab