Relecture code et tests du parseur HTML.

This commit is contained in:
Fabrice PENHOËT 2021-10-19 11:46:35 +02:00
parent df2658686d
commit 036c3b3400
2 changed files with 32 additions and 24 deletions

View File

@ -11,7 +11,6 @@ export class ParserForHTML implements Parsers
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<any>
{
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,

View File

@ -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,28 +48,40 @@ 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="<ul><li>Champ1</li><li> </li><li>Champ2</li><li>Champ2</li></ul>";
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="<ul><li> </li><li> </li></ul>";
parser.fieldsSelector="ul > li";
@ -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="<table><thead><tr><th>Champ1</th><th>Champ2</th></tr></thead></table>"
datasElt.innerHTML="<table><thead><tr><th>Champ1</th><th>Champ2</th></tr></thead></table>";
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);
});
});
});