Ajout parseur HTML + ses tests.

This commit is contained in:
Fabrice PENHOËT 2021-10-13 12:41:34 +02:00
parent 3e62258518
commit 0b7ed284ae
10 changed files with 379 additions and 11 deletions

View File

@ -1,6 +1,6 @@
{
"name": "freedatas2html",
"version": "0.7.4",
"version": "0.8.0",
"description": "Conversion and display of data in different formats (CSV, JSON, HTML) with the possibility of filtering, classifying and paginating the results.",
"main": "index.js",
"scripts": {

View File

@ -7,12 +7,12 @@
<meta name="robots" content="noindex">
<link rel="stylesheet" href="CSS/paper.min.css">
<link rel="stylesheet" href="CSS/fd2html.css">
<script src="JS/exampleWithCSV.app.js" defer></script>
<title>freeDatas2HTML : démo d'affichage de données venant d'un fichier CSV</title>
<script src="JS/exampleWithHTML.app.js" defer></script>
<title>freeDatas2HTML : démo d'affichage de données venant du DOM (HTML)</title>
</head>
<body class="paper container">
<h1>freeDatas2HTML</h1>
<p><a href="./withJSON.html">Autre page d'exemple avec des données transmises en JSON</a>.</p>
<p>Autres pages d'exemple avec des données transmises <a href="./withJSON.html">en JSON</a> ou <a href="./withCSV.html">en CSV</a>.</p>
<div class="row">
<div id="filtre1" class="sm-12 md-6 lg-4 col"></div>
@ -23,15 +23,113 @@
<h3>Nombre total de résultats : <span id="compteur"></span></h3>
<article id="datas">
<p>Si tout se passe bien, ce texte sera remplacé par un tableau de données extraites du fichier csv.</p>
<table>
<thead>
<tr>
<th>Séquence</th><th>Signification</th><th>Décrit en section</th><th>Défini originellement en CSS niveau</th>
</tr>
</thead
<tbody>
<tr><td>*</td><td>tout élément</td><td>Sélecteur universel</td><td>2</td></tr>
<tr><td>E</td><td>tout élément de type E</td><td>Sélecteur de type d'élément</td><td>1</td></tr>
<tr><td>E[foo]</td><td>tout élément E portant l'attribut "foo"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
<tr><td>E[foo="bar"]</td><td>tout élément E portant l'attribut"
foo" et dont la valeur de cet attribut est exactement "bar"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
<tr><td>E[foo~="bar"]</td><td>tout élément E dont l'attribut "foo"
contient une liste de valeurs séparées par des espaces, l'une
de ces valeurs étant exactement égale à "bar"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
<tr><td>E[foo^="bar"]</td><td>tout élément E dont la valeur de l'attribut
"foo" commence exactement par la chaîne "bar"</td><td>Sélecteurs d'attribut</td><td>3</td></tr>
<tr><td>E[foo$="bar"]</td><td>tout élément E dont la valeur de l'attribut
"foo" finit exactement par la chaîne "bar"</td><td>Sélecteurs d'attribut</td><td>3</td></tr>
<tr><td>E[foo*="bar"]</td><td>tout élément E dont la valeur de l'attribut
"foo" contient la sous-chaîne "bar"</td><td>Sélecteurs d'attribut</td><td>3</td></tr>
<tr><td>E[lang|="en"]</td><td>tout élément E dont l'attribut 'lang"
est une liste de valeurs séparées par des tirets et commençant
(à gauche) par "en"</td><td>Sélecteurs d'attribut</td><td>2</td></tr>
<tr><td>E:root</td><td>un élément E, racine du document</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:nth-child(n)</td><td>un élément E qui est le n-ième enfant
de son parent</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:nth-last-child(n)</td><td>un élément E qui est le n-ième enfant
de son parent en comptant depuis le dernier enfant</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:nth-of-type(n)</td><td>un élément E qui est le n-ième enfant
de son parent et de ce type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:nth-last-of-type(n)</td><td>un élément E qui est le n-ième enfant
de son parent et de ce type en comptant depuis le dernier enfant</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:first-child</td><td>un élément E, premier enfant de son parent</td><td>Pseudo-classes structurelles</td><td>2</td></tr>
<tr><td>E:last-child</td><td>un élément E, dernier enfant de son parent</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:first-of-type</td><td>un élément E, premier enfant de son type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:last-of-type</td><td>un élément E, dernier enfant de son type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr>
<td height="19">E:only-child</td><td height="19">un élément E, seul enfant de
son parent</td><td height="19">Pseudo-classes
structurelles</td><td height="19">3</td></tr>
<tr><td>E:only-of-type</td><td>un élément E, seul enfant de son type</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:empty</td><td>un élément E qui n'a aucun enfant (y compris
noeuds textuels purs)</td><td>Pseudo-classes structurelles</td><td>3</td></tr>
<tr><td>E:link <br>
E:visited</td><td>un élément E qui est la source d'un hyperlien
dont la cible n'a pas encore été visitée (:link) ou
a déjà été visitée (:visited)</td><td>Les pseudo-classes de lien</td><td>1</td></tr>
<tr><td>E:active <br>
E:hover <br>
E:focus</td><td>un élément E pendant certaines actions de
l'usager</td><td>Les pseudo-classes d'action
Usager </td><td>1 and 2</td></tr>
<tr><td>E:target</td><td>un élément E qui est la cible de l'URL d'origine
contenant lui-même un fragment identifiant.</td><td>La pseudo-classe :target</td><td>3</td></tr>
<tr><td>E:lang(c)</td><td>un élément E dont le langage (humain) est
c (le langage du document spécifie comment le langage humain est
déterminé)</td><td>La pseudo-classe :lang()&nbsp;</td><td>2</td></tr>
<tr><td>E:enabled<br>
E:disabled&nbsp;</td><td>un élément d'interface utilisateur E qui
est actif ou inactif.</td><td>Les pseudo-classes d'état
d'élément d'interface</td><td>3</td></tr>
<tr><td>E:checked<br>
E:indeterminate&nbsp;</td><td>un élément d'interface utilisateur E qui
est coché ou dont l'état est indéterminé (par
exemple un bouton-radio ou une case à cocher)</td><td>Les pseudo-classes d'état
d'élément d'interface</td><td>3</td></tr>
<tr><td>E:contains("foo")</td><td>un élément E dont le contenu textuel concaténé
contient la sous-chaîne "foo"</td><td>La pseudo-classe de contenu</td><td>3</td></tr>
<tr><td>E::first-line</td><td>la première ligne formatée d'un élément
E</td><td>The :first-line pseudo-element</td><td>1</td></tr>
<tr><td>E::first-letter</td><td>le premier caractère formaté d'un élément
E</td><td>Le pseudo-élément
::first-letter </td><td>1</td></tr>
<tr><td>E::selection</td><td>la partie d'un élément E qui est actuellement
sélectionnée/mise en exergue par l'usager</td><td>Les pseudo-éléments
fragments d'éléments d'interface</td><td>3</td></tr>
<tr><td>E::before</td><td>le contenu généré avant un élément
E</td><td>Le pseudo-élément
::before </td><td>2</td></tr>
<tr><td>E::after</td><td>le contenu généré après un
élément E</td><td>Le pseudo-élément
::after </td><td>2</td></tr>
<tr><td>E.warning</td><td><i>Uniquement en HTML</i>. Identique à E[class~="warning"].</td><td>Sélecteurs de classe</td><td>1</td></tr>
<tr><td>E#myid</td><td>un élément E dont l'ID est égal à
"myid".</td><td>Sélecteurs d'ID</td><td>1</td></tr>
<tr><td>E:not(s)</td><td> un élément E qui n'est pas représenté
par le sélecteur simple s</td><td>La pseudo-classe de négation</td><td>3</td></tr>
<tr><td>E F</td><td>un élément F qui est le descendant d'un
élément E</td><td>Combinateur de descendance
</td><td>1</td></tr>
<tr><td>E &gt; F</td><td>un élément F qui est le fils d'un élément
E</td><td>Combinateur filial</td><td>2</td></tr>
<tr><td>E + F</td><td>un élément F immédiatement précédé
par un élément E</td><td>Combinateur d'adjacence
directe</td><td>2</td></tr>
<tr><td>E ~ F</td><td>un élément F précédé
par un élément E</td><td>Combinateur d'adjacence
indirecte</td><td>3</td></tr>
</tbody>
</table>
</article>
<p id="pages"></p>
<aside><p>Les données affichées viennent d<a href="datas/elements-chimiques.csv">un fichier CSV</a> reprenant une partie des informations trouvées <a href="https://fr.wikipedia.org/wiki/%C3%89l%C3%A9ment_chimique#Caract%C3%A9ristiques_des_diff%C3%A9rents_%C3%A9l%C3%A9ments" target="_blank">sur Wikipédia</a>.</p>
<p>Le dernier filtre (Mots-clés) permet de montrer la possibilité d<b>extraire des données dun champ ayant plusieurs valeurs par ligne</b>, ici séparées par une virgule, sachant que lon peut désigner nimporte quel autre caractère séparateur.</p>
<aside><p>Le tableau vient du <a href="https://www.w3.org/Style/css3-selectors-updates/WD-css3-selectors-20010126.fr.html#selectors" targe="_blank">W3C</a> qui pourra vous donner quelques pistes pour créer vos sélecteurs CSS si besoin.</p>
<p>Certains champs (=colonnes) peuvent être désignées comme devant permettre de <b>classer les données</b>: un 1ᵉʳ clic lance un classement par ordre croissant, le 2ᵉ pour ordre décroissant et ainsi de suite.</p>
<p>Il est également possible dafficher le nombre total de résultats, avec prise en compte des éventuels filtres.</p>
<p>Une autre option permet de <b>paginer les résultats</b>.</p>
<p>Enfin il est possible d<b>afficher les données autrement que sous forme de tableau HTML</b>. Si vous affichez cette page sur un écran large de moins de 800px, les données seront simplement listées. Si vous avez un grand écran, vous pouvez visualiser cet affichage en faisant «Alt+Maj+M», puis «F5». Appuyez de nouveau sur «F5», une fois de retour sur grand écran pour revoir le tableau.</p>
<p>Design basé sur <a href="https://www.getpapercss.com" target="_blank">PaperCSS</a></p></aside>
</body>
</html>

View File

@ -10,11 +10,15 @@ module.exports =
paginationNeedOptionsValues: "Vous n'avez fourni aucune options possibles pour la pagination.",
paginationNeedPositiveInteger: "Merci de fournir un nombre entier supérieur à zéro pour désigner chaque option de pagination.",
parserDatasNotFound : "Aucune donnée n'a été trouvée.",
parserElementsNotFound: "Aucun élément trouvé dans le document pour le sélecteur fourni : ",
parserFail: "La lecture des données a échoué.",
parserFieldNameFail: "Les noms de champs fournis doivent être uniques et ne peuvent être vides.",
parserFieldsNotFound: "Aucun nom de champs n'a été trouvé par le parseur.",
parserMeetErrors : "Au moins une erreur a été rencontrée durant le traitement des données.",
parserNeedDatas: "Merci de fournir une chaîne de caractères valide à parser.",
parserNeedSource: "Merci de fournir une chaîne de caractères où une url pour les données à parser.",
parserRemoteFail: "Erreur rencontrée durant l'accès aux données distantes.",
parserSelectorsIsEmpty: "Les sélecteurs CSS ne peuvent pas être une chaîne vide.",
parserTypeError: "Une donnée a été trouvée avec un type imprévu : ",
remoteSourceHeaderUnallowed: "Le nom d'une des entêtes passées n'est pas autorisé.",
remoteSourceNeedUrl: "Merci de fournir une url valide pour la source distante de données.",

View File

@ -4,6 +4,7 @@ const errors=require("./errors.js");
import { Counter, Datas, DatasRenders, DOMElement, Paginations, Parsers, ParseErrors, RemoteSource, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces";
import { Pagination} from "./freeDatas2HTMLPagination";
import { ParserForCSV} from "./freeDatas2HTMLParserForCSV";
import { ParserForHTML} from "./freeDatas2HTMLParserForHTML";
import { ParserForJSON} from "./freeDatas2HTMLParserForJSON";
import { Render} from "./freeDatas2HTMLRender";
import { Selector } from "./freeDatas2HTMLSelector";
@ -61,8 +62,7 @@ export class FreeDatas2HTML
this.parser=new ParserForCSV(datasRemoteSource);
break;
case "HTML":
this.parser=new ParserForCSV(datasRemoteSource);
console.error("Appeler le parseur HTML");
this.parser=new ParserForHTML(datasRemoteSource);
break;
case "JSON":
this.parser=new ParserForJSON(datasRemoteSource);

View File

@ -67,7 +67,8 @@ export interface Parsers
{
datasRemoteSource: RemoteSource;
setRemoteSource(settings : RemoteSourceSettings): void;
datas2Parse: string;
datas2Parse?: string;
document2Parse?: HTMLDocument;
parseResults: ParseResults|undefined;
parse(): Promise<void>;
}

View File

@ -0,0 +1,142 @@
const errors=require("./errors.js");
import { ParseErrors, ParseResults, Parsers, RemoteSource, RemoteSourceSettings } from "./freeDatas2HTMLInterfaces";
import { RemoteSources } from "./freeDatas2HTMLRemoteSources";
export class ParserForHTML implements Parsers
{
private _datasRemoteSource: RemoteSource;
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?: RemoteSource)
{
if(datasRemoteSource !== undefined)
this._datasRemoteSource=datasRemoteSource;
else
this._datasRemoteSource=new RemoteSources({ url:"" });
}
public setRemoteSource(source: RemoteSourceSettings)
{
this._datasRemoteSource=new RemoteSources(source);
}
get datasRemoteSource() : RemoteSource
{
return this._datasRemoteSource;
}
get document2Parse() : HTMLDocument
{
return this._document2Parse;
}
set fieldsSelector(selector: string)
{
if(selector.trim() === "")
throw new Error(errors.parserSelectorsIsEmpty);
else
this._fieldsSelector=selector.trim();
}
get fieldsSelector() : string
{
return this._fieldsSelector;
}
set rowsSelector(selector: string)
{
if(selector.trim() === "")
throw new Error(errors.parserSelectorsIsEmpty);
else
this._rowsSelector=selector.trim();
}
get datasSelector() : string
{
return this._datasSelector;
}
set datasSelector(selector: string)
{
if(selector.trim() === "")
throw new Error(errors.parserSelectorsIsEmpty);
else
this._datasSelector=selector.trim();
}
get rowsSelector() : string
{
return this._rowsSelector;
}
get parseResults() : ParseResults|undefined
{
return this._parseResults;
}
public async parse(): Promise<any>
{
const parser=this;
const realFields: string[]=[], datas: {[index: string]:string}[]=[], parseErrors: ParseErrors[]=[];
// Document HTML distant ?
if(parser._datasRemoteSource.url !== "")
{
const settings: {}=parser._datasRemoteSource.getFetchSettings();
const response=await fetch(parser._datasRemoteSource.url, settings);
if (! response.ok)
throw new Error(errors.parserRemoteFail);
const responseHTML=await response.text();
const parserDOM=new DOMParser();
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
const fields=this._document2Parse.querySelectorAll(this._fieldsSelector);
if(fields.length === 0)
throw new Error(errors.parserElementsNotFound+this._fieldsSelector);
for(let i=0; i < fields.length; i++)
{
let checkField=(fields[i].textContent+"").trim(); // ajout de "" pour éviter erreur "TS2531: Object is possibly 'null'"
if(checkField !== "" && realFields.indexOf(checkField) === -1)
realFields.push(checkField);
else
console.error(errors.parserFieldNameFail); // lancer une exception, car risque de bugs par la suite ???
}
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.
const rows=this._document2Parse.querySelectorAll(this._rowsSelector);
let datasElts;
for(let i=0; i < rows.length; i++)
{
// 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});
// 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++)
dataObject[realFields[j]]=datasElts[j].textContent+"";
// Mais les lignes complétement vides doivent être ignorées.
if(Object.keys(dataObject).length !== 0)
datas.push(dataObject)
else
parseErrors.push({ row:i, message:errors.parserMeetErrors});
}
parser._parseResults =
{
datas: datas,
errors: parseErrors,
fields: realFields,
};
}
}

1
tests/datas/datas.html Normal file
View File

@ -0,0 +1 @@
<html><head></head><body><table><thead><tr><th>Champ 1</th><th>Champ 2</th></tr></thead></table></body></html>

View File

@ -13,4 +13,7 @@ module.exports =
lastLineForPageSelection1:"<td>100</td><td>Fermium</td><td>Fm</td><td>Actinide</td><td>Inexistant</td>",
firstLineForPageSelection2:"<td>101</td><td>Mendélévium</td><td>Md</td><td>Actinide</td><td>Inexistant</td>",
lastLineForPageSelection2:"<td>118</td><td>Oganesson</td><td>Og</td><td>Indéfinie</td><td>Inexistant</td>",
htmlParserDatas: "<table><thead><tr><th> Z (numéro atomique)</th><th> Élément </th><th>Symbole</th><th>Famille</th><th>Mots-clés</th></tr></thead><tbody><tr><td>1</td><td>Hydrogène</td><td>H</td><td>Non-métal</td><td></td></tr><tr><td>2</td><td>Hélium</td><td>He</td><td>Gaz noble</td><td>Mot-clé2</td></tr><tr><td>3</td><td>Lithium</td><td>Li</td><td>Métal alcalin</td><td>Mot-clé2,Mot-clé1</td></tr><tr><td>4</td><td>Béryllium</td><td>Be</td><td>Métal alcalino-terreux</td><td>Mot-clé3</td></tr></tbody></table>",
htmlParserDatasBadFields: "<table><thead><tr><th> Z (numéro atomique)</th><th> Élément </th><th>Symbole</th><th>Famille </th><th>Mots-clés</th></tr></thead><tbody><tr><td>1</td><td>Hydrogène</td><td>H</td><td>Non-métal</td></tr><tr><td>2</td><td>Hélium</td><td>He</td><td>Gaz noble</td><td>Mot-clé2</td><td>je suis le méchamp</td></tr><tr><td>3</td><td>Lithium</td><td>Li</td><td>Métal alcalin</td><td>Mot-clé2,Mot-clé1</td></tr><tr><td>4</td><td>Béryllium</td><td>Be</td><td>Métal alcalino-terreux</td><td>Mot-clé3</td></tr></tbody></table>",
htmlParserDatasEmptyLine: "<table><thead><tr><th> Z (numéro atomique)</th><th> Élément </th><th>Symbole</th><th>Famille</th><th>Mots-clés</th></tr></thead><tbody><tr><td>1</td><td>Hydrogène</td><td>H</td><td>Non-métal</td><td></td></tr><tr><td>2</td><td>Hélium</td><td>He</td><td>Gaz noble</td><td>Mot-clé2</td></tr><tr><td>3</td><td>Lithium</td><td>Li</td><td>Métal alcalin</td><td>Mot-clé2,Mot-clé1</td></tr><tr></tr><tr><td>4</td><td>Béryllium</td><td>Be</td><td>Métal alcalino-terreux</td><td>Mot-clé3</td></tr></tbody></table>",
}

118
tests/parserForHTMLSpec.ts Normal file
View File

@ -0,0 +1,118 @@
import { RemoteSource } from "../src/freeDatas2HTMLInterfaces";
import { ParserForHTML as Parser } from "../src/freeDatas2HTMLParserForHTML";
const errors=require("../src/errors.js");
const fixtures=require("./fixtures.js");
describe("Tests du parseur HTML", () =>
{
let parser: Parser, datasElt: HTMLElement;
beforeEach( () =>
{
parser=new Parser();
document.body.insertAdjacentHTML('afterbegin', "<div id='datas'></div>");
datasElt=document.getElementById("datas");
});
afterEach( () =>
{
document.body.removeChild(document.getElementById("datas"));
});
it("Doit avoir créé une instance du parseur.", () =>
{
expect(parser).toBeInstanceOf(Parser);
});
it("Ne doit pas accepter des valeurs vides pour les sélecteurs CSS.", () =>
{
expect(() => { return parser.fieldsSelector=""; }).toThrowError(errors.parserSelectorsIsEmpty);
expect(() => { return parser.rowsSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty);
expect(() => { return parser.datasSelector=" "; }).toThrowError(errors.parserSelectorsIsEmpty);
});
describe("Accès à des données distantes.", () =>
{
it("Doit générer une erreur, si l'accès aux données distantes ne fonctionne pas.", async () =>
{
parser.setRemoteSource({ url:"http://localhost:9876/datas/datas.htm" }); // une seule lettre vous manque...
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserRemoteFail));
});
it("Si le parseur est appelé avec une url correcte, le DOM du document distant doit être enregistré.", async () =>
{
parser.setRemoteSource({ url:"http://localhost:9876/datas/datas.html" });
await parser.parse();
const parserDOM=new DOMParser();
const htmlString="<html><head></head><body><table><thead><tr><th>Champ 1</th><th>Champ 2</th></tr></thead></table></body></html>";
const doc=parserDOM.parseFromString(htmlString, "text/html");
expect(parser.document2Parse).toEqual(doc);
});
});
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
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 () =>
{
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"]);
});
it("Au moins un nom de champ valide doit avoir été trouvé.", async () =>
{
datasElt.innerHTML="<ul><li></li><li> </li></ul>";
parser.fieldsSelector="ul > li";
await expectAsync(parser.parse()).toBeRejectedWith(new Error(errors.parserFieldsNotFound));
});
it("Les espaces entourant les noms de champs doivent être supprimés.", async () =>
{
datasElt.innerHTML=fixtures.htmlParserDatas;
await parser.parse();
expect(parser.parseResults.fields).toEqual(["Z (numéro atomique)","Élément", "Symbole", "Famille", "Mots-clés"]);
});
it("Si des champs en trop sont trouvés dans une ligne de données, ils doivent être ignorés. Idem pour les champs absents. Ces anomalies doivent être reportées.", async () =>
{
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});
});
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>"
await expectAsync(parser.parse()).toBeResolved();
});
it("Une ligne n'ayant aucune donnée sera ignorée et l'erreur reportée. Les valeurs vides sont par contre acceptées.", async () =>
{
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});
});
it("Si le code HTML fourni est ok, aucune erreur de lecture ne doit être reportée.", async () =>
{
datasElt.innerHTML=fixtures.htmlParserDatas;
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.length).toEqual(0);
});
});
});

View File

@ -6,6 +6,7 @@ module.exports =
entry:
{
exampleWithCSV: "./src/exampleWithCSV.ts",
exampleWithHTML: "./src/exampleWithHTML.ts",
exampleWithJSON: "./src/exampleWithJSON.ts",
},
output: