Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests/more tests to widgets with low coverage #5864

Merged
merged 10 commits into from
Jun 22, 2023
21 changes: 21 additions & 0 deletions napari/_qt/widgets/_tests/test_qt_color_swatch.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import numpy as np
import pytest
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication

from napari._qt.widgets.qt_color_swatch import (
TRANSPARENT,
QColorPopup,
QColorSwatch,
QColorSwatchEdit,
)
Expand All @@ -17,9 +20,18 @@ def test_succesfull_create_qcolorswatchedit(qtbot, color, tooltip):
test_color = color or TRANSPARENT
test_tooltip = tooltip or 'click to set color'

# check widget creation and base values
assert widget.color_swatch.toolTip() == test_tooltip
np.testing.assert_array_equal(widget.color, test_color)

# check widget popup
qtbot.mouseRelease(widget.color_swatch, Qt.MouseButton.LeftButton)
color_popup = None
for widget in QApplication.topLevelWidgets():
if isinstance(widget, QColorPopup):
color_popup = widget
assert color_popup


@pytest.mark.parametrize('color', [None, [1, 1, 1, 1]])
@pytest.mark.parametrize('tooltip', [None, 'This is a test'])
Expand All @@ -30,5 +42,14 @@ def test_succesfull_create_qcolorswatch(qtbot, color, tooltip):
test_color = color or TRANSPARENT
test_tooltip = tooltip or 'click to set color'

# check widget creation and base values
assert widget.toolTip() == test_tooltip
np.testing.assert_array_equal(widget.color, test_color)

# check widget popup
qtbot.mouseRelease(widget, Qt.MouseButton.LeftButton)
color_popup = None
for widget in QApplication.topLevelWidgets():
if isinstance(widget, QColorPopup):
color_popup = widget
assert color_popup
147 changes: 147 additions & 0 deletions napari/_qt/widgets/_tests/test_qt_plugin_sorter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import pytest

from napari._qt.widgets.qt_plugin_sorter import QtPluginSorter, rst2html


@pytest.mark.parametrize(
'text,expected_text',
[
("", ""),
(
"""Return a function capable of loading ``path`` into napari, or ``None``.

This is the primary "**reader plugin**" function. It accepts a path or
list of paths, and returns a list of data to be added to the ``Viewer``.
The function may return ``[(None, )]`` to indicate that the file was read
successfully, but did not contain any data.

The main place this hook is used is in :func:`Viewer.open()
<napari.components.viewer_model.ViewerModel.open>`, via the
:func:`~napari.plugins.io.read_data_with_plugins` function.

It will also be called on ``File -> Open...`` or when a user drops a file
or folder onto the viewer. This function must execute **quickly**, and
should return ``None`` if the filepath is of an unrecognized format for
this reader plugin. If ``path`` is determined to be recognized format,
this function should return a *new* function that accepts the same filepath
(or list of paths), and returns a list of ``LayerData`` tuples, where each
tuple is a 1-, 2-, or 3-tuple of ``(data,)``, ``(data, meta)``, or ``(data,
meta, layer_type)``.

``napari`` will then use each tuple in the returned list to generate a new
layer in the viewer using the :func:`Viewer._add_layer_from_data()
<napari.components.viewer_model.ViewerModel._add_layer_from_data>`
method. The first, (optional) second, and (optional) third items in each
tuple in the returned layer_data list, therefore correspond to the
``data``, ``meta``, and ``layer_type`` arguments of the
:func:`Viewer._add_layer_from_data()
<napari.components.viewer_model.ViewerModel._add_layer_from_data>`
method, respectively.

.. important::

``path`` may be either a ``str`` or a ``list`` of ``str``. If a
``list``, then each path in the list can be assumed to be one part of a
larger multi-dimensional stack (for instance: a list of 2D image files
that should be stacked along a third axis). Implementations should do
their own checking for ``list`` or ``str``, and handle each case as
desired.""",
'Return a function capable of loading <code>path</code> into napari, or <code>None</code>.<br><br> '
'This is the primary "<strong>reader plugin</strong>" function. It accepts a path or<br> '
'list of paths, and returns a list of data to be added to the <code>Viewer</code>.<br> '
'The function may return <code>[(None, )]</code> to indicate that the file was read<br> '
'successfully, but did not contain any data.<br><br> '
'The main place this hook is used is in <code>Viewer.open()</code>, via the<br> '
'<code>read_data_with_plugins</code> function.<br><br> '
'It will also be called on <code>File -> Open...</code> or when a user drops a file<br> '
'or folder onto the viewer. This function must execute <strong>quickly</strong>, and<br> '
'should return <code>None</code> if the filepath is of an unrecognized format for<br> '
'this reader plugin. If <code>path</code> is determined to be recognized format,<br> '
'this function should return a <em>new</em> function that accepts the same filepath<br> '
'(or list of paths), and returns a list of <code>LayerData</code> tuples, where each<br> '
'tuple is a 1-, 2-, or 3-tuple of <code>(data,)</code>, <code>(data, meta)</code>, or <code>(data,<br> '
'meta, layer_type)</code>.<br><br> <code>napari</code> will then use each tuple in the returned list to generate a new<br> '
'layer in the viewer using the <code>Viewer._add_layer_from_data()</code><br> '
'method. The first, (optional) second, and (optional) third items in each<br> '
'tuple in the returned layer_data list, therefore correspond to the<br> '
'<code>data</code>, <code>meta</code>, and <code>layer_type</code> arguments of the<br> '
'<code>Viewer._add_layer_from_data()</code><br> method, respectively.<br><br> .. important::<br><br>'
' <code>path</code> may be either a <code>str</code> or a <code>list</code> of <code>str</code>. If a<br>'
' <code>list</code>, then each path in the list can be assumed to be one part of a<br> '
'larger multi-dimensional stack (for instance: a list of 2D image files<br> '
'that should be stacked along a third axis). Implementations should do<br> '
'their own checking for <code>list</code> or <code>str</code>, and handle each case as<br> '
'desired.',
),
],
)
def test_rst2html(text, expected_text):
assert rst2html(text) == expected_text


def test_create_qt_plugin_sorter(qtbot):
plugin_sorter = QtPluginSorter()
qtbot.addWidget(plugin_sorter)

# Check initial hook combobox items
hook_combo_box = plugin_sorter.hook_combo_box
combobox_items = [
hook_combo_box.itemText(idx) for idx in range(hook_combo_box.count())
]
assert combobox_items == [
'select hook... ',
'get_reader',
'get_writer',
'write_image',
'write_labels',
'write_points',
'write_shapes',
'write_surface',
'write_vectors',
]


@pytest.mark.parametrize(
"hook_name,help_info",
[
('select hook... ', ''),
(
'get_reader',
'This is the primary "<strong>reader plugin</strong>" function. It accepts a path or<br> list of paths, and returns a list of data to be added to the <code>Viewer</code>.<br>',
),
(
'get_writer',
'This function will be called whenever the user attempts to save multiple<br> layers (e.g. via <code>File -> Save Layers</code>, or<br> <code>save_layers</code>).<br>',
),
(
'write_labels',
'It is the responsibility of the implementation to check any extension on<br> <code>path</code> and return <code>None</code> if it is an unsupported extension.',
),
(
'write_points',
'It is the responsibility of the implementation to check any extension on<br> <code>path</code> and return <code>None</code> if it is an unsupported extension.',
),
(
'write_shapes',
'It is the responsibility of the implementation to check any extension on<br> <code>path</code> and return <code>None</code> if it is an unsupported extension.',
),
(
'write_surface',
'It is the responsibility of the implementation to check any extension on<br> <code>path</code> and return <code>None</code> if it is an unsupported extension.',
),
(
'write_vectors',
'It is the responsibility of the implementation to check any extension on<br> <code>path</code> and return <code>None</code> if it is an unsupported extension.',
),
],
)
def test_qt_plugin_sorter_help_info(qtbot, hook_name, help_info):
plugin_sorter = QtPluginSorter()
qtbot.addWidget(plugin_sorter)

# Check hook combobox items help tooltip in the info widget
info_widget = plugin_sorter.info
hook_combo_box = plugin_sorter.hook_combo_box
hook_combo_box.setCurrentText(hook_name)

assert help_info in info_widget.toolTip()
23 changes: 23 additions & 0 deletions napari/_qt/widgets/_tests/test_qt_tooltip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys

import pytest
from qtpy.QtWidgets import QToolTip

from napari._qt.widgets.qt_tooltip import QtToolTipLabel


@pytest.mark.skipif(
sys.platform.startswith('linux') or sys.platform == 'darwin',
reason='Timeouts when running on CI with Linux or macOS',
)
def test_qt_tooltip_label(qtbot):
tooltip_text = "Test QtToolTipLabel showing a tooltip"
widget = QtToolTipLabel("Label with a tooltip")
widget.setToolTip(tooltip_text)
qtbot.addWidget(widget)
widget.show()

assert QToolTip.text() == ""
qtbot.mouseMove(widget)
qtbot.waitUntil(lambda: QToolTip.isVisible())
qtbot.waitUntil(lambda: QToolTip.text() == tooltip_text)