diff --git a/runtime-pyside6/.coverage b/runtime-pyside6/.coverage new file mode 100644 index 0000000..8eab2ed Binary files /dev/null and b/runtime-pyside6/.coverage differ diff --git a/runtime-pyside6/LogarithmPlotter/util/promise.py b/runtime-pyside6/LogarithmPlotter/util/promise.py index f129d41..41b916d 100644 --- a/runtime-pyside6/LogarithmPlotter/util/promise.py +++ b/runtime-pyside6/LogarithmPlotter/util/promise.py @@ -17,7 +17,7 @@ """ from typing import Callable -from PySide6.QtCore import QRunnable, Signal, QObject, Slot, QThreadPool +from PySide6.QtCore import QRunnable, Signal, Property, QObject, Slot, QThreadPool from PySide6.QtQml import QJSValue from LogarithmPlotter.util.js import PyJSValue @@ -51,10 +51,10 @@ class PyPromiseRunner(QRunnable): data = data.qjs_value else: raise InvalidReturnValue("Must return either a primitive, a valid QObject, JS Value, or None.") - self.promise.finished.emit(data) + self.promise.fulfilled.emit(data) except Exception as e: try: - self.promise.errored.emit(repr(e)) + self.promise.rejected.emit(repr(e)) except RuntimeError as e2: # Happens when the PyPromise has already been garbage collected. # In other words, nothing to report to nowhere. @@ -66,18 +66,22 @@ class PyPromise(QObject): Asynchronous Promise-like object meant to interface between Python and Javascript easily. Runs to_run in another thread, and calls fulfilled (populated by then) with its return value. """ - finished = Signal((QJSValue,), (QObject,)) - errored = Signal(Exception) + fulfilled = Signal((QJSValue,), (QObject,)) + rejected = Signal(Exception) - def __init__(self, to_run: Callable, args): + def __init__(self, to_run: Callable, args=[]): QObject.__init__(self) self._fulfills = [] self._rejects = [] - self.finished.connect(self._fulfill) - self.errored.connect(self._reject) + self._state = "pending" + self.fulfilled.connect(self._fulfill) + self.rejected.connect(self._reject) self._runner = PyPromiseRunner(to_run, self, args) QThreadPool.globalInstance().start(self._runner) + @Property(str) + def state(self): + return self._state @Slot(QJSValue, result=QObject) @Slot(QJSValue, QJSValue, result=QObject) @@ -98,6 +102,7 @@ class PyPromise(QObject): @Slot(QJSValue) @Slot(QObject) def _fulfill(self, data): + self._state = "fulfilled" no_return = [None, QJSValue.SpecialValue.UndefinedValue] for on_fulfill in self._fulfills: try: @@ -110,6 +115,7 @@ class PyPromise(QObject): @Slot(QJSValue) @Slot(str) def _reject(self, error): + self._state = "rejected" no_return = [None, QJSValue.SpecialValue.UndefinedValue] for on_reject in self._rejects: result = on_reject(error) diff --git a/runtime-pyside6/tests/test_helper.py b/runtime-pyside6/tests/test_helper.py index 73dfb23..5cfb239 100644 --- a/runtime-pyside6/tests/test_helper.py +++ b/runtime-pyside6/tests/test_helper.py @@ -17,7 +17,7 @@ """ import pytest -from os import getcwd, remove +from os import getcwd, remove, path from os.path import join from tempfile import TemporaryDirectory from json import loads @@ -25,11 +25,12 @@ from shutil import copy2 from PySide6.QtCore import QObject, Signal, QThreadPool from PySide6.QtGui import QImage +from PySide6.QtQml import QJSValue from PySide6.QtWidgets import QApplication from LogarithmPlotter import __VERSION__ as version from LogarithmPlotter.util import config, helper -from LogarithmPlotter.util.helper import ChangelogFetcher, Helper, InvalidFileException +from LogarithmPlotter.util.helper import Helper, InvalidFileException pwd = getcwd() helper.SHOW_GUI_MESSAGES = False @@ -43,41 +44,45 @@ def temporary(): directory.cleanup() -class MockHelperSignals(QObject): - changelogFetched = Signal(str) +def create_changelog_callback_asserter(promise, expect_404=False): + def cb(changelog, expect_404=expect_404): + # print("Got changelog", changelog) + assert isinstance(changelog, QJSValue) + assert changelog.isString() + changlogValue = changelog.toVariant() + assert ('404' in changlogValue) == expect_404 + def error(e): + raise eval(e) + promise.then(cb, error) - def __init__(self, expect_404): - QObject.__init__(self) - self.expect_404 = expect_404 - self.changelogFetched.connect(self.changelog_fetched) - self.changelog = None - - def changelog_fetched(self, changelog): - self.changelog = changelog - - -class TestChangelog: - - def test_exists(self, qtbot): - helper.CHANGELOG_VERSION = '0.5.0' - mock_helper = MockHelperSignals(False) - fetcher = ChangelogFetcher(mock_helper) - fetcher.run() # Does not raise an exception - qtbot.waitSignal(mock_helper.changelogFetched, timeout=10000) - assert type(mock_helper.changelog) == str - assert '404' not in mock_helper.changelog - - def tests_no_exist(self, qtbot): - mock_helper = MockHelperSignals(True) - helper.CHANGELOG_VERSION = '1.0.0' - fetcher = ChangelogFetcher(mock_helper) - fetcher.run() - qtbot.waitSignal(mock_helper.changelogFetched, timeout=10000) - assert type(mock_helper.changelog) == str - assert '404' in mock_helper.changelog +CHANGELOG_BASE_PATH = path.realpath(path.join(path.dirname(path.realpath(__file__)), "..", "CHANGELOG.md")) class TestHelper: + def test_changelog(self, temporary, qtbot): + helper.CHANGELOG_VERSION = '0.5.0' + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + promise = obj.fetchChangelog() + create_changelog_callback_asserter(promise, expect_404=False) + qtbot.waitSignal(promise.fulfilled, timeout=10000) + # No exist + helper.CHANGELOG_VERSION = '2.0.0' + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + promise = obj.fetchChangelog() + create_changelog_callback_asserter(promise, expect_404=True) + qtbot.waitSignal(promise.fulfilled, timeout=10000) + # Local + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + assert path.exists(CHANGELOG_BASE_PATH) + copy2(CHANGELOG_BASE_PATH, helper.CHANGELOG_CACHE_PATH) + assert path.exists(helper.CHANGELOG_CACHE_PATH) + promise = obj.fetchChangelog() + create_changelog_callback_asserter(promise, expect_404=False) + qtbot.waitSignal(promise.fulfilled, timeout=10000) # Local + def test_read(self, temporary): # Test file reading and information loading. tmpfile, directory = temporary @@ -168,15 +173,3 @@ class TestHelper: obj.setSetting("last_install_greet", obj.getSetting("last_install_greet")) obj.setSetting("check_for_updates", obj.getSetting("check_for_updates")) obj.setSetting("default_graph.xzoom", obj.getSetting("default_graph.xzoom")) - - def test_fetch_changelog(self, temporary, qtbot): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - copy2("../../CHANGELOG.md", "../../LogarithmPlotter/util/CHANGELOG.md") - obj.fetchChangelog() - assert QThreadPool.globalInstance().activeThreadCount() == 0 - qtbot.waitSignal(obj.changelogFetched, timeout=10000) - remove("../../LogarithmPlotter/util/CHANGELOG.md") - obj.fetchChangelog() - assert QThreadPool.globalInstance().activeThreadCount() > 0 - qtbot.waitSignal(obj.changelogFetched, timeout=10000) \ No newline at end of file diff --git a/runtime-pyside6/tests/test_latex.py b/runtime-pyside6/tests/test_latex.py index d74f9f1..6fd5c97 100644 --- a/runtime-pyside6/tests/test_latex.py +++ b/runtime-pyside6/tests/test_latex.py @@ -54,8 +54,8 @@ class TestLatex: # Reset [latex.DVIPNG_PATH, latex.LATEX_PATH] = bkp - def test_render(self, latex_obj: latex.Latex) -> None: - result = latex_obj.render(r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255)) + def test_render_sync(self, latex_obj: latex.Latex) -> None: + result = latex_obj.renderSync(r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255)) # Ensure result format assert type(result) == str [path, width, height] = result.split(",") @@ -64,17 +64,17 @@ class TestLatex: assert match(r"\d+", height) # Ensure it returns errors on invalid latex. with pytest.raises(latex.RenderError): - latex_obj.render(r"\nonexistant", 14, QColor(0, 0, 0, 255)) + latex_obj.renderSync(r"\nonexistant", 14, QColor(0, 0, 0, 255)) # Replace latex bin with one that returns errors bkp = latex.LATEX_PATH latex.LATEX_PATH = which("false") with pytest.raises(latex.RenderError): - latex_obj.render(r"\mathrm{f}(x)", 14, QColor(0, 0, 0, 255)) + latex_obj.renderSync(r"\mathrm{f}(x)", 14, QColor(0, 0, 0, 255)) latex.LATEX_PATH = bkp def test_prerendered(self, latex_obj: latex.Latex) -> None: args = [r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255)] - latex_obj.render(*args) + latex_obj.renderSync(*args) prerendered = latex_obj.findPrerendered(*args) assert type(prerendered) == str [path, width, height] = prerendered.split(",")