Compare commits
3 commits
37ac400f23
...
a85a4721e3
Author | SHA1 | Date | |
---|---|---|---|
a85a4721e3 | |||
aeaaba759f | |||
ccddb068a6 |
7 changed files with 156 additions and 75 deletions
|
@ -529,11 +529,15 @@ class CanvasAPI extends Module {
|
||||||
this.#canvas.loadImageAsync(imgData.source).then(() => {
|
this.#canvas.loadImageAsync(imgData.source).then(() => {
|
||||||
if(this.#redrawCount === currentRedrawCount)
|
if(this.#redrawCount === currentRedrawCount)
|
||||||
callback(imgData)
|
callback(imgData)
|
||||||
|
else
|
||||||
|
console.log("1Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Callback directly
|
// Callback directly
|
||||||
if(this.#redrawCount === currentRedrawCount)
|
if(this.#redrawCount === currentRedrawCount)
|
||||||
callback(imgData)
|
callback(imgData)
|
||||||
|
else
|
||||||
|
console.log("2Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
|
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
|
||||||
|
|
|
@ -201,7 +201,6 @@ class IOAPI extends Module {
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Canvas.redraw()
|
|
||||||
this.#alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
|
this.#alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
|
||||||
this.#saved = true
|
this.#saved = true
|
||||||
this.emit(new LoadedEvent())
|
this.emit(new LoadedEvent())
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
from PySide6.QtCore import QtMsgType, qInstallMessageHandler, QMessageLogContext
|
from PySide6.QtCore import QtMsgType, qInstallMessageHandler, QMessageLogContext
|
||||||
from math import ceil, log10
|
from math import ceil, log10
|
||||||
from os import path
|
from os import path
|
||||||
|
from re import compile
|
||||||
|
|
||||||
CURRENT_PATH = path.dirname(path.realpath(__file__))
|
CURRENT_PATH = path.dirname(path.realpath(__file__))
|
||||||
SOURCEMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/js/index.mjs.map")
|
SOURCEMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/js/index.mjs.map")
|
||||||
SOURCEMAP_INDEX = None
|
SOURCEMAP_INDEX = None
|
||||||
|
INDEX_REG = compile(r"build\/runtime-pyside6\/LogarithmPlotter\/qml\/eu\/ad5001\/LogarithmPlotter\/js\/index.mjs:(\d+)")
|
||||||
|
|
||||||
|
|
||||||
class LOG_COLORS:
|
class LOG_COLORS:
|
||||||
|
@ -77,6 +79,7 @@ def create_log_terminal_message(mode: QtMsgType, context: QMessageLogContext, me
|
||||||
# Check MJS
|
# Check MJS
|
||||||
if line is not None and source_file is not None and source_file.endswith("index.mjs"):
|
if line is not None and source_file is not None and source_file.endswith("index.mjs"):
|
||||||
source_file, line = map_javascript_source(source_file, line)
|
source_file, line = map_javascript_source(source_file, line)
|
||||||
|
# Parse message
|
||||||
prefix = f"{LOG_COLORS.INVERT}{mode[1]}[{mode[0].upper()}]{LOG_COLORS.RESET_INVERT}"
|
prefix = f"{LOG_COLORS.INVERT}{mode[1]}[{mode[0].upper()}]{LOG_COLORS.RESET_INVERT}"
|
||||||
message = message + LOG_COLORS.RESET
|
message = message + LOG_COLORS.RESET
|
||||||
context = f"{context.function} at {source_file}:{line}"
|
context = f"{context.function} at {source_file}:{line}"
|
||||||
|
|
|
@ -82,6 +82,7 @@ class Latex(QObject):
|
||||||
def __init__(self, cache_path):
|
def __init__(self, cache_path):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
self.tempdir = path.join(cache_path, "latex")
|
self.tempdir = path.join(cache_path, "latex")
|
||||||
|
self.render_pipeline_locks = {}
|
||||||
makedirs(self.tempdir, exist_ok=True)
|
makedirs(self.tempdir, exist_ok=True)
|
||||||
|
|
||||||
@Property(bool)
|
@Property(bool)
|
||||||
|
@ -117,19 +118,68 @@ class Latex(QObject):
|
||||||
valid_install = False # Should have sent an error message if failed to render
|
valid_install = False # Should have sent an error message if failed to render
|
||||||
return valid_install
|
return valid_install
|
||||||
|
|
||||||
|
def lock(self, markup_hash, render_hash, promise):
|
||||||
|
"""
|
||||||
|
Locks the render pipeline for a given markup hash and render hash.
|
||||||
|
"""
|
||||||
|
# print("Locking", markup_hash, render_hash)
|
||||||
|
if markup_hash not in self.render_pipeline_locks:
|
||||||
|
self.render_pipeline_locks[markup_hash] = promise
|
||||||
|
self.render_pipeline_locks[render_hash] = promise
|
||||||
|
|
||||||
|
|
||||||
|
def release_lock(self, markup_hash, render_hash):
|
||||||
|
"""
|
||||||
|
Release locks on the markup and render hashes.
|
||||||
|
"""
|
||||||
|
# print("Releasing", markup_hash, render_hash)
|
||||||
|
if markup_hash in self.render_pipeline_locks:
|
||||||
|
del self.render_pipeline_locks[markup_hash]
|
||||||
|
del self.render_pipeline_locks[render_hash]
|
||||||
|
|
||||||
@Slot(str, float, QColor, result=PyPromise)
|
@Slot(str, float, QColor, result=PyPromise)
|
||||||
def renderAsync(self, latex_markup: str, font_size: float, color: QColor) -> PyPromise:
|
def renderAsync(self, latex_markup: str, font_size: float, color: QColor) -> PyPromise:
|
||||||
"""
|
"""
|
||||||
Prepares and renders a latex string into a png file asynchronously.
|
Prepares and renders a latex string into a png file asynchronously.
|
||||||
"""
|
"""
|
||||||
return PyPromise(self.renderSync, [latex_markup, font_size, color])
|
markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
||||||
|
promise = None
|
||||||
|
if render_hash in self.render_pipeline_locks:
|
||||||
|
# A PyPromise for this specific render is already running.
|
||||||
|
# print("Already running render of", latex_markup)
|
||||||
|
promise = self.render_pipeline_locks[render_hash]
|
||||||
|
elif markup_hash in self.render_pipeline_locks:
|
||||||
|
# A PyPromise with the same markup, but not the same color or font size is already running.
|
||||||
|
# print("Chaining render of", latex_markup)
|
||||||
|
existing_promise = self.render_pipeline_locks[markup_hash]
|
||||||
|
promise = self._create_async_promise(latex_markup, font_size, color)
|
||||||
|
existing_promise.then(promise.start)
|
||||||
|
else:
|
||||||
|
# No such PyPromise is running.
|
||||||
|
promise = self._create_async_promise(latex_markup, font_size, color)
|
||||||
|
promise.start()
|
||||||
|
return promise
|
||||||
|
|
||||||
|
def _create_async_promise(self, latex_markup: str, font_size: float, color: QColor) -> PyPromise:
|
||||||
|
"""
|
||||||
|
Createsa PyPromise to render a latex string into a PNG file.
|
||||||
|
Internal method. Use renderAsync that makes use of locks.
|
||||||
|
"""
|
||||||
|
markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
||||||
|
promise = PyPromise(self.renderSync, [latex_markup, font_size, color], start_automatically=False)
|
||||||
|
self.lock(markup_hash, render_hash, promise)
|
||||||
|
# Make the lock release at the end.
|
||||||
|
def unlock(data, markup_hash=markup_hash, render_hash=render_hash):
|
||||||
|
self.release_lock(markup_hash, render_hash)
|
||||||
|
promise.then(unlock, unlock)
|
||||||
|
return promise
|
||||||
|
|
||||||
@Slot(str, float, QColor, result=str)
|
@Slot(str, float, QColor, result=str)
|
||||||
def renderSync(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
def renderSync(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
||||||
"""
|
"""
|
||||||
Prepares and renders a latex string into a png file.
|
Prepares and renders a latex string into a png file.
|
||||||
"""
|
"""
|
||||||
markup_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
||||||
if self.latexSupported and not path.exists(export_path + ".png"):
|
if self.latexSupported and not path.exists(export_path + ".png"):
|
||||||
print("Rendering", latex_markup, export_path)
|
print("Rendering", latex_markup, export_path)
|
||||||
# Generating file
|
# Generating file
|
||||||
|
@ -138,14 +188,12 @@ class Latex(QObject):
|
||||||
if not path.exists(latex_path + ".dvi"):
|
if not path.exists(latex_path + ".dvi"):
|
||||||
self.create_latex_doc(latex_path, latex_markup)
|
self.create_latex_doc(latex_path, latex_markup)
|
||||||
self.convert_latex_to_dvi(latex_path)
|
self.convert_latex_to_dvi(latex_path)
|
||||||
# self.cleanup(latex_path)
|
self.cleanup(latex_path)
|
||||||
# Creating four pictures of different sizes to better handle dpi.
|
# Creating four pictures of different sizes to better handle dpi.
|
||||||
self.convert_dvi_to_png(latex_path, export_path, font_size, color)
|
self.convert_dvi_to_png(latex_path, export_path, font_size, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
||||||
else:
|
|
||||||
sleep(0)
|
|
||||||
img = QImage(export_path)
|
img = QImage(export_path)
|
||||||
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
||||||
return f'{export_path}.png,{img.width()},{img.height()}'
|
return f'{export_path}.png,{img.width()},{img.height()}'
|
||||||
|
@ -155,7 +203,7 @@ class Latex(QObject):
|
||||||
"""
|
"""
|
||||||
Finds a prerendered image and returns its data if possible, and an empty string if not.
|
Finds a prerendered image and returns its data if possible, and an empty string if not.
|
||||||
"""
|
"""
|
||||||
markup_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
||||||
data = ""
|
data = ""
|
||||||
if path.exists(export_path + ".png"):
|
if path.exists(export_path + ".png"):
|
||||||
img = QImage(export_path)
|
img = QImage(export_path)
|
||||||
|
@ -165,10 +213,13 @@ class Latex(QObject):
|
||||||
def create_export_path(self, latex_markup: str, font_size: float, color: QColor):
|
def create_export_path(self, latex_markup: str, font_size: float, color: QColor):
|
||||||
"""
|
"""
|
||||||
Standardizes export path for renders.
|
Standardizes export path for renders.
|
||||||
|
Markup hash is unique for the markup
|
||||||
|
Render hash is unique for the markup, the font size and the color.
|
||||||
"""
|
"""
|
||||||
markup_hash = "render" + str(sha512(latex_markup.encode()).hexdigest())
|
markup_hash = "render" + str(sha512(latex_markup.encode()).hexdigest())
|
||||||
export_path = path.join(self.tempdir, f'{markup_hash}_{int(font_size)}_{color.rgb()}')
|
render_hash = f'{markup_hash}_{int(font_size)}_{color.rgb()}'
|
||||||
return markup_hash, export_path
|
export_path = path.join(self.tempdir, render_hash)
|
||||||
|
return markup_hash, render_hash, export_path
|
||||||
|
|
||||||
def create_latex_doc(self, export_path: str, latex_markup: str):
|
def create_latex_doc(self, export_path: str, latex_markup: str):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -17,12 +17,24 @@
|
||||||
"""
|
"""
|
||||||
from typing import Callable
|
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 PySide6.QtQml import QJSValue
|
||||||
|
|
||||||
from LogarithmPlotter.util.js import PyJSValue
|
from LogarithmPlotter.util.js import PyJSValue
|
||||||
|
|
||||||
|
|
||||||
|
def check_callable(function: Callable|QJSValue) -> Callable|None:
|
||||||
|
"""
|
||||||
|
Checks if the given function can be called (either a python callable
|
||||||
|
or a QJSValue function), and returns the object that can be called directly.
|
||||||
|
Returns None if not a function.
|
||||||
|
"""
|
||||||
|
if isinstance(function, QJSValue) and function.isCallable():
|
||||||
|
return PyJSValue(function)
|
||||||
|
elif isinstance(function, Callable):
|
||||||
|
return function
|
||||||
|
return None
|
||||||
|
|
||||||
class InvalidReturnValue(Exception): pass
|
class InvalidReturnValue(Exception): pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,10 +63,10 @@ class PyPromiseRunner(QRunnable):
|
||||||
data = data.qjs_value
|
data = data.qjs_value
|
||||||
else:
|
else:
|
||||||
raise InvalidReturnValue("Must return either a primitive, a valid QObject, JS Value, or None.")
|
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:
|
except Exception as e:
|
||||||
try:
|
try:
|
||||||
self.promise.errored.emit(repr(e))
|
self.promise.rejected.emit(repr(e))
|
||||||
except RuntimeError as e2:
|
except RuntimeError as e2:
|
||||||
# Happens when the PyPromise has already been garbage collected.
|
# Happens when the PyPromise has already been garbage collected.
|
||||||
# In other words, nothing to report to nowhere.
|
# In other words, nothing to report to nowhere.
|
||||||
|
@ -66,18 +78,36 @@ class PyPromise(QObject):
|
||||||
Asynchronous Promise-like object meant to interface between Python and Javascript easily.
|
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.
|
Runs to_run in another thread, and calls fulfilled (populated by then) with its return value.
|
||||||
"""
|
"""
|
||||||
finished = Signal((QJSValue,), (QObject,))
|
fulfilled = Signal((QJSValue,), (QObject,))
|
||||||
errored = Signal(Exception)
|
rejected = Signal(Exception)
|
||||||
|
|
||||||
def __init__(self, to_run: Callable, args):
|
def __init__(self, to_run: Callable|QJSValue, args=[], start_automatically=True):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
self._fulfills = []
|
self._fulfills = []
|
||||||
self._rejects = []
|
self._rejects = []
|
||||||
self.finished.connect(self._fulfill)
|
self._state = "pending"
|
||||||
self.errored.connect(self._reject)
|
self._started = False
|
||||||
|
self.fulfilled.connect(self._fulfill)
|
||||||
|
self.rejected.connect(self._reject)
|
||||||
|
to_run = check_callable(to_run)
|
||||||
|
if to_run is None:
|
||||||
|
raise ValueError("New PyPromise created with invalid function")
|
||||||
self._runner = PyPromiseRunner(to_run, self, args)
|
self._runner = PyPromiseRunner(to_run, self, args)
|
||||||
QThreadPool.globalInstance().start(self._runner)
|
if start_automatically:
|
||||||
|
self._start()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def start(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Starts the thread that will run the promise.
|
||||||
|
"""
|
||||||
|
if not self._started: # Avoid getting started twice.
|
||||||
|
QThreadPool.globalInstance().start(self._runner)
|
||||||
|
self._started = True
|
||||||
|
|
||||||
|
@Property(str)
|
||||||
|
def state(self):
|
||||||
|
return self._state
|
||||||
|
|
||||||
@Slot(QJSValue, result=QObject)
|
@Slot(QJSValue, result=QObject)
|
||||||
@Slot(QJSValue, QJSValue, result=QObject)
|
@Slot(QJSValue, QJSValue, result=QObject)
|
||||||
|
@ -85,23 +115,23 @@ class PyPromise(QObject):
|
||||||
"""
|
"""
|
||||||
Adds listeners for both fulfilment and catching errors of the Promise.
|
Adds listeners for both fulfilment and catching errors of the Promise.
|
||||||
"""
|
"""
|
||||||
if isinstance(on_fulfill, QJSValue):
|
on_fulfill = check_callable(on_fulfill)
|
||||||
self._fulfills.append(PyJSValue(on_fulfill))
|
on_reject = check_callable(on_reject)
|
||||||
elif isinstance(on_fulfill, Callable):
|
if on_fulfill is not None:
|
||||||
self._fulfills.append(on_fulfill)
|
self._fulfills.append(on_fulfill)
|
||||||
if isinstance(on_reject, QJSValue):
|
if on_reject is not None:
|
||||||
self._rejects.append(PyJSValue(on_reject))
|
|
||||||
elif isinstance(on_reject, Callable):
|
|
||||||
self._rejects.append(on_reject)
|
self._rejects.append(on_reject)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@Slot(QJSValue)
|
@Slot(QJSValue)
|
||||||
@Slot(QObject)
|
@Slot(QObject)
|
||||||
def _fulfill(self, data):
|
def _fulfill(self, data):
|
||||||
|
self._state = "fulfilled"
|
||||||
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
|
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
|
||||||
for on_fulfill in self._fulfills:
|
for on_fulfill in self._fulfills:
|
||||||
try:
|
try:
|
||||||
result = on_fulfill(data)
|
result = on_fulfill(data)
|
||||||
|
result = result.qjs_value if isinstance(result, PyJSValue) else result
|
||||||
data = result if result not in no_return else data # Forward data.
|
data = result if result not in no_return else data # Forward data.
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._reject(repr(e))
|
self._reject(repr(e))
|
||||||
|
@ -110,6 +140,7 @@ class PyPromise(QObject):
|
||||||
@Slot(QJSValue)
|
@Slot(QJSValue)
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
def _reject(self, error):
|
def _reject(self, error):
|
||||||
|
self._state = "rejected"
|
||||||
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
|
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
|
||||||
for on_reject in self._rejects:
|
for on_reject in self._rejects:
|
||||||
result = on_reject(error)
|
result = on_reject(error)
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from os import getcwd, remove
|
from os import getcwd, remove, path
|
||||||
from os.path import join
|
from os.path import join
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from json import loads
|
from json import loads
|
||||||
|
@ -25,11 +25,12 @@ from shutil import copy2
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, Signal, QThreadPool
|
from PySide6.QtCore import QObject, Signal, QThreadPool
|
||||||
from PySide6.QtGui import QImage
|
from PySide6.QtGui import QImage
|
||||||
|
from PySide6.QtQml import QJSValue
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
from LogarithmPlotter import __VERSION__ as version
|
from LogarithmPlotter import __VERSION__ as version
|
||||||
from LogarithmPlotter.util import config, helper
|
from LogarithmPlotter.util import config, helper
|
||||||
from LogarithmPlotter.util.helper import ChangelogFetcher, Helper, InvalidFileException
|
from LogarithmPlotter.util.helper import Helper, InvalidFileException
|
||||||
|
|
||||||
pwd = getcwd()
|
pwd = getcwd()
|
||||||
helper.SHOW_GUI_MESSAGES = False
|
helper.SHOW_GUI_MESSAGES = False
|
||||||
|
@ -43,41 +44,45 @@ def temporary():
|
||||||
directory.cleanup()
|
directory.cleanup()
|
||||||
|
|
||||||
|
|
||||||
class MockHelperSignals(QObject):
|
def create_changelog_callback_asserter(promise, expect_404=False):
|
||||||
changelogFetched = Signal(str)
|
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):
|
CHANGELOG_BASE_PATH = path.realpath(path.join(path.dirname(path.realpath(__file__)), "..", "CHANGELOG.md"))
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelper:
|
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):
|
def test_read(self, temporary):
|
||||||
# Test file reading and information loading.
|
# Test file reading and information loading.
|
||||||
tmpfile, directory = temporary
|
tmpfile, directory = temporary
|
||||||
|
@ -168,15 +173,3 @@ class TestHelper:
|
||||||
obj.setSetting("last_install_greet", obj.getSetting("last_install_greet"))
|
obj.setSetting("last_install_greet", obj.getSetting("last_install_greet"))
|
||||||
obj.setSetting("check_for_updates", obj.getSetting("check_for_updates"))
|
obj.setSetting("check_for_updates", obj.getSetting("check_for_updates"))
|
||||||
obj.setSetting("default_graph.xzoom", obj.getSetting("default_graph.xzoom"))
|
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)
|
|
|
@ -54,8 +54,8 @@ class TestLatex:
|
||||||
# Reset
|
# Reset
|
||||||
[latex.DVIPNG_PATH, latex.LATEX_PATH] = bkp
|
[latex.DVIPNG_PATH, latex.LATEX_PATH] = bkp
|
||||||
|
|
||||||
def test_render(self, latex_obj: latex.Latex) -> None:
|
def test_render_sync(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))
|
result = latex_obj.renderSync(r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255))
|
||||||
# Ensure result format
|
# Ensure result format
|
||||||
assert type(result) == str
|
assert type(result) == str
|
||||||
[path, width, height] = result.split(",")
|
[path, width, height] = result.split(",")
|
||||||
|
@ -64,17 +64,17 @@ class TestLatex:
|
||||||
assert match(r"\d+", height)
|
assert match(r"\d+", height)
|
||||||
# Ensure it returns errors on invalid latex.
|
# Ensure it returns errors on invalid latex.
|
||||||
with pytest.raises(latex.RenderError):
|
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
|
# Replace latex bin with one that returns errors
|
||||||
bkp = latex.LATEX_PATH
|
bkp = latex.LATEX_PATH
|
||||||
latex.LATEX_PATH = which("false")
|
latex.LATEX_PATH = which("false")
|
||||||
with pytest.raises(latex.RenderError):
|
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
|
latex.LATEX_PATH = bkp
|
||||||
|
|
||||||
def test_prerendered(self, latex_obj: latex.Latex) -> None:
|
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)]
|
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)
|
prerendered = latex_obj.findPrerendered(*args)
|
||||||
assert type(prerendered) == str
|
assert type(prerendered) == str
|
||||||
[path, width, height] = prerendered.split(",")
|
[path, width, height] = prerendered.split(",")
|
||||||
|
|
Loading…
Reference in a new issue