From 8cccb8e096ea606cec1f547e2a4b754c50340db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20PENHO=C3=8BT?= Date: Wed, 20 Oct 2021 12:36:09 +0200 Subject: [PATCH] =?UTF-8?q?Relecture=20de=20la=20classe=20g=C3=A9rant=20le?= =?UTF-8?q?s=20options=20de=20pagination=20+=20ses=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/Pagination.ts | 119 +++++++++++++------------- src/errors.js | 3 +- src/freeDatas2HTML.ts | 2 +- src/interfaces.ts | 8 +- tests/fixtures.js | 3 +- tests/paginationSpec.ts | 182 +++++++++++++++++++++++----------------- 7 files changed, 180 insertions(+), 139 deletions(-) diff --git a/package.json b/package.json index 56e6a35..9a0e38c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freedatas2html", - "version": "0.8.0", + "version": "0.8.5", "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/Pagination.ts b/src/Pagination.ts index cf37403..27af8c3 100644 --- a/src/Pagination.ts +++ b/src/Pagination.ts @@ -1,15 +1,12 @@ -const errors = require("./errors.js"); +const errors=require("./errors.js"); import { DOMElement, Paginations, PaginationsOptions, PaginationsPages } from "./interfaces"; import { FreeDatas2HTML } from "./freeDatas2HTML"; export class Pagination implements Paginations { private _converter: FreeDatas2HTML; - // la pagination en cours, qui peut être modifiée/initialisée par l'utilisateur, si des options lui sont proposées : private _selectedValue: number|undefined; - // les éventuelles options de pagination : - private _options: PaginationsOptions | undefined; - // les pages proposées, si le nombre d'enregistrement est > à la pagination en cours. + private _options: PaginationsOptions|undefined; private _pages: PaginationsPages; public static isPositiveInteger(nb: number) @@ -18,8 +15,8 @@ export class Pagination implements Paginations } // Injection de la classe principale, mais uniquement si les données ont été importées - // De plus l'élément du DOM devant recevoir la liste des pages doit exister - constructor(converter: FreeDatas2HTML, pagesElt: DOMElement, pagesName: string="Pages") + // L'élément du DOM devant recevoir la liste des pages doit exister + constructor(converter: FreeDatas2HTML, pagesElt: DOMElement, pagesName="Pages") { if(converter.fields === undefined) throw new Error(errors.paginationNeedDatas); @@ -30,12 +27,16 @@ export class Pagination implements Paginations } } + // undefined = ne pas paginer les données. set selectedValue(value : number|undefined) { - if(value === undefined || !Pagination.isPositiveInteger(value)) - throw new Error(errors.needPositiveInteger); - if(this.options !== undefined && this.options.values.indexOf(value) === -1) - throw new Error(errors.paginationNeedByfaultValueBeInOptions); + if(value !== undefined) + { + if(!Pagination.isPositiveInteger(value)) + throw new Error(errors.needPositiveInteger); + if(this.options !== undefined && this.options.values.indexOf(value) === -1) + throw new Error(errors.paginationNeedByfaultValueBeInOptions); + } this._selectedValue=value; } @@ -44,23 +45,32 @@ export class Pagination implements Paginations return this._selectedValue; } + // Les différentes valeurs de pagination proposées set options(options : PaginationsOptions|undefined) { if(options !== undefined) { options.displayElement=FreeDatas2HTML.checkInDOMById(options.displayElement); - if(options.values.length === 0) - throw new Error(errors.paginationNeedOptionsValues); + // Dédoublonnage et refus des valeurs incorrectes. + // Par contre pas de classement des valeurs restantes, car le "désordre" peut être volontaire :) + const realValues=[]; for(let option of options.values) { if(! Pagination.isPositiveInteger(option)) throw new Error(errors.needPositiveInteger); + if(realValues.indexOf(option) === -1) + realValues.push(option); + else + console.log(errors.paginationOptionsDuplicatedValues); } + if(realValues.length < 2) + throw new Error(errors.paginationNeedOptionsValues); + options.values=realValues; if(this.selectedValue !== undefined && options.values.indexOf(this.selectedValue) === -1) throw new Error(errors.paginationNeedByfaultValueBeInOptions); - options.name= (options.name===undefined) ? "Pagination" : options.name ; // on garde la possibilité d'une chaîne vide, si souhaité - this._options=options; + options.name=(options.name === undefined) ? "Pagination" : options.name ; // chaîne vide possible, si souhaité } + this._options=options; } get options() : PaginationsOptions|undefined @@ -73,10 +83,10 @@ export class Pagination implements Paginations return this._pages; } - // Création du permettant de choisir la pagination + public options2HTML() : void { - if(this._converter === undefined || this._options === undefined) + if(this._options === undefined) throw new Error(errors.pagination2HTMLFail); else { @@ -84,59 +94,56 @@ export class Pagination implements Paginations for(let i=0; i< this._options.values.length; i++) selectorsHTML+=""; selectorsHTML+=""; - this._options.displayElement.eltDOM!.innerHTML=selectorsHTML; // initialiser dans le setter + this._options.displayElement.eltDOM!.innerHTML=selectorsHTML; // "!" car displayElement testé par le constructeur + let selectElement=document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; - // Si une Pagination par défaut existe, on la sélectionne : + if(this._selectedValue !== undefined) - { - let indexSelectedValue=this._options.values.indexOf(this._selectedValue)+1; - selectElement.value=""+indexSelectedValue; - } + selectElement.value=""+(this._options.values.indexOf(this._selectedValue)+1); + const pagination=this; selectElement.addEventListener("change", function(e) { if(selectElement.value === "0") - pagination._selectedValue=undefined; // = pas de Pagination + pagination._selectedValue=undefined; // = pas de pagination else pagination._selectedValue=pagination._options!.values[Number(selectElement.value)-1]; - // on regénère le HTML : pagination._converter.refreshView(); - }); + }); } } - // Création du permettant de se déplacer entre les pages + public pages2HTML(nbTotal:number) : void { - if (this.selectedValue !== undefined) + if (this._selectedValue === undefined || nbTotal <= this._selectedValue) + this.pages.displayElement.eltDOM!.innerHTML=""; // "!" car displayElement testé par le constructeur + else { - if( nbTotal > this.selectedValue) - { - let nbPages=Math.ceil(nbTotal/this.selectedValue); - let selectorsHTML=""; - this.pages.displayElement.eltDOM!.innerHTML=selectorsHTML; - let selectElement=document.getElementById("freeDatas2HTMLPagesSelector") as HTMLInputElement; - if(this.pages.selectedValue !== undefined) - selectElement.value=""+this.pages.selectedValue; - this.pages.selectedValue=1; - let pagination=this; - selectElement.addEventListener('change', function(e) - { - pagination.pages.selectedValue=Number(selectElement.value); - pagination._converter.refreshView(); - }); + let nbPages=Math.ceil(nbTotal/this._selectedValue); + let selectorsHTML=""; + this.pages.displayElement.eltDOM!.innerHTML=selectorsHTML; + + let selectElement=document.getElementById("freeDatas2HTMLPagesSelector") as HTMLInputElement; + + if(this.pages.selectedValue !== undefined) + selectElement.value=""+this.pages.selectedValue; else - this.pages.displayElement.eltDOM!.innerHTML=""; + this.pages.selectedValue=1; + + let pagination=this; + selectElement.addEventListener("change", function(e) + { + pagination.pages.selectedValue=Number(selectElement.value); + pagination._converter.refreshView(); + }); + } } } \ No newline at end of file diff --git a/src/errors.js b/src/errors.js index 8ddb08d..c8fbe49 100644 --- a/src/errors.js +++ b/src/errors.js @@ -7,7 +7,8 @@ module.exports = 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.", - paginationNeedOptionsValues: "Vous n'avez fourni aucune options possibles pour la pagination.", + paginationNeedOptionsValues: "Vous n'avez fourni aucune options possibles pour la pagination.", + paginationOptionsDuplicatedValues: "Une valeur en doublon a été trouvée dans les options de pagination.", paginationNeedPositiveInteger: "Merci de fournir un nombre entier supérieur à zéro pour désigner chaque option de pagination.", parserDatasNotFound : "Aucune donnée n'a été trouvée.", parserElementsNotFound: "Aucun élément trouvé dans le document pour le sélecteur fourni : ", diff --git a/src/freeDatas2HTML.ts b/src/freeDatas2HTML.ts index 4340ceb..6194aa1 100644 --- a/src/freeDatas2HTML.ts +++ b/src/freeDatas2HTML.ts @@ -229,7 +229,7 @@ export class FreeDatas2HTML } // Tout réaffichage peut entraîner une modification du nombre de pages (évolution filtres, etc.) if(this.pagination !== undefined) - this.pagination.creaPageSelector(nbTotal); + this.pagination.pages2HTML(nbTotal); return this.datasRender.rend2HTML(datas2Display); } } diff --git a/src/interfaces.ts b/src/interfaces.ts index 1ae39a1..86625a0 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -28,10 +28,10 @@ export interface DOMElement export interface Paginations { options?: PaginationsOptions; - selectedValue?: number; // on peut utiliser une Pagination sans proposer d'options à l'utilisateur. + selectedValue: number|undefined; pages: PaginationsPages; - rend2HTML(): void; - creaPageSelector(nbTotal:number) : void; + options2HTML(): void; + pages2HTML(nbTotal:number) : void; } export interface PaginationsOptions { @@ -42,7 +42,7 @@ export interface PaginationsOptions export interface PaginationsPages { displayElement: DOMElement; - name: string; + name?: string; values?: number[]; selectedValue?: number; } diff --git a/tests/fixtures.js b/tests/fixtures.js index 64efadf..edb7daa 100644 --- a/tests/fixtures.js +++ b/tests/fixtures.js @@ -8,7 +8,8 @@ module.exports = sortingColumn1HTML: 'Z (numéro atomique)', sortingColumn2HTML: 'Symbole', selectorForPagination: '', - selectorForPages: '', + selectorFor2Pages: '', + selectorForManyPages: '', firstLineForPageSelection1:"51AntimoineSbMétalloïde> 1 et < 100 000", lastLineForPageSelection1:"100FermiumFmActinideInexistant", firstLineForPageSelection2:"101MendéléviumMdActinideInexistant", diff --git a/tests/paginationSpec.ts b/tests/paginationSpec.ts index 5e146ed..9c22198 100644 --- a/tests/paginationSpec.ts +++ b/tests/paginationSpec.ts @@ -1,5 +1,4 @@ import { FreeDatas2HTML, Pagination } from "../src/freeDatas2HTML"; - const errors=require("../src/errors.js"); const fixtures=require("./fixtures.js"); @@ -13,7 +12,7 @@ describe("Test de la pagination.", () => document.body.insertAdjacentHTML("afterbegin", fixtures.datasViewEltHTML); converter=new FreeDatas2HTML("CSV"); converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); - converter.datasViewElt={ id:"datas" }; + converter.datasViewElt={ id:"datas" }; await converter.run(); pagination=new Pagination(converter, { id:"pages" }, "Page à afficher :"); }); @@ -33,140 +32,173 @@ describe("Test de la pagination.", () => expect(Pagination.isPositiveInteger(1)).toBeTrue(); }); - it("Doit générer une erreur si la pagination est initialisée sans données à traiter.", () => + it("Doit générer une erreur si la pagination est initialisée sans données à traiter.", () => { converter=new FreeDatas2HTML("CSV"); - converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); expect(() => { return new Pagination(converter, { id:"pages" }); }).toThrowError(errors.paginationNeedDatas); }); - it("Ne doit pas générer d'erreur si initialisé correctement", () => + it("Ne doit pas générer d'erreur si initialisé correctement", () => { expect(() => { return new Pagination(converter, { id:"pages" }); }).not.toThrowError(); }); - it("Doit générer une erreur si les options de pagination sont initialisées avec un tableau de valeurs vide.", () => + it("Doit générer une erreur si la pagination par défaut n'est pas un entier positif.", () => + { + expect(() => { return pagination.selectedValue=0; }).toThrowError(errors.needPositiveInteger); + }); + + it("Doit accepter toute valeur de pagination par défaut valide, y compris \"undefined\".", () => + { + expect(() => { return pagination.selectedValue=10; }).not.toThrowError(); + expect(pagination.selectedValue).toEqual(10); + expect(() => { return pagination.selectedValue=undefined; }).not.toThrowError(); + expect(pagination.selectedValue).toBeUndefined(); + }); + + it("Doit générer une erreur si les options de pagination sont initialisées avec un tableau de valeurs comptant moins de 2 valeurs distinctes.", () => { expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [] }; }).toThrowError(errors.paginationNeedOptionsValues); + expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [10] }; }).toThrowError(errors.paginationNeedOptionsValues); + expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,10] }; }).toThrowError(errors.paginationNeedOptionsValues); }); it("Doit générer une erreur si au moins une des options de pagination fournies n'est pas un entier positif.", () => { expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [0,10,20] }; }).toThrowError(errors.needPositiveInteger); }); - - it("Ne doit pas générer d'erreur si les options de pagination fournies sont correctes.", () => + + it("Doit accepter toutes les options de pagination valides.", () => { - const test={ displayElement: { id:"paginationOptions" }, values: [10,20,50], name: "La pagination" }; - expect(() => { return pagination.options=test}).not.toThrowError(); - expect(pagination.options).toEqual(test); - }); - - it("Doit générer une erreur si la pagination par défaut n'est pas un entier positif.", () => - { - expect(() => { return pagination.selectedValue=0; }).toThrowError(errors.needPositiveInteger); + const domElement=document.getElementById("paginationOptions"); + expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [1,2] }; }).not.toThrowError(); + expect(pagination.options).toEqual({ displayElement: { id:"paginationOptions", eltDOM: domElement }, values: [1,2], name:"Pagination" }); + // Doublons ignorés mais anomalie non bloquante : + expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20,20] }; }).not.toThrowError(); + expect(pagination.options).toEqual({ displayElement: { id:"paginationOptions", eltDOM: domElement }, values: [10,20], name:"Pagination" }); + // L'ordre des valeurs proposées est libre : + expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [100,20,50,10] }; }).not.toThrowError(); + expect(pagination.options).toEqual({ displayElement: { id:"paginationOptions", eltDOM: domElement }, values: [100,20,50,10], name:"Pagination" }); + // Undefined accepté : + expect(() => { return pagination.options=undefined; }).not.toThrowError(); + expect(pagination.options).toBeUndefined(); + // Un libellé pour l'affichage peut aussi être fourni + expect(() => { return pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,50], name: "La pagination" } }).not.toThrowError(); + expect(pagination.options).toEqual({ displayElement: { id:"paginationOptions", eltDOM: domElement }, values: [10,50], name:"La pagination" }); }); - it("Doit générer une erreur si la Pagination par défaut ne fait pas partie des valeurs proposées en option.", () => + it("Doit générer une erreur si la pagination par défaut ne fait pas partie des valeurs proposées en option.", () => { expect(() => { pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20,50] }; return pagination.selectedValue=15; }).toThrowError(errors.paginationNeedByfaultValueBeInOptions); + // Idem dans l'autre sens : expect(() => { pagination.selectedValue=15; return pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20,50] }; }).toThrowError(errors.paginationNeedByfaultValueBeInOptions); }); - - it("Doit accepter une pagination par défaut correcte, avec ou sans options proposées.", () => + + it("Doit accepter des valeurs cohérentes pour les options et la valeur par défaut.", () => { - expect(() => { return pagination.selectedValue=10; }).not.toThrowError(); - pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20,50] }; - expect(() => { return pagination.selectedValue=10; }).not.toThrowError(); + expect(() => { pagination.options={ displayElement: { id:"paginationOptions" }, values: [20,50] }; return pagination.selectedValue=50; }).not.toThrowError(); + // Idem dans l'autre sens : + expect(() => { pagination.selectedValue=20; return pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20] }; }).not.toThrowError(); }); }); describe("Création et action des sélecteurs liés à la pagination des données.", () => { - beforeEach( async () => + beforeEach( () => { pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20,50,500] , name: "Choix de pagination :" }; pagination.selectedValue=10; - pagination.rend2HTML(); + pagination.options2HTML(); converter.pagination=pagination; - await converter.run(); }); - it("Si des options de pagination sont fournies, doit générer un élement listant les valeurs possibles.", () => + { + pagination.options2HTML(); expect(document.getElementById("paginationOptions").innerHTML).toEqual(fixtures.selectorForPagination); }); - - it("Si une valeur de pagination par défaut fournie, ne doit pas afficher plus de données.", () => + + it("Si une valeur de pagination par défaut est connue, elle doit être sélectionnée dans la liste.", () => { - let getTR=document.getElementsByTagName("tr"); - expect(getTR.length).toEqual(pagination.selectedValue+1); // 1er TR sert aux titres + pagination.selectedValue=20; + pagination.options2HTML(); + const selectElement=document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; + expect(selectElement.value).toEqual("2"); }); - - it("La manipulation du sélecteur de pagination doit appeler la fonction actualisant l'affichage.", () => + + it("La manipulation du sélecteur de pagination doit enregistrer la valeur sélectionnée et appeler la fonction actualisant l'affichage.", () => { spyOn(converter, "refreshView"); - let selectElement=document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; - selectElement.value="2"; + const selectElement=document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; + selectElement.value="2"; selectElement.dispatchEvent(new Event('change')); + expect(pagination.selectedValue).toEqual(20); expect(converter.refreshView).toHaveBeenCalledTimes(1); selectElement.value="0"; selectElement.dispatchEvent(new Event('change')); + expect(pagination.selectedValue).toBeUndefined(); expect(converter.refreshView).toHaveBeenCalledTimes(2); }); - - it("Si une des options de pagination fournies est sélectionnée, doit afficher le nombre de résultats correspondants.", () => + + it("S'il n'y a pas de pagination définie, le sélecteur de pages ne doit pas être proposé.", () => { - let selectElement=document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; - selectElement.value="2"; // = 20 éléments / page - selectElement.dispatchEvent(new Event('change')); - let getTR=document.getElementsByTagName("tr"); - expect(getTR.length).toEqual(21); - selectElement.value="3"; // = 50 éléments / page - selectElement.dispatchEvent(new Event('change')); - getTR=document.getElementsByTagName("tr"); - expect(getTR.length).toEqual(51); - selectElement.value="0"; // = pas de Pagination, on affiche les 118 lignes du fichier - selectElement.dispatchEvent(new Event('change')); - getTR=document.getElementsByTagName("tr"); - expect(getTR.length).toEqual(119); + pagination.selectedValue=undefined; + pagination.pages2HTML(118); + const selectElement=document.getElementById("pages").innerHTML; + expect(selectElement).toEqual(""); + }); + + it("S'il n'y pa plus de données que le nombre de lignes par page, le sélecteur de pages ne doit pas être proposé.", () => + { + pagination.pages2HTML(10); + const selectElement=document.getElementById("pages").innerHTML; + expect(selectElement).toEqual(""); }); - + it("Si il y a plus de données que le nombre de lignes autorisées par page, un