Compare commits

...

3 commits

Author SHA1 Message Date
c3daa92280
Testing for modules existance in globals.
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-19 02:34:49 +02:00
d566c285fd
Adding values and types to PyJS 2024-09-19 02:20:42 +02:00
a9e47dbc17
Adding main tests (note: full main app may not be completely tested though) 2024-09-19 01:52:23 +02:00
7 changed files with 262 additions and 39 deletions

View file

@ -48,16 +48,31 @@ from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex from LogarithmPlotter.util.latex import Latex
from LogarithmPlotter.util.js import PyJSValue from LogarithmPlotter.util.js import PyJSValue
LINUX_THEMES = { # See https://specifications.freedesktop.org/menu-spec/latest/onlyshowin-registry.html
"COSMIC": "Basic",
"GNOME": "Basic",
"GNOME-Classic": "Basic",
"GNOME-Flashback": "Basic",
"KDE": "Fusion",
"LXDE": "Basic",
"LXQt": "Fusion",
"MATE": "Fusion",
"TDE": "Fusion",
"Unity": "Basic",
"XFCE": "Basic",
"Cinnamon": "Fusion",
"Pantheon": "Basic",
"DDE": "Basic",
"EDE": "Fusion",
"Endless": "Basic",
"Old": "Fusion",
}
def get_linux_theme() -> str: def get_linux_theme() -> str:
des = {
"KDE": "Fusion",
"gnome": "Basic",
"lxqt": "Fusion",
"mate": "Fusion",
}
if "XDG_SESSION_DESKTOP" in environ: if "XDG_SESSION_DESKTOP" in environ:
if environ["XDG_SESSION_DESKTOP"] in des: if environ["XDG_SESSION_DESKTOP"] in LINUX_THEMES:
return des[environ["XDG_SESSION_DESKTOP"]] return LINUX_THEMES[environ["XDG_SESSION_DESKTOP"]]
return "Fusion" return "Fusion"
else: else:
# Android # Android
@ -77,18 +92,18 @@ def get_platform_qt_style(os) -> str:
def register_icon_directories() -> None: def register_icon_directories() -> None:
icon_fallbacks = QIcon.fallbackSearchPaths() icon_fallbacks = QIcon.fallbackSearchPaths()
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons") base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common"))) paths = [["common"], ["objects"], ["history"], ["settings"], ["settings", "custom"]]
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects"))) for p in paths:
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history"))) icon_fallbacks.append(path.realpath(path.join(base_icon_path, *p)))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks) QIcon.setFallbackSearchPaths(icon_fallbacks)
def create_qapp() -> QApplication: def create_qapp() -> QApplication:
app = QApplication(argv) app = QApplication(argv)
app.setApplicationName("LogarithmPlotter") app.setApplicationName("LogarithmPlotter")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter.desktop") app.setApplicationDisplayName("LogarithmPlotter")
app.setApplicationVersion(f"v{__VERSION__}")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter")
app.setOrganizationName("Ad5001") app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True) app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg")))) app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
@ -101,8 +116,10 @@ def install_translation(app: QApplication) -> QTranslator:
# Check if lang is forced. # Check if lang is forced.
forcedlang = [p for p in argv if p[:7] == "--lang="] forcedlang = [p for p in argv if p[:7] == "--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))): if not translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))):
app.installTranslator(translator) # Load default translation
translator.load(QLocale("en"), "lp", "_", path.realpath(path.join(getcwd(), "i18n")))
app.installTranslator(translator)
return translator return translator
@ -145,7 +162,7 @@ def run():
latex = Latex(tempdir) latex = Latex(tempdir)
engine, js_globals = create_engine(helper, latex, dep_time) engine, js_globals = create_engine(helper, latex, dep_time)
if len(engine.rootObjects()) == 0: # No root objects loaded if len(engine.rootObjects()) == 0: # No root objects loaded
print("No root object", path.realpath(path.join(getcwd(), "qml"))) print("No root object", path.realpath(path.join(getcwd(), "qml")))
exit(-1) exit(-1)

View file

@ -15,10 +15,15 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from re import Pattern
from PySide6.QtCore import QMetaObject, QObject, QDateTime
from PySide6.QtQml import QJSValue from PySide6.QtQml import QJSValue
class InvalidAttributeValueException(Exception): pass class InvalidAttributeValueException(Exception): pass
class NotAPrimitiveException(Exception): pass
class Function: pass
class URL: pass
class PyJSValue: class PyJSValue:
""" """
@ -48,11 +53,11 @@ class PyJSValue:
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, PyJSValue): if isinstance(other, PyJSValue):
return self.qjs_value.equals(other.qjs_value) return self.qjs_value.strictlyEquals(other.qjs_value)
elif isinstance(other, QJSValue): elif isinstance(other, QJSValue):
return self.qjs_value.equals(other) return self.qjs_value.strictlyEquals(other)
elif type(other) in (int, float, str, bool): elif type(other) in (int, float, str, bool):
return self.qjs_value.equals(QJSValue(other)) return self.qjs_value.strictlyEquals(QJSValue(other))
else: else:
return False return False
@ -68,3 +73,34 @@ class PyJSValue:
if isinstance(value, QJSValue): if isinstance(value, QJSValue):
value = PyJSValue(value) value = PyJSValue(value)
return value return value
def type(self) -> any:
matcher = [
(lambda: self.qjs_value.isArray(), list),
(lambda: self.qjs_value.isBool(), bool),
(lambda: self.qjs_value.isCallable(), Function),
(lambda: self.qjs_value.isDate(), QDateTime),
(lambda: self.qjs_value.isError(), Exception),
(lambda: self.qjs_value.isNull(), None),
(lambda: self.qjs_value.isNumber(), float),
(lambda: self.qjs_value.isQMetaObject(), QMetaObject),
(lambda: self.qjs_value.isQObject(), QObject),
(lambda: self.qjs_value.isRegExp(), Pattern),
(lambda: self.qjs_value.isUndefined(), None),
(lambda: self.qjs_value.isUrl(), URL),
(lambda: self.qjs_value.isString(), str),
(lambda: self.qjs_value.isObject(), object),
]
for (test, value) in matcher:
if test():
return value
return None
def primitive(self):
"""
Returns the pythonic value of the given primitive data.
Raises a NotAPrimitiveException() if this JS value is not a primitive.
"""
if self.type() not in [bool, float, str, None]:
raise NotAPrimitiveException()
return self.qjs_value.toPrimitive().toVariant()

View file

@ -1,20 +1,20 @@
""" """
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
if __name__ == "__main__": from LogarithmPlotter.logarithmplotter import create_qapp
from .logarithmplotter import run
run() app = create_qapp()

View file

@ -1,3 +1,21 @@
"""
* 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from os import getcwd from os import getcwd
from os.path import join from os.path import join

View file

@ -1,3 +1,21 @@
"""
* 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from shutil import which from shutil import which

View file

@ -1,6 +1,33 @@
import pytest """
* 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 <https://www.gnu.org/licenses/>.
"""
from LogarithmPlotter.logarithmplotter import get_linux_theme import pytest
from os import environ
from os.path import exists, join
from PySide6.QtGui import QIcon
from tempfile import TemporaryDirectory
from LogarithmPlotter.logarithmplotter import get_linux_theme, LINUX_THEMES, get_platform_qt_style, \
register_icon_directories, install_translation, create_engine
from LogarithmPlotter.util import config
from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex
from globals import app
THEMES = [ THEMES = [
"Basic", "Basic",
@ -11,6 +38,72 @@ THEMES = [
"macOS" "macOS"
] ]
OS_PLATFORMS = [
"linux",
"freebsd",
"win32",
"cygwin",
"darwin"
]
@pytest.fixture()
def temporary():
directory = TemporaryDirectory()
config.CONFIG_PATH = join(directory.name, "config.json")
tmpfile = join(directory.name, "graph.png")
yield tmpfile, directory
directory.cleanup()
class TestMain: class TestMain:
def test_themes(self): def test_linux_themes(self):
get_linux_theme() # Check without a desktop
if "XDG_SESSION_DESKTOP" in environ:
del environ["XDG_SESSION_DESKTOP"]
assert get_linux_theme() in THEMES
# Test various environments.
environ["XDG_SESSION_DESKTOP"] = "GNOME"
assert get_linux_theme() in THEMES
# Test various environments.
environ["XDG_SESSION_DESKTOP"] = "NON-EXISTENT"
assert get_linux_theme() in THEMES
# Check all linux themes are in list
for desktop, theme in LINUX_THEMES.items():
assert theme in THEMES
def test_os_themes(self):
for platform in OS_PLATFORMS:
assert get_platform_qt_style(platform) in THEMES
def test_icon_directories(self):
base_paths = QIcon.fallbackSearchPaths()
register_icon_directories()
# Check if registered
assert len(base_paths) < len(QIcon.fallbackSearchPaths())
# Check if all exists
for p in QIcon.fallbackSearchPaths():
assert exists(p)
def test_app(self, temporary):
assert not app.windowIcon().isNull()
# Translations
translator = install_translation(app)
assert not translator.isEmpty()
# Engine
tmpfile, tempdir = temporary
helper = Helper(".", tmpfile)
latex = Latex(tempdir)
engine, js_globals = create_engine(helper, latex, 0)
assert len(engine.rootObjects()) > 0 # QML File loaded.
assert type(engine.rootContext().contextProperty("TestBuild")) is bool
assert engine.rootContext().contextProperty("StartTime") == 0
assert js_globals.Latex.type() is not None
assert js_globals.Helper.type() is not None
assert js_globals.Modules.type() is not None
# Check if modules have loaded
assert js_globals.Modules.History.type() is not None
assert js_globals.Modules.Latex.type() is not None
assert js_globals.Modules.ObjectsCommon.type() is not None
assert js_globals.Modules.Canvas.type() is not None
assert js_globals.Modules.IO.type() is not None
assert js_globals.Modules.Objects.type() is not None
assert js_globals.Modules.Preferences.type() is not None

View file

@ -1,30 +1,53 @@
"""
* 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from re import Pattern
from PySide6.QtQml import QJSEngine, QJSValue from PySide6.QtQml import QJSEngine, QJSValue
from PySide6.QtWidgets import QApplication
from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException, NotAPrimitiveException
from globals import app
app = QApplication() @pytest.fixture()
engine = QJSEngine() def data():
obj = PyJSValue(engine.globalObject()) engine = QJSEngine()
obj = PyJSValue(engine.globalObject())
yield engine, obj
class TestPyJS: class TestPyJS:
def test_set(self): def test_set(self, data):
engine, obj = data
obj.num1 = 2 obj.num1 = 2
obj.num2 = QJSValue(2) obj.num2 = QJSValue(2)
obj.num3 = PyJSValue(QJSValue(2)) obj.num3 = PyJSValue(QJSValue(2))
with pytest.raises(InvalidAttributeValueException): with pytest.raises(InvalidAttributeValueException):
obj.num3 = object() obj.num3 = object()
def test_eq(self): def test_eq(self, data):
engine, obj = data
obj.num = QJSValue(2) obj.num = QJSValue(2)
assert obj.num == 2 assert obj.num == 2
assert obj.num == QJSValue(2) assert obj.num == QJSValue(2)
assert obj.num == PyJSValue(QJSValue(2)) assert obj.num == PyJSValue(QJSValue(2))
assert obj.num != object() assert obj.num != object()
def test_function(self): def test_function(self, data):
engine, obj = data
function = PyJSValue(engine.evaluate("(function(argument) {return argument*2})")) function = PyJSValue(engine.evaluate("(function(argument) {return argument*2})"))
assert function(3) == 6 assert function(3) == 6
assert function(10) == 20 assert function(10) == 20
@ -34,3 +57,21 @@ class TestPyJS:
function3 = PyJSValue(engine.evaluate("2+2")) function3 = PyJSValue(engine.evaluate("2+2"))
with pytest.raises(InvalidAttributeValueException): with pytest.raises(InvalidAttributeValueException):
function3() function3()
def test_type(self, data):
engine, obj = data
assert PyJSValue(engine.evaluate("[]")).type() == list
assert PyJSValue(engine.evaluate("undefined")).type() is None
assert PyJSValue(engine.evaluate("/[a-z]/g")).type() == Pattern
assert PyJSValue(QJSValue(2)).type() == float
assert PyJSValue(QJSValue("3")).type() == str
assert PyJSValue(QJSValue(True)).type() == bool
def test_primitive(self, data):
engine, obj = data
assert PyJSValue(QJSValue(2)).primitive() == 2
assert PyJSValue(QJSValue("string")).primitive() == "string"
assert PyJSValue(QJSValue(True)).primitive() == True
assert PyJSValue(engine.evaluate("undefined")).primitive() is None
with pytest.raises(NotAPrimitiveException):
assert PyJSValue(engine.evaluate("[]")).primitive() == []