-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create skeleton for napari plugin with collapsible widgets (#218)
* initialise napari plugin development * initialise napari plugin development * create skeleton for napari plugin with collapsible widgets * add basic widget smoke tests and allow headless testing * do not depend on napari from pip * include napari option in install instructions * make meta_widget module private * pin atlasapi version to avoid unnecessary dependencies * pin napari >= 0.4.19 from conda-forge * switched to pip install of napari[all] * seperation of concerns in widget tests * add pytest-mock dev dependency
- Loading branch information
Showing
9 changed files
with
174 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from napari.utils.notifications import show_info | ||
from napari.viewer import Viewer | ||
from qtpy.QtWidgets import ( | ||
QFormLayout, | ||
QPushButton, | ||
QWidget, | ||
) | ||
|
||
|
||
class Loader(QWidget): | ||
"""Widget for loading data from files.""" | ||
|
||
def __init__(self, napari_viewer: Viewer, parent=None): | ||
"""Initialize the loader widget.""" | ||
super().__init__(parent=parent) | ||
self.viewer = napari_viewer | ||
self.setLayout(QFormLayout()) | ||
# Create widgets | ||
self._create_hello_widget() | ||
|
||
def _create_hello_widget(self): | ||
"""Create the hello widget. | ||
This widget contains a button that, when clicked, shows a greeting. | ||
""" | ||
hello_button = QPushButton("Say hello") | ||
hello_button.clicked.connect(self._on_hello_clicked) | ||
self.layout().addRow("Greeting", hello_button) | ||
|
||
def _on_hello_clicked(self): | ||
"""Show a greeting.""" | ||
show_info("Hello, world!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"""The main napari widget for the ``movement`` package.""" | ||
|
||
from brainglobe_utils.qtpy.collapsible_widget import CollapsibleWidgetContainer | ||
from napari.viewer import Viewer | ||
|
||
from movement.napari._loader_widget import Loader | ||
|
||
|
||
class MovementMetaWidget(CollapsibleWidgetContainer): | ||
"""The widget to rule all ``movement`` napari widgets. | ||
This is a container of collapsible widgets, each responsible | ||
for handing specific tasks in the movement napari workflow. | ||
""" | ||
|
||
def __init__(self, napari_viewer: Viewer, parent=None): | ||
"""Initialize the meta-widget.""" | ||
super().__init__() | ||
|
||
self.add_widget( | ||
Loader(napari_viewer, parent=self), | ||
collapsible=True, | ||
widget_title="Load data", | ||
) | ||
|
||
self.loader = self.collapsible_widgets[0] | ||
self.loader.expand() # expand the loader widget by default |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
name: movement | ||
display_name: movement | ||
contributions: | ||
commands: | ||
- id: movement.make_widget | ||
python_name: movement.napari._meta_widget:MovementMetaWidget | ||
title: movement | ||
widgets: | ||
- command: movement.make_widget | ||
display_name: movement |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import pytest | ||
from qtpy.QtWidgets import QPushButton, QWidget | ||
|
||
from movement.napari._loader_widget import Loader | ||
from movement.napari._meta_widget import MovementMetaWidget | ||
|
||
|
||
@pytest.fixture | ||
def meta_widget(make_napari_viewer_proxy) -> MovementMetaWidget: | ||
"""Fixture to expose the MovementMetaWidget for testing. | ||
Simultaneously acts as a smoke test that the widget | ||
can be instantiated without crashing. | ||
""" | ||
viewer = make_napari_viewer_proxy() | ||
return MovementMetaWidget(viewer) | ||
|
||
|
||
@pytest.fixture | ||
def loader_widget(meta_widget) -> QWidget: | ||
"""Fixture to expose the Loader widget for testing.""" | ||
loader = meta_widget.loader.content() | ||
return loader | ||
|
||
|
||
def test_meta_widget(meta_widget): | ||
"""Test that the meta widget is properly instantiated.""" | ||
assert meta_widget is not None | ||
assert len(meta_widget.collapsible_widgets) == 1 | ||
|
||
first_widget = meta_widget.collapsible_widgets[0] | ||
assert first_widget._text == "Load data" | ||
assert first_widget.isExpanded() | ||
|
||
|
||
def test_loader_widget(loader_widget): | ||
"""Test that the loader widget is properly instantiated.""" | ||
assert loader_widget is not None | ||
assert loader_widget.layout().rowCount() == 1 | ||
|
||
|
||
def test_hello_button_calls_on_hello_clicked(make_napari_viewer_proxy, mocker): | ||
"""Test that clicking the hello button calls _on_hello_clicked. | ||
Here we have to create a new Loader widget after mocking the method. | ||
We cannot reuse the existing widget fixture because then it would be too | ||
late to mock (the widget has already "decided" which method to call). | ||
""" | ||
mock_method = mocker.patch( | ||
"movement.napari._loader_widget.Loader._on_hello_clicked" | ||
) | ||
loader = Loader(make_napari_viewer_proxy) | ||
hello_button = loader.findChildren(QPushButton)[0] | ||
hello_button.click() | ||
mock_method.assert_called_once() | ||
|
||
|
||
def test_on_hello_clicked_outputs_message(loader_widget, capsys): | ||
"""Test that _on_hello_clicked outputs the expected message.""" | ||
loader_widget._on_hello_clicked() | ||
captured = capsys.readouterr() | ||
assert "INFO: Hello, world!" in captured.out |