From a4b7e7af4a8b8c567e7a1edb21b35581aed0a37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20PENHO=C3=8BT?= Date: Thu, 9 Sep 2021 12:57:23 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20fonctionnalit=C3=A9=20pagination=20des?= =?UTF-8?q?=20donn=C3=A9es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- karma.conf.js | 2 +- public/index.html | 14 ++- src/errors.js | 3 + src/firstExample.ts | 16 +++ src/freeDatas2HTML.ts | 172 +++++++++++++++++++++++++--- src/freeDatas2HTMLInterfaces.ts | 21 +++- tests/fixtures.js | 8 +- tests/freeDatas2HTMLSpec.ts | 196 +++++++++++++++++++++++++++++--- 9 files changed, 397 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 5207a18..1c19412 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,15 @@ De même l'idée est de rester libre du rendu des données en n'imposant pas de La première version se contente de récupérer et parser des données présentes dans un fichier CSV via un appel Ajax. Les données trouvées sont affichées dans un tableau. En option, des colonnes peuvent être indiquées par filtrer les données et/ou les classer. Il est possible de fournir des fonctions spécifiques pour classer les données de certaines colonnes. +Il est également possible de paginer les résultats. + +Le tout **en options**, le développeur final devant pouvoir adapter le module à son besoin. Il reste à ajouter : -- la possibilité de paginer les données. - la possibilité d'utiliser des sources/formats différents qu'un fichier CSV pour extraire les données. -- la possibilité de spécifier un code HTML autre qu'un tableau pour lister les données. +- la possibilité de spécifier un code HTML autre qu'un tableau pour lister/récupérer les données. -Le tout en options, le développeur final devant pouvoir adapter le module à son besoin. +Mais avant toute chose, il me faut maintenant **remanier le code, le script approchant les 500 lignes** ! Bref, il reste beaucoup de choses à faire ! diff --git a/karma.conf.js b/karma.conf.js index 9f4a8b3..ce5a59a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -48,7 +48,7 @@ module.exports = function(config) { // start these browsers // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher browsers: ['Firefox', 'Chromium'], - + // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: true, diff --git a/public/index.html b/public/index.html index 0bc8478..65548e3 100644 --- a/public/index.html +++ b/public/index.html @@ -11,15 +11,23 @@

freeDatas2HTML

-

-

-

+ +
+
+
+
+

+
+ +

Si tout se passe bien, ce texte sera remplacé par un tableau de données extraites du fichier csv.

+

\ No newline at end of file diff --git a/src/errors.js b/src/errors.js index 42aaeb1..9fe274e 100644 --- a/src/errors.js +++ b/src/errors.js @@ -4,6 +4,9 @@ module.exports = elementNotFound : "Aucun élément HTML n'a été trouvé ayant comme \"id\" : ", needDatasElt: "Merci de fournir un id valide pour l'élément où afficher les données.", needNaturalNumber: "Merci de fournir un nombre entier supérieur ou égal à zéro pour désigner chaque colonne.", + needPagesSelectorElt: "Merci de fournir l'id de l'élément où afficher le sélecteur de pages.", + needPaginationByDefaultBeInOptions: "La valeur de pagination par défaut doit faire partie des options proposées.", + needPositiveInteger: "Merci de fournir un nombre entier supérieur à zéro pour désigner chaque option de pagination.", needUrl: "Merci de fournir une url valide pour le fichier CSV à parser.", parserFail: "La lecture des données du fichier a échoué.", selectorFieldNotFound: "Au moins une des colonnes devant servir à filtrer les données n'existe pas dans le fichier.", diff --git a/src/firstExample.ts b/src/firstExample.ts index 320aaa9..fed75e7 100644 --- a/src/firstExample.ts +++ b/src/firstExample.ts @@ -7,6 +7,7 @@ const initialise = async () => let converter=new freeDatas2HTML(); converter.datasViewElt={ id:"datas" }; converter.datasSelectors=[{ datasFieldNb:3, id:"filtre1"}, { datasFieldNb:4, id:"filtre2"},{ datasFieldNb:5, id:"filtre3", separator:"," }]; + const mySort = (a: any, b: any, order: "asc"|"desc" = "asc") => { const values = [ "> 100000", "> 1 et < 100 000", "≤ 1", "Traces", "Inexistant"]; @@ -21,6 +22,21 @@ const initialise = async () => }; converter.datasSortingColumns=[{ datasFieldNb:0 }, { datasFieldNb:1 },{ datasFieldNb:2 }, { datasFieldNb:4 }]; converter.datasSortingFunctions= [{ datasFieldNb:4, sort:mySort}]; + converter.pagination= + { + selectedValue:10, + options: + { + displayElement : { id:"paginationOptions" }, + values: [10,20,50,500], + name: "Choix de pagination :" + }, + pages: + { + displayElement : { id:"pages" }, + name: "Page à afficher :" + } + } converter.datasSourceUrl="http://localhost:8080/datas/elements-chimiques.csv"; await converter.run(); } diff --git a/src/freeDatas2HTML.ts b/src/freeDatas2HTML.ts index 7a0ee4c..b11cd27 100644 --- a/src/freeDatas2HTML.ts +++ b/src/freeDatas2HTML.ts @@ -3,7 +3,7 @@ const errors = require("./errors.js"); const { compare }= require('natural-orderby'); import { papaParseDatas, papaParseErrors, papaParseMeta } from "./papaParseInterfaces"; -import { domElement, selectors, sortingColumns, sortingFunctions } from "./freeDatas2HTMLInterfaces"; +import { domElement, pagination, selectors, sortingColumns, sortingFunctions } from "./freeDatas2HTMLInterfaces"; export class freeDatas2HTML { @@ -20,7 +20,14 @@ export class freeDatas2HTML public parseDatas: papaParseDatas[] = []; public parseErrors: papaParseErrors[] = []; public stopIfParseErrors: boolean = false; - + // Pagination : + private _pagination: pagination|undefined; + + public static isPositiveInteger(nb: number) + { + return (Number.isInteger(nb) === false || nb <= 0) ? false : true; + } + public static isNaturalNumber(nb: number) { return (Number.isInteger(nb) === false || nb < 0) ? false : true; @@ -50,7 +57,7 @@ export class freeDatas2HTML { this._datasSelectors=[]; let checkContainerExist: HTMLElement|null; - for(let i = 0; i < selectionElts.length; i++) + for(let i in selectionElts) { checkContainerExist=document.getElementById(selectionElts[i].id); if(checkContainerExist === null) @@ -66,10 +73,10 @@ export class freeDatas2HTML } } } - + get datasSelectors() : selectors[] { - return this._datasSelectors; + return this._datasSelectors; } set datasSortingColumns(sortingColumns: sortingColumns[]) @@ -106,7 +113,7 @@ export class freeDatas2HTML } // Retourne la fonction spécifique de classement associée à une colonne - public getSortingFunctionForField(datasFieldNb: number) : sortingFunctions|undefined + public getSortingFunctionForField(datasFieldNb: number): sortingFunctions|undefined { for(let i in this._datasSortingFunctions) { @@ -115,7 +122,70 @@ export class freeDatas2HTML } return undefined; } - + + // Long et tortueux ! créer une classe dédiée ? + set pagination(config: pagination) + { + this._pagination={}; + // Si une valeur par défaut est fournie ou des valeurs en option, un id valide doit être aussi fourni pour recueillir le sélecteur de pages : + if(config.selectedValue !== undefined || config.options !== undefined) + { + if(config.pages === undefined) + throw new Error(errors.needPagesSelectorElt); + let checkContainerExist=document.getElementById(config.pages.displayElement.id); + if(checkContainerExist === null) + throw new Error(errors.elementNotFound+config.pages.displayElement.id); + else + { + this.pagination.pages= + { + displayElement: + { + id:config.pages.displayElement.id, + eltDOM: checkContainerExist + }, + name: (config.pages.name) ? config.pages.name : "Pages :", + selectedValue:1,// 1ère page affichée par défaut + } + } + } + if(config.options !== undefined) + { + let checkContainerExist=document.getElementById(config.options.displayElement.id); + if(checkContainerExist === null) + throw new Error(errors.elementNotFound+config.options.displayElement.id); + else + { + for(let i = 0; i < config.options.values.length; i++) + { + if(freeDatas2HTML.isPositiveInteger(config.options.values[i]) === false) + throw new Error(errors.needPositiveInteger); + } + this._pagination.options = + { + displayElement: { id:config.options.displayElement.id, eltDOM:checkContainerExist }, + name: (config.options.name) ? config.options.name : "Pagination :", + values:config.options.values + }; + } + } + // Valeur de pagination par défaut qui peut être différente de celles éventuellement proposées en option : + if(config.selectedValue !== undefined) + { + if(config.options !== undefined && (config.options.values.indexOf(config.selectedValue) === -1)) + throw new Error(errors.needPaginationByDefaultBeInOptions); + if(freeDatas2HTML.isPositiveInteger(config.selectedValue)) + this._pagination.selectedValue=config.selectedValue; + else + throw new Error(errors.needPositiveInteger); + } + } + + get pagination(): pagination + { + return this._pagination; + } + public async parse(): Promise { const converter=this; @@ -227,12 +297,40 @@ export class freeDatas2HTML } } } + + // Si demandé, création d'une liste de paginations possibles + if(converter.pagination !==undefined && converter.pagination.options !==undefined && converter.pagination.options.values.length > 0) + { + const values=converter.pagination.options.values!; + let selectorsHTML=""; + converter.pagination.options.displayElement.eltDOM!.innerHTML=selectorsHTML; + let selectElement = document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; + // Si une pagination par défaut existe et la sélectionne : + if(converter.pagination.selectedValue !== undefined) + { + let indexSelectedValue=converter.pagination.options.values.indexOf(converter.pagination.selectedValue)+1; + selectElement.value=""+indexSelectedValue; + } + + selectElement.addEventListener('change', function(e) + { + if(selectElement.value === "0") + converter.pagination.selectedValue=undefined; + else + converter.pagination.selectedValue=values[Number(selectElement.value)-1]; + converter.datasHTML=converter.createDatasHTML(converter.parseMeta!.fields as string[], converter.parseDatas); + converter.refreshView(); + }); + } // Je teste aussi les colonnes devant servir à classer les données. for(let i in this._datasSortingColumns) { if(this._datasSortingColumns[i].datasFieldNb > (this.parseMeta!.fields.length-1)) - throw new Error(errors.sortingColumnsFieldNotFound); + throw new Error(errors.sortingColumnsFieldNotFound); } // Si tout est ok, affichage initial de toutes les données du fichier @@ -290,7 +388,7 @@ export class freeDatas2HTML if(checkSelectorExist != null && checkSelectorExist.selectedIndex != 0) filters.push({ field: this._datasSelectors[i].name, value: this._datasSelectors[i].values![checkSelectorExist.selectedIndex-1], separator:this._datasSelectors[i].separator }); } - + // Dois-je classer les données par rapport à une colonne ? if(this._datasSortedColumn !== undefined) { @@ -305,42 +403,51 @@ export class freeDatas2HTML else datas.sort( (a, b) => compare( {order: colOrder} )(a[col], b[col])); } + + // Dois-je prendre en compte une pagination ? + let firstData=0; + if (this.pagination !== undefined && this.pagination.selectedValue !== undefined && this.pagination.pages !== undefined && this.pagination.pages.selectedValue !== undefined) + firstData=this.pagination.selectedValue*(this.pagination.pages.selectedValue-1); + let maxData = (this.pagination !== undefined && this.pagination.selectedValue !== undefined) ? this.pagination.selectedValue : datas.length+1; // Création du tableau de données : let datasHTML=""; for (let i in fields) datasHTML+=""; datasHTML+=""; + let nbVisible=0, nbTotal=0; for (let row in datas) { let visible=true; if(filters.length !== 0) { - for(let i in filters) + let i=0; + while(filters[i] !== undefined && visible===true) { // Il faut réutiliser le trim() utilisé pour créer les filtres, sinon on risque de ne pas retrouver certaines valeurs if(filters[i].separator === undefined) { - if(datas[row][filters[i].field].trim() != filters[i].value) + if(datas[row][filters[i].field].trim() !== filters[i].value) visible=false; } else { - let checkedValues=datas[row][filters[i].field].split(filters[i].separator as string), finded=false; + visible=false; + let checkedValues=datas[row][filters[i].field].split(filters[i].separator as string); for(let j in checkedValues) { if(checkedValues[j].trim() === filters[i].value) { - finded=true; + visible=true; break; } } - if(!finded) - visible=false; } + i++; } } - if(visible) + + if(visible && nbTotal >= firstData && nbVisible < maxData) { datasHTML+=""; for(let field in datas[row]) @@ -350,9 +457,42 @@ export class freeDatas2HTML datasHTML+=""; } datasHTML+=""; + nbVisible++; + nbTotal++; } + else if(visible) + nbTotal++; } datasHTML+="
"+fields[i]+"
"+datas[row][field]+"
"; + + // Si pagination définie et tous les enregistrements n'ont pas été affichés, alors création d'un sélecteur de pages + if (this.pagination !== undefined && this.pagination.selectedValue !== undefined && this.pagination.pages !== undefined && nbTotal > this.pagination.selectedValue) + { + let nbPages=Math.ceil(nbTotal/this.pagination.selectedValue); + let selectorsHTML=""; + this.pagination.pages.displayElement.eltDOM!.innerHTML=selectorsHTML; + let selectElement = document.getElementById("freeDatas2HTMLPagesSelector") as HTMLInputElement; + if(this.pagination.pages.selectedValue !== undefined) + selectElement.value=""+this.pagination.pages.selectedValue; + let converter=this; + this.pagination.pages.selectedValue=1; + selectElement.addEventListener('change', function(e) + { + converter.pagination.pages!.selectedValue=Number(selectElement.value); + converter.datasHTML=converter.createDatasHTML(converter.parseMeta!.fields as string[], converter.parseDatas); + converter.refreshView(); + }); + } + else if(this.pagination !== undefined && this.pagination.pages !== undefined) + this.pagination.pages.displayElement.eltDOM!.innerHTML=""; + return datasHTML; } } \ No newline at end of file diff --git a/src/freeDatas2HTMLInterfaces.ts b/src/freeDatas2HTMLInterfaces.ts index 17629b5..b9141b7 100644 --- a/src/freeDatas2HTMLInterfaces.ts +++ b/src/freeDatas2HTMLInterfaces.ts @@ -3,12 +3,12 @@ export interface domElement id: string; eltDOM?: HTMLElement; } -export interface selectors extends domElement +export interface selectors extends domElement // revoir pour donner un autre nom { datasFieldNb: number; separator?: string; name?: string; - values? : string[]; + values?: string[]; } export interface sortingColumns { @@ -19,4 +19,21 @@ export interface sortingFunctions { datasFieldNb: number; sort(a: any,b: any, order?: "asc"|"desc"): number; // cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Array/sort +} +export interface pagination +{ + options?: + { + displayElement: domElement; + name?: string; // rendre obligatoire ? + values: number[]; + }; + selectedValue?: number; // on peut utiliser une pagination sans proposer d'options à l'utilisateur. + pages?: + { + displayElement: domElement; + name?: string; // rendre obligatoire ? + values?: number[]; + selectedValue?: number; + } } \ No newline at end of file diff --git a/tests/fixtures.js b/tests/fixtures.js index 5e5105e..ed47fc0 100644 --- a/tests/fixtures.js +++ b/tests/fixtures.js @@ -1,6 +1,6 @@ module.exports = { - datasViewEltHTML: '
', + datasViewEltHTML: '
', datasHTML : '
Z (numéro atomique)ÉlémentSymboleFamilleAbondance des éléments dans la croûte terrestre (μg/k)
1HydrogèneHNon-métal> 100000
2HéliumHeGaz noble> 1 et < 100 000
3LithiumLiMétal alcalin> 1 et < 100 000
4BérylliumBeMétal alcalino-terreux> 1 et < 100 000
5BoreBMétalloïde> 1 et < 100 000
6CarboneCNon-métal> 100000
7AzoteNNon-métal> 1 et < 100 000
8OxygèneONon-métal> 100000
9FluorFHalogène> 100000
10NéonNeGaz noble> 1 et < 100 000
11SodiumNaMétal alcalin> 100000
12MagnésiumMgMétal alcalino-terreux> 100000
13AluminiumAlMétal pauvre> 100000
14SiliciumSiMétalloïde> 100000
15PhosphorePNon-métal> 100000
16SoufreSNon-métal> 100000
17ChloreClHalogène> 100000
18ArgonArGaz noble> 1 et < 100 000
19PotassiumKMétal alcalin> 100000
20CalciumCaMétal alcalino-terreux> 100000
21ScandiumScMétal de transition> 1 et < 100 000
22TitaneTiMétal de transition> 100000
23VanadiumVMétal de transition> 100000
24ChromeCrMétal de transition> 100000
25ManganèseMnMétal de transition> 100000
26FerFeMétal de transition> 100000
27CobaltCoMétal de transition> 1 et < 100 000
28NickelNiMétal de transition> 1 et < 100 000
29CuivreCuMétal de transition> 1 et < 100 000
30ZincZnMétal pauvre> 1 et < 100 000
31GalliumGaMétal pauvre> 1 et < 100 000
32GermaniumGeMétalloïde> 1 et < 100 000
33ArsenicAsMétalloïde> 1 et < 100 000
34SéléniumSeNon-métal> 1 et < 100 000
35BromeBrHalogène> 1 et < 100 000
36KryptonKrgaz rare≤ 1
37RubidiumRbMétal alcalin> 1 et < 100 000
38StrontiumSrMétal alcalino-terreux> 100000
39YttriumYMétal de transition> 1 et < 100 000
40ZirconiumZrMétal de transition> 100000
41NiobiumNbMétal de transition> 1 et < 100 000
42MolybdèneMoMétal de transition> 1 et < 100 000
43TechnétiumTcMétal de transitionTraces
44RuthéniumRuMétal de transition≤ 1
45RhodiumRhMétal de transition≤ 1
46PalladiumPdMétal de transition> 1 et < 100 000
47ArgentAgMétal de transition> 1 et < 100 000
48CadmiumCdMétal pauvre> 1 et < 100 000
49IndiumInMétal pauvre> 1 et < 100 000
50ÉtainSnMétal pauvre> 1 et < 100 000
51AntimoineSbMétalloïde> 1 et < 100 000
52TellureTeMétalloïde≤ 1
53IodeIHalogène> 1 et < 100 000
54XénonXegaz rare≤ 1
55CésiumCsMétal alcalin> 1 et < 100 000
56BaryumBaMétal alcalino-terreux> 100000
57LanthaneLaLanthanide> 1 et < 100 000
58CériumCeLanthanide> 1 et < 100 000
59PraséodymePrLanthanide> 1 et < 100 000
60NéodymeNdLanthanide> 1 et < 100 000
61ProméthiumPmLanthanideTraces
62SamariumSmLanthanide> 1 et < 100 000
63EuropiumEuLanthanide> 1 et < 100 000
64GadoliniumGdLanthanide> 1 et < 100 000
65TerbiumTbLanthanide> 1 et < 100 000
66DysprosiumDyLanthanide> 1 et < 100 000
67HolmiumHoLanthanide> 1 et < 100 000
68ErbiumErLanthanide> 1 et < 100 000
69ThuliumTmLanthanide> 1 et < 100 000
70YtterbiumYbLanthanide> 1 et < 100 000
71LutéciumLuLanthanide> 1 et < 100 000
72HafniumHfMétal de transition> 1 et < 100 000
73TantaleTaMétal de transition> 1 et < 100 000
74TungstèneWMétal de transition> 1 et < 100 000
75RhéniumReMétal de transition≤ 1
76OsmiumOsMétal de transition> 1 et < 100 000
77IridiumIrMétal de transition≤ 1
78PlatinePtMétal de transition> 1 et < 100 000
79OrAuMétal de transition> 1 et < 100 000
80MercureHgMétal pauvre> 1 et < 100 000
81ThalliumTlMétal pauvre> 1 et < 100 000
82PlombPbMétal pauvre> 1 et < 100 000
83BismuthBiMétal pauvre> 1 et < 100 000
84PoloniumPoMétal pauvre≤ 1
85AstateAtMétalloïdeTraces
86RadonRnGaz noble≤ 1
87FranciumFrMétal alcalinTraces
88RadiumRaMétal alcalino-terreux≤ 1
89ActiniumAcActinide≤ 1
90ThoriumThActinide> 1 et < 100 000
91ProtactiniumPaActinide≤ 1
92UraniumUActinide> 1 et < 100 000
93NeptuniumNpActinideTraces
94PlutoniumPuActinideTraces
95AmériciumAmActinideInexistant
96CuriumCmActinideInexistant
97BerkéliumBkActinideInexistant
98CaliforniumCfActinideInexistant
99EinsteiniumEsActinideInexistant
100FermiumFmActinideInexistant
101MendéléviumMdActinideInexistant
102NobéliumNoActinideInexistant
103LawrenciumLrActinideInexistant
104RutherfordiumRfMétal de transitionInexistant
105DubniumDbMétal de transitionInexistant
106SeaborgiumSgMétal de transitionInexistant
107BohriumBhMétal de transitionInexistant
108HassiumHsMétal de transitionInexistant
109MeitnériumMtIndéfinieInexistant
110DarmstadtiumDsIndéfinieInexistant
111RoentgeniumRgIndéfinieInexistant
112CoperniciumCnMétal de transitionInexistant
113NihoniumNhIndéfinieInexistant
114FléroviumFlIndéfinieInexistant
115MoscoviumMcIndéfinieInexistant
116LivermoriumLvIndéfinieInexistant
117TennesseTsIndéfinieInexistant
118OganessonOgIndéfinieInexistant
', selector1HTML: '', selector2HTML: '', @@ -13,4 +13,10 @@ module.exports = sortingColumn2HTML: 'Symbole', datasHTMLFor2Select1Clic: '
Z (numéro atomique)ÉlémentSymboleFamilleAbondance des éléments dans la croûte terrestre (μg/k)
18ArgonArGaz noble> 1 et < 100 000
2HéliumHeGaz noble> 1 et < 100 000
10NéonNeGaz noble> 1 et < 100 000
', datasHTMLFor2Select2Clic: '
Z (numéro atomique)ÉlémentSymboleFamilleAbondance des éléments dans la croûte terrestre (μg/k)
10NéonNeGaz noble> 1 et < 100 000
2HéliumHeGaz noble> 1 et < 100 000
18ArgonArGaz noble> 1 et < 100 000
', + selectorForPagination: '', + selectorForPages: '', + firstLineForPageSelection1:"51AntimoineSbMétalloïde> 1 et < 100 000", + lastLineForPageSelection1:"100FermiumFmActinideInexistant", + firstLineForPageSelection2:"101MendéléviumMdActinideInexistant", + lastLineForPageSelection2:"118OganessonOgIndéfinieInexistant", } \ No newline at end of file diff --git a/tests/freeDatas2HTMLSpec.ts b/tests/freeDatas2HTMLSpec.ts index 8423006..f223f82 100644 --- a/tests/freeDatas2HTMLSpec.ts +++ b/tests/freeDatas2HTMLSpec.ts @@ -33,12 +33,10 @@ describe("freeDatas2HTML", () => { expect(() => { return converter.datasViewElt={ id:"datas" }; }).not.toThrowError(); }); - - it("Ne doit accepter que les sélecteurs pour lesquels un élément a été trouvé dans la page pour l'id fourni.", () => + + it("Doit générer une erreur si l'url fournie pour le fichier de données est vide.", () => { - converter.datasSelectors=[{ datasFieldNb:2, id:"selector2" },{ datasFieldNb:3, id:"selector3" }]; - expect(converter.datasSelectors.length).toEqual(1); - expect(converter.datasSelectors[0].id).toEqual("selector2"); + expect(() => { return converter.datasSourceUrl=" "; }).toThrowError(errors.needUrl); }); it("Doit retourner un booléen indiquant si un nombre est naturel ou non.", () => @@ -49,6 +47,22 @@ describe("freeDatas2HTML", () => expect(freeDatas2HTML.isNaturalNumber(1)).toBeTrue(); }); + it("Doit retourner un booléen indiquant si un nombre est un entier positif ou non.", () => + { + expect(freeDatas2HTML.isPositiveInteger(-1)).toBeFalse(); + expect(freeDatas2HTML.isPositiveInteger(1.25)).toBeFalse(); + expect(freeDatas2HTML.isPositiveInteger(0)).toBeFalse(); + expect(freeDatas2HTML.isPositiveInteger(1)).toBeTrue(); + }); + + // Filtres : + it("Ne doit accepter que les sélecteurs pour lesquels un élément a été trouvé dans la page pour l'id fourni.", () => + { + converter.datasSelectors=[{ datasFieldNb:2, id:"selector2" },{ datasFieldNb:3, id:"selector3" }]; + expect(converter.datasSelectors.length).toEqual(1); + expect(converter.datasSelectors[0].id).toEqual("selector2"); + }); + it("Si un séparateur vide est fourni pour un sélecteur, il doit être ignoré.", () => { converter.datasSelectors=[{ datasFieldNb:2, id:"selector2", separator:"" }]; @@ -61,11 +75,7 @@ describe("freeDatas2HTML", () => expect(converter.datasSelectors.length).toEqual(2); }); - it("Doit générer une erreur si l'url fournie pour le fichier de données est vide.", () => - { - expect(() => { return converter.datasSourceUrl=" "; }).toThrowError(errors.needUrl); - }); - + // Classement des données : it("Doit me retourner la fonction associée à une colonne, de manière à ce qu'elle soit utilisable pour comparer deux valeurs.", () => { // Fonction volontairement basique, car ce n'est pas la fonction que l'on teste ici, mais le fait que l'on puisse l'utiliser ! @@ -82,6 +92,58 @@ describe("freeDatas2HTML", () => expect(converter.getSortingFunctionForField(0)).toBeDefined(); expect([7,9,3,5].sort(converter.getSortingFunctionForField(0).sort)).toEqual([9,7,5,3]); }); + + // Pagination : + it("Doit générer une erreur quand aucun élément n'est fourni pour recevoir le sélecteur de pages, alors que cela est nécessaire.", () => + { + expect(() => { return converter.pagination={ selectedValue:10 }; }).toThrowError(errors.needPagesSelectorElt); + expect(() => { return converter.pagination={ options: { displayElement: { id:"paginationOptions" }, values: [10,20] }}; }).toThrowError(errors.needPagesSelectorElt); + }); + + it("Doit générer une erreur si l'élément fourni pour recevoir le sélecteur de pages n'existe pas dans le DOM.", () => + { + expect(() => { return converter.pagination={ selectedValue:10, pages: { displayElement: { id:"dontExist" }} }; }).toThrowError(errors.elementNotFound+"dontExist"); + }); + + it("Doit générer une erreur si l'élément fourni pour recevoir le sélecteur de pagination n'existe pas dans le DOM.", () => + { + expect(() => { return converter.pagination={ options: { displayElement: { id:"dontExist" }, values: [10,20] }, pages: { displayElement: { id:"pages" }}}; }).toThrowError(errors.elementNotFound+"dontExist"); + }); + + it("Doit générer une erreur si au moins une des options de pagination proposée n'est pas un entier positif.", () => + { + expect(() => { return converter.pagination={ options: { displayElement: { id:"paginationOptions" }, values:[0,10,20] }, pages: { displayElement: { id:"pages" }}}; }).toThrowError(errors.needPositiveInteger); + }); + + it("Doit générer une erreur si la pagination par défaut n'est pas un entier positif.", () => + { + expect(() => { return converter.pagination={ selectedValue:0, pages: { displayElement: { id:"pages" }} }; }).toThrowError(errors.needPositiveInteger); + }); + + it("Doit générer une erreur si la pagination par défaut ne fait pas partie des valeurs proposées en option.", () => + { + expect(() => { return converter.pagination={ selectedValue:15, options: { displayElement: { id:"paginationOptions" }, values:[10,20,50] }, pages: { displayElement: { id:"pages" }}}; }).toThrowError(errors.needPaginationByDefaultBeInOptions); + }); + + it("Doit accepter une configuration correcte pour la pagination.", () => + { + let paginationOk = + { + selectedValue:10, + options: + { + displayElement : { id:"paginationOptions" }, + values: [10,20,50], + name: "Choix de pagination :" + }, + pages: + { + displayElement : { id:"pages" }, + name: "Page à afficher :" + } + }; + expect(() => { return converter.pagination=paginationOk; }).not.toThrowError(); + }); }); describe("Parsage du fichier et création du tableau de données", () => @@ -257,6 +319,25 @@ describe("freeDatas2HTML", () => let txtDatasViewsElt=document.getElementById("datas").innerHTML; expect(txtDatasViewsElt).toEqual(fixtures.datasHTMLForSelectTagsField); }); + + it("Les sélecteurs basés sur un séparateur peuvent fonctionner avec un autre filtre.", async () => + { + converter.datasSourceUrl="http://localhost:9876/datas/datas1+tagsfield.csv"; + converter.datasSelectors=[{ datasFieldNb:4, id:"selector1"}, { datasFieldNb:5, id:"selector2", separator:"|"}]; + await converter.run(); + + let selectElement=document.getElementById("freeDatas2HTMLSelector1") as HTMLInputElement; + selectElement.value="11"; // = "Exemple10" retournant une seule ligne + selectElement.dispatchEvent(new Event('change')); + let txtDatasViewsElt=document.getElementById("datas").innerHTML; + expect(txtDatasViewsElt).toEqual(fixtures.datasHTMLForSelectTagsField); + + selectElement=document.getElementById("freeDatas2HTMLSelector0") as HTMLInputElement; + selectElement.value="3"; // doit supprimer la ligne restant + selectElement.dispatchEvent(new Event('change')); + txtDatasViewsElt=document.getElementById("datas").innerHTML; + expect(txtDatasViewsElt).toEqual("
Z (numéro atomique)ÉlémentSymboleFamilleAbondance des éléments dans la croûte terrestre (μg/k)Étiquettes
"); + }); }); describe("Création et action des colonnes permettant de classer les données affichées.", () => @@ -283,9 +364,9 @@ describe("freeDatas2HTML", () => { converter.datasSortingColumns=[{ datasFieldNb:0 },{ datasFieldNb:2 }]; await converter.run(); - let getTableTh=document.querySelectorAll("table th"); - expect(getTableTh[0].innerHTML).toEqual(fixtures.sortingColumn1HTML); - expect(getTableTh[2].innerHTML).toEqual(fixtures.sortingColumn2HTML); + let getTableTr=document.querySelectorAll("table th"); + expect(getTableTr[0].innerHTML).toEqual(fixtures.sortingColumn1HTML); + expect(getTableTr[2].innerHTML).toEqual(fixtures.sortingColumn2HTML); }); it("Le 1er click sur l'entête d'une des colonnes doit classer les données dans le sens ascendant, puis descendant et ainsi de suite, en prenant en compte les éventuels filtres.", async () => @@ -298,16 +379,97 @@ describe("freeDatas2HTML", () => selectElement = document.getElementById("freeDatas2HTMLSelector1") as HTMLInputElement; selectElement.value="1"; selectElement.dispatchEvent(new Event('change')); - let getTableThLink=document.querySelector("table th a") as HTMLElement; - getTableThLink.click();// tri ascendant + let getTableTrLink=document.querySelector("table th a") as HTMLElement; + getTableTrLink.click();// tri ascendant let txtDatasViewsElt=document.getElementById("datas").innerHTML; expect(txtDatasViewsElt).toEqual(fixtures.datasHTMLFor2Select1Clic); - getTableThLink.click();// tri descendant + getTableTrLink.click();// tri descendant txtDatasViewsElt=document.getElementById("datas").innerHTML; expect(txtDatasViewsElt).toEqual(fixtures.datasHTMLFor2Select2Clic); - getTableThLink.click();// de nouveau ascendant + getTableTrLink.click();// de nouveau ascendant txtDatasViewsElt=document.getElementById("datas").innerHTML; expect(txtDatasViewsElt).toEqual(fixtures.datasHTMLFor2Select1Clic); }); }); + + describe("Création et action des options permettant de paginer les données affichées.", () => + { + beforeEach( () => + { + converter.datasViewElt={ id:"datas" }; + converter.datasSourceUrl="http://localhost:9876/datas/datas1.csv"; + converter.pagination= + { + selectedValue:10, + options: + { + displayElement : { id:"paginationOptions" }, + values: [10,20,50,500], + name: "Choix de pagination :" + }, + pages: + { + displayElement : { id:"pages" }, + name: "Page à afficher :" + } + } + }); + + it("Si des options de pagination sont fournies, doit générer un élement listant les pages doit être affichés.", async () => + { + await converter.run(); + let btnPaginationElt=document.getElementById("pages").innerHTML; + expect(btnPaginationElt).toEqual(fixtures.selectorForPages); + }); + + it("Si l'utilisateur sélectionne une des pages proposées, l'affichage des résultats doit s'adapter en prenant en compte la pagination sélectionnée.", async () => + { + await converter.run(); + let selectElement = document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; + selectElement.value="3"; // = 50 éléments / page + selectElement.dispatchEvent(new Event('change')); + selectElement=document.getElementById("freeDatas2HTMLPagesSelector") as HTMLInputElement; + selectElement.value="2"; + selectElement.dispatchEvent(new Event('change')); + let getTableTr=document.getElementsByTagName("tr"); + expect(getTableTr[1].innerHTML).toEqual(fixtures.firstLineForPageSelection1); + expect(getTableTr[50].innerHTML).toEqual(fixtures.lastLineForPageSelection1); + selectElement.value="3"; // troisième page = incomplèet (18 enregistrements) + selectElement.dispatchEvent(new Event('change')); + getTableTr=document.getElementsByTagName("tr"); + expect(getTableTr[1].innerHTML).toEqual(fixtures.firstLineForPageSelection2); + expect(getTableTr[18].innerHTML).toEqual(fixtures.lastLineForPageSelection2); + }); + }); }); \ No newline at end of file