From 4c1b7052404e138bbe6bc366c7f14caaad26cd66 Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Sat, 12 Oct 2024 04:57:07 +0200 Subject: [PATCH] Mocking interfaces (+adding new method to canvas to make it more JS-like) --- common/src/module/canvas.mjs | 5 +- common/src/module/common.mjs | 2 +- common/src/module/interface.mjs | 10 ++- common/test/{general => basics}/events.mjs | 0 common/test/basics/interface.mjs | 51 ++++++++++++++++ common/test/{general => basics}/utils.mjs | 0 common/test/mock/canvas.mjs | 59 ++++++++++++++++++ common/test/mock/dialog.mjs | 23 +++++++ common/test/mock/root.mjs | 61 +++++++++++++++++++ .../LogarithmPlotter/LogGraphCanvas.qml | 16 ++++- .../LogarithmPlotter/LogarithmPlotter.qml | 2 - 11 files changed, 216 insertions(+), 13 deletions(-) rename common/test/{general => basics}/events.mjs (100%) create mode 100644 common/test/basics/interface.mjs rename common/test/{general => basics}/utils.mjs (100%) create mode 100644 common/test/mock/canvas.mjs create mode 100644 common/test/mock/dialog.mjs create mode 100644 common/test/mock/root.mjs diff --git a/common/src/module/canvas.mjs b/common/src/module/canvas.mjs index 5d42f5e..c46c9a1 100644 --- a/common/src/module/canvas.mjs +++ b/common/src/module/canvas.mjs @@ -522,8 +522,9 @@ class CanvasAPI extends Module { const onRendered = (imgData) => { if(!this.#canvas.isImageLoaded(imgData.source) && !this.#canvas.isImageLoading(imgData.source)) { // Wait until the image is loaded to callback. - this.#canvas.loadImage(imgData.source) - this.#canvas.imageLoaders[imgData.source] = [callback, imgData] + this.#canvas.loadImageAsync(imgData.source).then(() => { + callback(imgData) + }) } else { // Callback directly callback(imgData) diff --git a/common/src/module/common.mjs b/common/src/module/common.mjs index bdca62f..243fed2 100644 --- a/common/src/module/common.mjs +++ b/common/src/module/common.mjs @@ -57,7 +57,7 @@ export class Module extends BaseEventEmitter { if(!options.hasOwnProperty(name)) throw new Error(`Option '${name}' of initialize of module ${this.#name} does not exist.`) if(typeof value === "function" && value.prototype instanceof Interface) - Interface.check_implementation(value, options[name]) + Interface.checkImplementation(value, options[name]) else if(typeof value !== typeof options[name]) throw new Error(`Option '${name}' of initialize of module ${this.#name} is not a '${value}' (${typeof options[name]}).`) } diff --git a/common/src/module/interface.mjs b/common/src/module/interface.mjs index 2886f73..27fb7eb 100644 --- a/common/src/module/interface.mjs +++ b/common/src/module/interface.mjs @@ -35,9 +35,8 @@ export class Interface { * Throws an error if the implementation does not conform to the interface. * @param {typeof Interface} interface_ * @param {object} classToCheck - * @return {boolean} */ - static check_implementation(interface_, classToCheck) { + static checkImplementation(interface_, classToCheck) { const properties = new interface_() const interfaceName = interface_.name const toCheckName = classToCheck.constructor.name @@ -52,7 +51,7 @@ export class Interface { else if((typeof value) === "object") // Test type of object. if(value instanceof Interface) - Interface.check_implementation(value, classToCheck[property]) + Interface.checkImplementation(value, classToCheck[property]) else if(value.prototype && !(classToCheck[property] instanceof value)) throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is not '${value.constructor.name}'.`) } @@ -61,13 +60,12 @@ export class Interface { export class CanvasInterface extends Interface { - imageLoaders = OBJECT /** @type {function(string): CanvasRenderingContext2D} */ getContext = FUNCTION /** @type {function(rect)} */ markDirty = FUNCTION - /** @type {function(string)} */ - loadImage = FUNCTION + /** @type {function(string): Promise} */ + loadImageAsync = FUNCTION /** @type {function(string)} */ isImageLoading = FUNCTION /** @type {function(string)} */ diff --git a/common/test/general/events.mjs b/common/test/basics/events.mjs similarity index 100% rename from common/test/general/events.mjs rename to common/test/basics/events.mjs diff --git a/common/test/basics/interface.mjs b/common/test/basics/interface.mjs new file mode 100644 index 0000000..a8d8d32 --- /dev/null +++ b/common/test/basics/interface.mjs @@ -0,0 +1,51 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { describe, it } from "mocha" +import { expect } from "chai" + +import { MockLatex } from "../mock/latex.mjs" +import { MockHelper } from "../mock/helper.mjs" +import { + CanvasInterface, + DialogInterface, + HelperInterface, + Interface, + LatexInterface, + RootInterface +} from "../../src/module/interface.mjs" +import { MockDialog } from "../mock/dialog.mjs" +import { MockRootElement } from "../mock/root.mjs" +import { MockCanvas } from "../mock/canvas.mjs" + +describe("Interface", function() { + describe("#checkImplementation", function() { + it("should validate the implementation of mocks", function() { + const checkMockLatex = () => Interface.checkImplementation(LatexInterface, new MockLatex()) + const checkMockHelper = () => Interface.checkImplementation(HelperInterface, new MockHelper()) + const checkMockDialog = () => Interface.checkImplementation(DialogInterface, new MockDialog()) + const checkMockRoot = () => Interface.checkImplementation(RootInterface, new MockRootElement()) + const checkMockCanvas = () => Interface.checkImplementation(CanvasInterface, new MockCanvas()) + expect(checkMockLatex).to.not.throw() + expect(checkMockHelper).to.not.throw() + expect(checkMockDialog).to.not.throw() + expect(checkMockRoot).to.not.throw() + expect(checkMockCanvas).to.not.throw() + }) + }) +}) \ No newline at end of file diff --git a/common/test/general/utils.mjs b/common/test/basics/utils.mjs similarity index 100% rename from common/test/general/utils.mjs rename to common/test/basics/utils.mjs diff --git a/common/test/mock/canvas.mjs b/common/test/mock/canvas.mjs new file mode 100644 index 0000000..e770e10 --- /dev/null +++ b/common/test/mock/canvas.mjs @@ -0,0 +1,59 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +export class MockCanvas { + constructor(mockLoading = false) { + this.mockLoading = mockLoading + } + + getContext(context) { + throw new Error("MockCanvas.getContext not implemented") + } + + markDirty(rect) { + this.requestPaint() + } + + loadImageAsync(image) { + return new Promise((resolve, reject) => { + resolve() + }) + } + + /** + * Image loading is instantaneous. + * @param {string} image + * @return {boolean} + */ + isImageLoading(image) { + return this.mockLoading + } + + /** + * Image loading is instantaneous. + * @param {string} image + * @return {boolean} + */ + isImageLoaded(image) { + return !this.mockLoading + } + + requestPaint() { + } +} \ No newline at end of file diff --git a/common/test/mock/dialog.mjs b/common/test/mock/dialog.mjs new file mode 100644 index 0000000..3ff118f --- /dev/null +++ b/common/test/mock/dialog.mjs @@ -0,0 +1,23 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export class MockDialog { + constructor() {} + + show() {} +} \ No newline at end of file diff --git a/common/test/mock/root.mjs b/common/test/mock/root.mjs new file mode 100644 index 0000000..51807e2 --- /dev/null +++ b/common/test/mock/root.mjs @@ -0,0 +1,61 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Mock for root element with width and height property. + * setWidth, setHeight, getWidth, and getHeight methods can be spied on to check + * when the accessor is called. + */ +export class MockRootElement { + #width = 0 + #height = 0 + + constructor() {} + + setWidth(width) { + this.#width = width; + } + + getWidth() { + return this.#width + } + + setHeight(height) { + this.#height = height; + } + + getHeight() { + return this.#height + } + + get width() { + return this.getWidth() + } + + set width(value) { + this.setWidth(value) + } + + get height() { + return this.getHeight() + } + + set height(value) { + this.setHeight(value) + } +} \ No newline at end of file diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml index 07b6002..e1583f6 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml @@ -36,7 +36,7 @@ Canvas { width: parent.width /*! \qmlproperty var LogGraphCanvas::imageLoaders - Dictionary of format {image: [callback.image data]} containing data for defered image loading. + Dictionary of format {image: callback} containing data for deferred image loading. */ property var imageLoaders: {} @@ -66,9 +66,21 @@ Canvas { Object.keys(imageLoaders).forEach((key) => { if(isImageLoaded(key)) { // Calling callback - imageLoaders[key][0](imageLoaders[key][1]) + imageLoaders[key]() delete imageLoaders[key] } }) } + + /*! + \qmlmethod void LogGraphCanvas::loadImageAsync(string imageSource) + Loads an image data onto the canvas asynchronously. + Returns a Promise that is resolved when the image is loaded. + */ + function loadImageAsync(imageSource) { + return new Promise((resolve) => { + this.loadImage(imageSource) + this.imageLoaders[imageSource] = resolve + }) + } } diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml index e7cfc1a..d541a0c 100644 --- a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml @@ -257,13 +257,11 @@ ApplicationWindow { }) Modules.IO.on("saved loaded", (evt) => { // Refreshing sidebar - console.log(evt.name) updateObjectsLists() if(title.endsWith("*")) title = title.substring(0, title.length-1) }) Modules.IO.on("modified", () => { - console.log("modified") if(!title.endsWith("*")) title = title+"*" })