First steps to a real testing framework: first working version with mocha, chai and doctest-ts

This commit is contained in:
Pieter Vander Vennet 2022-03-14 22:57:01 +01:00
parent edc366149b
commit 4f4fc650b1
42 changed files with 9032 additions and 9275 deletions

1
.gitignore vendored
View file

@ -21,3 +21,4 @@ data/
Folder.DotSettings.user Folder.DotSettings.user
index_*.ts index_*.ts
.~lock.* .~lock.*
*.doctest.ts

View file

@ -41,6 +41,12 @@ export class Tag extends TagsFilter {
return [`["${this.key}"="${this.value}"]`]; return [`["${this.key}"="${this.value}"]`];
} }
/**
const t = new Tag("key", "value")
t.asHumanString() // => "key=value"
t.asHumanString(true) // => "<a href='https://wiki.openstreetmap.org/wiki/Key:key' target='_blank'>key</a>=<a href='https://wiki.openstreetmap.org/wiki/Tag:key%3Dvalue' target='_blank'>value</a>"
*/
asHumanString(linkToWiki?: boolean, shorten?: boolean, currentProperties?: any) { asHumanString(linkToWiki?: boolean, shorten?: boolean, currentProperties?: any) {
let v = this.value; let v = this.value;
if (shorten) { if (shorten) {

View file

@ -224,6 +224,11 @@ export default class Wikidata {
return responses return responses
} }
/**
* Gets the 'key' segment from a URL
*
* Wikidata.ExtractKey("https://www.wikidata.org/wiki/Lexeme:L614072") // => "L614072"
*/
public static ExtractKey(value: string | number): string { public static ExtractKey(value: string | number): string {
if (typeof value === "number") { if (typeof value === "number") {
return "Q" + value return "Q" + value

View file

@ -657,6 +657,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return bestColor ?? hex; return bestColor ?? hex;
} }
/**
* Reorders an object: creates a new object where the keys have been added alphabetically
*
* const sorted = Utils.sortKeys({ x: 'x', abc: {'x': 'x', 'a': 'a'}, def: 'def'})
* JSON.stringify(sorted) // => '{"abc":{"a":"a","x":"x"},"def":"def","x":"x"}'
*/
static sortKeys(o: any) { static sortKeys(o: any) {
const copy = {} const copy = {}
let keys = Object.keys(o) let keys = Object.keys(o)

View file

@ -335,26 +335,34 @@
}, },
"deletion": { "deletion": {
"softDeletionTags": { "softDeletionTags": {
"and": ["disused:amenity=bicycle_rental", "bicycle_rental=", "rental="] "and": [
"disused:amenity=bicycle_rental",
"bicycle_rental=",
"rental="
]
}, },
"neededChangesets": 10, "neededChangesets": 10,
"extraDeleteReasons": [{ "extraDeleteReasons": [
"explanation": { {
"nl": "{title()} is permanent gestopt", "explanation": {
"en": "{title()} has closed down permanently" "nl": "{title()} is permanent gestopt",
}, "en": "{title()} has closed down permanently"
"changesetMessage": "shop_closed" },
}], "changesetMessage": "shop_closed"
"nonDeleteMappings": [{
"if": {
"and": [
"service:bicycle:rental=no"
]
},
"then": {
"en": "This bicycle shop used to rent out bikes but doesn't rent out bikes anymore",
"nl": "Deze fietszaak verhuurde vroeger fietsen, maar nu niet meer"
} }
}] ],
"nonDeleteMappings": [
{
"if": {
"and": [
"service:bicycle:rental=no"
]
},
"then": {
"en": "This bicycle shop used to rent out bikes but doesn't rent out bikes anymore",
"nl": "Deze fietszaak verhuurde vroeger fietsen, maar nu niet meer"
}
}
]
} }
} }

View file

@ -200,13 +200,15 @@
"disused:amenity:={amenity}" "disused:amenity:={amenity}"
] ]
}, },
"extraDeleteReasons": [{ "extraDeleteReasons": [
"explanation": { {
"nl": "{title()} is permanent gestopt", "explanation": {
"en": "{title()} has closed down permanently" "nl": "{title()} is permanent gestopt",
}, "en": "{title()} has closed down permanently"
"changesetMessage": "shop_closed" },
}] "changesetMessage": "shop_closed"
}
]
}, },
"allowMove": true, "allowMove": true,
"mapRendering": [ "mapRendering": [

View file

@ -672,13 +672,15 @@
"disused:amenity:={amenity}" "disused:amenity:={amenity}"
] ]
}, },
"extraDeleteReasons": [{ "extraDeleteReasons": [
"explanation": { {
"nl": "{title()} is permanent gestopt", "explanation": {
"en": "{title()} has closed down permanently" "nl": "{title()} is permanent gestopt",
}, "en": "{title()} has closed down permanently"
"changesetMessage": "shop_closed" },
}] "changesetMessage": "shop_closed"
}
]
}, },
"allowMove": true, "allowMove": true,
"mapRendering": [ "mapRendering": [

View file

@ -357,13 +357,15 @@
"disused:amenity:={amenity}" "disused:amenity:={amenity}"
] ]
}, },
"extraDeleteReasons": [{ "extraDeleteReasons": [
"explanation": { {
"nl": "{title()} is permanent gestopt", "explanation": {
"en": "{title()} has closed down permanently" "nl": "{title()} is permanent gestopt",
}, "en": "{title()} has closed down permanently"
"changesetMessage": "shop_closed" },
}] "changesetMessage": "shop_closed"
}
]
}, },
"allowMove": true, "allowMove": true,
"mapRendering": [ "mapRendering": [

View file

@ -838,7 +838,8 @@
"zh_Hant": "位於地下一樓", "zh_Hant": "位於地下一樓",
"de": "Ist im 1. Untergeschoss", "de": "Ist im 1. Untergeschoss",
"hu": "Az első alagsori szinten", "hu": "Az első alagsori szinten",
"id": "Terletak di lantai basement pertama" "id": "Terletak di lantai basement pertama",
"fr": "Sous-sol"
} }
} }
] ]

View file

@ -523,6 +523,18 @@
"splitTitle": "Choose on the map where to split this road" "splitTitle": "Choose on the map where to split this road"
}, },
"validation": { "validation": {
"color": {
"description": "A color or hexcode"
},
"date": {
"description": "A date, starting with the year"
},
"decimal": {
"description": "A number"
},
"direction": {
"description": "An orientation"
},
"email": { "email": {
"description": "email-adres", "description": "email-adres",
"feedback": "This is not a valid email address", "feedback": "This is not a valid email address",
@ -541,6 +553,9 @@
"mustBeWhole": "Only whole numbers are allowed", "mustBeWhole": "Only whole numbers are allowed",
"notANumber": "Enter a number" "notANumber": "Enter a number"
}, },
"opening_hours": {
"description": "Opening hours"
},
"pfloat": { "pfloat": {
"description": "a positive number" "description": "a positive number"
}, },
@ -555,10 +570,16 @@
"string": { "string": {
"description": "a piece of text" "description": "a piece of text"
}, },
"text": {
"description": "a piece of text"
},
"tooLong": "Text is to long, at most 255 characters are allowed. You do have {count} characters now", "tooLong": "Text is to long, at most 255 characters are allowed. You do have {count} characters now",
"url": { "url": {
"description": "link to a website", "description": "link to a website",
"feedback": "This is not a valid web address" "feedback": "This is not a valid web address"
},
"wikidata": {
"description": "A Wikidata identifier"
} }
} }
} }

View file

@ -494,6 +494,9 @@
}, },
"5": { "5": {
"then": "Tandem bicycles can be rented here" "then": "Tandem bicycles can be rented here"
},
"6": {
"then": "Race bicycles can be rented here"
} }
}, },
"question": "What kind of bicycles and accessories are rented here?", "question": "What kind of bicycles and accessories are rented here?",

View file

@ -494,6 +494,9 @@
}, },
"5": { "5": {
"then": "Tandems kunnen hier gehuurd worden" "then": "Tandems kunnen hier gehuurd worden"
},
"6": {
"then": "Wielerfietsen (sportfietsen) kunnen hier gehuurd worden"
} }
}, },
"question": "Wat voor soort fietsen en fietstoebehren worden hier verhuurd?", "question": "Wat voor soort fietsen en fietstoebehren worden hier verhuurd?",

1413
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,8 @@
"strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html", "strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html",
"watch:css": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", "watch:css": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch",
"generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css",
"test": "ts-node test/TestAll.ts", "generate:doctests": "doctest-ts --mocha Logic/**/*.ts && doctest-ts --mocha UI/**/*.ts && doctest-ts --mocha *.ts && doctest-ts --mocha Models/**/*.ts",
"test": "(npm run generate:doctests 2>&1 | grep -v \"No doctests found in\") && mocha --require ts-node/register \"Logic/**/*.doctest.ts\" \"tests/*\" \"tests/**/*.ts\"",
"init": "npm ci && npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean", "init": "npm ci && npm run generate && npm run generate:editor-layer-index && npm run generate:layouts && npm run clean",
"add-weblate-upstream": "git remote add weblate-layers https://hosted.weblate.org/git/mapcomplete/layer-translations/ ; git remote add weblate-core https://hosted.weblate.org/git/mapcomplete/layer-core/; git remote add weblate-themes https://hosted.weblate.org/git/mapcomplete/layer-themes/; git remote add weblate-github git@github.com:weblate/MapComplete.git", "add-weblate-upstream": "git remote add weblate-layers https://hosted.weblate.org/git/mapcomplete/layer-translations/ ; git remote add weblate-core https://hosted.weblate.org/git/mapcomplete/layer-core/; git remote add weblate-themes https://hosted.weblate.org/git/mapcomplete/layer-themes/; git remote add weblate-github git@github.com:weblate/MapComplete.git",
"fix-weblate": "git remote update weblate-layers; git merge weblate-layers/master", "fix-weblate": "git remote update weblate-layers; git merge weblate-layers/master",
@ -41,7 +42,7 @@
"prepare-deploy": "./scripts/build.sh", "prepare-deploy": "./scripts/build.sh",
"gittag": "ts-node scripts/printVersion.ts | bash", "gittag": "ts-node scripts/printVersion.ts | bash",
"lint": "tslint --project . -c tslint.json '**.ts' ", "lint": "tslint --project . -c tslint.json '**.ts' ",
"clean": "rm -rf .cache/ && (find *.html | grep -v \"\\(404\\|index\\|land\\|test\\|preferences\\|customGenerator\\|professional\\|automaton\\|import_helper\\|import_viewer\\|theme\\).html\" | xargs rm) && (ls | grep \"^index_[a-zA-Z_]\\+\\.ts$\" | xargs rm) && (ls | grep \".*.webmanifest$\" | xargs rm)", "clean": "rm -rf .cache/ && (find . -type f -name \"*.doctest.ts\" | xargs rm) && (find *.html | grep -v \"\\(404\\|index\\|land\\|test\\|preferences\\|customGenerator\\|professional\\|automaton\\|import_helper\\|import_viewer\\|theme\\).html\" | xargs rm) && (ls | grep \"^index_[a-zA-Z_]\\+\\.ts$\" | xargs rm) && (ls | grep \".*.webmanifest$\" | xargs rm)",
"generate:dependency-graph": "node_modules/.bin/depcruise --exclude \"^node_modules\" --output-type dot Logic/State/MapState.ts > dependencies.dot && dot dependencies.dot -T svg -o dependencies.svg && rm dependencies.dot", "generate:dependency-graph": "node_modules/.bin/depcruise --exclude \"^node_modules\" --output-type dot Logic/State/MapState.ts > dependencies.dot && dot dependencies.dot -T svg -o dependencies.svg && rm dependencies.dot",
"script": "ts-node" "script": "ts-node"
}, },
@ -58,15 +59,18 @@
"@turf/distance": "^6.5.0", "@turf/distance": "^6.5.0",
"@turf/length": "^6.5.0", "@turf/length": "^6.5.0",
"@turf/turf": "^6.5.0", "@turf/turf": "^6.5.0",
"@types/chai": "^4.3.0",
"@types/jquery": "^3.5.5", "@types/jquery": "^3.5.5",
"@types/leaflet-markercluster": "^1.0.3", "@types/leaflet-markercluster": "^1.0.3",
"@types/leaflet-providers": "^1.2.0", "@types/leaflet-providers": "^1.2.0",
"@types/lz-string": "^1.3.34", "@types/lz-string": "^1.3.34",
"@types/mocha": "^9.1.0",
"@types/papaparse": "^5.3.1", "@types/papaparse": "^5.3.1",
"@types/prompt-sync": "^4.1.0", "@types/prompt-sync": "^4.1.0",
"@types/wikidata-sdk": "^6.1.0", "@types/wikidata-sdk": "^6.1.0",
"@types/xml2js": "^0.4.9", "@types/xml2js": "^0.4.9",
"country-language": "^0.1.7", "country-language": "^0.1.7",
"doctest-ts": "^0.5.0",
"email-validator": "^2.0.4", "email-validator": "^2.0.4",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"i18next-client": "^1.11.4", "i18next-client": "^1.11.4",
@ -101,8 +105,10 @@
"@babel/polyfill": "^7.10.4", "@babel/polyfill": "^7.10.4",
"@types/node": "^7.0.5", "@types/node": "^7.0.5",
"assert": "^2.0.0", "assert": "^2.0.0",
"chai": "^4.3.6",
"dependency-cruiser": "^10.4.0", "dependency-cruiser": "^10.4.0",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"mocha": "^9.2.2",
"read-file": "^0.2.0", "read-file": "^0.2.0",
"sharp": "^0.28.3", "sharp": "^0.28.3",
"ts-node": "^9.0.0", "ts-node": "^9.0.0",

View file

@ -1,27 +0,0 @@
import T from "./TestHelper";
import ValidatedTextField from "../UI/Input/ValidatedTextField";
import Translations from "../UI/i18n/Translations";
export default class ValidatedTextFieldTranslationsSpec extends T {
constructor() {
super([
["Test all", () => {
const ts = Translations.t.validation;
console.log("Hello world!")
const allErrors = Array.from(ValidatedTextField.allTypes.keys()).map(key => {
const errors = []
const t = ts[key]
if (t === undefined) {
errors.push("No tranlations at all for " + key)
}
return errors;
})
const errs = [].concat(...allErrors)
if (errs.length > 0) {
errs.forEach(e => console.log(e))
// throw errs.join("\n")
}
}]
]);
}
}

File diff suppressed because it is too large Load diff

View file

@ -10,18 +10,15 @@ import RelationSplitHandlerSpec from "./RelationSplitHandler.spec";
import SplitActionSpec from "./SplitAction.spec"; import SplitActionSpec from "./SplitAction.spec";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import TileFreshnessCalculatorSpec from "./TileFreshnessCalculator.spec"; import TileFreshnessCalculatorSpec from "./TileFreshnessCalculator.spec";
import WikidataSpecTest from "./Wikidata.spec.test";
import ImageProviderSpec from "./ImageProvider.spec"; import ImageProviderSpec from "./ImageProvider.spec";
import ActorsSpec from "./Actors.spec"; import ActorsSpec from "./Actors.spec";
import ReplaceGeometrySpec from "./ReplaceGeometry.spec"; import ReplaceGeometrySpec from "./ReplaceGeometry.spec";
import LegacyThemeLoaderSpec from "./LegacyThemeLoader.spec"; import LegacyThemeLoaderSpec from "./LegacyThemeLoader.spec";
import T from "./TestHelper"; import T from "./TestHelper";
import CreateNoteImportLayerSpec from "./CreateNoteImportLayer.spec"; import CreateNoteImportLayerSpec from "./CreateNoteImportLayer.spec";
import ValidatedTextFieldTranslationsSpec from "./ValidatedTextFieldTranslations.spec";
import CreateCacheSpec from "./CreateCache.spec"; import CreateCacheSpec from "./CreateCache.spec";
import CodeQualitySpec from "./CodeQuality.spec"; import CodeQualitySpec from "./CodeQuality.spec";
import ImportMultiPolygonSpec from "./ImportMultiPolygon.spec"; import ImportMultiPolygonSpec from "./ImportMultiPolygon.spec";
import {ChangesetHandler} from "../Logic/Osm/ChangesetHandler";
import ChangesetHandlerSpec from "./ChangesetHandler.spec"; import ChangesetHandlerSpec from "./ChangesetHandler.spec";
import ChangesSpec from "./Changes.spec"; import ChangesSpec from "./Changes.spec";
@ -41,13 +38,11 @@ async function main() {
new RelationSplitHandlerSpec(), new RelationSplitHandlerSpec(),
new SplitActionSpec(), new SplitActionSpec(),
new TileFreshnessCalculatorSpec(), new TileFreshnessCalculatorSpec(),
new WikidataSpecTest(),
new ImageProviderSpec(), new ImageProviderSpec(),
new ActorsSpec(), new ActorsSpec(),
new ReplaceGeometrySpec(), new ReplaceGeometrySpec(),
new LegacyThemeLoaderSpec(), new LegacyThemeLoaderSpec(),
new CreateNoteImportLayerSpec(), new CreateNoteImportLayerSpec(),
new ValidatedTextFieldTranslationsSpec(),
new CreateCacheSpec(), new CreateCacheSpec(),
new CodeQualitySpec(), new CodeQualitySpec(),
new ImportMultiPolygonSpec(), new ImportMultiPolygonSpec(),

15
tests/Chai.spec.ts Normal file
View file

@ -0,0 +1,15 @@
import {describe} from 'mocha'
import {expect} from 'chai'
describe("TestSuite", () => {
describe("function onder test", () => {
it("should work", () => {
expect("abc").eq("abc")
})
})
})
it("global test", () => {
expect("abc").eq("abc")
})

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
import {describe} from 'mocha'
import {expect} from 'chai'
import Translations from "../../UI/i18n/Translations";
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
describe("ValidatedTextFields", () => {
it("All validated text fields should have a name and description", () => {
const ts = Translations.t.validation;
const missingTranslations = Array.from(ValidatedTextField.allTypes.keys())
.filter(key => ts[key] === undefined || ts[key].description === undefined)
expect(missingTranslations, "These validated text fields don't have a type name defined in en.json. (Did you just add one? Run `npm run generate:translations`)").to.be.empty
})
})