3 Commits

Author SHA1 Message Date
stalbe_j
ca2caad46c new tests from before 2023-04-13 14:49:04 +02:00
stalbe_j
b13b91e55b Merge branch 'tests' of gitlab.psi.ch:augustin_s/grum into tests 2023-03-14 14:53:45 +01:00
stalbe_j
7780281e0c Merge branch 'master' of gitlab.psi.ch:augustin_s/grum into tests 2023-02-06 13:34:23 +01:00
18 changed files with 107 additions and 323 deletions

View File

@@ -1,64 +1,29 @@
stages: stages:
- Tests - Test
- OptionalTests
.install-grum-test: &install-grum-test
- pip install pytest pytest-random-order pytest-cov
- pip install -e ./
- apt-get update
- apt-get install -y ffmpeg libnss3 libxcomposite1 libxtst6 libxdamage1
tests: tests:
stage: Tests stage: Test
image: python:3.8
variables: variables:
QT_QPA_PLATFORM: "offscreen" QT_QPA_PLATFORM: "offscreen"
XDG_RUNTIME_DIR: "/tmp/runtime-root" XDG_RUNTIME_DIR: "/tmp/runtime-root"
PYTHONFAULTHANDLER: 1
script: script:
- *install-grum-test - pip install pytest pytest-random-order pytest-cov
- coverage run --source=./grum -m pytest ./tests --junitxml=report-junit.xml - pip install -e ./
- pip install PyQtWebEngine
- apt-get update
- apt-get install -y ffmpeg libnss3 libxcomposite1 libxtst6
# - python -m unittest discover -f ./tests
# - coverage run --source=./grum -m unittest discover -f ./tests
- coverage run --source=./grum -m pytest ./tests
- coverage report - coverage report
- coverage xml - coverage xml
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
artifacts: artifacts:
when: always
reports: reports:
junit: report.xml cobertura: coverage.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml
tests-3.6:
stage: OptionalTests
image: python:3.6
needs: ["tests"]
allow_failure: true
variables:
QT_QPA_PLATFORM: "offscreen"
XDG_RUNTIME_DIR: "/tmp/runtime-root"
PYTHONFAULTHANDLER: 1
script:
- *install-grum-test
- pytest ./tests
tests-3.7:
extends: "tests-3.6"
image: python:3.7
#tests-3.8:
# extends: "tests-3.6"
# image: python:3.8
tests-3.9:
extends: "tests-3.6"
image: python:3.9
tests-3.10:
extends: "tests-3.6"
image: python:3.10
tests-3.11:
extends: "tests-3.6"
image: python:3.11

View File

@@ -1,6 +1,6 @@
# grum # grum
<img src="https://gitea.psi.ch/SwissFEL/grum/wiki/raw/uploads%2Fe4cd2be847d26bb7ac7100080edbccce%2Fscreenshot.png" width="50%" /> <img src="https://gitlab.psi.ch/augustin_s/grum/-/wikis/uploads/1a259a1d74e7b79e0230e7bbad3b1284/screenshot2.png" width="50%" />
## Overview ## Overview
@@ -22,11 +22,11 @@ Via the RPC server, new plots can be created and new data appended to existing p
- `new_plot(name, cfg)` - `new_plot(name, cfg)`
Creates a new plot named `name` in the grum list. The configuration dict `cfg` is used as arguments for the constructor of [`PlotDescription`](https://gitea.psi.ch/SwissFEL/grum/src/grum/descs/plotdesc.py#L4). Creates a new plot named `name` in the grum list. The configuration dict `cfg` is used as arguments for the constructor of [`PlotDescription`](https://gitlab.psi.ch/augustin_s/grum/-/blob/master/grum/plotdesc.py#L4).
- `append_data(name, point)` - `append_data(name, point)`
Append data point to the plot named `name`. The new `point` is forwarded to [`PlotDescription.append()`](https://gitea.psi.ch/SwissFEL/grum/src/grum/descs/plotdesc.py#L24). Append data point to the plot named `name`. The new `point` is forwarded to [`PlotDescription.append()`](https://gitlab.psi.ch/augustin_s/grum/-/blob/master/grum/plotdesc.py#L18).
### Utility functions ### Utility functions

View File

@@ -1,13 +0,0 @@
from .desc import Description
from .imgdesc import ImageDescription
from .plotdesc import PlotDescription
DESC_TYPES = {
ImageDescription.get_type(): ImageDescription,
PlotDescription.get_type(): PlotDescription
}

View File

@@ -1,23 +0,0 @@
class Description:
def to_dict(self):
res = {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None}
tn = self.get_type()
res.setdefault("type", tn)
return res
@classmethod
def get_type(cls):
tn = cls.__name__
suffix = "Description"
if not tn.endswith(suffix):
raise ValueError(f'"{tn}" does not end with "{suffix}"')
tn = tn[:-len(suffix)]
tn = tn.casefold()
tn = tn or None
return tn

View File

@@ -1,52 +0,0 @@
import numpy as np
import pyqtgraph as pg
from .desc import Description
class ImageDescription(Description):
def __init__(self, name, title=None, xlabel=None, ylabel=None, image=None, levels=None, cmap="viridis"):
self.name = name
self.title = title
self.xlabel = xlabel
self.ylabel = ylabel
self.image = image
self.levels = levels #TODO: might be better to use vmin and vmax
self.cmap = cmap
@property
def data(self):
return np.asarray(self.image)
@data.setter
def data(self, value):
self.image = value
def append(self, xy):
print("ignored image append")
def extend(self, data):
print("ignored image extend")
def make_plot(self, plotwidget, style):
res = plotwidget.setImage(self.data, levels=self.levels)
if self.title:
plotwidget.setTitle(self.title)
if self.xlabel:
plotwidget.getView().setLabel("bottom", self.xlabel)
if self.ylabel:
plotwidget.getView().setLabel("left", self.ylabel)
if self.cmap:
cm = pg.colormap.get(self.cmap)
plotwidget.setColorMap(cm)
return res

View File

@@ -1,6 +1,6 @@
import numpy as np import numpy as np
from .descs import PlotDescription, ImageDescription from .plotdesc import PlotDescription
X = np.arange(100) / 10 X = np.arange(100) / 10
@@ -31,20 +31,4 @@ for name, (xs, ys) in exampledata_raw.items():
) )
name = "image"
xdim = ydim = 100
size = xdim * ydim
shape = (xdim, ydim)
img = np.arange(size).reshape(shape) / size
img += np.random.random(shape) / 10
exampledata[name] = ImageDescription(
name,
image=img,
xlabel="x",
ylabel="y"
)

View File

@@ -2,27 +2,21 @@ from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import QMainWindow, QSplitter from PyQt5.QtWidgets import QMainWindow, QSplitter
from . import assets from . import assets
from .descs import DESC_TYPES, Description, ImageDescription, PlotDescription
from .dictlist import DictList from .dictlist import DictList
from .exampledata import exampledata from .exampledata import exampledata
from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog from .h5filedlg import open_h5_files_dialog, save_h5_file_dialog
from .io import write_dict, read_dict from .io import write_dict, read_dict
from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDISubImage, MDIWindowMode from .mdi import MDIArea, MDISubMultiPlot, MDISubPlot, MDIWindowMode
from .menus import BarMenu from .menus import BarMenu
from .plotdesc import PlotDescription
from .rpc import RPCServerThread from .rpc import RPCServerThread
from .shortcut import shortcut from .shortcut import shortcut
from .webview import WebView from .webview import WebView
DESC_TYPE_TO_MDI_SUB_TYPE = {
ImageDescription: MDISubImage,
PlotDescription: MDISubPlot
}
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
sig_make_new_subwin = pyqtSignal(str, Description) sig_make_new_plot = pyqtSignal(str, PlotDescription)
def __init__(self, *args, title="grum", host="localhost", port=8000, offline=False, add_examples=False, window_mode=MDIWindowMode.MULTI, **kwargs): def __init__(self, *args, title="grum", host="localhost", port=8000, offline=False, add_examples=False, window_mode=MDIWindowMode.MULTI, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -90,13 +84,10 @@ class MainWindow(QMainWindow):
if not offline: if not offline:
self.rst = rst = RPCServerThread(host, port, doc_title_suffix=title) self.rst = rst = RPCServerThread(host, port, doc_title_suffix=title)
rst.start() rst.start()
rst.server.register_function(self.new_image)
rst.server.register_function(self.new_plot) rst.server.register_function(self.new_plot)
rst.server.register_function(self.append_data) rst.server.register_function(self.append_data)
rst.server.register_function(self.extend_data)
rst.server.register_function(self.set_data)
self.sig_make_new_subwin.connect(self.on_make_new_subwin) self.sig_make_new_plot.connect(self.on_make_new_plot)
def keyPressEvent(self, event): def keyPressEvent(self, event):
@@ -106,30 +97,16 @@ class MainWindow(QMainWindow):
# Remote API calls # Remote API calls
def new_image(self, name, cfg):
"""
Create a new image <name> using the configuration dict <cfg>.
The configuration is forwarded to the constructor of ImageDescription.
Allowed keys are: title, xlabel, ylabel, image, levels, cmap.
"""
desc = self.add_new_desc_to_list(ImageDescription, name, cfg)
if self.menu_settings.checkboxes["Open new plots"].isChecked():
sub = self.mdi.findSubWindow(name)
if sub:
sub.pw.setImage(desc.data) #TODO lacks the list sync
else:
self.sig_make_new_subwin.emit(name, desc)
def new_plot(self, name, cfg): def new_plot(self, name, cfg):
""" """
Create a new plot <name> using the configuration dict <cfg>. Create a new plot <name> using the configuration dict <cfg>.
The configuration is forwarded to the constructor of PlotDescription. The configuration is forwarded to the constructor of PlotDescription.
Allowed keys are: title, xlabel, ylabel, xs, ys. Allowed keys are: title, xlabel, ylabel, xs, ys.
""" """
desc = self.add_new_desc_to_list(PlotDescription, name, cfg) desc = self.add_new_desc_to_list(name, cfg)
if self.menu_settings.checkboxes["Open new plots"].isChecked(): if self.menu_settings.checkboxes["Open new plots"].isChecked():
if not self.mdi.findSubWindow(name): if not self.mdi.findSubWindow(name):
self.sig_make_new_subwin.emit(name, desc) self.sig_make_new_plot.emit(name, desc)
def append_data(self, name, point): def append_data(self, name, point):
""" """
@@ -139,35 +116,20 @@ class MainWindow(QMainWindow):
item = self.lst.get(name) item = self.lst.get(name)
desc = item.value desc = item.value
desc.append(point) desc.append(point)
self.sync_item_and_plots(item) alarm = True
for sub in self.mdi.subWindowList():
def extend_data(self, name, data): if name in sub.plots:
""" plot = sub.plots[name]
Extend the current data of the (existing) plot <name>.by <data> plot.setData(*desc.data)
The data is forwarded to the extend method of PlotDescription. alarm = False
""" item.timestamps.modification.update()
item = self.lst.get(name) item.set_alarm(alarm)
desc = item.value
desc.extend(data)
self.sync_item_and_plots(item)
def set_data(self, name, data):
"""
Set <data> as the data of the (existing) plot <name>.
The data is assigned to the data attribute of PlotDescription.
"""
item = self.lst.get(name)
desc = item.value
desc.data = data
self.sync_item_and_plots(item)
# Signal callbacks # Signal callbacks
def on_make_new_subwin(self, name, desc): def on_make_new_plot(self, *args, **kwargs):
DescType = type(desc) self.make_subwin(MDISubPlot, *args, **kwargs)
MDISubType = DESC_TYPE_TO_MDI_SUB_TYPE[DescType]
self.make_subwin(MDISubType, name, desc)
def on_dclick_list_item(self, item): def on_dclick_list_item(self, item):
self.plot_single_item(item) self.plot_single_item(item)
@@ -210,10 +172,8 @@ class MainWindow(QMainWindow):
for fn in fns: for fn in fns:
data = read_dict(fn) data = read_dict(fn)
for name, cfg in data.items(): for k, v in data.items():
tn = cfg.pop("type") self.add_new_desc_to_list(k, v)
DescType = DESC_TYPES[tn]
self.add_new_desc_to_list(DescType, name, cfg)
def on_file_save(self): def on_file_save(self):
@@ -231,42 +191,28 @@ class MainWindow(QMainWindow):
# Plumbing # Plumbing
def add_new_desc_to_list(self, DescType, name, cfg): def add_new_desc_to_list(self, name, cfg):
desc = DescType(name, **cfg) desc = PlotDescription(name, **cfg)
self.lst.set(name, desc) self.lst.set(name, desc)
return desc return desc
def sync_item_and_plots(self, item):
name, desc = item.key, item.value
alarm = True
for sub in self.mdi.subWindowList():
if name in sub.plots:
plot = sub.plots[name]
plot.setData(*desc.data)
alarm = False
item.timestamps.modification.update()
item.set_alarm(alarm)
def plot_single_item(self, item): def plot_single_item(self, item):
item.timestamps.access.update() item.timestamps.access.update()
item.set_alarm(False) item.set_alarm(False)
name, desc = item.key, item.value name, desc = item.key, item.value
DescType = type(desc) self.activate_or_make_subwin(MDISubPlot, name, desc)
MDISubType = DESC_TYPE_TO_MDI_SUB_TYPE[DescType]
self.activate_or_make_subwin(MDISubType, name, desc)
def plot_multiple_items(self, items): def plot_multiple_items(self, items):
for i in items: for i in items:
i.timestamps.access.update() i.timestamps.access.update()
i.set_alarm(False) i.set_alarm(False)
items = (i for i in items if isinstance(i.value, PlotDescription)) #TODO: for now, only overlay plots
descs = {i.key: i.value for i in items} descs = {i.key: i.value for i in items}
names = descs.keys() names = descs.keys()
name = " | ".join(names) name = " | ".join(names)
self.activate_or_make_subwin(MDISubMultiPlot, name, descs) self.activate_or_make_subwin(MDISubMultiPlot, name, descs)
#TODO: the following two could be methods of MDIArea? #TODO: the following two could be methods to MDIArea?
def activate_or_make_subwin(self, MDISubType, name, *args, **kwargs): def activate_or_make_subwin(self, MDISubType, name, *args, **kwargs):
sub = self.mdi.findSubWindow(name) sub = self.mdi.findSubWindow(name)

View File

@@ -1,6 +1,5 @@
from .mdiarea import MDIArea, MDIWindowMode from .mdiarea import MDIArea, MDIWindowMode
from .mdisubplot import MDISubPlot, MDISubMultiPlot from .mdisubplot import MDISubPlot, MDISubMultiPlot
from .mdisubimg import MDISubImage

View File

@@ -1,38 +0,0 @@
import pyqtgraph as pg
from .mdisubwin import MDISubWindow
from ..theme import pg_plot_style
class MDISubImage(MDISubWindow):
def __init__(self, name, desc, *args, **kwargs):
super().__init__(name, *args, **kwargs)
self.pw = pw = pg.ImageView(view=pg.PlotItem()) # for axis ticks and labels, view needs to be a PlotItem
self.setWidget(pw)
# connect to plot mouse-over event
pw.scene.sigMouseMoved.connect(self.on_hover)
style = pg_plot_style()
plot = desc.make_plot(self.pw, style)
self.plots = {name: plot}
self.image = desc.data
def on_hover(self, event):
coord = self.pw.imageItem.mapFromScene(event)
x = coord.x()
y = coord.y()
x = int(x)
y = int(y)
try:
z = self.image[x, y]
except IndexError:
return
z = round(z, 3)
self.setToolTip(f"x = {x}\ny = {y}\nz = {z}")

View File

@@ -4,17 +4,12 @@ from PyQt5.QtWidgets import QMdiSubWindow
from .. import assets from .. import assets
SUB_WIN_WIDTH = 640
SUB_WIN_HEIGHT = 480
class MDISubWindow(QMdiSubWindow): class MDISubWindow(QMdiSubWindow):
def __init__(self, title, *args, **kwargs): def __init__(self, title, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.setWindowTitle(title) self.setWindowTitle(title)
self.setWindowIcon(assets.char()) self.setWindowIcon(assets.char())
self.resize(SUB_WIN_WIDTH, SUB_WIN_HEIGHT)
# without this, the SubWindow is not removed from the subWindowList # without this, the SubWindow is not removed from the subWindowList
self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DeleteOnClose)

View File

@@ -1,7 +1,5 @@
from .desc import Description
class PlotDescription:
class PlotDescription(Description):
def __init__(self, name, title=None, xlabel=None, ylabel=None, xs=None, ys=None): def __init__(self, name, title=None, xlabel=None, ylabel=None, xs=None, ys=None):
self.name = name self.name = name
@@ -16,21 +14,12 @@ class PlotDescription(Description):
def data(self): def data(self):
return (self.xs, self.ys) return (self.xs, self.ys)
@data.setter
def data(self, value):
self.xs, self.ys = value
def append(self, xy): def append(self, xy):
x, y = xy x, y = xy
self.xs.append(x) self.xs.append(x)
self.ys.append(y) self.ys.append(y)
def extend(self, data):
xs, ys = data
self.xs.extend(xs)
self.ys.extend(ys)
def make_plot(self, plotwidget, style): def make_plot(self, plotwidget, style):
res = plotwidget.plot(self.xs, self.ys, name=self.name, **style) res = plotwidget.plot(self.xs, self.ys, name=self.name, **style)
@@ -47,4 +36,8 @@ class PlotDescription(Description):
return res return res
def to_dict(self):
return {k: v for k, v in self.__dict__.items() if not k.startswith("_") and k != "name" and v is not None}

View File

@@ -16,10 +16,4 @@ class RPCClient(xrc.ServerProxy):
return head + help return head + help
def __dir__(self):
d1 = super().__dir__()
d2 = self.utils.info().keys()
return [*d1, *d2]

View File

@@ -2,7 +2,7 @@ from PyQt5.QtCore import QUrl
try: try:
from PyQt5.QtWebKitWidgets import QWebView from PyQt5.QtWebKitWidgets import QWebView
except (ImportError, ModuleNotFoundError): except ImportError:
from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView

View File

@@ -4,9 +4,9 @@ version = 0.0.1
description = GUI for Remote Unified Monitoring description = GUI for Remote Unified Monitoring
long_description = file: README.md long_description = file: README.md
long_description_content_type = text/markdown long_description_content_type = text/markdown
url = https://gitea.psi.ch/SwissFEL/grum url = https://gitlab.psi.ch/augustin_s/grum
project_urls = project_urls =
Bug Tracker = https://gitea.psi.ch/SwissFEL/grum/issues Bug Tracker = https://gitlab.psi.ch/augustin_s/grum/issues
classifiers = classifiers =
Programming Language :: Python :: 3 Programming Language :: Python :: 3
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
@@ -16,7 +16,7 @@ classifiers =
package_dir = package_dir =
= . = .
packages = find: packages = find:
python_requires = >=3.6 python_requires = >=3.8
[options.packages.find] [options.packages.find]
where = . where = .

View File

@@ -2,7 +2,7 @@ from setuptools import setup
if __name__ == "__main__": if __name__ == "__main__":
setup( setup(
install_requires=["pyqt5", "pyqtgraph", "h5py", "PyQtWebEngine"], install_requires=["pyqt5", "pyqtgraph", "h5py"],
entry_points={"console_scripts": ["grum=grum:main"]}, entry_points={"console_scripts": ["grum=grum:main"]},
) )

View File

@@ -0,0 +1,27 @@
from grum.dictlist.dictlistwidget import DictListWidget
from grum.menus.rclickmenu import RClickMenu
class DictListWidgetMock(DictListWidget):
def __init__(self) -> None:
self.items = {}
self.nkeep = None
def get_DictListWidgetMock():
return DictListWidgetMock()
# def test_defaults():
# dlw = get_DictListWidgetMock()
# assert dlw.items == {}
# assert dlw.nkeep == None
# def test_add_menu():
# dlw = get_DictListWidgetMock()
# dlw._add_menu()
# assert dlw.menu == RClickMenu(dlw)

View File

@@ -14,14 +14,13 @@ from grum.mainwin import MainWindow
from grum.mdi import MDIArea, MDISubMultiPlot, MDISubPlot from grum.mdi import MDIArea, MDISubMultiPlot, MDISubPlot
from grum.menus import BarMenu from grum.menus import BarMenu
from grum.menus.rclickmenu import RClickMenu from grum.menus.rclickmenu import RClickMenu
from grum.descs import Description, PlotDescription from grum.plotdesc import PlotDescription
from grum.rpc import RPCServerThread from grum.rpc import RPCServerThread
class TestMainWin: class TestMainWin:
def setup_method(self): def setup_method(self):
print("setup")
self.app = QApplication(sys.argv) self.app = QApplication(sys.argv)
theme.apply(self.app) theme.apply(self.app)
# ctrl_c.setup(self.app) # ctrl_c.setup(self.app)
@@ -30,10 +29,8 @@ class TestMainWin:
def teardown_method(self): def teardown_method(self):
print("teardown")
self.mw.rst.wait_for_stop() self.mw.rst.wait_for_stop()
self.app.quit() self.app.quit()
print("app quit called")
del self.mw del self.mw
del self.app del self.app
@@ -47,7 +44,7 @@ class TestMainWin:
for key in mw.lst.lst.items: for key in mw.lst.lst.items:
assert isinstance(mw.lst.lst.get(key), DictListItem) assert isinstance(mw.lst.lst.get(key), DictListItem)
assert isinstance(mw.lst.lst.get(key).value, Description) assert isinstance(mw.lst.lst.get(key).value, PlotDescription)
assert isinstance(mw.lst.menu, RClickMenu) assert isinstance(mw.lst.menu, RClickMenu)
assert isinstance(mw.menu_settings, BarMenu) assert isinstance(mw.menu_settings, BarMenu)
@@ -65,7 +62,7 @@ class TestMainWin:
xlabel = "xlabel" xlabel = "xlabel"
ylabel = "ylabel" ylabel = "ylabel"
cfg = {"title": title, "xlabel": xlabel, "ylabel": ylabel} cfg = {"title": title, "xlabel": xlabel, "ylabel": ylabel}
spy_sig_make_new_subwin = QSignalSpy(mw.sig_make_new_subwin) spy_sig_make_new_plot = QSignalSpy(mw.sig_make_new_plot)
mw.new_plot(name, cfg=cfg) mw.new_plot(name, cfg=cfg)
@@ -76,28 +73,28 @@ class TestMainWin:
assert mw.lst.lst.get(name).value.ylabel == ylabel assert mw.lst.lst.get(name).value.ylabel == ylabel
assert mw.menu_settings.checkboxes["Open new plots"].isChecked() assert mw.menu_settings.checkboxes["Open new plots"].isChecked()
assert len(spy_sig_make_new_subwin) == 1 # assert called once assert len(spy_sig_make_new_plot) == 1 # assert called once
assert spy_sig_make_new_subwin[0][0] == name # assert called with name assert spy_sig_make_new_plot[0][0] == name # assert called with name
assert isinstance(spy_sig_make_new_subwin[0][1], PlotDescription) assert isinstance(spy_sig_make_new_plot[0][1], PlotDescription)
mw.menu_settings.checkboxes["Open new plots"].setChecked(False) mw.menu_settings.checkboxes["Open new plots"].setChecked(False)
assert mw.menu_settings.checkboxes["Open new plots"].isChecked() == False assert mw.menu_settings.checkboxes["Open new plots"].isChecked() == False
spy_sig_make_new_subwin = QSignalSpy(mw.sig_make_new_subwin) spy_sig_make_new_plot = QSignalSpy(mw.sig_make_new_plot)
mw.new_plot("new_name", cfg) mw.new_plot("new_name", cfg)
assert len(spy_sig_make_new_subwin) == 0 # assert not called assert len(spy_sig_make_new_plot) == 0 # assert not called
mw.menu_settings.checkboxes["Open new plots"].setChecked(True) mw.menu_settings.checkboxes["Open new plots"].setChecked(True)
assert mw.menu_settings.checkboxes["Open new plots"].isChecked() == True assert mw.menu_settings.checkboxes["Open new plots"].isChecked() == True
spy_sig_make_new_subwin = QSignalSpy(mw.sig_make_new_subwin) spy_sig_make_new_plot = QSignalSpy(mw.sig_make_new_plot)
new_name_item = mw.lst.lst.get("new_name") new_name_item = mw.lst.lst.get("new_name")
sub = MDISubPlot("new_name", new_name_item.value) sub = MDISubPlot("new_name", new_name_item.value)
mw.mdi.add(sub) mw.mdi.add(sub)
mw.new_plot("new_name", cfg) mw.new_plot("new_name", cfg)
assert len(spy_sig_make_new_subwin) == 0 # assert not called assert len(spy_sig_make_new_plot) == 0 # assert not called
def test_append_data(self): def test_append_data(self):
@@ -122,19 +119,17 @@ class TestMainWin:
assert sine_item.set_alarm.call_args[0][0] == False assert sine_item.set_alarm.call_args[0][0] == False
def test_on_make_new_subwin(self): def test_on_make_new_plot(self):
mw = self.mw mw = self.mw
mw.make_subwin = mock.MagicMock() mw.make_subwin = mock.MagicMock()
name = "test" args = (1, 2, "name")
cfg = {"title": "title"} kwargs = {"title": "plot_title"}
desc = PlotDescription(name, *cfg) mw.on_make_new_plot(args, kwargs)
mw.on_make_new_subwin(name, desc) mw.make_subwin.assert_called_once_with(MDISubPlot, args, kwargs)
mw.make_subwin.assert_called_once_with(MDISubPlot, name, desc)
def test_on_dclick_list_item(self): def test_on_dclick_list_item(self):
@@ -169,6 +164,19 @@ class TestMainWin:
mw.on_plot_selected() mw.on_plot_selected()
mw.plot_multiple_items.assert_called_once_with([sine_item, cosine_item]) mw.plot_multiple_items.assert_called_once_with([sine_item, cosine_item])
def test_on_sort_by_name(self):
mw = self.mw
mw.lst = DictList()
mw.new_plot("bb", {})
mw.new_plot("dd", {})
mw.new_plot("aa", {})
mw.new_plot("cc", {})
assert mw.lst.lst.items == 111
assert [key for key in mw.lst.lst.items.keys()] == ['bb', 'dd', 'aa', 'cc']
mw.on_sort_by_name()
assert mw.lst.lst.items == 111
def test_plot_single_item(self): def test_plot_single_item(self):
mw = self.mw mw = self.mw

View File

@@ -6,7 +6,7 @@ import pyqtgraph as pg
from grum import theme from grum import theme
from grum.mainwin import MainWindow from grum.mainwin import MainWindow
from grum.mdi.mdisubplot import MDISubPlot from grum.mdi.mdisubplot import MDISubPlot
from grum.descs import PlotDescription from grum.plotdesc import PlotDescription
from grum.theme import pg_plot_style from grum.theme import pg_plot_style
@@ -78,7 +78,6 @@ def test_to_dict():
"xs": [1, 2], "xs": [1, 2],
"ylabel": "plot_ylabel", "ylabel": "plot_ylabel",
"ys": [3, 4], "ys": [3, 4],
"type": "plot"
} }