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 retourner la liste des champs devant être affichés en respectant l'ordre demandé.", () =>
{
converter.fields2Rend=[3,4,1];
expect(converter.realFields2Rend()).toEqual([converter.fields[3], converter.fields[4], converter.fields[1]]);
});
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" }]);
});
});
});