From a58c7da008f8a197a56150f04b444006ec6c443a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20PENHO=C3=8BT?= Date: Thu, 21 Oct 2021 17:09:57 +0200 Subject: [PATCH] =?UTF-8?q?Nouvelle=20version=20classe=20des=20filtres/s?= =?UTF-8?q?=C3=A9lecteurs=20+=20revue=20des=20tests=20associ=C3=A9s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/Selector.ts | 181 +++++++++++++++++--------------- src/errors.js | 6 +- src/interfaces.ts | 13 +-- tests/fixtures.js | 15 +-- tests/selectorSpec.ts | 234 +++++++++++++++++++++++++----------------- 6 files changed, 256 insertions(+), 195 deletions(-) diff --git a/package.json b/package.json index 0c163c3..f0f9481 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freedatas2html", - "version": "0.8.9", + "version": "0.9.0", "description": "Conversion and display of data in different formats (CSV, JSON, HTML) with the possibility of filtering, classifying and paginating the results.", "main": "index.js", "scripts": { diff --git a/src/Selector.ts b/src/Selector.ts index 846978e..94739e6 100644 --- a/src/Selector.ts +++ b/src/Selector.ts @@ -1,40 +1,40 @@ -const { compare }= require('natural-orderby'); -const errors = require("./errors.js"); +const { compare }=require('natural-orderby'); +const errors=require("./errors.js"); import { DOMElement, Selectors } from "./interfaces"; import { FreeDatas2HTML } from "./freeDatas2HTML"; export class Selector implements Selectors { - _converter: FreeDatas2HTML; - _datasViewElt: DOMElement= { id:"", eltDOM:undefined }; // élément du DOM dans lequel afficher le "select" - _datasFieldNb: number; // numéro du champ dont les données serviront au filtre - _separator: string|undefined; // séparateur éventuel pour les données du champ - name: string = ""; // nom à afficher dans le DOM comme "label" du "select" - values: string[]=[]; // données proposées par le filtre, après traitement des données reçues + private _converter: FreeDatas2HTML; + private _datasFieldNb: number; + private _datasViewElt: DOMElement={ id: "", eltDOM: undefined }; + private _selectedValue: number|undefined=undefined; + private _separator: string|undefined; + private _values: string[]=[]; + private _name: string=""; - // Injection de la classe principale, mais uniquement si les données ont été importées - constructor(converter: FreeDatas2HTML, datasFieldNb: number, elt: DOMElement) + // Injection de la classe principale, mais uniquement si des données ont été importées + // Le champ duquel le sélecteur tire ses données doit exister ? + constructor(converter: FreeDatas2HTML, datasFieldNb: number, elt: DOMElement, separator?: string) { if(converter.fields === undefined || converter.datas.length === 0) - throw new Error(errors.selectorNeedDatas); + throw new Error(errors.filterNeedDatas); else if(! converter.checkFieldExist(Number(datasFieldNb))) throw new Error(errors.selectorFieldNotFound); else { - this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt); // provoque une erreur, si élement non trouvé dans DOM + this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt); this._converter=converter; this._datasFieldNb=datasFieldNb; + // Pas de trim(), car l'espace peut être le séparateur : + if(separator !== undefined && separator !== "") + this._separator=separator; } } - // Ignore un séparateur de données vide - // Attention : pas de trim(), car l'espace peut être un séparateur - set separator(separator: string|undefined) + get converter() : FreeDatas2HTML { - if(separator === "") - this._separator=undefined; - else - this._separator=separator; + return this._converter; } get datasViewElt() : DOMElement @@ -47,91 +47,102 @@ export class Selector implements Selectors return this._datasFieldNb; } + get name() : string + { + return this._name; + } + + get selectedValue() : number|undefined + { + return this._selectedValue; + } + get separator() : string|undefined { return this._separator; } - // Création du "; // l'option zéro permet d'actualiser l'affichage en ignorant ce filtre - for(let i=0; i< this.values.length; i++) - selectorsHTML+=""; - selectorsHTML+=""; - this. _datasViewElt.eltDOM.innerHTML=selectorsHTML; - const selectElement=document.getElementById("freeDatas2HTML_"+this._datasViewElt.id) as HTMLInputElement, mySelector=this; - selectElement.addEventListener("change", function(e) - { - mySelector._converter.refreshView(); - }); + // Création du "; // l'option zéro permet d'actualiser l'affichage en ignorant ce filtre + for(let i=0; i< this._values.length; i++) + selectorsHTML+=""; + selectorsHTML+=""; + this. _datasViewElt.eltDOM!.innerHTML=selectorsHTML;// "!" car l'existence de "eltDOM" est testé par le constructeur + + const selectElement=document.getElementById("freeDatas2HTML_"+this._datasViewElt.id) as HTMLInputElement, mySelector=this; + selectElement.addEventListener("change", function(e) + { + if(selectElement.value === "0") + mySelector._selectedValue=undefined; + else + mySelector._selectedValue=parseInt(selectElement.value,10)-1; + mySelector._converter.refreshView(); + }); + } } - // Vérifie sur l'enregistrement passé correspond à la valeur sélectionnée par l'utilisateur dans le filtre - public dataIsOk(data: any) : boolean + public dataIsOk(data: {[index: string]:string}) : boolean { - if(this.name === undefined || this.name === "") - throw new Error(errors.selectorCheckIsOkFail); - - let selectedValue = this.getSelectionnedId(); - if(selectedValue === 0) // = pas de valeur sélectionnée = pas de filtre sur ce champ + // Permet de vérifier que filter2HTML() a été préalablement appelée : + if(this._name === "") + throw new Error(errors.filterCheckIsOkFail); + + // Pas de valeur sélectionnée = pas de filtre sur ce champ + if(this._selectedValue === undefined) return true; - else - selectedValue=selectedValue-1; - if(this.values[selectedValue] === undefined) + if(this._values[this._selectedValue] === undefined) // théoriquement impossible, mais cela vient du client... throw new Error(errors.selectorSelectedIndexNotFound); - if(data[this.name] === undefined) // champ absent pour cet enregistrement, qui est donc refusé + // Si le champ est absent pour un enregistrement, il est refusé + if(data[this._name] === undefined) return false; - const selectedValueTxt=this.values[selectedValue] ; + const selectedValueTxt=this._values[this._selectedValue]; if(this._separator === undefined) { - if(data[this.name] !== selectedValueTxt) + if(data[this._name].trim() !== selectedValueTxt) return false; else return true; @@ -139,10 +150,10 @@ export class Selector implements Selectors else { let find=false; - let checkedValues=data[this.name].split(this._separator); + let checkedValues=data[this._name].split(this._separator); for(let value of checkedValues) { - if(value === selectedValueTxt) + if(value.trim() === selectedValueTxt) { find=true; break; diff --git a/src/errors.js b/src/errors.js index ad208c7..e4a695d 100644 --- a/src/errors.js +++ b/src/errors.js @@ -4,6 +4,8 @@ module.exports = converterFieldNotFound : "Le champ n'existe pas dans les données ou les données n'ont pas encore été chargées.", converterNeedDatasElt: "Merci de fournir un id valide pour l'élément où afficher les données.", converterRefreshFail: "Le nom des champs et l'élement du DOM receveur sont nécessaires à l'affichage des données.", + filterCheckIsOkFail: "Le test est lancé sur un filtre incorrectement initialisé ou sur un attribut absent de la donnée à tester.", + filterNeedDatas: "Le création d'un filtre nécessite la présence des données à filtrer.", pagination2HTMLFail : "Toutes les donnée nécessaires à la création des sélecteurs de pagination n'ont pas été fournies.", paginationNeedByfaultValueBeInOptions: "La valeur de pagination par défaut doit faire partie des options proposées.", paginationNeedDatas: "Il ne peut y avoir de pagination, si les données n'ont pas été récupérées.", @@ -28,11 +30,9 @@ module.exports = remoteSourceUrlFail: "L'url fournie ne semble pas valide.", renderNeedFields: "Les noms de champs doivent être fournis avant de demander l'affichage des données.", selector2HTMLFail: "Le création d'un filtre dans le DOM nécessite l'initialisation de l'élément HTML et du numéro du champs à filter.", - selectorCheckIsOkFail: "Le test est lancé sur un filtre incorrectement initialisé ou sur un attribut absent de la donnée à tester.", + selectorFieldIsEmpty: "Aucune donnée trouvée pour le champ du filtre", selectorFieldNotFound: "Au moins un des champs devant servir à filtrer les données n'existe pas dans le fichier.", - selectorNeedDatas: "Le création d'un filtre nécessite la transmission des données à filtrer.", selectorSelectedIndexNotFound: "La valeur sélectionnée n'a pas été trouvée dans la liste des champs.", - sortingField2HTMLFail: "Toutes les donnée nécessaires à la création du lien de classement n'ont pas été fournies.", sortingFieldNeedDatas: "Le création d'un champ de classement nécessite la transmission de la liste des champs.", sortingFieldNotFound: "Au moins un des champs devant permettre de classer les données n'existe pas dans le fichier.", sortingFieldsNbFail: "Le nombre de champs trouvés dans le DOM ne correspond pas à celui des données à classer.", diff --git a/src/interfaces.ts b/src/interfaces.ts index 6400069..4fec8a4 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -31,7 +31,7 @@ export interface Filters { datasViewElt: DOMElement; filter2HTML() : void; - dataIsOk(data: any) : boolean; + dataIsOk(data: {[index: string]:string}) : boolean; } export interface Paginations { @@ -54,10 +54,6 @@ export interface PaginationsPages values?: number[]; selectedValue?: number; } -export interface Datas -{ - [key: string]: string; -} export interface ParseErrors { code?: string; @@ -93,9 +89,10 @@ export interface RemoteSources extends RemoteSourceSettings export interface Selectors extends Filters { datasFieldNb: number; - separator?: string|undefined; - name?: string; - values?: string[]; + name: string; + selectedValue: number|undefined; + separator: string|undefined; + values: string[]; } export interface SortingFields { diff --git a/tests/fixtures.js b/tests/fixtures.js index edb7daa..8a2d594 100644 --- a/tests/fixtures.js +++ b/tests/fixtures.js @@ -1,12 +1,15 @@ module.exports = { datasViewEltHTML: '
', - selector1HTML: '', - selector2HTML: '', - selector1HTMLWithSeparator: '', - selector1HTMLWithFunction: '', - sortingColumn1HTML: 'Z (numéro atomique)', - sortingColumn2HTML: 'Symbole', + selector1HTML: '', + selector2HTML: '', + selector2HTMLWithLabel: '', + selector1HTMLWithSeparator: '', + selectorHTMLWithTrim: '', + selector1HTMLWithFunction: '', + selectorHTMLWithFakeItem: '', + sortingColumn1HTML: 'Z (numéro atomique)', + sortingColumn2HTML: 'Symbole', selectorForPagination: '', selectorFor2Pages: '', selectorForManyPages: '', diff --git a/tests/selectorSpec.ts b/tests/selectorSpec.ts index 97fba22..ea18a4a 100644 --- a/tests/selectorSpec.ts +++ b/tests/selectorSpec.ts @@ -1,12 +1,12 @@ import { FreeDatas2HTML, Selector } from "../src/freeDatas2HTML"; - const errors=require("../src/errors.js"); const fixtures=require("./fixtures.js"); -describe("Test des filtres de données", () => +describe("Test des sélecteurs de données", () => { let converter: FreeDatas2HTML; let selector: Selector; + let selectElement : HTMLInputElement; beforeEach( async () => { @@ -22,13 +22,18 @@ describe("Test des filtres de données", () => document.body.removeChild(document.getElementById("fixture")); }); - describe("Test des données reçues pour configurer un filtre.", () => + describe("Test des données de configuration.", () => { - it("Doit générer une erreur, si initialisé sans fournir la liste des champs servant à classer les données.", () => + it("Doit générer une erreur, si initialisé sans avoir au préalable charger des données.", async () => { + // Convertisseur non lancé : converter=new FreeDatas2HTML("CSV"); - converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); - expect(() => { return new Selector(converter, 0, { id:"selector1" }); }).toThrowError(errors.selectorNeedDatas); + expect(() => { return new Selector(converter, 0, { id:"selector1" }); }).toThrowError(errors.filterNeedDatas); + // Note : les parseurs vont générer une erreur en amont s'ils ne trouvent pas de noms de champs. + // Par contre, ils acceptent de ne pas trouver de données : + converter.parser.datas2Parse="Z (numéro atomique),Élément,Symbole,Famille,Abondance des éléments dans la croûte terrestre (μg/k)"; + await converter.run(); + expect(() => { return new Selector(converter, 0, { id:"selector1" }); }).toThrowError(errors.filterNeedDatas); }); it("Doit générer une erreur, si le numéro de champ fourni n'existe pas dans les données.", () => @@ -40,24 +45,20 @@ describe("Test des filtres de données", () => it("Si un séparateur vide est fourni pour un filtre, il doit être ignoré.", () => { - selector=new Selector(converter, 0, { id:"selector1" }); - selector.separator=""; + selector=new Selector(converter, 0, { id:"selector1" }, ""); expect(selector.separator).toBeUndefined(); }); it("Si toutes les paramètres sont correctes, ils doivent être acceptés.", () => { - const elt=document.getElementById("selector1"); - const selector=new Selector(converter, 2, { id:"selector1" }); - selector.separator=","; - expect(selector.datasFieldNb).toEqual(2); - expect(selector.datasViewElt).toEqual({ id:"selector1", eltDOM:elt }); - expect(selector.separator).toEqual(","); + expect(() => { selector=new Selector(converter, 2, { id:"selector1" }, ","); return true; }).not.toThrowError(); + expect(selector.datasFieldNb).toEqual(2); + expect(selector.datasViewElt).toEqual({ id:"selector1", eltDOM:document.getElementById("selector1") }); + expect(selector.separator).toEqual(","); }); - }); - describe("Création et action des sélecteurs permettant de filter les données affichées.", () => + describe("Création des sélecteurs.", () => { beforeEach( async () => { @@ -73,22 +74,34 @@ describe("Test des filtres de données", () => expect(document.getElementById("selector2").innerHTML).toEqual(fixtures.selector2HTML); }); + it("Doit prendre en compte l'éventuel label fourni pour le SELECT.", () => + { + selector=new Selector(converter, 4, { id:"selector2" }); + selector.filter2HTML("Abondance des éléments"); + expect(document.getElementById("selector2").innerHTML).toEqual(fixtures.selector2HTMLWithLabel); + }); + it("Si des valeurs vides sont présentes dans une champ utilisé pour un sélecteur, elles doivent être ignorées.", async () => { - converter=new FreeDatas2HTML("CSV"); - converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1-emtyinfield.csv" }); - await converter.run(); + selector.converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1-emtyinfield.csv" }); + await selector.converter.run(); selector.filter2HTML(); expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selector1HTML); }); + + it("Si des espaces entourent certaines valeurs pour ce champ, ils doivent être supprimés.", async () => + { + selector.converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datasNeedTrim.csv" }); + await selector.converter.run(); + selector.filter2HTML(); + expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selectorHTMLWithTrim); + }); it("Si un séparateur est fourni, les valeurs distinctes extraites de ce champ doivent le prendre en compte.", async () => { - converter=new FreeDatas2HTML("CSV"); converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1+tagsfield.csv" }); await converter.run(); - selector=new Selector(converter, 5, { id:"selector1" }); - selector.separator="|"; + selector=new Selector(converter, 5, { id:"selector1" }, "|"); selector.filter2HTML(); expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selector1HTMLWithSeparator); }); @@ -109,93 +122,130 @@ describe("Test des filtres de données", () => }; converter.datasSortingFunctions=[{ datasFieldNb: 4, sort:mySort }]; selector=new Selector(converter, 4, { id:"selector1" }); - selector.separator="|"; - selector.filter2HTML(); + selector.filter2HTML(); expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selector1HTMLWithFunction); }); - it("Doit générer une erreur si une donnée est testée pour un sélecteur non correctement initialisé.", () => + it("Doit générer une erreur, si aucune donnée n'a été trouvée pour créer le