import { FreeDatas2HTML, Pagination, Render, Selector, SortingField } from "../src/FreeDatas2HTML"; import { ParserForCSV} from "../src/ParserForCSV"; import { ParserForHTML} from "../src/ParserForHTML"; import { ParserForJSON} from "../src/ParserForJSON"; import { RemoteSource} from "../src/RemoteSource"; const { compare }=require("natural-orderby"); const errors=require("../src/errors.js"); const fixtures=require("./fixtures.js"); describe("Tests du script central de FreeDatas2HTML", () => { let converter: FreeDatas2HTML; beforeEach( () => { converter=new FreeDatas2HTML("CSV"); document.body.insertAdjacentHTML('afterbegin', fixtures.datasViewEltHTML); }); afterEach( () => { document.body.removeChild(document.getElementById('fixture')); }); it("Doit avoir créé une instance de FreeDatas2HTML", () => { expect(converter).toBeInstanceOf(FreeDatas2HTML); }); describe("Test des paramètres de configuration reçus.", () => { it("Doit instancier le bon parseur.", () => { converter=new FreeDatas2HTML("CSV"); expect(converter.parser).toBeInstanceOf(ParserForCSV); converter=new FreeDatas2HTML("HTML"); expect(converter.parser).toBeInstanceOf(ParserForHTML); converter=new FreeDatas2HTML("JSON"); expect(converter.parser).toBeInstanceOf(ParserForJSON); }); it("S'il est fourni une chaîne vide comme données à parser, elle ne doit pas être passée au parseur.", () => { converter=new FreeDatas2HTML("CSV", ""); expect(converter.parser.datas2Parse).toEqual(""); // Idem avec espaces bidons : converter=new FreeDatas2HTML("CSV", " "); expect(converter.parser.datas2Parse).toEqual(""); }); it("S'il est fourni une chaîne de caractères non vide, elle doit être passée au parseur.", () => { converter=new FreeDatas2HTML("CSV", "datas"); expect(converter.parser.datas2Parse).toEqual("datas"); }); it("Si une source de données distante est fournie en paramètre, elle doit être passée en parseur.", () => { const remoteSource=new RemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); converter=new FreeDatas2HTML("CSV", "", remoteSource); expect(converter.parser.datasRemoteSource).toEqual(remoteSource); }); it("Doit générer une erreur s'il n'est pas trouvé d'élément dans la page pour l'id fourni.", () => { expect(() => { return FreeDatas2HTML.checkInDOMById({ id:"dontExist" }); }).toThrowError(errors.converterElementNotFound+"dontExist"); }); it("S'il y a bien un élément trouvé dans la page pour l'id fourni, doit retourner l'élement DOM complété.", () => { const eltInDOM=document.getElementById("datas"); const checkElt=FreeDatas2HTML.checkInDOMById({ id:"datas" }); expect(checkElt).toEqual({ id:"datas", eltDOM: eltInDOM }); }); }); describe("Parsage et récupération des données.", () => { beforeEach( async () => { converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); await converter.run(); }); it("Doit générer une erreur si le parseur ne retourne aucun résultat.", async () => { converter=new FreeDatas2HTML("CSV"); spyOn(converter.parser, "parse"); // bloque le fonctionnement de parse() qui sinon bloquerait la suite await expectAsync(converter.run()).toBeRejectedWith(new Error(errors.parserFail)); }); it("Doit générer une erreur si des anomalies sont rencontrées durant le parsage et que cela n'est pas toléré.", async () => { const remoteSource=new RemoteSource({ url:"http://localhost:9876/datas/datas-errors1.csv" }); converter=new FreeDatas2HTML("CSV", "", remoteSource); converter.stopIfParseErrors=true; await expectAsync(converter.run()).toBeRejectedWith(new Error(errors.parserMeetErrors)); }); it("Ne doit pas générer une erreur si des anomalies sont rencontrées durant le parsage, mais que cela est toléré.", async () => { const remoteSource=new RemoteSource({ url:"http://localhost:9876/datas/datas-errors1.csv" }); converter=new FreeDatas2HTML("CSV", "", remoteSource); await expectAsync(converter.run()).toBeResolved(); }); it("Si le parsage s'est bien déroulé, le résultat doit être récupéré.", () => { expect(converter.datas).toEqual(converter.parser.parseResults.datas); expect(converter.fields).toEqual(converter.parser.parseResults.fields); }); it("Si le parsage s'est bien déroulé et qu'un élément HTML est renseigné pour recevoir les données, un premier affichage doit être demandé.", async () => { spyOn(converter, "refreshView"); converter.datasViewElt={ id:"datas" }; await converter.run(); expect(converter.refreshView).toHaveBeenCalled(); }); it("Si le parsage s'est bien déroulé, mais qu'aucun élément HTML n'est renseigné pour recevoir les données, l'affichage ne doit pas être demandé.", async () => { spyOn(converter, "refreshView"); await converter.run(); expect(converter.refreshView).not.toHaveBeenCalled(); }); }); describe("Tests et configurations après parsage.", () => { let simpleSort: (a: number, b: number) => number; beforeEach( async () => { converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); await converter.run(); // simple parsage, car aucun élément indiqué pour l'affichage simpleSort = (a: number, b: number) => { if(a < b) return 1; else if(a > b) return -1; else return 0; }; }); it("Le test d'existence d'un champ doit retourner false s'il est lancé avant que les données n'aient été parsées.", () => { converter=new FreeDatas2HTML("CSV"); expect(converter.checkFieldExist(0)).toBeFalse(); // Dans le cas d'un parsage ne retournant rien, c'est le parseur qui doit générer une erreur. }); it("Doit retourner false si le numéro de champ n'est pas trouvé dans les données parsées.", () => { let check=converter.checkFieldExist(-2); expect(check).toBeFalse(); check=converter.checkFieldExist(1.1); expect(check).toBeFalse(); check=converter.checkFieldExist(10); expect(check).toBeFalse(); }); it("Doit retourner true si le numéro de champ est bien trouvé dans les données parsées.", () => { let check=converter.checkFieldExist(0); expect(check).toBeTrue(); check=converter.checkFieldExist(2); expect(check).toBeTrue(); }); it("Doit générer une erreur si tous les champs devant être affichés ne sont pas présents dans les données parsées.", () => { expect(() => { return converter.fields2Rend=[0,2,8]; }).toThrowError(errors.converterFieldNotFound); }); it("Doit accepter un tableau vide pour la liste de champs à afficher.", () => { expect(() => { return converter.fields2Rend=[]; }).not.toThrowError(); expect(converter.fields2Rend.length).toEqual(0); }); it("Si tous les champs devant être affichés sont trouvés dans les données, ils doivent être acceptés.", () => { expect(() => { return converter.fields2Rend=[0,1,2]; }).not.toThrowError(); expect(converter.fields2Rend).toEqual([0,1,2]); // On peut même changer d'avis : converter.fields2Rend=[1,2,3]; expect(converter.fields2Rend).toEqual([1,2,3]); }); it("Doit retourner false si le numéro de champ n'est pas trouvé dans ceux devant être affichés.", () => { let check=converter.checkField2Rend(10); expect(check).toBeFalse(); converter.fields2Rend=[]; // = tous les champs affichés check=converter.checkField2Rend(10); expect(check).toBeFalse(); converter.fields2Rend=[1,3,4]; check=converter.checkField2Rend(2); expect(check).toBeFalse(); }); it("Doit retourner true si le numéro de champ est bien trouvé dans ceux devant être affichés.", () => { let check=converter.checkField2Rend(2); expect(check).toBeTrue(); converter.fields2Rend=[]; // = tous les champs affichés check=converter.checkField2Rend(2); expect(check).toBeTrue(); converter.fields2Rend=[1,3,4]; check=converter.checkField2Rend(4); expect(check).toBeTrue(); }); it("Doit retourner le rang d'un champ parmis ceux devant être affichés et -1 s'il n'est pas trouvé.", () => { // Pas de champs sélectionnés, on recherche parmis tous les champs parsés : expect(converter.getFieldDisplayRank(10)).toEqual(-1); expect(converter.getFieldDisplayRank(0)).toEqual(0); expect(converter.getFieldDisplayRank(3)).toEqual(3); // Seuls certains champs doivent être affichés : converter.fields2Rend=[1,3,4]; expect(converter.getFieldDisplayRank(2)).toEqual(-1); expect(converter.getFieldDisplayRank(1)).toEqual(0); expect(converter.getFieldDisplayRank(3)).toEqual(1); expect(converter.getFieldDisplayRank(4)).toEqual(2); }); it("Doit retourner la liste des champs devant être affichés.", () => { // Pas de champs sélectionnés = tous les champs parsés doivent être affichés : expect(converter.realFields2Rend()).toEqual(converter.fields); // Seuls certains champs doivent être affichés : converter.fields2Rend=[1,3,4]; expect(converter.realFields2Rend()).toEqual([converter.fields[1], converter.fields[3], converter.fields[4]]); }); it("Doit générer une erreur si une fonction est associée à un champ n'existant pas dans les données.", () => { expect(() => { return converter.datasSortingFunctions=[{ datasFieldNb:10, sort:simpleSort }]; }).toThrowError(errors.converterFieldNotFound); }); it("Doit accepter la fonction associée à un champ, de manière à ce qu'elle soit utilisable pour comparer deux valeurs.", () => { expect(() => { return converter.datasSortingFunctions=[{ datasFieldNb:0, sort:simpleSort }]; }).not.toThrowError(); expect(converter.getSortingFunctionForField(0)).toBeDefined(); expect([7,9,3,5].sort(converter.getSortingFunctionForField(0).sort)).toEqual([9,7,5,3]); }); }); describe("Fonction actualisant l'affichage.", () => { beforeEach( async () => { converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); await converter.run(); // récupére les données sans actualiser affichage car élement HTML non connu converter.datasViewElt={ id:"datas" }; // pour la suite, si ! :) }); it("Doit générer une erreur si appelée avant d'avoir récupérer des données à afficher.", () => { converter=new FreeDatas2HTML("CSV"); converter.datasViewElt={ id:"datas" }; expect(() => { return converter.refreshView(); }).toThrowError(errors.converterRefreshFail); }); it("Doit générer une erreur si appelée sans avoir fourni d'élément HTML où afficher les données.", async () => { converter=new FreeDatas2HTML("CSV"); converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); await converter.run(); expect(() => { return converter.refreshView(); }).toThrowError(errors.converterRefreshFail); }); it("Si les champs à afficher n'ont pas été renseignés, tous ceux trouvés par le parseur doivent tous être transmis au moteur de rendu.", () => { converter.refreshView(); expect(converter.datasRender.fields).toEqual(converter.parser.parseResults.fields); }); it("Si dans champs à afficher ont été renseignés, ils doivent être transmis au moteur de rendu", () => { converter.fields2Rend=[0,2]; converter.refreshView(); expect(converter.datasRender.fields).toEqual([converter.parser.parseResults.fields[0],converter.parser.parseResults.fields[2]]); }); it("Doit appeler la fonction préparant les données à afficher et transmettre le résultat au moteur de rendu.", () => { spyOn(converter, "datas2HTML").and.callThrough(); converter.refreshView(); expect(converter.datas2HTML).toHaveBeenCalled(); expect(converter.datasRender.datas).toEqual(converter.datas2Rend); }); it("Doit appeler le moteur de rendu et afficher le résultat dans la page.", async () => { converter=new FreeDatas2HTML("CSV", "name,firstname,birthday\ndoe,john,2000/12/25"); await converter.run();// parse sans rien afficher converter.datasViewElt={ id:"datas" }; spyOn(converter.datasRender, "rend2HTML").and.callThrough(); converter.refreshView(); expect(converter.datasRender.rend2HTML).toHaveBeenCalled(); // Pour ce test, les données à afficher doivent être assez simples, car certains caractères peuvent être remplacés par innerHTML (exemples :"<" ou ">") expect(document.getElementById("datas").innerHTML).toEqual(converter.datasRender.rend2HTML()); }); it("Doit appeler la fonction actualisant le compteur d'enregistrements.", () => { spyOn(converter, "datasCounter2HTML"); converter.refreshView(); expect(converter.datasCounter2HTML).toHaveBeenCalled(); }); it("Si un élément HTML devant affiché le nombre de résultats est connu, il doit être actualisé.", async () => { converter.datasCounterElt={ id: "counter" }; converter.refreshView(); expect(document.getElementById("counter").innerHTML).toEqual(""+converter.nbDatasValid); // Y compris quand aucune donnée trouvée : converter.parser=new ParserForCSV(); converter.parser.datas2Parse="name,firstname,city"; await converter.run(); expect(document.getElementById("counter").textContent).toEqual("0"); }); it("Si des champs de classement existent, leur code HTML doit être actualisé.", () => { const sortingField1=new SortingField(converter, 0); const sortingField2=new SortingField(converter, 1); converter.datasSortingFields=[sortingField1,sortingField2]; spyOn(sortingField1, "field2HTML"); spyOn(sortingField2, "field2HTML"); converter.refreshView(); expect(sortingField1.field2HTML).toHaveBeenCalled(); expect(sortingField2.field2HTML).toHaveBeenCalled(); }); it("Si une pagination est configurée, le code HTML listant les pages doit être actualisé.", () => { const pagination=new Pagination(converter, { id:"pages" }, "Page à afficher :"); converter.pagination=pagination; spyOn(pagination, "pages2HTML"); converter.refreshView(); expect(pagination.pages2HTML).toHaveBeenCalled(); }); }); describe("Fonction filtrant les données à afficher.", () => { beforeEach( async () => { converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" }); converter.datasViewElt={ id:"datas" }; await converter.run(); }); it("Si un champ de classement est activé par l'utilisateur, les données doivent être classées via ce champ.", () => { // Semble compliqué de tester que sort() a été appelée avec la bonne fonction de classement en paramètre. // Donc je compare les résultats à ceux attendus en cliquant sur le 1er champ. const sortingField=new SortingField(converter, 0); converter.datasSortingFields=[sortingField]; sortingField.field2HTML(); const getTHLink=document.querySelector("th a") as HTMLElement; const fieldName=converter.fields[0]; getTHLink.click(); converter.datas.sort( (a, b) => compare( {order: "asc"} )(a[fieldName], b[fieldName])); expect(converter.datas2Rend).toEqual(converter.datas); getTHLink.click(); converter.datas.sort( (a, b) => compare( {order: "desc"} )(a[fieldName], b[fieldName])); expect(converter.datas2Rend).toEqual(converter.datas); getTHLink.click(); converter.datas.sort( (a, b) => compare( {order: "asc"} )(a[fieldName], b[fieldName])); expect(converter.datas2Rend).toEqual(converter.datas); }); it("Si une fonction de classement est définie pour le champ cliqué par l'utilisateur, elle doit être prise en compte.", () => { const mySort = (a: any, b: any, order: "asc"|"desc" = "asc") => { const values = [ "> 100000", "> 1 et < 100 000", "≤ 1", "Traces", "Inexistant"]; if(order === "desc") values.reverse(); if(values.indexOf(a) > values.indexOf(b)) return -1; else if(values.indexOf(a) < values.indexOf(b)) return 1; else return 0; }; converter.datasSortingFunctions=[{ datasFieldNb:4, sort:mySort }]; const sortingField=new SortingField(converter, 4); converter.datasSortingFields=[sortingField]; sortingField.field2HTML(); const fieldName=converter.fields[4]; const getTHLink=document.querySelectorAll("th a") as NodeListOf; getTHLink[0].click(); converter.datas.sort( (a, b) => { return mySort(a[fieldName], b[fieldName], "asc"); }); expect(converter.datas2Rend).toEqual(converter.datas); getTHLink[0].click(); converter.datas.sort( (a, b) => { return mySort(a[fieldName], b[fieldName], "desc"); }); expect(converter.datas2Rend).toEqual(converter.datas); getTHLink[0].click(); converter.datas.sort( (a, b) => { return mySort(a[fieldName], b[fieldName], "asc"); }); expect(converter.datas2Rend).toEqual(converter.datas); }); it("Par défaut, tous les champs trouvés doivent être affichés.", async () => { converter=new FreeDatas2HTML("CSV","id,firstname,name,birthday\n1,john,doe,2001/12/25\n2,donald,duck,1934/06/09"); converter.datasViewElt={ id:"datas" }; await converter.run(); converter.refreshView(); expect(converter.datas2Rend).toEqual([{ id:"1", firstname:"john", name:"doe", birthday:"2001/12/25" }, { id:"2", firstname:"donald", name:"duck", birthday:"1934/06/09" }]); }); it("Si des options de pagination sont activées, seules les données de la page choisie doivent être retournées.", () => { const pagination=new Pagination(converter, { id:"pages" }, "Page à afficher :"); pagination.options={ displayElement: { id:"paginationOptions" }, values: [10,20,50] , name: "Choix de pagination :" }; pagination.selectedValue=10; converter.pagination=pagination; pagination.options2HTML(); converter.refreshView(); // il ne doit plus rester que les 10 premiers enregistrement expect(converter.datas2Rend).toEqual(converter.datas.slice(0,10)); // Sélection de la dernière page, avec une pagination à 50 : const selectPagination=document.getElementById("freeDatas2HTMLPaginationSelector") as HTMLInputElement; selectPagination.value="3"; selectPagination.dispatchEvent(new Event('change')); const selectPage=document.getElementById("freeDatas2HTMLPagesSelector") as HTMLInputElement; selectPage.value="3"; selectPage.dispatchEvent(new Event('change')); expect(converter.datas2Rend).toEqual(converter.datas.slice(100)); // Annulation de la pagination. Affiche toutes les données : selectPagination.value="0"; selectPagination.dispatchEvent(new Event('change')); expect(converter.datas2Rend).toEqual(converter.datas); }); it("Si des filtres sont déclarés, ils doivent tous être appelés pour tester les données à afficher.", () => { const filter1=new Selector(converter, 3, { id:"selector1"} ); filter1.filter2HTML(); const filter2=new Selector(converter, 4, { id:"selector2"} ); converter.datasFilters=[filter1,filter2]; // Nécessaire de vraiment lancer le 1er, sinon le second est bloqué, car cela retourne un "false" spyOn(filter1, "dataIsOk").and.callThrough(); spyOn(filter2, "dataIsOk"); converter.refreshView(); expect(filter1.dataIsOk).toHaveBeenCalledTimes(118); expect(filter2.dataIsOk).toHaveBeenCalledTimes(118); }); it("Quand il y a plusieurs filtres, seules les données positives aux précédents sont testées par les suivants.", () => { const filter1=new Selector(converter, 3, { id:"selector1"} ); filter1.filter2HTML(); const filter2=new Selector(converter, 4, { id:"selector2"} ); converter.datasFilters=[filter1,filter2]; const selectElement=document.getElementById("freeDatas2HTML_selector1") as HTMLInputElement; selectElement.value="2"; // correspond à 4 enregistrements spyOn(filter1, "dataIsOk").and.callThrough(); spyOn(filter2, "dataIsOk"); // Doit vraiment être lancé pour que la valeur sélectionnée soit retenue pour filter les données selectElement.dispatchEvent(new Event('change')); expect(filter1.dataIsOk).toHaveBeenCalledTimes(118); expect(filter2.dataIsOk).toHaveBeenCalledTimes(4); }); it("Si une liste de champs à afficher a été renseignée, elle doit être prise en compte.", async () => { converter=new FreeDatas2HTML("CSV","id,firstname,name,birthday\n1,john,doe,2001/12/25\n2,donald,duck,1934/06/09"); converter.datasViewElt={ id:"datas" }; await converter.run(); converter.fields2Rend=[1,2]; converter.refreshView(); expect(converter.datas2Rend).toEqual([{ firstname:"john", name:"doe" }, { firstname:"donald", name:"duck" }]); // Même quand les données sont dans le désordre : converter=new FreeDatas2HTML("JSON", JSON.stringify([{nom:"dugenoux","prénom":"henri","âge":25},{nom:"michu","âge":58,"prénom":"mariette"}])); await converter.run(); converter.fields2Rend=[0,1]; converter.datasViewElt={ id:"datas" }; converter.refreshView(); expect(converter.datas2Rend).toEqual([{"nom": "dugenoux", "prénom": "henri"}, { nom:"michu", "prénom":"mariette" }]); }); }); });