More unit tests

This commit is contained in:
Pieter Vander Vennet 2022-03-15 01:42:38 +01:00
parent 4f4fc650b1
commit 82e59bc1eb
16 changed files with 433 additions and 375 deletions

45
tests/CodeQuality.spec.ts Normal file
View file

@ -0,0 +1,45 @@
import {describe} from 'mocha'
import {exec} from "child_process";
/**
*
* @param forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot
* @param reason
* @private
*/
function detectInCode(forbidden: string, reason: string) {
const excludedDirs = [".git", "node_modules", "dist", ".cache", ".parcel-cache", "assets"]
exec("grep -n \"" + forbidden + "\" -r . " + excludedDirs.map(d => "--exclude-dir=" + d).join(" "), ((error, stdout, stderr) => {
if (error?.message?.startsWith("Command failed: grep")) {
console.warn("Command failed!")
return;
}
if (error !== null) {
throw error
}
if (stderr !== "") {
throw stderr
}
const found = stdout.split("\n").filter(s => s !== "").filter(s => !s.startsWith("./tests/") && !s.startsWith("./testLegacy/"));
if (found.length > 0) {
throw `Found a '${forbidden}' at \n ${found.join("\n ")}.\n ${reason}`
}
}))
}
describe("Code quality", () => {
it("should not contain reverse", () => {
detectInCode("reverse()", "Reverse is stateful and changes the source list. This often causes subtle bugs")
})
it("should not contain 'constructor.name'", () => {
detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.")
})
})

View file

@ -0,0 +1,150 @@
import {describe} from 'mocha'
import {expect} from 'chai'
import {TagsFilter} from "../../../Logic/Tags/TagsFilter";
import {And} from "../../../Logic/Tags/And";
import {Tag} from "../../../Logic/Tags/Tag";
import {TagUtils} from "../../../Logic/Tags/TagUtils";
import {Or} from "../../../Logic/Tags/Or";
import {RegexTag} from "../../../Logic/Tags/RegexTag";
describe("Tag optimalization", () => {
describe("And", () => {
it("with condition and nested and should be flattened", () => {
const t = new And(
[
new And([
new Tag("x", "y")
]),
new Tag("a", "b")
]
)
const opt =<TagsFilter> t.optimize()
expect(TagUtils.toString(opt)).eq(`a=b&x=y`)
})
it("with nested ors and commons property should be extracted", () => {
// foo&bar & (x=y | a=b) & (x=y | c=d) & foo=bar is equivalent too foo=bar & ((x=y) | (a=b & c=d))
const t = new And([
new Tag("foo","bar"),
new Or([
new Tag("x", "y"),
new Tag("a", "b")
]),
new Or([
new Tag("x", "y"),
new Tag("c", "d")
])
])
const opt =<TagsFilter> t.optimize()
expect(TagUtils.toString(opt)).eq("foo=bar& (x=y| (a=b&c=d) )")
})
it("should move regextag to the end", () => {
const t = new And([
new RegexTag("x","y"),
new Tag("a","b")
])
const opt =<TagsFilter> t.optimize()
expect(TagUtils.toString(opt)).eq("a=b&x~^y$")
})
it("should sort tags by their popularity (least popular first)", () => {
const t = new And([
new Tag("bicycle","yes"),
new Tag("amenity","binoculars")
])
const opt =<TagsFilter> t.optimize()
expect(TagUtils.toString(opt)).eq("amenity=binoculars&bicycle=yes")
})
it("should optimize an advanced, real world case", () => {
const filter = TagUtils.Tag( {or: [
{
"and": [
{
"or": ["amenity=charging_station","disused:amenity=charging_station","planned:amenity=charging_station","construction:amenity=charging_station"]
},
"bicycle=yes"
]
},
{
"and": [
{
"or": ["amenity=charging_station","disused:amenity=charging_station","planned:amenity=charging_station","construction:amenity=charging_station"]
},
]
},
"amenity=toilets",
"amenity=bench",
"leisure=picnic_table",
{
"and": [
"tower:type=observation"
]
},
{
"and": [
"amenity=bicycle_repair_station"
]
},
{
"and": [
{
"or": [
"amenity=bicycle_rental",
"bicycle_rental~*",
"service:bicycle:rental=yes",
"rental~.*bicycle.*"
]
},
"bicycle_rental!=docking_station"
]
},
{
"and": [
"leisure=playground",
"playground!=forest"
]
}
]});
const opt = <TagsFilter> filter.optimize()
const expected = "amenity=charging_station|" +
"amenity=toilets|" +
"amenity=bench|" +
"amenity=bicycle_repair_station" +
"|construction:amenity=charging_station|" +
"disused:amenity=charging_station|" +
"leisure=picnic_table|" +
"planned:amenity=charging_station|" +
"tower:type=observation| " +
"( (amenity=bicycle_rental|service:bicycle:rental=yes|bicycle_rental~^..*$|rental~^.*bicycle.*$) &bicycle_rental!~^docking_station$) |" +
" (leisure=playground&playground!~^forest$)"
expect(TagUtils.toString(opt).replace(/ /g, ""))
.eq(expected.replace(/ /g, ""))
})
})
describe("Or", () => {
it("with nested And which has a common property should be dropped", () => {
const t = new Or([
new Tag("foo","bar"),
new And([
new Tag("foo", "bar"),
new Tag("x", "y"),
])
])
const opt =<TagsFilter> t.optimize()
expect(TagUtils.toString(opt)).eq("foo=bar")
})
})
})

View file

@ -0,0 +1,56 @@
import {describe} from 'mocha'
import {expect} from 'chai'
import {TagUtils} from "../../../Logic/Tags/TagUtils";
import {Tag} from "../../../Logic/Tags/Tag";
import {RegexTag} from "../../../Logic/Tags/RegexTag";
import {And} from "../../../Logic/Tags/And";
describe("TagUtils", () => {
describe("ParseTag", () => {
it("should parse a simple key=value tag", () => {
const tag = TagUtils.Tag("key=value") as Tag;
expect(tag.key).eq( "key");
expect(tag.value).eq("value");
})
it("should parse an 'empty spec' key= tag", () => {
const tag = TagUtils.Tag("key=") as Tag;
expect(tag.key).eq( "key");
expect(tag.value).eq("");
})
it("should parse a 'not-empty spec' key!= tag", () => {
const tag = TagUtils.Tag("key!=") as RegexTag;
expect(tag.invert).true
expect(tag.key).eq( "key");
expect(tag.value["source"]).eq("^..*$");
})
it("should parse a normal 'not-equals-value' key!=value tag", () => {
const tag = TagUtils.Tag("key!=value") as RegexTag
expect(tag.invert).true
expect(tag.key).eq( "key");
expect(tag.value["source"]).eq("^value$");
})
it("should refuse a key!=* tag", () => {
expect(() => TagUtils.Tag("key!=*")).to.throw();
})
it("should parse regextags", () => {
const notReg = TagUtils.Tag("x!~y") as RegexTag;
expect(notReg.key).eq("x")
expect(notReg.value["source"]).eq("^y$")
expect(notReg.invert).true
})
it("should parse and", () => {
const and = TagUtils.Tag({"and": ["key=value", "x=y"]}) as And;
expect(and.and[0]["key"]).eq("key")
expect(and.and[0]["value"]).eq("value")
expect(and.and[1]["key"]).eq("x")
expect(and.and[1]["value"]).eq("y")
})
})
})

View file

@ -7473,7 +7473,7 @@ Utils.injectJsonDownloadForTests( "https://www.wikidata.org/wiki/Special:Enti
describe("Wikidata", () => {
it("Download lion",
it("should download Q140 (lion)",
async () => {
const wikidata = await Wikidata.LoadWikidataEntryAsync("Q140")
@ -7481,14 +7481,14 @@ describe("Wikidata", () => {
}
)
it("download wikidata",
it("should download wikidata",
async () => {
const wdata = await Wikidata.LoadWikidataEntryAsync(14517013)
expect(wdata.wikisites).to.have.key("nl")
expect(wdata.wikisites.get("nl")).eq("Vredesmolen")
})
it("Download a lexeme", async () => {
it("should download a lexeme", async () => {
const response = await Wikidata.LoadWikidataEntryAsync("https://www.wikidata.org/wiki/Lexeme:L614072")
expect(response).not.undefined

View file

@ -5,7 +5,7 @@ import ValidatedTextField from "../../UI/Input/ValidatedTextField";
describe("ValidatedTextFields", () => {
it("All validated text fields should have a name and description", () => {
it("should all have description in the translations", () => {
const ts = Translations.t.validation;
const missingTranslations = Array.from(ValidatedTextField.allTypes.keys())
.filter(key => ts[key] === undefined || ts[key].description === undefined)

View file

@ -0,0 +1,75 @@
import {Utils} from "../Utils";
import LZString from "lz-string";
import {describe} from 'mocha'
import {expect} from 'chai'
const example = {
"id": "bookcases",
"maintainer": "MapComplete",
"version": "2020-08-29",
"language": [
"en",
"nl",
"de",
"fr"
],
"title": {
"en": "Open Bookcase Map",
"nl": "Open Boekenruilkastenkaart",
"de": "Öffentliche Bücherschränke Karte",
"fr": "Carte des microbibliothèques"
},
"description": {
"en": "A public bookcase is a small streetside cabinet, box, old phone boot or some other objects where books are stored. Everyone can place or take a book. This map aims to collect all these bookcases. You can discover new bookcases nearby and, with a free OpenStreetMap account, quickly add your favourite bookcases.",
"nl": "Een boekenruilkast is een kastje waar iedereen een boek kan nemen of achterlaten. Op deze kaart kan je deze boekenruilkasten terugvinden en met een gratis OpenStreetMap-account, ook boekenruilkasten toevoegen of informatie verbeteren",
"de": "Ein öffentlicher Bücherschrank ist ein kleiner Bücherschrank am Straßenrand, ein Kasten, eine alte Telefonzelle oder andere Gegenstände, in denen Bücher aufbewahrt werden. Jeder kann ein Buch hinstellen oder mitnehmen. Diese Karte zielt darauf ab, all diese Bücherschränke zu sammeln. Sie können neue Bücherschränke in der Nähe entdecken und mit einem kostenlosen OpenStreetMap-Account schnell Ihre Lieblingsbücherschränke hinzufügen.",
"fr": "Une microbibliothèques, également appelée boite à livre, est un élément de mobilier urbain (étagère, armoire, etc) dans lequel sont stockés des livres et autres objets en accès libre. Découvrez les boites à livres prêt de chez vous, ou ajouter en une nouvelle à l'aide de votre compte OpenStreetMap."
},
"icon": "./assets/themes/bookcases/bookcase.svg",
"socialImage": null,
"startLat": 0,
"startLon": 0,
"startZoom": 1,
"widenFactor": 0.05,
"roamingRenderings": [],
"layers": [
"public_bookcase"
]
}
describe("Utils", () => {
describe("Minify-json", () => {
it("should work in two directions", () => {
const str = JSON.stringify({title: "abc", "and": "xyz", "render": "somevalue"});
const minified = Utils.MinifyJSON(str);
const restored = Utils.UnMinify(minified)
expect(str).eq(restored)
})
it("should minify and restore the bookcase example", () => {
const str = JSON.stringify(example, null, 0)
const minified = Utils.MinifyJSON(str);
const restored = Utils.UnMinify(minified)
expect(str).eq(restored)
})
it("should LZ-compress a theme", () => {
const str = JSON.stringify(example, null, 0)
const minified = LZString.compressToBase64(Utils.MinifyJSON(str));
const restored = Utils.UnMinify(LZString.decompressFromBase64(minified))
expect(str).eq(restored)
})
it("shoud be able to decode the LZ-compression of a theme", () => {
const str = JSON.stringify(example, null, 0)
const minified = LZString.compressToBase64(str);
const restored = LZString.decompressFromBase64(minified)
expect(str).eq(restored)
})
})
})