From 6d007ad92337a8c2e290037280f41a421f349d43 Mon Sep 17 00:00:00 2001 From: jsteuer <jan.steuer.htw@gmail.com> Date: Wed, 18 Dec 2019 12:57:47 +0100 Subject: [PATCH] docs, runtime type checks for createStorageBasedApi --- test/Helpers.spec.ts | 33 ++++++++++++---- src/Helpers.ts | 61 ++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 26 deletions(-) diff --git a/src/Helpers.ts b/src/Helpers.ts index a50e2a8..e0dccb0 100644 --- a/src/Helpers.ts +++ b/src/Helpers.ts @@ -9,9 +9,7 @@ /** * Bildet die MathCoach API nach, sodass diese auch Offline (ohne IDE) - * verfügbar ist. Aktuell wird eine Implementierung auf Basis des LocalStorage - * verwendet. Funktionen wie der Navigator sind nicht verfügbar. Alle API Aufrufe - * werden in der Browser-Console geloggt. + * verfügbar ist. (Siehe auch `createStorageBasedApi`). * * **Hinweis**: Wenn die echte MathCoach-API der IDE verfügbar ist, hat der Aufruf * dieser Funktion keinen Seiteneffekt. @@ -39,26 +37,48 @@ } /** - * Implementierung der MathCoach IDE API zu Testzwecken. + * Implementierung der MathCoach API zu Testzwecken. * - * Das Dateisystem wird durch den LocalStorage des Browsers (oder InMemory) implementiert. - * Einige Features der IDE (beispielsweise der Navigator) sind nicht verfügbar - * und führen keine Aktionen durch. Alle Aktionen werden in der Browser-Console - * geloggt. + * Das Dateisystem wird durch einen Storage implementiert. Einige Features der + * IDE (beispielsweise der Navigator) sind nicht verfügbar und führen keine Aktionen + * durch. Alle Aktionen werden in der Browser-Console geloggt. * * Anwendungsbeispiel * * import { Helpers } from "@mathcoach/ide-api"; * const _MC = Helpers.createStorageBasedApi(); * const contextFile = await _MC.ide.getContextFile() // use the api + * + * @param contextFileExtension Die Datei-Erweiterung der Kontext-Datei. Das Werkzeug sollte + * unabhängig davon implementiert sein, da der Administrator der MathCoach IDE unter + * Umständen auf eine Alternative ausweichen muss. + * @param storage Storage-Implementierung, die zum Speichern von Dateien verwendet werden + * soll. Standardmäßig wird der `LocalStorage` des Browsers verwendet + * bzw. falls nicht verfügbar der `InMemoryStorage`. + * */ - export function createStorageBasedApi(contextFileExtension: string = "dummy.json"): MathCoach.Api { + export function createStorageBasedApi( + contextFileExtension: string = "dummy.json", + storage: Storage = (typeof localStorage === "undefined") ? new InMemoryStorage() : localStorage + ): MathCoach.Api { const fileIdentifier = (file: MathCoach.File) => `mock-file://${file.owner}@${file.part}/${file.path}`; const traceMethod = (method: string, args?: any[]) => console.log(["[MC MOCK API]", " ", method, "(", (args ? args : [""]).join(","), ")"].join("")); - let storage: Storage = (typeof localStorage === "undefined") ? new InMemoryStorage() : localStorage; - class LocalStorageBasedApi implements MathCoach.Api { - - public ide: MathCoach.IdeApi = { + const runtimeCheck = (errorMessage: string, isInvalid: (param: any) => boolean) => { + return (paramName: string, value: any, optional: boolean = false) => { + if (!optional && (value === null || value === undefined)) { + throw new Error(`parameter '${paramName}' is null or undefined`); + } else { + if (isInvalid(value)) { + throw new Error(`parameter '${paramName}' is invalid: ${errorMessage}`); + } + } + } + } + const runtimeCheckString = runtimeCheck("not a string", v => typeof v !== "string"); + const runtimeCheckBoolean = runtimeCheck("not a boolean", v => typeof v !== "boolean"); + const runtimeCheckFile = runtimeCheck("not a valid file", v => !isFile(v)); + const api: MathCoach.Api = { + ide: { async getContextFile(): Promise<MathCoach.File> { traceMethod("MC.ide.getContextFile"); return { @@ -74,28 +94,35 @@ fs: { async readFile(file: MathCoach.File) { traceMethod("MC.ide.fs.readFile", [JSON.stringify(file)]); + runtimeCheckFile("file", file); return storage.getItem(fileIdentifier(file)) || ""; }, async writeFile(file: MathCoach.File, text: string) { traceMethod("MC.ide.fs.writeFile", [JSON.stringify(file), JSON.stringify(`...${text.length} chars...`)]); + runtimeCheckFile("file", file); + runtimeCheckString("text", text); return storage.setItem(fileIdentifier(file), text); } }, navigator: { async navigateTo(link: string, forceOpen?: boolean) { traceMethod("MC.ide.navigator.navigateTo", [JSON.stringify(link), JSON.stringify(forceOpen ? true : false)]); + runtimeCheckString("link", link); + runtimeCheckBoolean("forceOpen", forceOpen, true); }, async navigateToExercise(file: MathCoach.File, forceOpen?: boolean) { traceMethod("MC.ide.navigator.navigateToExercise", [JSON.stringify(file), JSON.stringify(forceOpen ? true : false)]); + runtimeCheckFile("file", file); + runtimeCheckBoolean("forceOpen", forceOpen, true); } } - }; - public async isReady(): Promise<boolean> { + }, + async isReady() { traceMethod("MC.isReady"); return true; } - } - return new LocalStorageBasedApi(); + }; + return api; } diff --git a/test/Helpers.spec.ts b/test/Helpers.spec.ts index 01102be..46872b4 100644 --- a/test/Helpers.spec.ts +++ b/test/Helpers.spec.ts @@ -1,4 +1,4 @@ -import test from "ava"; +import test, { ExecutionContext } from "ava"; import { Helpers } from "../src/Helpers"; @@ -56,12 +56,12 @@ }) -test("Helpers.createLocalStorageBasedApi() MC.isReady", async t => { +test("Helpers.createStorageBasedApi() MC.isReady", async t => { const MC = Helpers.createStorageBasedApi(); t.true(await MC.isReady()); }) -test("Helpers.createLocalStorageBasedApi() MC.ide.getContextFile", async t => { +test("Helpers.createStorageBasedApi() MC.ide.getContextFile", async t => { const MC = Helpers.createStorageBasedApi(); const contextFile = await MC.ide.getContextFile() t.true(Helpers.isFile(contextFile)); @@ -70,13 +70,22 @@ t.is(contextFile.path, "/file.dummy.json"); }) -test("Helpers.createLocalStorageBasedApi() MC.ide.getUserName", async t => { +test("Helpers.createStorageBasedApi() MC.ide.getUserName", async t => { const MC = Helpers.createStorageBasedApi(); t.is(await MC.ide.getUserName(), "jdoe"); }) -test("Helpers.createLocalStorageBasedApi() MC.ide.fs readFile+writeFile", async t => { - const MC = Helpers.createStorageBasedApi(); +async function expectThrows(t: ExecutionContext, callback: () => Promise<any>) { + try { + await callback(); + t.fail() + } catch (error) { + t.pass() + } +} + +test("Helpers.createStorageBasedApi() MC.ide.fs readFile+writeFile", async t => { + const MC = Helpers.createStorageBasedApi(); // expect InMemory Storage const contextFile = await MC.ide.getContextFile(); const exerciseFile = Helpers.contextFileToExerciseFile(contextFile); @@ -88,11 +97,13 @@ t.is(await MC.ide.fs.readFile(contextFile), "123"); t.is(await MC.ide.fs.readFile(exerciseFile), "456"); + + expectThrows(t, () => MC.ide.fs.writeFile({} as any, "")); + expectThrows(t, () => MC.ide.fs.writeFile(exerciseFile, 42 as any)); + expectThrows(t, () => MC.ide.fs.writeFile(null as any, null as any)); }) - - -test("Helpers.createLocalStorageBasedApi() MC.ide.navigator", async t => { +test("Helpers.createStorageBasedApi() MC.ide.navigator", async t => { const MC = Helpers.createStorageBasedApi(); const contextFile = await MC.ide.getContextFile(); const exerciseFile = Helpers.contextFileToExerciseFile(contextFile); @@ -100,5 +111,9 @@ MC.ide.navigator.navigateTo("www.google.de", true); MC.ide.navigator.navigateToExercise(exerciseFile, true); + expectThrows(t, () => MC.ide.navigator.navigateTo(null as any)); + expectThrows(t, () => MC.ide.navigator.navigateTo(42 as any)); + expectThrows(t, () => MC.ide.navigator.navigateTo("www.google.de", 42 as any)); + t.pass(); }) \ No newline at end of file -- Gitblit v1.10.0-SNAPSHOT