Nouvelle version classe des filtres/sélecteurs + revue des tests associés.

This commit is contained in:
Fabrice PENHOËT 2021-10-21 17:09:57 +02:00
parent 8a129d169b
commit a58c7da008
6 changed files with 256 additions and 195 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "freedatas2html", "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.", "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", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,40 +1,40 @@
const { compare }= require('natural-orderby'); const { compare }=require('natural-orderby');
const errors = require("./errors.js"); const errors=require("./errors.js");
import { DOMElement, Selectors } from "./interfaces"; import { DOMElement, Selectors } from "./interfaces";
import { FreeDatas2HTML } from "./freeDatas2HTML"; import { FreeDatas2HTML } from "./freeDatas2HTML";
export class Selector implements Selectors export class Selector implements Selectors
{ {
_converter: FreeDatas2HTML; private _converter: FreeDatas2HTML;
_datasViewElt: DOMElement= { id:"", eltDOM:undefined }; // élément du DOM dans lequel afficher le "select" private _datasFieldNb: number;
_datasFieldNb: number; // numéro du champ dont les données serviront au filtre private _datasViewElt: DOMElement={ id: "", eltDOM: undefined };
_separator: string|undefined; // séparateur éventuel pour les données du champ private _selectedValue: number|undefined=undefined;
name: string = ""; // nom à afficher dans le DOM comme "label" du "select" private _separator: string|undefined;
values: string[]=[]; // données proposées par le filtre, après traitement des données reçues private _values: string[]=[];
private _name: string="";
// Injection de la classe principale, mais uniquement si les données ont été importées // Injection de la classe principale, mais uniquement si des données ont été importées
constructor(converter: FreeDatas2HTML, datasFieldNb: number, elt: DOMElement) // 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) if(converter.fields === undefined || converter.datas.length === 0)
throw new Error(errors.selectorNeedDatas); throw new Error(errors.filterNeedDatas);
else if(! converter.checkFieldExist(Number(datasFieldNb))) else if(! converter.checkFieldExist(Number(datasFieldNb)))
throw new Error(errors.selectorFieldNotFound); throw new Error(errors.selectorFieldNotFound);
else else
{ {
this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt); // provoque une erreur, si élement non trouvé dans DOM this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt);
this._converter=converter; this._converter=converter;
this._datasFieldNb=datasFieldNb; 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 get converter() : FreeDatas2HTML
// Attention : pas de trim(), car l'espace peut être un séparateur
set separator(separator: string|undefined)
{ {
if(separator === "") return this._converter;
this._separator=undefined;
else
this._separator=separator;
} }
get datasViewElt() : DOMElement get datasViewElt() : DOMElement
@ -47,91 +47,102 @@ export class Selector implements Selectors
return this._datasFieldNb; return this._datasFieldNb;
} }
get name() : string
{
return this._name;
}
get selectedValue() : number|undefined
{
return this._selectedValue;
}
get separator() : string|undefined get separator() : string|undefined
{ {
return this._separator; return this._separator;
} }
// Création du <select> dans le HTML correspondant au filtre get values(): string[]
public filter2HTML() : void
{ {
if(this._converter === undefined || this._datasViewElt.eltDOM === undefined || this._datasFieldNb === undefined) return this._values;
throw new Error(errors.filter2HTMLFail); }
else
{
this.name=this._converter.fields![this._datasFieldNb]; // this._converter.parse... ne peuvent être indéfinis si this._converter existe (cf constructeur)
for (let row of this._converter.datas)
{
if(this._separator === undefined)
{
if(row[this.name] !== "" && this.values.indexOf(row[this.name]) === -1)
this.values.push(row[this.name]);
}
else
{
let checkedValues=row[this.name].split(this._separator);
for(let value of checkedValues)
{
if(value !== "" && this.values.indexOf(value) === -1)
this.values.push(value);
}
}
}
if(this.values.length > 0)
{
// Classement des données à l'aide (ou non) d'une fonction spécifique :
if(this._converter.getSortingFunctionForField(this._datasFieldNb) !== undefined)
this.values.sort(this._converter.getSortingFunctionForField(this._datasFieldNb)!.sort); // sans le "!" : TS2532: Object is possibly 'undefined' ??
else
this.values.sort(compare());
// Création et injection du SELECT dans le DOM // Création du <select> dans le DOM correspondant au filtre
let selectorsHTML="<label for='freeDatas2HTML_"+this._datasViewElt.id+"'>"+this.name+" : </label><select name='freeDatas2HTML_"+this._datasViewElt.id+"' id='freeDatas2HTML_"+this._datasViewElt.id+"'><option value='0'>----</option>"; // l'option zéro permet d'actualiser l'affichage en ignorant ce filtre public filter2HTML(label:string="") : void
for(let i=0; i< this.values.length; i++) {
selectorsHTML+="<option value='"+(i+1)+"'>"+this.values[i]+"</option>"; this._name=this._converter.fields![this._datasFieldNb]; // "!" car l'existence du champ est testé par le constructeur
selectorsHTML+="</select>"; for (let row of this._converter.datas)
this. _datasViewElt.eltDOM.innerHTML=selectorsHTML; {
const selectElement=document.getElementById("freeDatas2HTML_"+this._datasViewElt.id) as HTMLInputElement, mySelector=this; let checkedValue;
selectElement.addEventListener("change", function(e) if(this._separator === undefined)
{ {
mySelector._converter.refreshView(); checkedValue=row[this._name].trim(); // trim() évite des problèmes de classement des éléments du SELECT
}); if(checkedValue !== "" && this._values.indexOf(checkedValue) === -1)
this._values.push(checkedValue);
}
else
{
let checkedValues=row[this._name].split(this._separator);
for(let value of checkedValues)
{
checkedValue=value.trim();
if(checkedValue !== "" && this._values.indexOf(checkedValue) === -1)
this._values.push(checkedValue);
}
} }
} }
}
if(this._values.length === 0) // possible si uniquement des valeurs vides pour ce champ
// Vérifie si une valeur est sélectionnée dans la liste et, si oui, la retourne throw new Error(errors.selectorFieldIsEmpty);
public getSelectionnedId() : number
{
const selectElement=document.getElementById("freeDatas2HTML_"+this._datasViewElt.id) as HTMLInputElement;
if(selectElement === undefined)
return 0;
else else
return parseInt(selectElement.value,10); {
// Classement des données à l'aide (ou non) d'une fonction spécifique :
if(this._converter.getSortingFunctionForField(this._datasFieldNb) !== undefined)
this._values.sort(this._converter.getSortingFunctionForField(this._datasFieldNb)!.sort); // sans le "!" : TS2532: Object is possibly 'undefined' ???
else
this._values.sort(compare());
// Création et injection du SELECT dans le DOM
label=(label === "") ? this._name : label;
let selectorsHTML="<label for='freeDatas2HTML_"+this._datasViewElt.id+"'>"+label+" :</label><select name='freeDatas2HTML_"+this._datasViewElt.id+"' id='freeDatas2HTML_"+this._datasViewElt.id+"'><option value='0'>----</option>"; // l'option zéro permet d'actualiser l'affichage en ignorant ce filtre
for(let i=0; i< this._values.length; i++)
selectorsHTML+="<option value='"+(i+1)+"'>"+this._values[i]+"</option>";
selectorsHTML+="</select>";
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: {[index: string]:string}) : boolean
public dataIsOk(data: any) : boolean
{ {
if(this.name === undefined || this.name === "") // Permet de vérifier que filter2HTML() a été préalablement appelée :
throw new Error(errors.selectorCheckIsOkFail); if(this._name === "")
throw new Error(errors.filterCheckIsOkFail);
let selectedValue = this.getSelectionnedId();
if(selectedValue === 0) // = pas de valeur sélectionnée = pas de filtre sur ce champ // Pas de valeur sélectionnée = pas de filtre sur ce champ
if(this._selectedValue === undefined)
return true; 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); 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; return false;
const selectedValueTxt=this.values[selectedValue] ; const selectedValueTxt=this._values[this._selectedValue];
if(this._separator === undefined) if(this._separator === undefined)
{ {
if(data[this.name] !== selectedValueTxt) if(data[this._name].trim() !== selectedValueTxt)
return false; return false;
else else
return true; return true;
@ -139,10 +150,10 @@ export class Selector implements Selectors
else else
{ {
let find=false; let find=false;
let checkedValues=data[this.name].split(this._separator); let checkedValues=data[this._name].split(this._separator);
for(let value of checkedValues) for(let value of checkedValues)
{ {
if(value === selectedValueTxt) if(value.trim() === selectedValueTxt)
{ {
find=true; find=true;
break; break;

View File

@ -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.", 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.", 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.", 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.", 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.", 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.", 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.", remoteSourceUrlFail: "L'url fournie ne semble pas valide.",
renderNeedFields: "Les noms de champs doivent être fournis avant de demander l'affichage des données.", 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.", 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.", 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.", 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.", 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.", 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.", sortingFieldsNbFail: "Le nombre de champs trouvés dans le DOM ne correspond pas à celui des données à classer.",

View File

@ -31,7 +31,7 @@ export interface Filters
{ {
datasViewElt: DOMElement; datasViewElt: DOMElement;
filter2HTML() : void; filter2HTML() : void;
dataIsOk(data: any) : boolean; dataIsOk(data: {[index: string]:string}) : boolean;
} }
export interface Paginations export interface Paginations
{ {
@ -54,10 +54,6 @@ export interface PaginationsPages
values?: number[]; values?: number[];
selectedValue?: number; selectedValue?: number;
} }
export interface Datas
{
[key: string]: string;
}
export interface ParseErrors export interface ParseErrors
{ {
code?: string; code?: string;
@ -93,9 +89,10 @@ export interface RemoteSources extends RemoteSourceSettings
export interface Selectors extends Filters export interface Selectors extends Filters
{ {
datasFieldNb: number; datasFieldNb: number;
separator?: string|undefined; name: string;
name?: string; selectedValue: number|undefined;
values?: string[]; separator: string|undefined;
values: string[];
} }
export interface SortingFields export interface SortingFields
{ {

View File

@ -1,12 +1,15 @@
module.exports = module.exports =
{ {
datasViewEltHTML: '<div id="fixture"><div id="selector1"></div><div id="selector2"></div><div id="paginationOptions"></div><div id="counter"></div><div id="datas"></div><div id="pages"></div></div>', datasViewEltHTML: '<div id="fixture"><div id="selector1"></div><div id="selector2"></div><div id="paginationOptions"></div><div id="counter"></div><div id="datas"></div><div id="pages"></div></div>',
selector1HTML: '<label for="freeDatas2HTML_selector1">Famille : </label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Actinide</option><option value="2">Gaz noble</option><option value="3">gaz rare</option><option value="4">Halogène</option><option value="5">Indéfinie</option><option value="6">Lanthanide</option><option value="7">Métal alcalin</option><option value="8">Métal alcalino-terreux</option><option value="9">Métal de transition</option><option value="10">Métal pauvre</option><option value="11">Métalloïde</option><option value="12">Non-métal</option></select>', selector1HTML: '<label for="freeDatas2HTML_selector1">Famille :</label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Actinide</option><option value="2">Gaz noble</option><option value="3">gaz rare</option><option value="4">Halogène</option><option value="5">Indéfinie</option><option value="6">Lanthanide</option><option value="7">Métal alcalin</option><option value="8">Métal alcalino-terreux</option><option value="9">Métal de transition</option><option value="10">Métal pauvre</option><option value="11">Métalloïde</option><option value="12">Non-métal</option></select>',
selector2HTML: '<label for="freeDatas2HTML_selector2">Abondance des éléments dans la croûte terrestre (μg/k) : </label><select name="freeDatas2HTML_selector2" id="freeDatas2HTML_selector2"><option value="0">----</option><option value="1">&gt; 1 et &lt; 100 000</option><option value="2">&gt; 100000</option><option value="3">≤ 1</option><option value="4">Inexistant</option><option value="5">Traces</option></select>', selector2HTML: '<label for="freeDatas2HTML_selector2">Abondance des éléments dans la croûte terrestre (μg/k) :</label><select name="freeDatas2HTML_selector2" id="freeDatas2HTML_selector2"><option value="0">----</option><option value="1">&gt; 1 et &lt; 100 000</option><option value="2">&gt; 100000</option><option value="3">≤ 1</option><option value="4">Inexistant</option><option value="5">Traces</option></select>',
selector1HTMLWithSeparator: '<label for="freeDatas2HTML_selector1">Étiquettes : </label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Exemple0</option><option value="2">Exemple1</option><option value="3">Exemple2</option><option value="4">Exemple3</option><option value="5">Exemple4</option><option value="6">Exemple5</option><option value="7">Exemple6</option><option value="8">Exemple7</option><option value="9">Exemple8</option><option value="10">Exemple9</option><option value="11">Exemple10</option></select>', selector2HTMLWithLabel: '<label for="freeDatas2HTML_selector2">Abondance des éléments :</label><select name="freeDatas2HTML_selector2" id="freeDatas2HTML_selector2"><option value="0">----</option><option value="1">&gt; 1 et &lt; 100 000</option><option value="2">&gt; 100000</option><option value="3">≤ 1</option><option value="4">Inexistant</option><option value="5">Traces</option></select>',
selector1HTMLWithFunction: '<label for="freeDatas2HTML_selector1">Abondance des éléments dans la croûte terrestre (μg/k) : </label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Inexistant</option><option value="2">Traces</option><option value="3">≤ 1</option><option value="4">&gt; 1 et &lt; 100 000</option><option value="5">&gt; 100000</option></select>', selector1HTMLWithSeparator: '<label for="freeDatas2HTML_selector1">Étiquettes :</label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Exemple0</option><option value="2">Exemple1</option><option value="3">Exemple2</option><option value="4">Exemple3</option><option value="5">Exemple4</option><option value="6">Exemple5</option><option value="7">Exemple6</option><option value="8">Exemple7</option><option value="9">Exemple8</option><option value="10">Exemple9</option><option value="11">Exemple10</option></select>',
sortingColumn1HTML: '<a href="#freeDatas2HTMLSorting0" id="freeDatas2HTMLSorting0">Z (numéro atomique)</a>', selectorHTMLWithTrim: '<label for="freeDatas2HTML_selector1">Famille :</label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Gaz noble</option><option value="2">Métal alcalin</option><option value="3">Métal alcalino-terreux</option><option value="4">Métalloïde</option><option value="5">Non-métal</option></select>',
sortingColumn2HTML: '<a href="#freeDatas2HTMLSorting2" id="freeDatas2HTMLSorting2">Symbole</a>', selector1HTMLWithFunction: '<label for="freeDatas2HTML_selector1">Abondance des éléments dans la croûte terrestre (μg/k) :</label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Inexistant</option><option value="2">Traces</option><option value="3">≤ 1</option><option value="4">&gt; 1 et &lt; 100 000</option><option value="5">&gt; 100000</option></select>',
selectorHTMLWithFakeItem: '<label for="freeDatas2HTML_selector1">Famille :</label><select name="freeDatas2HTML_selector1" id="freeDatas2HTML_selector1"><option value="0">----</option><option value="1">Actinide</option><option value="2">Gaz noble</option><option value="3">gaz rare</option><option value="4">Halogène</option><option value="5">Indéfinie</option><option value="6">Lanthanide</option><option value="7">Métal alcalin</option><option value="8">Métal alcalino-terreux</option><option value="9">Métal de transition</option><option value="10">Métal pauvre</option><option value="11">Métalloïde</option><option value="12">Non-métal</option><option value="13">Je suis un gros fake !</option></select>',
sortingColumn1HTML: '<a href="#freeDatas2HTMLSorting0" id="freeDatas2HTMLSorting0">Z (numéro atomique)</a>',
sortingColumn2HTML: '<a href="#freeDatas2HTMLSorting2" id="freeDatas2HTMLSorting2">Symbole</a>',
selectorForPagination: '<label for="freeDatas2HTMLPaginationSelector">Choix de pagination : </label><select name="freeDatas2HTMLPaginationSelector" id="freeDatas2HTMLPaginationSelector"><option value="0">----</option><option value="1">10</option><option value="2">20</option><option value="3">50</option><option value="4">500</option></select>', selectorForPagination: '<label for="freeDatas2HTMLPaginationSelector">Choix de pagination : </label><select name="freeDatas2HTMLPaginationSelector" id="freeDatas2HTMLPaginationSelector"><option value="0">----</option><option value="1">10</option><option value="2">20</option><option value="3">50</option><option value="4">500</option></select>',
selectorFor2Pages: '<label for="freeDatas2HTMLPagesSelector">Page à afficher :</label><select name="freeDatas2HTMLPagesSelector" id="freeDatas2HTMLPagesSelector"><option value="1">1</option><option value="2">2</option></select>', selectorFor2Pages: '<label for="freeDatas2HTMLPagesSelector">Page à afficher :</label><select name="freeDatas2HTMLPagesSelector" id="freeDatas2HTMLPagesSelector"><option value="1">1</option><option value="2">2</option></select>',
selectorForManyPages: '<label for="freeDatas2HTMLPagesSelector">Page à afficher :</label><select name="freeDatas2HTMLPagesSelector" id="freeDatas2HTMLPagesSelector"><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option></select>', selectorForManyPages: '<label for="freeDatas2HTMLPagesSelector">Page à afficher :</label><select name="freeDatas2HTMLPagesSelector" id="freeDatas2HTMLPagesSelector"><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option></select>',

View File

@ -1,12 +1,12 @@
import { FreeDatas2HTML, Selector } from "../src/freeDatas2HTML"; import { FreeDatas2HTML, Selector } from "../src/freeDatas2HTML";
const errors=require("../src/errors.js"); const errors=require("../src/errors.js");
const fixtures=require("./fixtures.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 converter: FreeDatas2HTML;
let selector: Selector; let selector: Selector;
let selectElement : HTMLInputElement;
beforeEach( async () => beforeEach( async () =>
{ {
@ -22,13 +22,18 @@ describe("Test des filtres de données", () =>
document.body.removeChild(document.getElementById("fixture")); 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=new FreeDatas2HTML("CSV");
converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); expect(() => { return new Selector(converter, 0, { id:"selector1" }); }).toThrowError(errors.filterNeedDatas);
expect(() => { return new Selector(converter, 0, { id:"selector1" }); }).toThrowError(errors.selectorNeedDatas); // 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.", () => 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é.", () => it("Si un séparateur vide est fourni pour un filtre, il doit être ignoré.", () =>
{ {
selector=new Selector(converter, 0, { id:"selector1" }); selector=new Selector(converter, 0, { id:"selector1" }, "");
selector.separator="";
expect(selector.separator).toBeUndefined(); expect(selector.separator).toBeUndefined();
}); });
it("Si toutes les paramètres sont correctes, ils doivent être acceptés.", () => it("Si toutes les paramètres sont correctes, ils doivent être acceptés.", () =>
{ {
const elt=document.getElementById("selector1"); expect(() => { selector=new Selector(converter, 2, { id:"selector1" }, ","); return true; }).not.toThrowError();
const selector=new Selector(converter, 2, { id:"selector1" }); expect(selector.datasFieldNb).toEqual(2);
selector.separator=","; expect(selector.datasViewElt).toEqual({ id:"selector1", eltDOM:document.getElementById("selector1") });
expect(selector.datasFieldNb).toEqual(2); expect(selector.separator).toEqual(",");
expect(selector.datasViewElt).toEqual({ id:"selector1", eltDOM:elt });
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 () => beforeEach( async () =>
{ {
@ -73,22 +74,34 @@ describe("Test des filtres de données", () =>
expect(document.getElementById("selector2").innerHTML).toEqual(fixtures.selector2HTML); 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 () => 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"); selector.converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1-emtyinfield.csv" });
converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1-emtyinfield.csv" }); await selector.converter.run();
await converter.run();
selector.filter2HTML(); selector.filter2HTML();
expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selector1HTML); 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 () => 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" }); converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1+tagsfield.csv" });
await converter.run(); await converter.run();
selector=new Selector(converter, 5, { id:"selector1" }); selector=new Selector(converter, 5, { id:"selector1" }, "|");
selector.separator="|";
selector.filter2HTML(); selector.filter2HTML();
expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selector1HTMLWithSeparator); 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 }]; converter.datasSortingFunctions=[{ datasFieldNb: 4, sort:mySort }];
selector=new Selector(converter, 4, { id:"selector1" }); selector=new Selector(converter, 4, { id:"selector1" });
selector.separator="|"; selector.filter2HTML();
selector.filter2HTML();
expect(document.getElementById("selector1").innerHTML).toEqual(fixtures.selector1HTMLWithFunction); 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 <SELECT>.", async () =>
{ {
let data2Test= { selector.converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datasEmptyField.csv" });
"Z (numéro atomique)" : "53", await selector.converter.run();
"Élément": "Iode", expect(() => { return selector.filter2HTML(); }).toThrowError(errors.selectorFieldIsEmpty);
"Symbole": "I", });
"Famille": "Halogène", });
"Abondance des éléments dans la croûte terrestre (μg/k)": "> 1 et < 100 000",
}; describe("Manipulation des sélecteurs et filtre des données.", () =>
expect(() => { selector.dataIsOk(data2Test); }).toThrowError(errors.selectorCheckIsOkFail); {
beforeEach( async () =>
{
selector=new Selector(converter, 3, { id:"selector1" }); // filtre sur le champ "famille"
selector.filter2HTML();
selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement;
}); });
it("Doit retourner false, si la donnée testée ne possède pas le champ sur lequel les données sont filtrées.", () => it("La manipulation d'un sélecteur doit enregistrer la valeur sélectionnée et appeler la fonction actualisant l'affichage.", () =>
{ {
selector.filter2HTML(); spyOn(converter, "refreshView");
let selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement;
selectElement.value="4"; selectElement.value="4";
let data2Test= { // le champ à filtrer ("Famille") est manquant selectElement.dispatchEvent(new Event('change'));
"Z (numéro atomique)" : "53", expect(selector.selectedValue).toEqual(3);
"Élément": "Iode", expect(converter.refreshView).toHaveBeenCalledTimes(1);
"Symbole": "I",
"Abondance des éléments dans la croûte terrestre (μg/k)": "> 1 et < 100 000", selectElement.value="0"; // 0 = annulation de ce filtre, puisqu'aucune valeur choisie
}; selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk(data2Test)).toBeFalse(); expect(selector.selectedValue).toBeUndefined();
expect(converter.refreshView).toHaveBeenCalledTimes(2);
}); });
it("Doit retourner false, si une donnée testée ne correspond pas à la valeur sélectionnée pour le filtre.", () => it("Doit générer une erreur si une donnée est testée sur un sélecteur non affiché dans la page.", () =>
{ {
selector.filter2HTML(); selector=new Selector(converter, 3, { id:"selector1" });
let selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement; expect(() => { selector.dataIsOk({ "nom" : "oui" }); }).toThrowError(errors.filterCheckIsOkFail);
selectElement.value="4"; });
let data2Test= {
"Z (numéro atomique)" : "53", it("Doit toujours retourner true si aucune des valeurs du filtre n'est sélectionnée.", () =>
"Élément": "Iode", {
"Symbole": "I", // Le filtre est sur 0 par défaut
"Famille": "Halogene", // manque un accent :) expect(selector.dataIsOk({ "nom" : "oui" })).toBeTrue();
"Abondance des éléments dans la croûte terrestre (μg/k)": "> 1 et < 100 000", // Même comportement après un retour :
}; selectElement.value="2";
expect(selector.dataIsOk(data2Test)).toBeFalse(); selectElement.dispatchEvent(new Event('change'));
selectElement.value="0";
selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk({ "nom" : "oui" })).toBeTrue();
});
it("Doit générer une erreur si la valeur sélectionnée n'est pas trouvé dans la liste des valeurs connues.", () =>
{
selectElement.innerHTML=fixtures.selectorHTMLWithFakeItem;
selectElement.value="13";
selectElement.dispatchEvent(new Event('change'));
expect(() => { selector.dataIsOk({ "nom" : "oui" }); }).toThrowError(errors.selectorSelectedIndexNotFound);
});
it("Doit retourner false, si la donnée testée ne possède pas le champ sur lequel les données sont filtrées.", () =>
{
selectElement.value="2";
selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk({ "nom" : "rémi sans famille" })).toBeFalse();
});
it("Doit retourner false, si une donnée testée ne correspond pas à la valeur sélectionnée pour le filtre.", async () =>
{
selectElement.value="4"; // = Halogène
selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk({ "Famille": "Hallo Eugène !" })).toBeFalse();
}); });
it("Doit retourner true, si une donnée testée correspond pas à la valeur sélectionnée pour ce filtre.", () => it("Doit retourner true, si une donnée testée correspond pas à la valeur sélectionnée pour ce filtre.", () =>
{ {
selector.filter2HTML();
let selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement;
selectElement.value="4";
let data2Test= {
"Z (numéro atomique)" : "53",
"Élément": "Iode",
"Symbole": "I",
"Famille": "Halogène",
"Abondance des éléments dans la croûte terrestre (μg/k)": "> 1 et < 100 000",
};
expect(selector.dataIsOk(data2Test)).toBeTrue();
});
it("Doit toujours retourner true, si aucune valeur sélectionnée dans la liste.", () =>
{
selector.filter2HTML();
let data2Test= {
"Z (numéro atomique)" : "530",
"Élément": "Yode",
"Symbole": "L",
"Famille": "Halogene",
"Abondance des éléments": "> 1 et < 100 000",
};
expect(selector.dataIsOk(data2Test)).toBeTrue();
});
it("La manipulation d'un sélecteur doit appeler la fonction actualisant l'affichage, y compris pour supprimer ce filtre (0).", () =>
{
selector=new Selector(converter, 3, { id:"selector1" });
selector.filter2HTML();
converter.datasFilters=[selector];
spyOn(converter, "refreshView");
let selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement;
selectElement.value="4"; selectElement.value="4";
selectElement.dispatchEvent(new Event('change')); selectElement.dispatchEvent(new Event('change'));
expect(converter.refreshView).toHaveBeenCalledTimes(1); expect(selector.dataIsOk({ "Famille": "Halogène" })).toBeTrue();
selectElement.value="0"; // Y compris si entouré d'espaces :
selectElement.dispatchEvent(new Event('change')); expect(selector.dataIsOk({ "Famille": " Halogène " })).toBeTrue();
expect(converter.refreshView).toHaveBeenCalledTimes(2);
}); });
}); });
describe("Manipulation des sélecteurs avec séparateur.", () =>
{
beforeEach( async () =>
{
converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1+tagsfield.csv" });
await converter.run();
selector=new Selector(converter, 5, { id:"selector1" }, "|"); // filtre sur le champ "Étiquettes"
selector.filter2HTML();
selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement;
});
it("Doit retourner false, si la donnée testée ne possède pas le champ sur lequel les données sont filtrées.", () =>
{
selectElement.value="2";
selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk({ "nom" : "oui" })).toBeFalse();
});
it("Doit retourner false, si une donnée testée ne correspond pas à la valeur sélectionnée pour le filtre.", () =>
{
selectElement.value="4"; // = Exemple3
selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk({ "Étiquettes": "Mauvais exemple" })).toBeFalse();
});
it("Doit retourner true, si une donnée testée correspond pas à la valeur sélectionnée pour ce filtre.", () =>
{
selectElement.value="4";
selectElement.dispatchEvent(new Event('change'));
expect(selector.dataIsOk({ "Étiquettes": "Exemple3" })).toBeTrue();
// Même pas seule :
expect(selector.dataIsOk({ "Étiquettes": "Exemple3|Exemple1|Exemple9" })).toBeTrue();
expect(selector.dataIsOk({ "Étiquettes": "Exemple0|Exemple3|Exemple2" })).toBeTrue();
expect(selector.dataIsOk({ "Étiquettes": "Exemple0|Exemple4|Exemple3" })).toBeTrue();
// Y compris si entourée d'espaces :
expect(selector.dataIsOk({ "Étiquettes": "Exemple0|Exemple4| Exemple3 " })).toBeTrue();
expect(selector.dataIsOk({ "Étiquettes": " Exemple3 " })).toBeTrue();
});
});
}); });