diff --git a/brainrender_napari/data_models/atlas_table_model.py b/brainrender_napari/data_models/atlas_table_model.py index e05b587..ef94e05 100644 --- a/brainrender_napari/data_models/atlas_table_model.py +++ b/brainrender_napari/data_models/atlas_table_model.py @@ -1,20 +1,18 @@ from bg_atlasapi.list_atlases import ( get_all_atlases_lastversions, get_atlases_lastversions, - get_downloaded_atlases, get_local_atlas_version, ) from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt +from qtpy.QtWidgets import QTableView -from brainrender_napari.utils.load_user_data import ( - read_atlas_metadata_from_file, -) +from brainrender_napari.utils.formatting import format_atlas_name class AtlasTableModel(QAbstractTableModel): """A table data model for atlases.""" - def __init__(self): + def __init__(self, view_type: QTableView): super().__init__() self.column_headers = [ "Raw name", @@ -22,6 +20,11 @@ def __init__(self): "Local version", "Latest version", ] + assert hasattr( + view_type, "get_tooltip_text" + ), "Views for this model must implement" + "a `classmethod` called `get_tooltip_text`" + self.view_type = view_type self.refresh_data() def refresh_data(self) -> None: @@ -33,30 +36,24 @@ def refresh_data(self) -> None: data.append( [ name, - self._format_name(name), + format_atlas_name(name), get_local_atlas_version(name), latest_version, ] ) else: data.append( - [name, self._format_name(name), "n/a", latest_version] + [name, format_atlas_name(name), "n/a", latest_version] ) self._data = data - def _format_name(self, name: str) -> str: - formatted_name = name.split("_") - formatted_name[0] = formatted_name[0].capitalize() - formatted_name[-1] = f"({formatted_name[-1].split('um')[0]} \u03BCm)" - return " ".join([formatted for formatted in formatted_name]) - def data(self, index: QModelIndex, role=Qt.DisplayRole): if role == Qt.DisplayRole: return self._data[index.row()][index.column()] if role == Qt.ToolTipRole: hovered_atlas_name = self._data[index.row()][0] - return AtlasTableModel._get_tooltip_text(hovered_atlas_name) + return self.view_type.get_tooltip_text(hovered_atlas_name) def rowCount(self, index: QModelIndex = QModelIndex()): return len(self._data) @@ -76,21 +73,3 @@ def headerData( raise ValueError("Unexpected horizontal header value.") else: return super().headerData(section, orientation, role) - - @classmethod - def _get_tooltip_text(cls, atlas_name: str): - """Returns the atlas metadata as a formatted string, - as well as instructions on how to interact with the atlas.""" - if atlas_name in get_downloaded_atlases(): - metadata = read_atlas_metadata_from_file(atlas_name) - metadata_as_string = "" - for key, value in metadata.items(): - metadata_as_string += f"{key}:\t{value}\n" - - tooltip_text = f"{atlas_name} (double-click to add to viewer)\ - \n{metadata_as_string}" - elif atlas_name in get_all_atlases_lastversions().keys(): - tooltip_text = f"{atlas_name} (double-click to download)" - else: - raise ValueError("Tooltip text called with invalid atlas name.") - return tooltip_text diff --git a/brainrender_napari/utils/formatting.py b/brainrender_napari/utils/formatting.py new file mode 100644 index 0000000..9b2862e --- /dev/null +++ b/brainrender_napari/utils/formatting.py @@ -0,0 +1,12 @@ +from bg_atlasapi.list_atlases import get_all_atlases_lastversions + + +def format_atlas_name(name: str) -> str: + """Format an atlas name nicely. + Assumes input in the form of atlas_name_in_snake_case_RESOLUTIONum, + e.g. allen_mouse_100um""" + assert name in get_all_atlases_lastversions().keys(), "invalid atlas name!" + formatted_name = name.split("_") + formatted_name[0] = formatted_name[0].capitalize() + formatted_name[-1] = f"({formatted_name[-1].split('um')[0]} \u03BCm)" + return " ".join([formatted for formatted in formatted_name]) diff --git a/brainrender_napari/widgets/atlas_download_dialog.py b/brainrender_napari/widgets/atlas_manager_dialog.py similarity index 79% rename from brainrender_napari/widgets/atlas_download_dialog.py rename to brainrender_napari/widgets/atlas_manager_dialog.py index d8c39a7..08782f3 100644 --- a/brainrender_napari/widgets/atlas_download_dialog.py +++ b/brainrender_napari/widgets/atlas_manager_dialog.py @@ -8,16 +8,16 @@ ) -class AtlasDownloadDialog(QDialog): - """A modal dialog to ask users to confirm they'd like to download +class AtlasManagerDialog(QDialog): + """A modal dialog to ask users to confirm they'd like to download/update the selected atlas, and warn them that it may be slow. """ - def __init__(self, atlas_name): + def __init__(self, atlas_name: str, action: str) -> None: if atlas_name in get_all_atlases_lastversions().keys(): super().__init__() - self.setWindowTitle(f"Download {atlas_name} Atlas") + self.setWindowTitle(f"{action} {atlas_name} Atlas") self.setModal(True) self.label = QLabel("Are you sure?\n(It may take a while)") @@ -36,5 +36,6 @@ def __init__(self, atlas_name): self.setLayout(layout) else: raise ValueError( - "Download Dialog constructor called with invalid atlas name." + "Atlas manager dialog constructor" + "called with invalid atlas name." ) diff --git a/brainrender_napari/widgets/atlas_manager_view.py b/brainrender_napari/widgets/atlas_manager_view.py new file mode 100644 index 0000000..7a2868d --- /dev/null +++ b/brainrender_napari/widgets/atlas_manager_view.py @@ -0,0 +1,118 @@ +"""The purpose of this file is to provide interactive table view to download +and update atlases. Users interacting with the table can request to +* download an atlas (double-click on row of a not-yet downloaded atlas) +* update an atlas (double-click on row of outdated local atlas) +They can also hover over an up-to-date local atlas and see that +it's up to date. + +It is designed to be agnostic from the viewer framework by emitting signals +that any interested observers can connect to. +""" + +from typing import Callable + +from bg_atlasapi.list_atlases import ( + get_all_atlases_lastversions, + get_atlases_lastversions, + get_downloaded_atlases, +) +from bg_atlasapi.update_atlases import install_atlas, update_atlas +from napari.qt import thread_worker +from qtpy.QtCore import Signal +from qtpy.QtWidgets import QTableView, QWidget + +from brainrender_napari.data_models.atlas_table_model import AtlasTableModel +from brainrender_napari.utils.formatting import format_atlas_name +from brainrender_napari.widgets.atlas_manager_dialog import AtlasManagerDialog + + +class AtlasManagerView(QTableView): + download_atlas_confirmed = Signal(str) + update_atlas_confirmed = Signal(str) + + def __init__(self, parent: QWidget = None): + """Initialises an atlas table view with latest atlas versions. + + Also responsible for appearance, behaviour on selection, and + setting up signal-slot connections. + """ + super().__init__(parent) + + self.setModel(AtlasTableModel(AtlasManagerView)) + self.setEnabled(True) + self.verticalHeader().hide() + self.resizeColumnsToContents() + + self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) + self.setSelectionMode(QTableView.SelectionMode.SingleSelection) + + self.doubleClicked.connect(self._on_row_double_clicked) + self.hideColumn( + self.model().column_headers.index("Raw name") + ) # hide raw name + + def _on_row_double_clicked(self): + atlas_name = self.selected_atlas_name() + if atlas_name in get_downloaded_atlases(): + up_to_date = get_atlases_lastversions()[atlas_name]["updated"] + if not up_to_date: + update_dialog = AtlasManagerDialog(atlas_name, "Update") + update_dialog.ok_button.clicked.connect( + self._on_update_atlas_confirmed + ) + update_dialog.exec() + else: + download_dialog = AtlasManagerDialog(atlas_name, "Download") + download_dialog.ok_button.clicked.connect( + self._on_download_atlas_confirmed + ) + download_dialog.exec() + + def _on_download_atlas_confirmed(self): + """Downloads the currently selected atlas and signals this.""" + atlas_name = self.selected_atlas_name() + worker = self._apply_in_thread(install_atlas, atlas_name) + worker.returned.connect(self.download_atlas_confirmed.emit) + worker.start() + + def _on_update_atlas_confirmed(self): + """Updates the currently selected atlas and signals this.""" + atlas_name = self.selected_atlas_name() + worker = self._apply_in_thread(update_atlas, atlas_name) + worker.returned.connect(self.update_atlas_confirmed.emit) + worker.start() + + def selected_atlas_name(self) -> str: + """A single place to get a valid selected atlas name.""" + selected_index = self.selectionModel().currentIndex() + assert selected_index.isValid() + selected_atlas_name_index = selected_index.siblingAtColumn(0) + selected_atlas_name = self.model().data(selected_atlas_name_index) + return selected_atlas_name + + @thread_worker + def _apply_in_thread(self, apply: Callable, atlas_name: str): + """Calls `apply` on the given atlas in a separate thread.""" + apply(atlas_name) + self.model().refresh_data() + return atlas_name + + @classmethod + def get_tooltip_text(cls, atlas_name: str): + """Returns the atlas name as a formatted string, + as well as instructions on how to interact with the atlas.""" + if atlas_name in get_downloaded_atlases(): + is_up_to_date = get_atlases_lastversions()[atlas_name]["updated"] + if is_up_to_date: + tooltip_text = f"{format_atlas_name(atlas_name)} is up-to-date" + else: # needs updating + tooltip_text = ( + f"{format_atlas_name(atlas_name)} (double-click to update)" + ) + elif atlas_name in get_all_atlases_lastversions().keys(): + tooltip_text = ( + f"{format_atlas_name(atlas_name)} (double-click to download)" + ) + else: + raise ValueError("Tooltip text called with invalid atlas name.") + return tooltip_text diff --git a/brainrender_napari/widgets/atlas_viewer_view.py b/brainrender_napari/widgets/atlas_viewer_view.py index 596e504..4dd217e 100644 --- a/brainrender_napari/widgets/atlas_viewer_view.py +++ b/brainrender_napari/widgets/atlas_viewer_view.py @@ -15,6 +15,7 @@ from qtpy.QtWidgets import QMenu, QTableView, QWidget from brainrender_napari.data_models.atlas_table_model import AtlasTableModel +from brainrender_napari.utils.formatting import format_atlas_name from brainrender_napari.utils.load_user_data import ( read_atlas_metadata_from_file, ) @@ -34,7 +35,7 @@ def __init__(self, parent: QWidget = None): """ super().__init__(parent) - self.setModel(AtlasTableModel()) + self.setModel(AtlasTableModel(AtlasViewerView)) self.setEnabled(True) self.verticalHeader().hide() @@ -102,3 +103,19 @@ def _on_row_double_clicked(self) -> None: def _on_current_changed(self) -> None: """Emits a signal with the newly selected atlas name""" self.selected_atlas_changed.emit(self.selected_atlas_name()) + + @classmethod + def get_tooltip_text(cls, atlas_name: str): + """Returns the atlas metadata as a formatted string, + as well as instructions on how to interact with the atlas.""" + if atlas_name in get_downloaded_atlases(): + metadata = read_atlas_metadata_from_file(atlas_name) + metadata_as_string = "" + for key, value in metadata.items(): + metadata_as_string += f"{key}:\t{value}\n" + tooltip_text = f"{format_atlas_name(atlas_name)}\ + (double-click to add to viewer)\ + \n{metadata_as_string}" + else: + raise ValueError("Tooltip text called with invalid atlas name.") + return tooltip_text diff --git a/tests/conftest.py b/tests/conftest.py index 19e7541..31de9c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,9 @@ import os +import shutil from pathlib import Path import pytest -from bg_atlasapi import BrainGlobeAtlas, config +from bg_atlasapi import BrainGlobeAtlas, config, list_atlases from qtpy.QtCore import Qt @@ -88,3 +89,28 @@ def inner_double_click_on_view(view, index): ) return inner_double_click_on_view + + +@pytest.fixture +def mock_newer_atlas_version_available(): + current_version_path = Path.home() / ".brainglobe/example_mouse_100um_v1.2" + older_version_path = Path.home() / ".brainglobe/example_mouse_100um_v1.1" + assert current_version_path.exists() and not older_version_path.exists() + + current_version_path.rename(older_version_path) + assert older_version_path.exists() and not current_version_path.exists() + assert ( + list_atlases.get_atlases_lastversions()["example_mouse_100um"][ + "latest_version" + ] + == "1.2" + ) + assert list_atlases.get_local_atlas_version("example_mouse_100um") == "1.1" + + yield # run test with outdated version + + # cleanup: ensure version is up-to-date again + if older_version_path.exists(): + shutil.rmtree(path=older_version_path) + _ = BrainGlobeAtlas("example_mouse_100um") + assert current_version_path.exists() and not older_version_path.exists() diff --git a/tests/test_unit/test_atlas_manager_view.py b/tests/test_unit/test_atlas_manager_view.py new file mode 100644 index 0000000..557f16d --- /dev/null +++ b/tests/test_unit/test_atlas_manager_view.py @@ -0,0 +1,202 @@ +import shutil +from importlib import import_module, reload +from pathlib import Path + +import napari.qt +import pytest +from qtpy.QtCore import Qt + +from brainrender_napari.utils.formatting import format_atlas_name +from brainrender_napari.widgets.atlas_manager_view import AtlasManagerView + + +@pytest.fixture +def atlas_manager_view(qtbot): + return AtlasManagerView() + + +def test_update_atlas_confirmed( + qtbot, + mock_newer_atlas_version_available, + atlas_manager_view, +): + """The order of fixtures matters here: + call mock before view constructor!""" + outdated_atlas_directory = ( + Path.home() / ".brainglobe/example_mouse_100um_v1.1" + ) + updated_atlas_directory = ( + Path.home() / ".brainglobe/example_mouse_100um_v1.2" + ) + assert ( + outdated_atlas_directory.exists() + and not updated_atlas_directory.exists() + ) + local_version_index = atlas_manager_view.model().index(0, 2) + assert atlas_manager_view.model().data(local_version_index) == "1.1" + + with qtbot.waitSignal( + atlas_manager_view.update_atlas_confirmed, + timeout=150000, # assumes atlas can be updated in 2.5 minutes! + ) as update_atlas_confirmed_signal: + # replace with double-click on view? + model_index = atlas_manager_view.model().index(0, 0) + atlas_manager_view.setCurrentIndex(model_index) + atlas_manager_view._on_update_atlas_confirmed() + + assert atlas_manager_view.model().data(local_version_index) == "1.2" + assert update_atlas_confirmed_signal.args == ["example_mouse_100um"] + assert ( + updated_atlas_directory.exists() + and not outdated_atlas_directory.exists() + ) + + +@pytest.mark.parametrize( + "row", + [ + 1, # "allen_mouse_10um" + 6, # "allen_human_500um" + ], +) +def test_double_click_on_not_yet_downloaded_atlas_row( + atlas_manager_view, mocker, double_click_on_view, row +): + """Check for a few yet-to-be-downloaded atlases that double-clicking + them on the atlas table view executes the download dialog. + """ + + model_index = atlas_manager_view.model().index(row, 1) + atlas_manager_view.setCurrentIndex(model_index) + + dialog_exec_mock = mocker.patch( + "brainrender_napari.widgets.atlas_manager_view.AtlasManagerDialog.exec" + ) + double_click_on_view(atlas_manager_view, model_index) + dialog_exec_mock.assert_called_once() + + +def test_download_confirmed_callback(atlas_manager_view, qtbot): + """Checks that confirming atlas download creates local copy of + example atlas files and emits expected signal. + + Test setup consists of remembering the expected files and folders + of a preexisting atlas and then removing them. This allows checking + that the function triggers the creation of the same local copy + of the atlas as the `bg_atlasapi` itself. + """ + atlas_directory = Path.home() / ".brainglobe/example_mouse_100um_v1.2" + expected_filenames = atlas_directory.iterdir() + shutil.rmtree( + path=atlas_directory + ) # now remove local copy so button has to trigger download + assert not Path.exists( + atlas_directory + ) # sanity check that local copy is gone + + with qtbot.waitSignal( + atlas_manager_view.download_atlas_confirmed, + timeout=150000, # assumes atlas can be installed in 2.5 minutes! + ) as download_atlas_confirmed_signal: + model_index = atlas_manager_view.model().index(0, 0) + atlas_manager_view.setCurrentIndex(model_index) + atlas_manager_view._on_download_atlas_confirmed() + + assert download_atlas_confirmed_signal.args == ["example_mouse_100um"] + for file in expected_filenames: + assert Path.exists(file) + + +def test_double_click_on_outdated_atlas_row( + atlas_manager_view, + mocker, + double_click_on_view, + mock_newer_atlas_version_available, +): + """Check for an outdated atlas that double-clicking + it on the atlas manager view executes the update dialog. + """ + + outdated_atlas_index = atlas_manager_view.model().index(0, 1) + atlas_manager_view.setCurrentIndex(outdated_atlas_index) + + dialog_exec_mock = mocker.patch( + "brainrender_napari.widgets.atlas_manager_view.AtlasManagerDialog.exec" + ) + double_click_on_view(atlas_manager_view, outdated_atlas_index) + dialog_exec_mock.assert_called_once() + + +def test_hover_atlas_manager_view(atlas_manager_view, mocker): + """Check tooltip is called when hovering over view""" + index = atlas_manager_view.model().index(2, 1) + + get_tooltip_text_mock = mocker.patch( + "brainrender_napari.widgets" + ".atlas_manager_view.AtlasManagerView.get_tooltip_text" + ) + + atlas_manager_view.model().data(index, Qt.ToolTipRole) + + get_tooltip_text_mock.assert_called_once() + + +def test_get_tooltip_not_locally_available(): + """Check tooltip on an example in not-downloaded test data""" + tooltip_text = AtlasManagerView.get_tooltip_text("allen_human_500um") + assert format_atlas_name("allen_human_500um") in tooltip_text + assert "double-click to download" in tooltip_text + + +def test_get_tooltip_not_up_to_date(mock_newer_atlas_version_available): + """Check tooltip on an atlas that is not up-to-date""" + tooltip_text = AtlasManagerView.get_tooltip_text("example_mouse_100um") + assert format_atlas_name("example_mouse_100um") in tooltip_text + assert "double-click to update" in tooltip_text + + +def test_get_tooltip_is_up_to_date(): + """Check tooltip on an atlas that is already up-to-date""" + tooltip_text = AtlasManagerView.get_tooltip_text("example_mouse_100um") + assert format_atlas_name("example_mouse_100um") in tooltip_text + assert "is up-to-date" in tooltip_text + + +def test_get_tooltip_invalid_name(): + """Check tooltip on non-existent test data""" + with pytest.raises(ValueError) as e: + _ = AtlasManagerView.get_tooltip_text("wrong_atlas_name") + assert "invalid atlas name" in e + + +def test_apply_in_thread(qtbot, mocker): + """ + Checks the _apply_in_thread method of AtlasManagerView + - calls its first argument (a function) on its second argument (a string) + - returns its second argument + + We manually replace the @thread_worker decorator during this test, + so _apply_in_thread is executed in the same thread. This ensures + coverage picks up lines inside _apply_in_thread. + """ + + # replace the @thread_worker decorator with an identity function + def identity(func): + return func + + napari.qt.thread_worker = identity + # reload the module for the replaced decorator to take effect + module_name = AtlasManagerView.__module__ + module = import_module(module_name) + reload(module) + assert module.thread_worker == identity + atlas_manager_view = module.AtlasManagerView() + + # check that mock_dummy_apply is applied as expected + mock_dummy_apply = mocker.Mock() + actual = atlas_manager_view._apply_in_thread( + mock_dummy_apply, "example_mouse_100um" + ) + expected = "example_mouse_100um" + assert actual == expected + mock_dummy_apply.assert_called_once_with(expected) diff --git a/tests/test_unit/test_atlas_table_model.py b/tests/test_unit/test_atlas_table_model.py index b3c458c..703c18d 100644 --- a/tests/test_unit/test_atlas_table_model.py +++ b/tests/test_unit/test_atlas_table_model.py @@ -5,8 +5,9 @@ @pytest.fixture -def atlas_table_model(): - return AtlasTableModel() +def atlas_table_model(mocker): + mock_view = mocker.Mock(spec=["get_tooltip_text"]) + return AtlasTableModel(view_type=mock_view) @pytest.mark.parametrize( @@ -39,22 +40,11 @@ def test_model_header_invalid_column(atlas_table_model): ) -def test_get_tooltip_downloaded(): - """Check tooltip on an example in the downloaded test data""" - tooltip_text = AtlasTableModel._get_tooltip_text("example_mouse_100um") - assert "example_mouse" in tooltip_text - assert "add to viewer" in tooltip_text - - -def test_get_tooltip_not_locally_available(): - """Check tooltip on an example in not-downloaded test data""" - tooltip_text = AtlasTableModel._get_tooltip_text("allen_human_500um") - assert "allen_human_500um" in tooltip_text - assert "double-click to download" in tooltip_text - - -def test_get_tooltip_invalid_name(): - """Check tooltip on non-existent test data""" - with pytest.raises(ValueError) as e: - _ = AtlasTableModel._get_tooltip_text("wrong_atlas_name") - assert "invalid atlas name" in e +def test_model_header_invalid_view(): + """Checks that the model complains + if its view_type is not valid.""" + with pytest.raises(AssertionError) as error: + _ = AtlasTableModel(view_type=None) + assert "Views" in error + assert "classmethod" in error + assert "get_tooltip_text" in error diff --git a/tests/test_unit/test_atlas_viewer_view.py b/tests/test_unit/test_atlas_viewer_view.py index 35186d7..9b54c5d 100644 --- a/tests/test_unit/test_atlas_viewer_view.py +++ b/tests/test_unit/test_atlas_viewer_view.py @@ -3,6 +3,7 @@ import pytest from qtpy.QtCore import QModelIndex, Qt +from brainrender_napari.utils.formatting import format_atlas_name from brainrender_napari.widgets.atlas_viewer_view import ( AtlasViewerView, ) @@ -56,13 +57,13 @@ def test_atlas_view_not_downloaded_selection(qtbot, atlas_viewer_view): assert "selected_atlas_name" in traceback.format_tb(collected_traceback)[0] -def test_hover_atlas_view(atlas_viewer_view, mocker): +def test_hover_atlas_viewer_view(atlas_viewer_view, mocker): """Check tooltip is called when hovering over view""" index = atlas_viewer_view.model().index(2, 1) get_tooltip_text_mock = mocker.patch( - "brainrender_napari.data_models" - ".atlas_table_model.AtlasTableModel._get_tooltip_text" + "brainrender_napari.widgets" + ".atlas_viewer_view.AtlasViewerView.get_tooltip_text" ) atlas_viewer_view.model().data(index, Qt.ToolTipRole) @@ -119,3 +120,17 @@ def test_additional_reference_menu(atlas_viewer_view, qtbot, mocker): assert additional_reference_requested_signal.args == [ "mock_additional_reference" ] + + +def test_get_tooltip(): + """Check tooltip on an example in the downloaded test data""" + tooltip_text = AtlasViewerView.get_tooltip_text("example_mouse_100um") + assert format_atlas_name("example_mouse_100um") in tooltip_text + assert "add to viewer" in tooltip_text + + +def test_get_tooltip_invalid_name(): + """Check tooltip on non-existent test data""" + with pytest.raises(ValueError) as e: + _ = AtlasViewerView.get_tooltip_text("wrong_atlas_name") + assert "invalid atlas name" in e diff --git a/tests/test_unit/test_download_dialog.py b/tests/test_unit/test_download_dialog.py deleted file mode 100644 index dd02ab4..0000000 --- a/tests/test_unit/test_download_dialog.py +++ /dev/null @@ -1,22 +0,0 @@ -import pytest - -from brainrender_napari.widgets.atlas_download_dialog import ( - AtlasDownloadDialog, -) - - -def test_download_dialog(qtbot): - """Check download dialog constructor and buttons connections""" - dialog = AtlasDownloadDialog("example_mouse_100um") - with qtbot.waitSignal(dialog.accepted): - dialog.ok_button.click() - - with qtbot.waitSignal(dialog.rejected): - dialog.cancel_button.click() - - -def test_download_dialog_raises(): - """Check download dialog constructor errors on invalid input""" - with pytest.raises(ValueError) as e: - _ = AtlasDownloadDialog("wrong_atlas_name") - assert "invalid atlas name" in e diff --git a/tests/test_unit/test_download_update_dialog.py b/tests/test_unit/test_download_update_dialog.py new file mode 100644 index 0000000..bcb5297 --- /dev/null +++ b/tests/test_unit/test_download_update_dialog.py @@ -0,0 +1,24 @@ +import pytest + +from brainrender_napari.widgets.atlas_manager_dialog import ( + AtlasManagerDialog, +) + + +@pytest.mark.parametrize("action", ["Download", "Update"]) +def test_download_update_dialog(action, qtbot): + """Check download dialog constructor and buttons connections""" + dialog = AtlasManagerDialog("example_mouse_100um", action) + with qtbot.waitSignal(dialog.accepted): + dialog.ok_button.click() + + with qtbot.waitSignal(dialog.rejected): + dialog.cancel_button.click() + + +@pytest.mark.parametrize("action", ["Download", "Update"]) +def test_download_update_dialog_raises(action): + """Check download dialog constructor errors on invalid input""" + with pytest.raises(ValueError) as e: + _ = AtlasManagerDialog("wrong_atlas_name", action) + assert [action, "invalid atlas name"] in e