napari-plugin-manager 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- napari_plugin_manager/__init__.py +0 -0
- napari_plugin_manager/_tests/__init__.py +0 -0
- napari_plugin_manager/_tests/conftest.py +63 -0
- napari_plugin_manager/_tests/test_installer_process.py +338 -0
- napari_plugin_manager/_tests/test_npe2api.py +51 -0
- napari_plugin_manager/_tests/test_qt_plugin_dialog.py +563 -0
- napari_plugin_manager/_tests/test_utils.py +27 -0
- napari_plugin_manager/_version.py +16 -0
- napari_plugin_manager/npe2api.py +131 -0
- napari_plugin_manager/qt_package_installer.py +660 -0
- napari_plugin_manager/qt_plugin_dialog.py +1495 -0
- napari_plugin_manager/qt_widgets.py +14 -0
- napari_plugin_manager/styles.qss +352 -0
- napari_plugin_manager/utils.py +22 -0
- napari_plugin_manager-0.1.0.dist-info/LICENSE +29 -0
- napari_plugin_manager-0.1.0.dist-info/METADATA +255 -0
- napari_plugin_manager-0.1.0.dist-info/RECORD +19 -0
- napari_plugin_manager-0.1.0.dist-info/WHEEL +5 -0
- napari_plugin_manager-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Generator, Optional, Tuple
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
import napari.plugins
|
|
9
|
+
import npe2
|
|
10
|
+
import pytest
|
|
11
|
+
import qtpy
|
|
12
|
+
from napari.plugins._tests.test_npe2 import mock_pm # noqa
|
|
13
|
+
from napari.utils.translations import trans
|
|
14
|
+
from qtpy.QtCore import QMimeData, QPointF, Qt, QUrl
|
|
15
|
+
from qtpy.QtGui import QDropEvent
|
|
16
|
+
|
|
17
|
+
if (qtpy.API_NAME == 'PySide2') or (
|
|
18
|
+
sys.version_info[:2] > (3, 10) and platform.system() == "Linux"
|
|
19
|
+
):
|
|
20
|
+
pytest.skip(
|
|
21
|
+
"Known PySide2 x Python incompatibility: "
|
|
22
|
+
"... object cannot be interpreted as an integer",
|
|
23
|
+
allow_module_level=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from napari_plugin_manager import qt_plugin_dialog
|
|
27
|
+
from napari_plugin_manager.qt_package_installer import InstallerActions
|
|
28
|
+
|
|
29
|
+
N_MOCKED_PLUGINS = 2
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _iter_napari_pypi_plugin_info(
|
|
33
|
+
conda_forge: bool = True,
|
|
34
|
+
) -> Generator[
|
|
35
|
+
Tuple[Optional[npe2.PackageMetadata], bool], None, None
|
|
36
|
+
]: # pragma: no cover (this function is used in thread and codecov has a problem with the collection of coverage in such cases)
|
|
37
|
+
"""Mock the pypi method to collect available plugins.
|
|
38
|
+
|
|
39
|
+
This will mock napari.plugins.pypi.iter_napari_plugin_info` for pypi.
|
|
40
|
+
|
|
41
|
+
It will return two fake plugins that will populate the available plugins
|
|
42
|
+
list (the bottom one).
|
|
43
|
+
"""
|
|
44
|
+
# This mock `base_data`` will be the same for both fake plugins.
|
|
45
|
+
packages = ['pyzenhub', 'requests', 'my-plugin', 'my-test-old-plugin-1']
|
|
46
|
+
base_data = {
|
|
47
|
+
"metadata_version": "1.0",
|
|
48
|
+
"version": "0.1.0",
|
|
49
|
+
"summary": "some test package",
|
|
50
|
+
"home_page": "http://napari.org",
|
|
51
|
+
"author": "test author",
|
|
52
|
+
"license": "UNKNOWN",
|
|
53
|
+
}
|
|
54
|
+
for i in range(len(packages)):
|
|
55
|
+
yield npe2.PackageMetadata(name=f"{packages[i]}", **base_data), bool(
|
|
56
|
+
i
|
|
57
|
+
), {
|
|
58
|
+
"home_page": 'www.mywebsite.com',
|
|
59
|
+
"pypi_versions": ['2.31.0'],
|
|
60
|
+
"conda_versions": ['2.32.1'],
|
|
61
|
+
'display_name': packages[i].upper(),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class PluginsMock:
|
|
66
|
+
def __init__(self):
|
|
67
|
+
self.plugins = {
|
|
68
|
+
'requests': True,
|
|
69
|
+
'pyzenhub': True,
|
|
70
|
+
'my-plugin': True,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class OldPluginsMock:
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self.plugins = [
|
|
77
|
+
('my-test-old-plugin-1', False, 'my-test-old-plugin-1')
|
|
78
|
+
]
|
|
79
|
+
self.enabled = [True]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.fixture
|
|
83
|
+
def old_plugins(qtbot):
|
|
84
|
+
return OldPluginsMock()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture
|
|
88
|
+
def plugins(qtbot):
|
|
89
|
+
return PluginsMock()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class WarnPopupMock:
|
|
93
|
+
def __init__(self, text):
|
|
94
|
+
self._is_visible = False
|
|
95
|
+
|
|
96
|
+
def show(self):
|
|
97
|
+
self._is_visible = True
|
|
98
|
+
|
|
99
|
+
def exec_(self):
|
|
100
|
+
self._is_visible = True
|
|
101
|
+
|
|
102
|
+
def move(self, pos):
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def isVisible(self):
|
|
106
|
+
return self._is_visible
|
|
107
|
+
|
|
108
|
+
def close(self):
|
|
109
|
+
self._is_visible = False
|
|
110
|
+
|
|
111
|
+
def width(self):
|
|
112
|
+
return 100
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.fixture(params=[True, False], ids=["constructor", "no-constructor"])
|
|
116
|
+
def plugin_dialog(
|
|
117
|
+
request,
|
|
118
|
+
qtbot,
|
|
119
|
+
monkeypatch,
|
|
120
|
+
mock_pm, # noqa
|
|
121
|
+
plugins,
|
|
122
|
+
old_plugins,
|
|
123
|
+
):
|
|
124
|
+
"""Fixture that provides a plugin dialog for a normal napari install."""
|
|
125
|
+
|
|
126
|
+
class PluginManagerMock:
|
|
127
|
+
def instance(self):
|
|
128
|
+
return PluginManagerInstanceMock(plugins)
|
|
129
|
+
|
|
130
|
+
class PluginManagerInstanceMock:
|
|
131
|
+
def __init__(self, plugins):
|
|
132
|
+
self.plugins = plugins.plugins
|
|
133
|
+
|
|
134
|
+
def __iter__(self):
|
|
135
|
+
yield from self.plugins
|
|
136
|
+
|
|
137
|
+
def iter_manifests(self):
|
|
138
|
+
yield from [mock_pm.get_manifest('my-plugin')]
|
|
139
|
+
|
|
140
|
+
def is_disabled(self, name):
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def discover(self):
|
|
144
|
+
return ['plugin']
|
|
145
|
+
|
|
146
|
+
def enable(self, plugin):
|
|
147
|
+
self.plugins[plugin] = True
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
def disable(self, plugin):
|
|
151
|
+
self.plugins[plugin] = False
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
def mock_metadata(name):
|
|
155
|
+
meta = {
|
|
156
|
+
'version': '0.1.0',
|
|
157
|
+
'summary': '',
|
|
158
|
+
'Home-page': '',
|
|
159
|
+
'author': '',
|
|
160
|
+
'license': '',
|
|
161
|
+
}
|
|
162
|
+
return meta
|
|
163
|
+
|
|
164
|
+
class OldPluginManagerMock:
|
|
165
|
+
def __init__(self):
|
|
166
|
+
self.plugins = old_plugins.plugins
|
|
167
|
+
self.enabled = old_plugins.enabled
|
|
168
|
+
|
|
169
|
+
def iter_available(self):
|
|
170
|
+
return self.plugins
|
|
171
|
+
|
|
172
|
+
def discover(self):
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def is_blocked(self, plugin):
|
|
176
|
+
return self.plugins[0][1]
|
|
177
|
+
|
|
178
|
+
def set_blocked(self, plugin, blocked):
|
|
179
|
+
self.enabled[0] = not blocked
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
monkeypatch.setattr(
|
|
183
|
+
qt_plugin_dialog,
|
|
184
|
+
"iter_napari_plugin_info",
|
|
185
|
+
_iter_napari_pypi_plugin_info,
|
|
186
|
+
)
|
|
187
|
+
monkeypatch.setattr(qt_plugin_dialog, 'WarnPopup', WarnPopupMock)
|
|
188
|
+
|
|
189
|
+
# This is patching `napari.utils.misc.running_as_constructor_app` function
|
|
190
|
+
# to mock a normal napari install.
|
|
191
|
+
monkeypatch.setattr(
|
|
192
|
+
qt_plugin_dialog, "running_as_constructor_app", lambda: request.param
|
|
193
|
+
)
|
|
194
|
+
monkeypatch.setattr(
|
|
195
|
+
qt_plugin_dialog, "IS_NAPARI_CONDA_INSTALLED", request.param
|
|
196
|
+
)
|
|
197
|
+
monkeypatch.setattr(qt_plugin_dialog, "ON_BUNDLE", request.param)
|
|
198
|
+
monkeypatch.setattr(
|
|
199
|
+
napari.plugins, 'plugin_manager', OldPluginManagerMock()
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
monkeypatch.setattr(importlib.metadata, 'metadata', mock_metadata)
|
|
203
|
+
|
|
204
|
+
monkeypatch.setattr(npe2, 'PluginManager', PluginManagerMock())
|
|
205
|
+
|
|
206
|
+
widget = qt_plugin_dialog.QtPluginDialog()
|
|
207
|
+
# monkeypatch.setattr(widget, '_tag_outdated_plugins', lambda: None)
|
|
208
|
+
widget.show()
|
|
209
|
+
qtbot.waitUntil(widget.isVisible, timeout=300)
|
|
210
|
+
|
|
211
|
+
def available_list_populated():
|
|
212
|
+
return widget.available_list.count() == N_MOCKED_PLUGINS
|
|
213
|
+
|
|
214
|
+
qtbot.waitUntil(available_list_populated, timeout=3000)
|
|
215
|
+
qtbot.add_widget(widget)
|
|
216
|
+
yield widget
|
|
217
|
+
widget.hide()
|
|
218
|
+
widget._add_items_timer.stop()
|
|
219
|
+
assert not widget._add_items_timer.isActive()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_filter_not_available_plugins(request, plugin_dialog):
|
|
223
|
+
"""
|
|
224
|
+
Check that the plugins listed under available plugins are
|
|
225
|
+
enabled and disabled accordingly.
|
|
226
|
+
"""
|
|
227
|
+
if "no-constructor" in request.node.name:
|
|
228
|
+
pytest.skip(
|
|
229
|
+
reason="This test is only relevant for constructor-based installs"
|
|
230
|
+
)
|
|
231
|
+
item = plugin_dialog.available_list.item(0)
|
|
232
|
+
widget = plugin_dialog.available_list.itemWidget(item)
|
|
233
|
+
if widget:
|
|
234
|
+
assert not widget.action_button.isEnabled()
|
|
235
|
+
assert widget.warning_tooltip.isVisible()
|
|
236
|
+
|
|
237
|
+
item = plugin_dialog.available_list.item(1)
|
|
238
|
+
widget = plugin_dialog.available_list.itemWidget(item)
|
|
239
|
+
assert widget.action_button.isEnabled()
|
|
240
|
+
assert not widget.warning_tooltip.isVisible()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_filter_available_plugins(plugin_dialog):
|
|
244
|
+
"""
|
|
245
|
+
Test the dialog is correctly filtering plugins in the available plugins
|
|
246
|
+
list (the bottom one).
|
|
247
|
+
"""
|
|
248
|
+
plugin_dialog.filter("")
|
|
249
|
+
assert plugin_dialog.available_list.count() == 2
|
|
250
|
+
assert plugin_dialog.available_list.count_visible() == 2
|
|
251
|
+
|
|
252
|
+
plugin_dialog.filter("no-match@123")
|
|
253
|
+
assert plugin_dialog.available_list.count_visible() == 0
|
|
254
|
+
|
|
255
|
+
plugin_dialog.filter("")
|
|
256
|
+
plugin_dialog.filter("requests")
|
|
257
|
+
assert plugin_dialog.available_list.count_visible() == 1
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_filter_installed_plugins(plugin_dialog):
|
|
261
|
+
"""
|
|
262
|
+
Test the dialog is correctly filtering plugins in the installed plugins
|
|
263
|
+
list (the top one).
|
|
264
|
+
"""
|
|
265
|
+
plugin_dialog.filter("")
|
|
266
|
+
assert plugin_dialog.installed_list.count_visible() >= 0
|
|
267
|
+
|
|
268
|
+
plugin_dialog.filter("no-match@123")
|
|
269
|
+
assert plugin_dialog.installed_list.count_visible() == 0
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_visible_widgets(request, plugin_dialog):
|
|
273
|
+
"""
|
|
274
|
+
Test that the direct entry button and textbox are visible
|
|
275
|
+
"""
|
|
276
|
+
if "no-constructor" not in request.node.name:
|
|
277
|
+
pytest.skip(
|
|
278
|
+
reason="Tested functionality not available in constructor-based installs"
|
|
279
|
+
)
|
|
280
|
+
assert plugin_dialog.direct_entry_edit.isVisible()
|
|
281
|
+
assert plugin_dialog.direct_entry_btn.isVisible()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_version_dropdown(plugin_dialog, qtbot):
|
|
285
|
+
"""
|
|
286
|
+
Test that when the source drop down is changed, it displays the other versions properly.
|
|
287
|
+
"""
|
|
288
|
+
# qtbot.wait(10000)
|
|
289
|
+
widget = plugin_dialog.available_list.item(0).widget
|
|
290
|
+
count = widget.version_choice_dropdown.count()
|
|
291
|
+
if count == 2:
|
|
292
|
+
assert widget.version_choice_dropdown.currentText() == "2.31.0"
|
|
293
|
+
# switch from PyPI source to conda one.
|
|
294
|
+
widget.source_choice_dropdown.setCurrentIndex(1)
|
|
295
|
+
assert widget.version_choice_dropdown.currentText() == "2.32.1"
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_plugin_list_count_items(plugin_dialog):
|
|
299
|
+
assert plugin_dialog.installed_list.count_visible() == 2
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def test_plugin_list_handle_action(plugin_dialog, qtbot):
|
|
303
|
+
item = plugin_dialog.installed_list.item(0)
|
|
304
|
+
with patch.object(qt_plugin_dialog.PluginListItem, "set_busy") as mock:
|
|
305
|
+
plugin_dialog.installed_list.handle_action(
|
|
306
|
+
item,
|
|
307
|
+
'my-test-old-plugin-1',
|
|
308
|
+
InstallerActions.UPGRADE,
|
|
309
|
+
)
|
|
310
|
+
mock.assert_called_with(
|
|
311
|
+
trans._("updating..."), InstallerActions.UPGRADE
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
with patch.object(qt_plugin_dialog.WarnPopup, "exec_") as mock:
|
|
315
|
+
plugin_dialog.installed_list.handle_action(
|
|
316
|
+
item,
|
|
317
|
+
'my-test-old-plugin-1',
|
|
318
|
+
InstallerActions.UNINSTALL,
|
|
319
|
+
)
|
|
320
|
+
assert mock.called
|
|
321
|
+
|
|
322
|
+
item = plugin_dialog.available_list.item(0)
|
|
323
|
+
with patch.object(qt_plugin_dialog.PluginListItem, "set_busy") as mock:
|
|
324
|
+
|
|
325
|
+
plugin_dialog.available_list.handle_action(
|
|
326
|
+
item,
|
|
327
|
+
'my-test-old-plugin-1',
|
|
328
|
+
InstallerActions.INSTALL,
|
|
329
|
+
version='3',
|
|
330
|
+
)
|
|
331
|
+
mock.assert_called_with(
|
|
332
|
+
trans._("installing..."), InstallerActions.INSTALL
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
plugin_dialog.available_list.handle_action(
|
|
336
|
+
item, 'my-test-old-plugin-1', InstallerActions.CANCEL, version='3'
|
|
337
|
+
)
|
|
338
|
+
mock.assert_called_with("", InstallerActions.CANCEL)
|
|
339
|
+
|
|
340
|
+
qtbot.waitUntil(lambda: not plugin_dialog.worker.is_running)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_on_enabled_checkbox(plugin_dialog, qtbot, plugins, old_plugins):
|
|
344
|
+
# checks npe2 lines
|
|
345
|
+
item = plugin_dialog.installed_list.item(0)
|
|
346
|
+
widget = plugin_dialog.installed_list.itemWidget(item)
|
|
347
|
+
|
|
348
|
+
assert plugins.plugins['my-plugin'] is True
|
|
349
|
+
with qtbot.waitSignal(widget.enabled_checkbox.stateChanged, timeout=500):
|
|
350
|
+
widget.enabled_checkbox.setChecked(False)
|
|
351
|
+
assert plugins.plugins['my-plugin'] is False
|
|
352
|
+
|
|
353
|
+
# checks npe1 lines
|
|
354
|
+
item = plugin_dialog.installed_list.item(1)
|
|
355
|
+
widget = plugin_dialog.installed_list.itemWidget(item)
|
|
356
|
+
|
|
357
|
+
assert old_plugins.enabled[0] is True
|
|
358
|
+
with qtbot.waitSignal(widget.enabled_checkbox.stateChanged, timeout=500):
|
|
359
|
+
widget.enabled_checkbox.setChecked(False)
|
|
360
|
+
assert old_plugins.enabled[0] is False
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_add_items_outdated_and_update(plugin_dialog, qtbot):
|
|
364
|
+
"""
|
|
365
|
+
Test that a plugin is tagged as outdated (a newer version is available), the update button becomes visible.
|
|
366
|
+
|
|
367
|
+
Also check that after doing an update the update button gets hidden.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
# The plugin is being added to the available plugins list. When the dialog is being built
|
|
371
|
+
# this one will be listed as available, and it will be found as already installed.
|
|
372
|
+
# Then, it will check if the installed version is a lower version than the one available.
|
|
373
|
+
# In this case, my-plugin is installed with version 0.1.0, so the one we are trying to install
|
|
374
|
+
# is newer, so the update button should pop up.
|
|
375
|
+
new_plugin = (
|
|
376
|
+
npe2.PackageMetadata(name="my-plugin", version="0.4.0"),
|
|
377
|
+
True,
|
|
378
|
+
{
|
|
379
|
+
"home_page": 'www.mywebsite.com',
|
|
380
|
+
"pypi_versions": ['0.4.0', '0.1.0'],
|
|
381
|
+
"conda_versions": ['0.4.0', '0.1.0'],
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
plugin_dialog._plugin_data_map["my-plugin"] = new_plugin
|
|
385
|
+
plugin_dialog._plugin_queue = [new_plugin]
|
|
386
|
+
plugin_dialog._add_items()
|
|
387
|
+
item = plugin_dialog.installed_list.item(0)
|
|
388
|
+
widget = plugin_dialog.installed_list.itemWidget(item)
|
|
389
|
+
initial_version = "0.1.0"
|
|
390
|
+
mod_initial_version = initial_version.replace('.', '․') # noqa: RUF001
|
|
391
|
+
assert widget.update_btn.isVisible()
|
|
392
|
+
assert widget.version.text() == mod_initial_version
|
|
393
|
+
assert widget.version.toolTip() == initial_version
|
|
394
|
+
|
|
395
|
+
# Trigger process finished handler to simulated that an update was done
|
|
396
|
+
plugin_dialog._on_process_finished(
|
|
397
|
+
{
|
|
398
|
+
'exit_code': 1,
|
|
399
|
+
'exit_status': 0,
|
|
400
|
+
'action': InstallerActions.UPGRADE,
|
|
401
|
+
'pkgs': ['my-plugin==0.4.0'],
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
updated_version = "0.4.0"
|
|
405
|
+
mod_updated_version = updated_version.replace('.', '․') # noqa: RUF001
|
|
406
|
+
assert not widget.update_btn.isVisible()
|
|
407
|
+
assert widget.version.text() == mod_updated_version
|
|
408
|
+
assert widget.version.toolTip() == updated_version
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@pytest.mark.skipif(
|
|
412
|
+
qtpy.API_NAME.lower().startswith('pyside')
|
|
413
|
+
and sys.version_info[:2] > (3, 10)
|
|
414
|
+
and platform.system() == "Darwin",
|
|
415
|
+
reason='pyside specific bug',
|
|
416
|
+
)
|
|
417
|
+
def test_refresh(qtbot, plugin_dialog):
|
|
418
|
+
with qtbot.waitSignal(plugin_dialog._add_items_timer.timeout, timeout=500):
|
|
419
|
+
plugin_dialog.refresh(clear_cache=False)
|
|
420
|
+
|
|
421
|
+
with qtbot.waitSignal(plugin_dialog._add_items_timer.timeout, timeout=500):
|
|
422
|
+
plugin_dialog.refresh(clear_cache=True)
|
|
423
|
+
|
|
424
|
+
with qtbot.waitSignal(plugin_dialog._add_items_timer.timeout, timeout=500):
|
|
425
|
+
plugin_dialog._refresh_and_clear_cache()
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@pytest.mark.skipif(
|
|
429
|
+
qtpy.API_NAME.lower().startswith('pyside')
|
|
430
|
+
and sys.version_info[:2] > (3, 10)
|
|
431
|
+
and platform.system() == "Darwin",
|
|
432
|
+
reason='pyside specific bug',
|
|
433
|
+
)
|
|
434
|
+
def test_toggle_status(plugin_dialog):
|
|
435
|
+
plugin_dialog.toggle_status(True)
|
|
436
|
+
assert plugin_dialog.stdout_text.isVisible()
|
|
437
|
+
plugin_dialog.toggle_status(False)
|
|
438
|
+
assert not plugin_dialog.stdout_text.isVisible()
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@pytest.mark.skipif(
|
|
442
|
+
qtpy.API_NAME.lower().startswith('pyside')
|
|
443
|
+
and sys.version_info[:2] > (3, 10)
|
|
444
|
+
and platform.system() == "Darwin",
|
|
445
|
+
reason='pyside specific bug',
|
|
446
|
+
)
|
|
447
|
+
def test_exec(plugin_dialog):
|
|
448
|
+
plugin_dialog.exec_()
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@pytest.mark.skipif(
|
|
452
|
+
qtpy.API_NAME.lower().startswith('pyside')
|
|
453
|
+
and sys.version_info[:2] > (3, 10)
|
|
454
|
+
and platform.system() == "Darwin",
|
|
455
|
+
reason='pyside specific bug',
|
|
456
|
+
)
|
|
457
|
+
def test_search_in_available(plugin_dialog):
|
|
458
|
+
idxs = plugin_dialog._search_in_available("test")
|
|
459
|
+
assert idxs == [0, 1, 2, 3]
|
|
460
|
+
idxs = plugin_dialog._search_in_available("*&%$")
|
|
461
|
+
assert idxs == []
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def test_drop_event(plugin_dialog, tmp_path):
|
|
465
|
+
path_1 = tmp_path / "example-1.txt"
|
|
466
|
+
path_2 = tmp_path / "example-2.txt"
|
|
467
|
+
url_prefix = 'file:///' if os.name == 'nt' else 'file://'
|
|
468
|
+
data = QMimeData()
|
|
469
|
+
data.setUrls(
|
|
470
|
+
[QUrl(f'{url_prefix}{path_1}'), QUrl(f'{url_prefix}{path_2}')]
|
|
471
|
+
)
|
|
472
|
+
event = QDropEvent(
|
|
473
|
+
QPointF(5.0, 5.0), Qt.CopyAction, data, Qt.LeftButton, Qt.NoModifier
|
|
474
|
+
)
|
|
475
|
+
plugin_dialog.dropEvent(event)
|
|
476
|
+
assert plugin_dialog.direct_entry_edit.text() == str(path_1)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@pytest.mark.skipif(
|
|
480
|
+
qtpy.API_NAME.lower().startswith('pyside'), reason='pyside specific bug'
|
|
481
|
+
)
|
|
482
|
+
def test_installs(qtbot, tmp_virtualenv, plugin_dialog, request):
|
|
483
|
+
if "[constructor]" in request.node.name:
|
|
484
|
+
pytest.skip(
|
|
485
|
+
reason="This test is only relevant for constructor-based installs"
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
plugin_dialog.set_prefix(str(tmp_virtualenv))
|
|
489
|
+
item = plugin_dialog.available_list.item(1)
|
|
490
|
+
widget = plugin_dialog.available_list.itemWidget(item)
|
|
491
|
+
with qtbot.waitSignal(
|
|
492
|
+
plugin_dialog.installer.processFinished, timeout=60_000
|
|
493
|
+
) as blocker:
|
|
494
|
+
widget.action_button.click()
|
|
495
|
+
|
|
496
|
+
process_finished_data = blocker.args[0]
|
|
497
|
+
assert process_finished_data['action'] == InstallerActions.INSTALL
|
|
498
|
+
assert process_finished_data['pkgs'][0].startswith("requests")
|
|
499
|
+
qtbot.wait(5000)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@pytest.mark.skipif(
|
|
503
|
+
qtpy.API_NAME.lower().startswith('pyside'), reason='pyside specific bug'
|
|
504
|
+
)
|
|
505
|
+
def test_cancel(qtbot, tmp_virtualenv, plugin_dialog, request):
|
|
506
|
+
if "[constructor]" in request.node.name:
|
|
507
|
+
pytest.skip(
|
|
508
|
+
reason="This test is only relevant for constructor-based installs"
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
plugin_dialog.set_prefix(str(tmp_virtualenv))
|
|
512
|
+
item = plugin_dialog.available_list.item(1)
|
|
513
|
+
widget = plugin_dialog.available_list.itemWidget(item)
|
|
514
|
+
with qtbot.waitSignal(
|
|
515
|
+
plugin_dialog.installer.processFinished, timeout=60_000
|
|
516
|
+
) as blocker:
|
|
517
|
+
widget.action_button.click()
|
|
518
|
+
widget.cancel_btn.click()
|
|
519
|
+
|
|
520
|
+
process_finished_data = blocker.args[0]
|
|
521
|
+
assert process_finished_data['action'] == InstallerActions.CANCEL
|
|
522
|
+
assert process_finished_data['pkgs'][0].startswith("requests")
|
|
523
|
+
assert plugin_dialog.available_list.count() == 2
|
|
524
|
+
assert plugin_dialog.installed_list.count() == 2
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@pytest.mark.skipif(
|
|
528
|
+
qtpy.API_NAME.lower().startswith('pyside'), reason='pyside specific bug'
|
|
529
|
+
)
|
|
530
|
+
def test_cancel_all(qtbot, tmp_virtualenv, plugin_dialog, request):
|
|
531
|
+
if "[constructor]" in request.node.name:
|
|
532
|
+
pytest.skip(
|
|
533
|
+
reason="This test is only relevant for constructor-based installs"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
plugin_dialog.set_prefix(str(tmp_virtualenv))
|
|
537
|
+
item_1 = plugin_dialog.available_list.item(0)
|
|
538
|
+
item_2 = plugin_dialog.available_list.item(1)
|
|
539
|
+
widget_1 = plugin_dialog.available_list.itemWidget(item_1)
|
|
540
|
+
widget_2 = plugin_dialog.available_list.itemWidget(item_2)
|
|
541
|
+
with qtbot.waitSignal(plugin_dialog.installer.allFinished, timeout=60_000):
|
|
542
|
+
widget_1.action_button.click()
|
|
543
|
+
widget_2.action_button.click()
|
|
544
|
+
plugin_dialog.cancel_all_btn.click()
|
|
545
|
+
|
|
546
|
+
assert plugin_dialog.available_list.count() == 2
|
|
547
|
+
assert plugin_dialog.installed_list.count() == 2
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def test_shortcut_close(plugin_dialog, qtbot):
|
|
551
|
+
qtbot.keyClicks(
|
|
552
|
+
plugin_dialog, 'W', modifier=Qt.KeyboardModifier.ControlModifier
|
|
553
|
+
)
|
|
554
|
+
qtbot.wait(200)
|
|
555
|
+
assert not plugin_dialog.isVisible()
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def test_shortcut_quit(plugin_dialog, qtbot):
|
|
559
|
+
qtbot.keyClicks(
|
|
560
|
+
plugin_dialog, 'Q', modifier=Qt.KeyboardModifier.ControlModifier
|
|
561
|
+
)
|
|
562
|
+
qtbot.wait(200)
|
|
563
|
+
assert not plugin_dialog.isVisible()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from napari_plugin_manager.utils import is_conda_package
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.parametrize(
|
|
10
|
+
"pkg_name,expected",
|
|
11
|
+
[
|
|
12
|
+
("some-package", True),
|
|
13
|
+
("some-other-package", False),
|
|
14
|
+
("some-package-other", False),
|
|
15
|
+
("other-some-package", False),
|
|
16
|
+
("package", False),
|
|
17
|
+
("some", False),
|
|
18
|
+
],
|
|
19
|
+
)
|
|
20
|
+
def test_is_conda_package(pkg_name, expected, tmp_path):
|
|
21
|
+
mocked_conda_meta = tmp_path / 'conda-meta'
|
|
22
|
+
mocked_conda_meta.mkdir()
|
|
23
|
+
mocked_package = mocked_conda_meta / 'some-package-0.1.1-0.json'
|
|
24
|
+
mocked_package.touch()
|
|
25
|
+
|
|
26
|
+
with patch.object(sys, 'prefix', tmp_path):
|
|
27
|
+
assert is_conda_package(pkg_name) is expected
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.1.0'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|