From 036c3b3400a436f671d8ba91af49569598dec01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20PENHO=C3=8BT?= Date: Tue, 19 Oct 2021 11:46:35 +0200 Subject: [PATCH] Relecture code et tests du parseur HTML. --- src/ParserForHTML.ts | 24 ++++++++++-------------- tests/parserForHTMLSpec.ts | 32 ++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/ParserForHTML.ts b/src/ParserForHTML.ts index d69428f..5ecc1ab 100644 --- a/src/ParserForHTML.ts +++ b/src/ParserForHTML.ts @@ -5,13 +5,12 @@ import { RemoteSource } from "./RemoteSource"; export class ParserForHTML implements Parsers { private _datasRemoteSource: RemoteSources; - private _document2Parse: HTMLDocument=document; + private _document2Parse: HTMLDocument=document; private _parseResults: ParseResults|undefined=undefined; private _fieldsSelector: string="table > thead > tr > th"; private _rowsSelector: string="table > tbody > tr"; private _datasSelector: string="tr > td"; - // L'instance d'une autre classe que RemoteSource peut être passée au constructeur constructor(datasRemoteSource?: RemoteSources) { if(datasRemoteSource !== undefined) @@ -81,14 +80,13 @@ export class ParserForHTML implements Parsers public async parse(): Promise { - const parser=this; const realFields: string[]=[], datas: {[index: string]:string}[]=[], parseErrors: ParseErrors[]=[]; // Document HTML distant ? - if(parser._datasRemoteSource.url !== "") + if(this._datasRemoteSource.url !== "") { - const settings: {}=parser._datasRemoteSource.getFetchSettings(); - const response=await fetch(parser._datasRemoteSource.url, settings); + const settings: {}=this._datasRemoteSource.getFetchSettings(); + const response=await fetch(this._datasRemoteSource.url, settings); if (! response.ok) throw new Error(errors.parserRemoteFail); const responseHTML=await response.text(); @@ -96,8 +94,7 @@ export class ParserForHTML implements Parsers this._document2Parse=parserDOM.parseFromString(responseHTML, "text/html"); } - // Récupérer les noms des champs qui doivent avoir été fournis - // Ils ne peuvent être une chaîne vide ou en doublon + // Récupération du noms de champs const fields=this._document2Parse.querySelectorAll(this._fieldsSelector); if(fields.length === 0) throw new Error(errors.parserElementsNotFound+this._fieldsSelector); @@ -107,13 +104,12 @@ export class ParserForHTML implements Parsers if(checkField !== "" && realFields.indexOf(checkField) === -1) realFields.push(checkField); else - console.error(errors.parserFieldNameFail); // lancer une exception, car risque de bugs par la suite ??? + parseErrors.push({ row:-1, message: errors.parserFieldNameFail}); } if(realFields.length === 0) throw new Error(errors.parserFieldsNotFound); - // Puis récupération des données. - // Il peut n'y avoir aucune ligne si aucune donnée trouvée. + // Puis récupération des éventuelles données. const rows=this._document2Parse.querySelectorAll(this._rowsSelector); let datasElts; for(let i=0; i < rows.length; i++) @@ -121,7 +117,7 @@ export class ParserForHTML implements Parsers // Les nombre de données par ligne ne devrait pas être différent du nombre de champs. datasElts=rows[i].querySelectorAll(this._datasSelector); if(datasElts.length !== realFields.length) - parseErrors.push({ row:i, message:errors.parserMeetErrors}); + parseErrors.push({ row:i, message:errors.parserNumberOfFieldsFail}); // Les chaînes vides sont par contre acceptées ici. let dataObject: {[index: string]: string} = {} for(let j=0; j < datasElts.length && j < realFields.length; j++) @@ -130,9 +126,9 @@ export class ParserForHTML implements Parsers if(Object.keys(dataObject).length !== 0) datas.push(dataObject) else - parseErrors.push({ row:i, message:errors.parserMeetErrors}); + parseErrors.push({ row:i, message: errors.parserLineWithoutDatas}); } - parser._parseResults = + this._parseResults = { datas: datas, errors: parseErrors, diff --git a/tests/parserForHTMLSpec.ts b/tests/parserForHTMLSpec.ts index 4ad9a8e..ef686b1 100644 --- a/tests/parserForHTMLSpec.ts +++ b/tests/parserForHTMLSpec.ts @@ -21,9 +21,10 @@ describe("Tests du parseur HTML", () => it("Doit avoir créé une instance du parseur.", () => { expect(parser).toBeInstanceOf(Parser); + expect(parser.datasRemoteSource.url).toEqual(""); }); - it("Ne doit pas accepter des valeurs vides pour les sélecteurs CSS.", () => + it("Ne doit pas accepter de valeurs vides pour les sélecteurs CSS.", () => { expect(() => { return parser.fieldsSelector=""; }).toThrowError(errors.parserSelectorsIsEmpty); expect(() => { return parser.rowsSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty); @@ -47,30 +48,42 @@ describe("Tests du parseur HTML", () => const doc=parserDOM.parseFromString(htmlString, "text/html"); expect(parser.document2Parse).toEqual(doc); }); + + it("Si des options de connexion distante sont fournies, elles doivent être utilisées.", async () => + { + spyOn(window, "fetch").and.callThrough(); + parser.setRemoteSource({ url:"http://localhost:9876/datas/datas.html", headers: [{ key:"token", value:"1234" }, { key:"userName", value:"Toto" }], withCredentials:true }); + await parser.parse(); + const headers=new Headers(); + headers.append("token", "1234"); + headers.append("userName", "Toto"); + expect(window.fetch).toHaveBeenCalledWith("http://localhost:9876/datas/datas.html", { method: "GET", headers: headers, credentials: "include" }); + }); }); describe("Extraction des données du document.", () => { it("Doit générer une erreur si aucun élément n'est trouvé dans le document pour les sélecteurs CSS fournis pour les noms de champs.", async () => { - await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserElementsNotFound+"table > thead > tr > th")); // valeurs par défaut, mais rien dans le DOM + await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserElementsNotFound+"table > thead > tr > th")); // = valeurs par défaut, mais rien dans le DOM datasElt.innerHTML=fixtures.htmlParserDatas; await expectAsync(parser.parse()).not.toBeRejectedWith(new Error(errors.parserElementsNotFound+"table > thead > tr > th")); parser.fieldsSelector="ul#dontExist > li"; // n'existe pas dans ce qui a été injecté await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserElementsNotFound+"ul#dontExist > li")); }); - it("Les noms de champ vides ou en doublon doivent être ignorés.", async () => + it("Les noms de champ vides ou en doublon doivent être ignorés et les erreurs reportées.", async () => { datasElt.innerHTML=""; parser.fieldsSelector="ul > li"; await parser.parse(); expect(parser.parseResults.fields).toEqual(["Champ1","Champ2"]); + expect(parser.parseResults.errors).toEqual([{ row:-1, message: errors.parserFieldNameFail}, { row:-1, message: errors.parserFieldNameFail}]); }); - it("Au moins un nom de champ valide doit avoir été trouvé.", async () => + it("Doit générer une erreur, si aucun nom de champ valide n'est trouvé dans le document.", async () => { - datasElt.innerHTML=""; + datasElt.innerHTML=""; parser.fieldsSelector="ul > li"; await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserFieldsNotFound)); }); @@ -87,13 +100,13 @@ describe("Tests du parseur HTML", () => datasElt.innerHTML=fixtures.htmlParserDatasBadFields; // un "td" manquant en ligne 0 et un en trop en ligne 1 await parser.parse(); expect(parser.parseResults.datas).toEqual([{"Z (numéro atomique)":"1","Élément":"Hydrogène", Symbole:"H", Famille:"Non-métal" }, {"Z (numéro atomique)":"2","Élément":"Hélium", Symbole:"He", Famille:"Gaz noble", "Mots-clés":"Mot-clé2" }, {"Z (numéro atomique)":"3","Élément":"Lithium", Symbole:"Li", Famille:"Métal alcalin", "Mots-clés":"Mot-clé2,Mot-clé1" }, {"Z (numéro atomique)":"4","Élément":"Béryllium", Symbole:"Be", Famille:"Métal alcalino-terreux", "Mots-clés":"Mot-clé3" }]); - expect(parser.parseResults.errors[0]).toEqual({row:0,message:errors.parserMeetErrors}); - expect(parser.parseResults.errors[1]).toEqual({row:1,message:errors.parserMeetErrors}); + expect(parser.parseResults.errors[0]).toEqual({row:0,message:errors.parserNumberOfFieldsFail}); + expect(parser.parseResults.errors[1]).toEqual({row:1,message:errors.parserNumberOfFieldsFail}); }); it("Le fait qu'aucune donnée ne soit trouvée ne doit pas générer une erreur.", async () => { - datasElt.innerHTML="
Champ1Champ2
" + datasElt.innerHTML="
Champ1Champ2
"; await expectAsync(parser.parse()).toBeResolved(); }); @@ -102,7 +115,7 @@ describe("Tests du parseur HTML", () => datasElt.innerHTML=fixtures.htmlParserDatasEmptyLine; // avant dernière ligne sans "td", "Mots-clé" vide pour la 1ière ligne. await parser.parse(); expect(parser.parseResults.datas).toEqual([{"Z (numéro atomique)":"1","Élément":"Hydrogène", Symbole:"H", Famille:"Non-métal", "Mots-clés":"" }, {"Z (numéro atomique)":"2","Élément":"Hélium", Symbole:"He", Famille:"Gaz noble", "Mots-clés":"Mot-clé2" }, {"Z (numéro atomique)":"3","Élément":"Lithium", Symbole:"Li", Famille:"Métal alcalin", "Mots-clés":"Mot-clé2,Mot-clé1" }, {"Z (numéro atomique)":"4","Élément":"Béryllium", Symbole:"Be", Famille:"Métal alcalino-terreux", "Mots-clés":"Mot-clé3" }]); - expect(parser.parseResults.errors[0]).toEqual({row:3,message:errors.parserMeetErrors}); + expect(parser.parseResults.errors[1]).toEqual({row:3,message:errors.parserLineWithoutDatas});// errors[0] = erreur nombre de champs 1ère ligne }); it("Si le code HTML fourni est ok, aucune erreur de lecture ne doit être reportée.", async () => @@ -112,6 +125,5 @@ describe("Tests du parseur HTML", () => expect(parser.parseResults.datas).toEqual([{"Z (numéro atomique)":"1","Élément":"Hydrogène", Symbole:"H", Famille:"Non-métal", "Mots-clés":"" }, {"Z (numéro atomique)":"2","Élément":"Hélium", Symbole:"He", Famille:"Gaz noble", "Mots-clés":"Mot-clé2" }, {"Z (numéro atomique)":"3","Élément":"Lithium", Symbole:"Li", Famille:"Métal alcalin", "Mots-clés":"Mot-clé2,Mot-clé1" }, {"Z (numéro atomique)":"4","Élément":"Béryllium", Symbole:"Be", Famille:"Métal alcalino-terreux", "Mots-clés":"Mot-clé3" }]); expect(parser.parseResults.errors.length).toEqual(0); }); - }); }); \ No newline at end of file