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 valide, 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() 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 que tous les champs doivent être affichés, leur liste doit être transmise au moteur de rendu sans altération.", () => { expect(converter.datasRender.fields).toEqual(converter.parser.parseResults.fields); }); it("Si le parsage s'est bien déroulé et qu'une liste de champs à afficher a été fournie, seuls ceux demandés doivent être transmis au moteur de rendu.", async () => { converter.fields2Rend=[1,2]; await converter.run(); expect(converter.datasRender.fields).toEqual([converter.parser.parseResults.fields[1],converter.parser.parseResults.fields[2]]); }); 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(); 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"); const check=converter.checkFieldExist(0); 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.", () => { 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.", () => { 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 reçues.", () => { 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 à afficher 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]); }); 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("Doit appelé 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 appelé 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(); // 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("Si un élément HTML devant affiché le nombre de résultats est connu, il doit être actualisé.", () => { converter.datasCounterElt={ id: "counter" }; converter.refreshView(); expect(document.getElementById("counter").innerHTML).toEqual(""+converter.nbDatasValid); }); it("Si des champs de classement existent, leur code HTML doit être actualisé.", () => { converter.refreshView(); // nécessaire pour que les champs soit trouvés dans le HTML 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 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" }]); }); 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 un champ de classement est activé par l'utilisateur, les données doivent être classées via ce champ.", () => { // Compliqué de tester avec spyOn que sort() a été appelée avec la bonne fonction de classement en paramètre // Donc je compare les résultats à ceux attendus const sortingField=new SortingField(converter, 0); converter.datasSortingFields=[sortingField]; sortingField.field2HTML(); const fieldName=converter.fields[0]; const getTHLink=document.querySelector("th a") as HTMLElement; 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 activé 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[0]; const getTHLink=document.querySelector("th a") as HTMLElement; getTHLink.click(); converter.datas.sort( (a, b) => { return mySort(a[fieldName], b[fieldName], "asc"); }); expect(converter.datas2Rend).toEqual(converter.datas); getTHLink.click(); converter.datas.sort( (a, b) => { return mySort(a[fieldName], b[fieldName], "desc"); }); expect(converter.datas2Rend).toEqual(converter.datas); getTHLink.click(); converter.datas.sort( (a, b) => { return mySort(a[fieldName], b[fieldName], "asc"); }); expect(converter.datas2Rend).toEqual(converter.datas); }); it("Si des options de pagination sont activées par l'utilisateur, 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]; // si le 1er n'est pas réellement lancé, 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); }); }); });