Skip to content

Commit

Permalink
Add tests/more tests to widgets with low coverage (napari#5864)
Browse files Browse the repository at this point in the history
# Description

* [x] Add more tests for `QColorSwatch` related widgets
* [x] Add test for `QtToolTipLabel`
* [x] Add test for `QtPluginSorter`

## Notes

While checking the addition of new tests to widgets using the [code
coverage as
guidance](https://app.codecov.io/gh/napari/napari/tree/main/napari/_qt/widgets),
seems like a widget which doesn't have coverage at all exist:
`QtDictTable`. But more interestingly, seems like this widget is not
used anymore? I was unable to find a place inside napari source code
where this widget is used. Maybe it should be removed or moved to an
upstream project like `superqt`?
  • Loading branch information
dalthviz authored and melissawm committed Jul 3, 2023
1 parent a3398b6 commit dd89a24
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
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)

0 comments on commit dd89a24

Please sign in to comment.