From 1d97427c7559b10c6db88d0d4b341c872b6614ca Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 08:45:18 +1000 Subject: [PATCH 1/8] Add some widget tests, avoid duplicate signals --- koordinates/gui/access_filter_widget.py | 12 +- koordinates/gui/date_filter_widget.py | 19 +- koordinates/gui/group_filter_widget.py | 5 +- koordinates/gui/license_filter_widget.py | 14 +- koordinates/gui/publisher_filter_widget.py | 16 +- koordinates/test/test_filter_widgets.py | 522 +++++++++++++++++++++ 6 files changed, 576 insertions(+), 12 deletions(-) create mode 100644 koordinates/test/test_filter_widgets.py diff --git a/koordinates/gui/access_filter_widget.py b/koordinates/gui/access_filter_widget.py index 9892cb5..40e1738 100644 --- a/koordinates/gui/access_filter_widget.py +++ b/koordinates/gui/access_filter_widget.py @@ -1,3 +1,4 @@ +from typing import Optional from qgis.PyQt.QtWidgets import ( QWidget, QVBoxLayout, @@ -17,7 +18,7 @@ class AccessFilterWidget(FilterWidgetComboBase): Custom widget for access based filtering """ - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget]=None): super().__init__(parent) self.drop_down_widget = QWidget() @@ -39,7 +40,10 @@ def __init__(self, parent): self.set_contents_widget(self.drop_down_widget) - self.clear() + self._update_visible_frames() + self._block_changes += 1 + self._update_value() + self._block_changes -= 1 def _access_group_member_clicked(self, clicked_button): self._block_changes += 1 @@ -56,6 +60,10 @@ def _update_visible_frames(self): self._floating_widget.reflow() def clear(self): + if not any((self.public_radio.isChecked(), + self.private_radio.isChecked())): + return + self.public_radio.setChecked(False) self.private_radio.setChecked(False) self._update_visible_frames() diff --git a/koordinates/gui/date_filter_widget.py b/koordinates/gui/date_filter_widget.py index 54cf8b9..3701baf 100644 --- a/koordinates/gui/date_filter_widget.py +++ b/koordinates/gui/date_filter_widget.py @@ -55,7 +55,7 @@ class DateFilterWidget(FilterWidgetComboBase): Custom widget for date selection """ - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.drop_down_widget = QWidget() @@ -393,6 +393,14 @@ def set_created_limits(self, min_date: Optional[QDate], max_date: Optional[QDate self.set_published_range(prev_min_created, prev_max_created) def clear(self): + if self.published_date_slider.lowerValue() == self.published_date_slider.minimum() and \ + self.published_date_slider.upperValue() == \ + self.published_date_slider.maximum() and \ + self.updated_date_slider.lowerValue() == self.updated_date_slider.minimum() and \ + self.updated_date_slider.upperValue() == self.updated_date_slider.maximum(): + return + + self._block_changes += 1 self.updated_date_slider.setRange( self.updated_date_slider.minimum(), self.updated_date_slider.maximum() @@ -401,6 +409,7 @@ def clear(self): self.published_date_slider.minimum(), self.published_date_slider.maximum() ) + self._block_changes -= 1 self._update_labels() def should_show_clear(self): @@ -416,12 +425,20 @@ def should_show_clear(self): def apply_constraints_to_query(self, query: DataBrowserQuery): if self.min_published_date_edit.date() != self.min_published_date_edit.default_date(): query.created_minimum = QDateTime(self.min_published_date_edit.date()) + else: + query.created_minimum = None if self.max_published_date_edit.date() != self.max_published_date_edit.default_date(): query.created_maximum = QDateTime(self.max_published_date_edit.date()) + else: + query.created_maximum = None if self.min_updated_date_edit.date() != self.min_updated_date_edit.default_date(): query.updated_minimum = QDateTime(self.min_updated_date_edit.date()) + else: + query.updated_minimum = None if self.max_updated_date_edit.date() != self.max_updated_date_edit.default_date(): query.updated_maximum = QDateTime(self.max_updated_date_edit.date()) + else: + query.updated_maximum = None def set_from_query(self, query: DataBrowserQuery): self._block_changes += 1 diff --git a/koordinates/gui/group_filter_widget.py b/koordinates/gui/group_filter_widget.py index 82320bb..cfedaff 100644 --- a/koordinates/gui/group_filter_widget.py +++ b/koordinates/gui/group_filter_widget.py @@ -18,7 +18,7 @@ class GroupFilterWidget(FilterWidgetComboBase): Custom widget for group based filtering """ - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self._current_context: Optional[str] = None @@ -51,6 +51,9 @@ def _update_visible_frames(self): self._floating_widget.reflow() def clear(self): + if not any(radio.isChecked() for radio in self._radios): + return + for radio in self._radios: radio.setChecked(False) diff --git a/koordinates/gui/license_filter_widget.py b/koordinates/gui/license_filter_widget.py index ed8472d..f840bdd 100644 --- a/koordinates/gui/license_filter_widget.py +++ b/koordinates/gui/license_filter_widget.py @@ -1,3 +1,4 @@ +from typing import Optional from qgis.PyQt.QtWidgets import ( QWidget, QVBoxLayout, @@ -18,7 +19,7 @@ class LicenseFilterWidget(FilterWidgetComboBase): Custom widget for license filtering """ - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.drop_down_widget = QWidget() @@ -80,7 +81,7 @@ def __init__(self, parent): self.cc_4_checkbox.toggled.connect(self._update_value) self.cc_3_checkbox.toggled.connect(self._update_value) - self.clear() + self._update_value() def _update_visible_frames(self): self.cc_options_widget.setVisible(self.cc_3_checkbox.isChecked() @@ -90,11 +91,16 @@ def _update_visible_frames(self): self._floating_widget.reflow() def clear(self): + if not self.cc_4_checkbox.isChecked() and not self.cc_3_checkbox.isChecked(): + return + + self._block_changes = True self.cc_4_checkbox.setChecked(False) self.cc_3_checkbox.setChecked(False) self.derivatives_allowed_radio.setChecked(True) self.commercial_use_allowed_radio.setChecked(True) self.no_changes_need_to_be_shared_radio.setChecked(True) + self._block_changes = False self._update_visible_frames() self._update_value() @@ -143,8 +149,12 @@ def apply_constraints_to_query(self, query: DataBrowserQuery): if self.cc_3_checkbox.isChecked(): query.cc_license_versions.add(CreativeCommonLicenseVersions.Version3) + elif CreativeCommonLicenseVersions.Version3 in query.cc_license_versions: + query.cc_license_versions.remove(CreativeCommonLicenseVersions.Version3) if self.cc_4_checkbox.isChecked(): query.cc_license_versions.add(CreativeCommonLicenseVersions.Version4) + elif CreativeCommonLicenseVersions.Version4 in query.cc_license_versions: + query.cc_license_versions.remove(CreativeCommonLicenseVersions.Version4) def set_from_query(self, query: DataBrowserQuery): self._block_changes += 1 diff --git a/koordinates/gui/publisher_filter_widget.py b/koordinates/gui/publisher_filter_widget.py index 016cf0a..e0d143d 100644 --- a/koordinates/gui/publisher_filter_widget.py +++ b/koordinates/gui/publisher_filter_widget.py @@ -616,7 +616,7 @@ class PublisherFilterWidget(FilterWidgetComboBase): Custom widget for publisher based filtering """ - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.drop_down_widget = PublisherSelectionWidget() @@ -628,7 +628,7 @@ def __init__(self, parent): self._current_publisher: Optional[Publisher] = None - self.clear() + self._update_value() def current_publisher(self) -> Optional[Publisher]: """ @@ -637,6 +637,9 @@ def current_publisher(self) -> Optional[Publisher]: return self._current_publisher def _selection_changed(self, publisher: Publisher): + if self._current_publisher is not None and self._current_publisher.id() == publisher.id(): + return + self._current_publisher = publisher self._update_value() self.collapse() @@ -646,6 +649,9 @@ def _update_visible_frames(self): self._floating_widget.reflow() def clear(self): + if self._current_publisher is None: + return + self._current_publisher = None self._update_value() @@ -666,14 +672,12 @@ def _update_value(self): self.changed.emit() def apply_constraints_to_query(self, query: DataBrowserQuery): - if self._current_publisher: - query.publisher = self._current_publisher + query.publisher = self._current_publisher def set_from_query(self, query: DataBrowserQuery): self._block_changes += 1 - if query.publisher: - self._current_publisher = query.publisher + self._current_publisher = query.publisher self._update_value() self._update_visible_frames() diff --git a/koordinates/test/test_filter_widgets.py b/koordinates/test/test_filter_widgets.py new file mode 100644 index 0000000..ae42627 --- /dev/null +++ b/koordinates/test/test_filter_widgets.py @@ -0,0 +1,522 @@ +"""Tests Filter Widgets + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" + +import unittest + +from qgis.PyQt.QtCore import ( + QDate, + QTime, + QDateTime +) +from qgis.PyQt.QtTest import QSignalSpy + +from ..api import ( + DataBrowserQuery, + Publisher, + VectorFilter, + RasterFilter, + RasterFilterOptions, + RasterBandFilter, + GridFilterOptions, + CreativeCommonLicenseVersions, + AccessType, + SortOrder, + Capability +) +from ..gui.access_filter_widget import AccessFilterWidget +from ..gui.group_filter_widget import GroupFilterWidget +from ..gui.date_filter_widget import DateFilterWidget +from ..gui.license_filter_widget import LicenseFilterWidget +from ..gui.publisher_filter_widget import PublisherFilterWidget + + +from .utilities import get_qgis_app + +QGIS_APP = get_qgis_app() + +class TestFilterWidgets(unittest.TestCase): + """ + Test filter widgets + """ + + def test_access_widget(self): + w = AccessFilterWidget() + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertIsNone(query.access_type) + w.apply_constraints_to_query(query) + self.assertIsNone(query.access_type) + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertIsNone(query.access_type) + self.assertFalse(w.should_show_clear()) + + w.public_radio.click() + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.access_type = None + w.apply_constraints_to_query(query) + self.assertEqual(query.access_type, AccessType.Public) + + # reapply same, should be no signal + w.set_from_query(query) + self.assertEqual(len(spy), 1) + + w.private_radio.click() + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.access_type = None + w.apply_constraints_to_query(query) + self.assertEqual(query.access_type, AccessType.Private) + w.set_from_query(query) + self.assertEqual(len(spy), 2) + + # clear using query params + # this should never raise signals + query.access_type = None + w.set_from_query(query) + self.assertEqual(len(spy), 2) + query.access_type = AccessType.Private + w.apply_constraints_to_query(query) + self.assertIsNone(query.access_type) + + # clear using clear button + query.access_type = AccessType.Private + w.set_from_query(query) + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 3) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertIsNone(query.access_type) + w.clear() + self.assertEqual(len(spy), 3) + + def test_group_widget(self): + w = GroupFilterWidget() + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertIsNone(query.group) + w.apply_constraints_to_query(query) + self.assertIsNone(query.group) + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertIsNone(query.group) + self.assertFalse(w.should_show_clear()) + + # set facets to build buttons + w.set_facets({ + 'group': [ + {'name': 'Transport', 'key': 'transport'}, + {'name': 'Water', 'key': 'water'}, + {'name': 'Environment', 'key': 'enviro'}, + ], + 'from': 'my_org' + }) + + w._radios[0].click() + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.group = None + w.apply_constraints_to_query(query) + self.assertEqual(query.group, 'transport') + + # reapply same, should be no signal + w.set_from_query(query) + self.assertEqual(len(spy), 1) + + w._radios[1].click() + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.group = None + w.apply_constraints_to_query(query) + self.assertEqual(query.group, 'water') + w.set_from_query(query) + self.assertEqual(len(spy), 2) + + # clear using query params + # this should never raise signals + query.group = None + w.set_from_query(query) + self.assertEqual(len(spy), 2) + query.group = 'water' + w.apply_constraints_to_query(query) + self.assertIsNone(query.group) + + # clear using clear button + w._radios[0].click() + self.assertEqual(len(spy), 3) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 4) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertIsNone(query.group) + + w.clear() + self.assertEqual(len(spy), 4) + + w._radios[0].click() + self.assertEqual(len(spy), 5) + # reapply same facet -- should not change setting, or raise signal + w.set_facets({ + 'group': [ + {'name': 'Transport', 'key': 'transport'}, + {'name': 'Water', 'key': 'water'}, + {'name': 'Environment', 'key': 'enviro'}, + ], + 'from': 'my_org' + }) + self.assertEqual(len(spy), 5) + query.group = None + w.apply_constraints_to_query(query) + self.assertEqual(query.group, 'transport') + + def test_date_widget(self): + w = DateFilterWidget() + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertIsNone(query.updated_maximum) + w.apply_constraints_to_query(query) + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertIsNone(query.updated_maximum) + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertIsNone(query.updated_maximum) + self.assertFalse(w.should_show_clear()) + + # set facets to ranges + w.set_facets({ + 'updated_at': { + 'min': '2022-01-01T00:00', + 'max': '2022-05-01T00:00'}, + 'created_at': { + 'min': '2021-01-01T00:00', + 'max': '2021-05-01T00:00'}, + }) + + w.min_updated_date_edit.setDate(QDate(2022,3,4)) + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.updated_minimum = None + w.apply_constraints_to_query(query) + self.assertEqual(query.updated_minimum, QDateTime(2022,3,4,0,0)) + + # reapply same, should be no signal + w.min_updated_date_edit.setDate(QDate(2022,3,4)) + self.assertEqual(len(spy), 1) + w.set_from_query(query) + self.assertEqual(len(spy), 1) + + w.max_updated_date_edit.setDate(QDate(2022,3,6)) + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.updated_maximum = None + w.apply_constraints_to_query(query) + self.assertEqual(query.updated_minimum, QDateTime(2022,3,4,0,0)) + self.assertEqual(query.updated_maximum, QDateTime(2022,3,6,0,0)) + w.set_from_query(query) + self.assertEqual(len(spy), 2) + + # clear using query params + # this should never raise signals + query.updated_minimum = None + query.updated_maximum = None + w.set_from_query(query) + self.assertEqual(len(spy), 2) + w.apply_constraints_to_query(query) + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertIsNone(query.updated_maximum) + query.updated_minimum = QDateTime(2022,2,4,0,0) + query.updated_maximum = QDateTime(2022, 2, 6, 0, 0) + w.apply_constraints_to_query(query) + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertIsNone(query.updated_maximum) + + # clear using clear button + w.max_updated_date_edit.setDate(QDate(2022,3,6)) + self.assertEqual(len(spy), 3) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 4) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertIsNone(query.updated_maximum) + + w.clear() + self.assertEqual(len(spy), 4) + + w.max_updated_date_edit.setDate(QDate(2022, 3, 6)) + self.assertEqual(len(spy), 5) + # reapply same facet -- should not change setting, or raise signal + w.set_facets({ + 'updated_at': { + 'min': '2022-01-01T00:00', + 'max': '2022-05-01T00:00'}, + 'created_at': { + 'min': '2021-01-01T00:00', + 'max': '2021-05-01T00:00'}, + }) + self.assertEqual(len(spy), 5) + query.updated_minimum = None + query.updated_maximum = None + w.apply_constraints_to_query(query) + self.assertIsNone(query.created_minimum) + self.assertIsNone(query.created_maximum) + self.assertIsNone(query.updated_minimum) + self.assertEqual(query.updated_maximum, QDateTime(2022,3,6,0,0)) + + def test_license_widget(self): + w = LicenseFilterWidget() + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertFalse(query.cc_license_versions) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + w.apply_constraints_to_query(query) + self.assertFalse(query.cc_license_versions) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertFalse(query.cc_license_versions) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + self.assertFalse(w.should_show_clear()) + + w.cc_3_checkbox.setChecked(True) + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.cc_license_versions = set() + query.cc_license_allow_derivates = False + query.cc_license_allow_commercial = False + query.cc_license_changes_must_be_shared = False + w.apply_constraints_to_query(query) + self.assertEqual(query.cc_license_versions, { + CreativeCommonLicenseVersions.Version3 + }) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + + # reapply same, should be no signal + w.set_from_query(query) + self.assertEqual(len(spy), 1) + + w.cc_4_checkbox.setChecked(True) + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.cc_license_versions = set() + query.cc_license_allow_derivates = False + query.cc_license_allow_commercial = False + query.cc_license_changes_must_be_shared = False + w.apply_constraints_to_query(query) + self.assertEqual(query.cc_license_versions, { + CreativeCommonLicenseVersions.Version3, + CreativeCommonLicenseVersions.Version4 + }) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + w.set_from_query(query) + self.assertEqual(len(spy), 2) + + # clear using query params + # this should never raise signals + query.cc_license_versions = set() + query.cc_license_allow_derivates = False + query.cc_license_allow_commercial = False + query.cc_license_changes_must_be_shared = False + w.set_from_query(query) + self.assertEqual(len(spy), 2) + query.cc_license_versions = { + CreativeCommonLicenseVersions.Version3 + } + w.apply_constraints_to_query(query) + self.assertFalse(query.cc_license_versions) + + # clear using clear button + w.cc_4_checkbox.setChecked(True) + self.assertEqual(len(spy), 3) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 4) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertFalse(query.cc_license_versions) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + + w.clear() + self.assertEqual(len(spy), 4) + + # try options + w.cc_3_checkbox.setChecked(True) + self.assertEqual(len(spy), 5) + w.derivatives_allowed_radio.click() + self.assertEqual(len(spy), 6) + w.apply_constraints_to_query(query) + self.assertEqual(query.cc_license_versions, { + CreativeCommonLicenseVersions.Version3 + }) + self.assertTrue(query.cc_license_allow_derivates) + w.set_from_query(query) + self.assertEqual(len(spy), 6) + w.clear() + self.assertEqual(len(spy), 7) + + def test_publisher_widget(self): + w = PublisherFilterWidget() + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertIsNone(query.publisher) + w.apply_constraints_to_query(query) + self.assertIsNone(query.publisher) + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertIsNone(query.publisher) + self.assertFalse(w.should_show_clear()) + + publisher1 = Publisher({ + 'id': 'site:1' + }) + publisher2 = Publisher({ + 'id': 'site:2' + }) + w._selection_changed(publisher1) + + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.publisher = None + w.apply_constraints_to_query(query) + self.assertEqual(query.publisher.id(), 'site:1') + + # reapply same, should be no signal + w.set_from_query(query) + self.assertEqual(len(spy), 1) + + w._selection_changed(publisher1) + self.assertEqual(len(spy), 1) + + w._selection_changed(publisher2) + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.publisher = None + w.apply_constraints_to_query(query) + self.assertEqual(query.publisher.id(), 'site:2') + w.set_from_query(query) + self.assertEqual(len(spy), 2) + + # clear using query params + # this should never raise signals + query.publisher = None + w.set_from_query(query) + self.assertEqual(len(spy), 2) + query.publisher = publisher1 + w.apply_constraints_to_query(query) + self.assertIsNone(query.publisher) + + # clear using clear button + w._selection_changed(publisher2) + self.assertEqual(len(spy), 3) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 4) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertIsNone(query.publisher) + + w.clear() + self.assertEqual(len(spy), 4) + + w._selection_changed(publisher1) + self.assertEqual(len(spy), 5) + w._selection_changed(publisher1) + self.assertEqual(len(spy), 5) + query.publisher = None + w.apply_constraints_to_query(query) + self.assertEqual(query.publisher.id(), 'site:1') + + +if __name__ == '__main__': + unittest.main() From 4691c8f58dad7114eed1dcb6bb90fd94a66e49cf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 09:39:19 +1000 Subject: [PATCH 2/8] Code shuffle for better organisation --- koordinates/gui/advanced_filter_widget.py | 18 ++++---- koordinates/gui/filter_widgets/__init__.py | 9 ++++ .../access_filter_widget.py | 2 +- .../category_filter_widget.py | 2 +- .../{ => filter_widgets}/custom_combo_box.py | 0 .../data_type_filter_widget.py | 2 +- .../date_filter_widget.py | 2 +- .../filter_widget_combo_base.py | 2 +- .../group_filter_widget.py | 2 +- .../license_filter_widget.py | 2 +- .../publisher_filter_widget.py | 12 +++--- .../gui/{ => filter_widgets}/range_slider.py | 0 .../resolution_filter_widget.py | 2 +- .../rounded_highlight_box.py | 0 .../gui/results_panel/publishers_panel.py | 2 +- koordinates/test/test_filter_widgets.py | 43 ++++++++----------- 16 files changed, 51 insertions(+), 49 deletions(-) create mode 100644 koordinates/gui/filter_widgets/__init__.py rename koordinates/gui/{ => filter_widgets}/access_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/category_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/custom_combo_box.py (100%) rename koordinates/gui/{ => filter_widgets}/data_type_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/date_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/filter_widget_combo_base.py (96%) rename koordinates/gui/{ => filter_widgets}/group_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/license_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/publisher_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/range_slider.py (100%) rename koordinates/gui/{ => filter_widgets}/resolution_filter_widget.py (99%) rename koordinates/gui/{ => filter_widgets}/rounded_highlight_box.py (100%) diff --git a/koordinates/gui/advanced_filter_widget.py b/koordinates/gui/advanced_filter_widget.py index b840d13..a54a7ca 100644 --- a/koordinates/gui/advanced_filter_widget.py +++ b/koordinates/gui/advanced_filter_widget.py @@ -19,16 +19,18 @@ QStyleOption ) -from .access_filter_widget import AccessFilterWidget -# from .category_filter_widget import CategoryFilterWidget -from .data_type_filter_widget import DataTypeFilterWidget -from .date_filter_widget import DateFilterWidget -from .publisher_filter_widget import PublisherFilterWidget +from .filter_widgets import ( + AccessFilterWidget, + # CategoryFilterWidget, + DataTypeFilterWidget, + DateFilterWidget, + PublisherFilterWidget, + GroupFilterWidget, + LicenseFilterWidget, + ResolutionFilterWidget +) from .enums import FilterWidgetAppearance from .flow_layout import FlowLayout -from .group_filter_widget import GroupFilterWidget -from .license_filter_widget import LicenseFilterWidget -from .resolution_filter_widget import ResolutionFilterWidget from ..api import ( DataBrowserQuery, DataType, diff --git a/koordinates/gui/filter_widgets/__init__.py b/koordinates/gui/filter_widgets/__init__.py new file mode 100644 index 0000000..71894bc --- /dev/null +++ b/koordinates/gui/filter_widgets/__init__.py @@ -0,0 +1,9 @@ +from .access_filter_widget import AccessFilterWidget # NOQA +from .category_filter_widget import CategoryFilterWidget # NOQA +from .data_type_filter_widget import DataTypeFilterWidget # NOQA +from .date_filter_widget import DateFilterWidget # NOQA +from .group_filter_widget import GroupFilterWidget # NOQA +from .license_filter_widget import LicenseFilterWidget # NOQA +from .publisher_filter_widget import PublisherFilterWidget # NOQA +from .publisher_filter_widget import PublisherSelectionWidget # NOQA +from .resolution_filter_widget import ResolutionFilterWidget # NOQA diff --git a/koordinates/gui/access_filter_widget.py b/koordinates/gui/filter_widgets/access_filter_widget.py similarity index 99% rename from koordinates/gui/access_filter_widget.py rename to koordinates/gui/filter_widgets/access_filter_widget.py index 40e1738..a8bc7d1 100644 --- a/koordinates/gui/access_filter_widget.py +++ b/koordinates/gui/filter_widgets/access_filter_widget.py @@ -7,7 +7,7 @@ ) from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import ( +from ...api import ( DataBrowserQuery, AccessType ) diff --git a/koordinates/gui/category_filter_widget.py b/koordinates/gui/filter_widgets/category_filter_widget.py similarity index 99% rename from koordinates/gui/category_filter_widget.py rename to koordinates/gui/filter_widgets/category_filter_widget.py index 2b7bdc7..90631b5 100644 --- a/koordinates/gui/category_filter_widget.py +++ b/koordinates/gui/filter_widgets/category_filter_widget.py @@ -7,7 +7,7 @@ ) from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import ( +from ...api import ( DataBrowserQuery ) diff --git a/koordinates/gui/custom_combo_box.py b/koordinates/gui/filter_widgets/custom_combo_box.py similarity index 100% rename from koordinates/gui/custom_combo_box.py rename to koordinates/gui/filter_widgets/custom_combo_box.py diff --git a/koordinates/gui/data_type_filter_widget.py b/koordinates/gui/filter_widgets/data_type_filter_widget.py similarity index 99% rename from koordinates/gui/data_type_filter_widget.py rename to koordinates/gui/filter_widgets/data_type_filter_widget.py index 33c1bb1..6693960 100644 --- a/koordinates/gui/data_type_filter_widget.py +++ b/koordinates/gui/filter_widgets/data_type_filter_widget.py @@ -9,7 +9,7 @@ ) from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import ( +from ...api import ( DataBrowserQuery, DataType, RasterFilter, diff --git a/koordinates/gui/date_filter_widget.py b/koordinates/gui/filter_widgets/date_filter_widget.py similarity index 99% rename from koordinates/gui/date_filter_widget.py rename to koordinates/gui/filter_widgets/date_filter_widget.py index 3701baf..18513ac 100644 --- a/koordinates/gui/date_filter_widget.py +++ b/koordinates/gui/filter_widgets/date_filter_widget.py @@ -17,8 +17,8 @@ QgsDateEdit ) +from ...api import DataBrowserQuery from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import DataBrowserQuery from .range_slider import RangeSlider DATE_FORMAT = 'dd MMM yyyy' diff --git a/koordinates/gui/filter_widget_combo_base.py b/koordinates/gui/filter_widgets/filter_widget_combo_base.py similarity index 96% rename from koordinates/gui/filter_widget_combo_base.py rename to koordinates/gui/filter_widgets/filter_widget_combo_base.py index c269458..077e2cb 100644 --- a/koordinates/gui/filter_widget_combo_base.py +++ b/koordinates/gui/filter_widgets/filter_widget_combo_base.py @@ -1,7 +1,7 @@ from qgis.PyQt.QtCore import pyqtSignal from .custom_combo_box import CustomComboBox -from ..api import DataBrowserQuery +from ...api import DataBrowserQuery class FilterWidgetComboBase(CustomComboBox): diff --git a/koordinates/gui/group_filter_widget.py b/koordinates/gui/filter_widgets/group_filter_widget.py similarity index 99% rename from koordinates/gui/group_filter_widget.py rename to koordinates/gui/filter_widgets/group_filter_widget.py index cfedaff..ed43108 100644 --- a/koordinates/gui/group_filter_widget.py +++ b/koordinates/gui/filter_widgets/group_filter_widget.py @@ -8,7 +8,7 @@ ) from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import ( +from ...api import ( DataBrowserQuery ) diff --git a/koordinates/gui/license_filter_widget.py b/koordinates/gui/filter_widgets/license_filter_widget.py similarity index 99% rename from koordinates/gui/license_filter_widget.py rename to koordinates/gui/filter_widgets/license_filter_widget.py index f840bdd..0edc95d 100644 --- a/koordinates/gui/license_filter_widget.py +++ b/koordinates/gui/filter_widgets/license_filter_widget.py @@ -8,7 +8,7 @@ ) from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import ( +from ...api import ( DataBrowserQuery, CreativeCommonLicenseVersions ) diff --git a/koordinates/gui/publisher_filter_widget.py b/koordinates/gui/filter_widgets/publisher_filter_widget.py similarity index 99% rename from koordinates/gui/publisher_filter_widget.py rename to koordinates/gui/filter_widgets/publisher_filter_widget.py index e0d143d..dcb3355 100644 --- a/koordinates/gui/publisher_filter_widget.py +++ b/koordinates/gui/filter_widgets/publisher_filter_widget.py @@ -43,21 +43,21 @@ QgsFilterLineEdit ) -from .dataset_utils import DatasetGuiUtils -from .explore_tab_bar import FlatUnderlineTabBar +from ..dataset_utils import DatasetGuiUtils +from ..explore_tab_bar import FlatUnderlineTabBar from .filter_widget_combo_base import FilterWidgetComboBase from .rounded_highlight_box import RoundedHighlightBox -from .thumbnails import ( +from ..thumbnails import ( GenericThumbnailManager, PublisherThumbnailProcessor ) -from ..api import ( +from ...api import ( KoordinatesClient, DataBrowserQuery, Publisher, + PublisherType ) -from ..api import PublisherType -from .gui_utils import GuiUtils +from ..gui_utils import GuiUtils class PublisherDelegate(QStyledItemDelegate): diff --git a/koordinates/gui/range_slider.py b/koordinates/gui/filter_widgets/range_slider.py similarity index 100% rename from koordinates/gui/range_slider.py rename to koordinates/gui/filter_widgets/range_slider.py diff --git a/koordinates/gui/resolution_filter_widget.py b/koordinates/gui/filter_widgets/resolution_filter_widget.py similarity index 99% rename from koordinates/gui/resolution_filter_widget.py rename to koordinates/gui/filter_widgets/resolution_filter_widget.py index d411195..d8ea825 100644 --- a/koordinates/gui/resolution_filter_widget.py +++ b/koordinates/gui/filter_widgets/resolution_filter_widget.py @@ -8,7 +8,7 @@ ) from .filter_widget_combo_base import FilterWidgetComboBase -from ..api import DataBrowserQuery +from ...api import DataBrowserQuery from .range_slider import RangeSlider diff --git a/koordinates/gui/rounded_highlight_box.py b/koordinates/gui/filter_widgets/rounded_highlight_box.py similarity index 100% rename from koordinates/gui/rounded_highlight_box.py rename to koordinates/gui/filter_widgets/rounded_highlight_box.py diff --git a/koordinates/gui/results_panel/publishers_panel.py b/koordinates/gui/results_panel/publishers_panel.py index 47f6042..24821b5 100644 --- a/koordinates/gui/results_panel/publishers_panel.py +++ b/koordinates/gui/results_panel/publishers_panel.py @@ -10,7 +10,7 @@ ) from .results_panel_widget import ResultsPanelWidget -from ..publisher_filter_widget import PublisherSelectionWidget +from ..filter_widgets import PublisherSelectionWidget from ...api import Publisher pluginPath = os.path.split(os.path.dirname(__file__))[0] diff --git a/koordinates/test/test_filter_widgets.py b/koordinates/test/test_filter_widgets.py index ae42627..6e969d4 100644 --- a/koordinates/test/test_filter_widgets.py +++ b/koordinates/test/test_filter_widgets.py @@ -10,35 +10,26 @@ from qgis.PyQt.QtCore import ( QDate, - QTime, QDateTime ) from qgis.PyQt.QtTest import QSignalSpy +from .utilities import get_qgis_app from ..api import ( DataBrowserQuery, Publisher, - VectorFilter, - RasterFilter, - RasterFilterOptions, - RasterBandFilter, - GridFilterOptions, CreativeCommonLicenseVersions, - AccessType, - SortOrder, - Capability + AccessType ) -from ..gui.access_filter_widget import AccessFilterWidget -from ..gui.group_filter_widget import GroupFilterWidget -from ..gui.date_filter_widget import DateFilterWidget -from ..gui.license_filter_widget import LicenseFilterWidget -from ..gui.publisher_filter_widget import PublisherFilterWidget - - -from .utilities import get_qgis_app +from ..gui.filter_widgets.access_filter_widget import AccessFilterWidget +from ..gui.filter_widgets.date_filter_widget import DateFilterWidget +from ..gui.filter_widgets.group_filter_widget import GroupFilterWidget +from ..gui.filter_widgets.license_filter_widget import LicenseFilterWidget +from ..gui.filter_widgets.publisher_filter_widget import PublisherFilterWidget QGIS_APP = get_qgis_app() + class TestFilterWidgets(unittest.TestCase): """ Test filter widgets @@ -242,26 +233,26 @@ def test_date_widget(self): 'max': '2021-05-01T00:00'}, }) - w.min_updated_date_edit.setDate(QDate(2022,3,4)) + w.min_updated_date_edit.setDate(QDate(2022, 3, 4)) self.assertEqual(len(spy), 1) self.assertTrue(w.should_show_clear()) query.updated_minimum = None w.apply_constraints_to_query(query) - self.assertEqual(query.updated_minimum, QDateTime(2022,3,4,0,0)) + self.assertEqual(query.updated_minimum, QDateTime(2022, 3, 4, 0, 0)) # reapply same, should be no signal - w.min_updated_date_edit.setDate(QDate(2022,3,4)) + w.min_updated_date_edit.setDate(QDate(2022, 3, 4)) self.assertEqual(len(spy), 1) w.set_from_query(query) self.assertEqual(len(spy), 1) - w.max_updated_date_edit.setDate(QDate(2022,3,6)) + w.max_updated_date_edit.setDate(QDate(2022, 3, 6)) self.assertEqual(len(spy), 2) self.assertTrue(w.should_show_clear()) query.updated_maximum = None w.apply_constraints_to_query(query) - self.assertEqual(query.updated_minimum, QDateTime(2022,3,4,0,0)) - self.assertEqual(query.updated_maximum, QDateTime(2022,3,6,0,0)) + self.assertEqual(query.updated_minimum, QDateTime(2022, 3, 4, 0, 0)) + self.assertEqual(query.updated_maximum, QDateTime(2022, 3, 6, 0, 0)) w.set_from_query(query) self.assertEqual(len(spy), 2) @@ -276,7 +267,7 @@ def test_date_widget(self): self.assertIsNone(query.created_maximum) self.assertIsNone(query.updated_minimum) self.assertIsNone(query.updated_maximum) - query.updated_minimum = QDateTime(2022,2,4,0,0) + query.updated_minimum = QDateTime(2022, 2, 4, 0, 0) query.updated_maximum = QDateTime(2022, 2, 6, 0, 0) w.apply_constraints_to_query(query) self.assertIsNone(query.created_minimum) @@ -285,7 +276,7 @@ def test_date_widget(self): self.assertIsNone(query.updated_maximum) # clear using clear button - w.max_updated_date_edit.setDate(QDate(2022,3,6)) + w.max_updated_date_edit.setDate(QDate(2022, 3, 6)) self.assertEqual(len(spy), 3) self.assertTrue(w.should_show_clear()) w.clear() @@ -318,7 +309,7 @@ def test_date_widget(self): self.assertIsNone(query.created_minimum) self.assertIsNone(query.created_maximum) self.assertIsNone(query.updated_minimum) - self.assertEqual(query.updated_maximum, QDateTime(2022,3,6,0,0)) + self.assertEqual(query.updated_maximum, QDateTime(2022, 3, 6, 0, 0)) def test_license_widget(self): w = LicenseFilterWidget() From f9d27cfa5aade8da3624c90584c6822cf2e6adf2 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 09:48:46 +1000 Subject: [PATCH 3/8] Restore 'group' text, add tests for combobox labels --- .../gui/filter_widgets/custom_combo_box.py | 13 ++++- .../gui/filter_widgets/group_filter_widget.py | 2 +- koordinates/test/test_filter_widgets.py | 56 ++++++++++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/koordinates/gui/filter_widgets/custom_combo_box.py b/koordinates/gui/filter_widgets/custom_combo_box.py index cac3388..289bd43 100644 --- a/koordinates/gui/filter_widgets/custom_combo_box.py +++ b/koordinates/gui/filter_widgets/custom_combo_box.py @@ -145,7 +145,7 @@ def __init__(self, parent): self._show_clear_button = True self._clear_action = None - self._current_text = '' + self._current_text: str = '' self._hover_state = None self._floating_widget = CustomComboBox.ContentsWidget(self) @@ -249,10 +249,19 @@ def expand(self): if self.parent() and self.parent().parent(): self.parent().parent().update() - def set_current_text(self, text): + def set_current_text(self, text: str): + """ + Sets the current text shown in the collapsed combo box + """ self._current_text = text self.update() + def current_text(self) -> str: + """ + Returns the current text shown in the collapsed combo box + """ + return self._current_text + def paintEvent(self, event): super().paintEvent(event) diff --git a/koordinates/gui/filter_widgets/group_filter_widget.py b/koordinates/gui/filter_widgets/group_filter_widget.py index ed43108..491e3d7 100644 --- a/koordinates/gui/filter_widgets/group_filter_widget.py +++ b/koordinates/gui/filter_widgets/group_filter_widget.py @@ -35,7 +35,7 @@ def __init__(self, parent: Optional[QWidget] = None): self.set_contents_widget(self.drop_down_widget) - self.clear() + self._update_value() def _group_member_clicked(self, clicked_button): self._block_changes += 1 diff --git a/koordinates/test/test_filter_widgets.py b/koordinates/test/test_filter_widgets.py index 6e969d4..f43fdef 100644 --- a/koordinates/test/test_filter_widgets.py +++ b/koordinates/test/test_filter_widgets.py @@ -37,6 +37,7 @@ class TestFilterWidgets(unittest.TestCase): def test_access_widget(self): w = AccessFilterWidget() + self.assertEqual(w.current_text(), 'Access') # should start cleared self.assertFalse(w.should_show_clear()) @@ -50,6 +51,7 @@ def test_access_widget(self): w.clear() self.assertEqual(len(spy), 0) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Access') # apply same query to widget, should be no signals w.set_from_query(query) @@ -57,6 +59,7 @@ def test_access_widget(self): w.apply_constraints_to_query(query) self.assertIsNone(query.access_type) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Access') w.public_radio.click() self.assertEqual(len(spy), 1) @@ -64,6 +67,7 @@ def test_access_widget(self): query.access_type = None w.apply_constraints_to_query(query) self.assertEqual(query.access_type, AccessType.Public) + self.assertEqual(w.current_text(), 'Only public data') # reapply same, should be no signal w.set_from_query(query) @@ -77,6 +81,7 @@ def test_access_widget(self): self.assertEqual(query.access_type, AccessType.Private) w.set_from_query(query) self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), 'Shared with me') # clear using query params # this should never raise signals @@ -86,6 +91,7 @@ def test_access_widget(self): query.access_type = AccessType.Private w.apply_constraints_to_query(query) self.assertIsNone(query.access_type) + self.assertEqual(w.current_text(), 'Access') # clear using clear button query.access_type = AccessType.Private @@ -99,9 +105,11 @@ def test_access_widget(self): self.assertIsNone(query.access_type) w.clear() self.assertEqual(len(spy), 3) + self.assertEqual(w.current_text(), 'Access') def test_group_widget(self): w = GroupFilterWidget() + self.assertEqual(w.current_text(), 'Group') # should start cleared self.assertFalse(w.should_show_clear()) @@ -109,12 +117,14 @@ def test_group_widget(self): self.assertIsNone(query.group) w.apply_constraints_to_query(query) self.assertIsNone(query.group) + self.assertEqual(w.current_text(), 'Group') spy = QSignalSpy(w.changed) # re-clearing already cleared should not emit signals w.clear() self.assertEqual(len(spy), 0) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Group') # apply same query to widget, should be no signals w.set_from_query(query) @@ -122,6 +132,7 @@ def test_group_widget(self): w.apply_constraints_to_query(query) self.assertIsNone(query.group) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Group') # set facets to build buttons w.set_facets({ @@ -139,6 +150,7 @@ def test_group_widget(self): query.group = None w.apply_constraints_to_query(query) self.assertEqual(query.group, 'transport') + self.assertEqual(w.current_text(), 'Transport') # reapply same, should be no signal w.set_from_query(query) @@ -152,6 +164,7 @@ def test_group_widget(self): self.assertEqual(query.group, 'water') w.set_from_query(query) self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), 'Water') # clear using query params # this should never raise signals @@ -161,6 +174,7 @@ def test_group_widget(self): query.group = 'water' w.apply_constraints_to_query(query) self.assertIsNone(query.group) + self.assertEqual(w.current_text(), 'Group') # clear using clear button w._radios[0].click() @@ -171,6 +185,7 @@ def test_group_widget(self): self.assertFalse(w.should_show_clear()) w.apply_constraints_to_query(query) self.assertIsNone(query.group) + self.assertEqual(w.current_text(), 'Group') w.clear() self.assertEqual(len(spy), 4) @@ -190,9 +205,11 @@ def test_group_widget(self): query.group = None w.apply_constraints_to_query(query) self.assertEqual(query.group, 'transport') + self.assertEqual(w.current_text(), 'Transport') def test_date_widget(self): w = DateFilterWidget() + self.assertEqual(w.current_text(), 'Date') # should start cleared self.assertFalse(w.should_show_clear()) @@ -206,12 +223,14 @@ def test_date_widget(self): self.assertIsNone(query.created_maximum) self.assertIsNone(query.updated_minimum) self.assertIsNone(query.updated_maximum) + self.assertEqual(w.current_text(), 'Date') spy = QSignalSpy(w.changed) # re-clearing already cleared should not emit signals w.clear() self.assertEqual(len(spy), 0) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Date') # apply same query to widget, should be no signals w.set_from_query(query) @@ -222,6 +241,7 @@ def test_date_widget(self): self.assertIsNone(query.updated_minimum) self.assertIsNone(query.updated_maximum) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Date') # set facets to ranges w.set_facets({ @@ -239,12 +259,14 @@ def test_date_widget(self): query.updated_minimum = None w.apply_constraints_to_query(query) self.assertEqual(query.updated_minimum, QDateTime(2022, 3, 4, 0, 0)) + self.assertEqual(w.current_text(), '04 Mar 2022 - 01 May 2022') # reapply same, should be no signal w.min_updated_date_edit.setDate(QDate(2022, 3, 4)) self.assertEqual(len(spy), 1) w.set_from_query(query) self.assertEqual(len(spy), 1) + self.assertEqual(w.current_text(), '04 Mar 2022 - 01 May 2022') w.max_updated_date_edit.setDate(QDate(2022, 3, 6)) self.assertEqual(len(spy), 2) @@ -255,6 +277,7 @@ def test_date_widget(self): self.assertEqual(query.updated_maximum, QDateTime(2022, 3, 6, 0, 0)) w.set_from_query(query) self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), '04 Mar 2022 - 06 Mar 2022') # clear using query params # this should never raise signals @@ -274,6 +297,7 @@ def test_date_widget(self): self.assertIsNone(query.created_maximum) self.assertIsNone(query.updated_minimum) self.assertIsNone(query.updated_maximum) + self.assertEqual(w.current_text(), 'Date') # clear using clear button w.max_updated_date_edit.setDate(QDate(2022, 3, 6)) @@ -287,6 +311,7 @@ def test_date_widget(self): self.assertIsNone(query.created_maximum) self.assertIsNone(query.updated_minimum) self.assertIsNone(query.updated_maximum) + self.assertEqual(w.current_text(), 'Date') w.clear() self.assertEqual(len(spy), 4) @@ -310,9 +335,11 @@ def test_date_widget(self): self.assertIsNone(query.created_maximum) self.assertIsNone(query.updated_minimum) self.assertEqual(query.updated_maximum, QDateTime(2022, 3, 6, 0, 0)) + self.assertEqual(w.current_text(), '01 Jan 2022 - 06 Mar 2022') def test_license_widget(self): w = LicenseFilterWidget() + self.assertEqual(w.current_text(), 'License') # should start cleared self.assertFalse(w.should_show_clear()) @@ -326,12 +353,14 @@ def test_license_widget(self): self.assertFalse(query.cc_license_allow_derivates) self.assertFalse(query.cc_license_allow_commercial) self.assertFalse(query.cc_license_changes_must_be_shared) + self.assertEqual(w.current_text(), 'License') spy = QSignalSpy(w.changed) # re-clearing already cleared should not emit signals w.clear() self.assertEqual(len(spy), 0) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'License') # apply same query to widget, should be no signals w.set_from_query(query) @@ -342,6 +371,7 @@ def test_license_widget(self): self.assertFalse(query.cc_license_allow_commercial) self.assertFalse(query.cc_license_changes_must_be_shared) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'License') w.cc_3_checkbox.setChecked(True) self.assertEqual(len(spy), 1) @@ -357,10 +387,12 @@ def test_license_widget(self): self.assertFalse(query.cc_license_allow_derivates) self.assertFalse(query.cc_license_allow_commercial) self.assertFalse(query.cc_license_changes_must_be_shared) + self.assertEqual(w.current_text(), 'CC3 BY-NC-ND') # reapply same, should be no signal w.set_from_query(query) self.assertEqual(len(spy), 1) + self.assertEqual(w.current_text(), 'CC3 BY-NC-ND') w.cc_4_checkbox.setChecked(True) self.assertEqual(len(spy), 2) @@ -379,6 +411,7 @@ def test_license_widget(self): self.assertFalse(query.cc_license_changes_must_be_shared) w.set_from_query(query) self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), 'CC4 + CC3 BY-NC-ND') # clear using query params # this should never raise signals @@ -393,6 +426,7 @@ def test_license_widget(self): } w.apply_constraints_to_query(query) self.assertFalse(query.cc_license_versions) + self.assertEqual(w.current_text(), 'License') # clear using clear button w.cc_4_checkbox.setChecked(True) @@ -406,9 +440,11 @@ def test_license_widget(self): self.assertFalse(query.cc_license_allow_derivates) self.assertFalse(query.cc_license_allow_commercial) self.assertFalse(query.cc_license_changes_must_be_shared) + self.assertEqual(w.current_text(), 'License') w.clear() self.assertEqual(len(spy), 4) + self.assertEqual(w.current_text(), 'License') # try options w.cc_3_checkbox.setChecked(True) @@ -422,11 +458,14 @@ def test_license_widget(self): self.assertTrue(query.cc_license_allow_derivates) w.set_from_query(query) self.assertEqual(len(spy), 6) + self.assertEqual(w.current_text(), 'CC3 BY') w.clear() self.assertEqual(len(spy), 7) + self.assertEqual(w.current_text(), 'License') def test_publisher_widget(self): w = PublisherFilterWidget() + self.assertEqual(w.current_text(), 'Publishers') # should start cleared self.assertFalse(w.should_show_clear()) @@ -434,12 +473,14 @@ def test_publisher_widget(self): self.assertIsNone(query.publisher) w.apply_constraints_to_query(query) self.assertIsNone(query.publisher) + self.assertEqual(w.current_text(), 'Publishers') spy = QSignalSpy(w.changed) # re-clearing already cleared should not emit signals w.clear() self.assertEqual(len(spy), 0) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Publishers') # apply same query to widget, should be no signals w.set_from_query(query) @@ -447,12 +488,15 @@ def test_publisher_widget(self): w.apply_constraints_to_query(query) self.assertIsNone(query.publisher) self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Publishers') publisher1 = Publisher({ - 'id': 'site:1' + 'id': 'site:1', + 'name': 'Site 1' }) publisher2 = Publisher({ - 'id': 'site:2' + 'id': 'site:2', + 'name': 'Site 2' }) w._selection_changed(publisher1) @@ -461,13 +505,16 @@ def test_publisher_widget(self): query.publisher = None w.apply_constraints_to_query(query) self.assertEqual(query.publisher.id(), 'site:1') + self.assertEqual(w.current_text(), 'Site 1') # reapply same, should be no signal w.set_from_query(query) self.assertEqual(len(spy), 1) + self.assertEqual(w.current_text(), 'Site 1') w._selection_changed(publisher1) self.assertEqual(len(spy), 1) + self.assertEqual(w.current_text(), 'Site 1') w._selection_changed(publisher2) self.assertEqual(len(spy), 2) @@ -477,6 +524,7 @@ def test_publisher_widget(self): self.assertEqual(query.publisher.id(), 'site:2') w.set_from_query(query) self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), 'Site 2') # clear using query params # this should never raise signals @@ -486,6 +534,7 @@ def test_publisher_widget(self): query.publisher = publisher1 w.apply_constraints_to_query(query) self.assertIsNone(query.publisher) + self.assertEqual(w.current_text(), 'Publishers') # clear using clear button w._selection_changed(publisher2) @@ -496,9 +545,11 @@ def test_publisher_widget(self): self.assertFalse(w.should_show_clear()) w.apply_constraints_to_query(query) self.assertIsNone(query.publisher) + self.assertEqual(w.current_text(), 'Publishers') w.clear() self.assertEqual(len(spy), 4) + self.assertEqual(w.current_text(), 'Publishers') w._selection_changed(publisher1) self.assertEqual(len(spy), 5) @@ -507,6 +558,7 @@ def test_publisher_widget(self): query.publisher = None w.apply_constraints_to_query(query) self.assertEqual(query.publisher.id(), 'site:1') + self.assertEqual(w.current_text(), 'Site 1') if __name__ == '__main__': From fab4c74cd32c8c658afde2709f735adbfcefe7c6 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 10:44:54 +1000 Subject: [PATCH 4/8] Fix duplicate signals in resolution filter widget, fix logic and add tests --- .../filter_widgets/data_type_filter_widget.py | 12 +- .../resolution_filter_widget.py | 63 ++-- koordinates/test/test_filter_widgets.py | 305 +++++++++++++++++- 3 files changed, 353 insertions(+), 27 deletions(-) diff --git a/koordinates/gui/filter_widgets/data_type_filter_widget.py b/koordinates/gui/filter_widgets/data_type_filter_widget.py index 6693960..e08a796 100644 --- a/koordinates/gui/filter_widgets/data_type_filter_widget.py +++ b/koordinates/gui/filter_widgets/data_type_filter_widget.py @@ -1,4 +1,7 @@ -from typing import Set +from typing import ( + Optional, + Set +) from qgis.PyQt.QtWidgets import ( QWidget, @@ -27,7 +30,7 @@ class DataTypeFilterWidget(FilterWidgetComboBase): WITH_SETS = False - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self._block_geometry_type_constraint_update = 0 @@ -194,7 +197,7 @@ def __init__(self, parent): self.has_z_elevation_checkbox.toggled.connect(self._update_value) self.vector_has_primary_key_checkbox.toggled.connect(self._update_value) - self.clear() + self._update_value() def _type_group_member_clicked(self, clicked_button): self._block_changes += 1 @@ -258,6 +261,9 @@ def _enforce_geometry_type_constraints(self): self._block_geometry_type_constraint_update -= 1 def clear(self): + if self.layers_radio.isChecked(): + return + self._block_changes += 1 self.layers_radio.setChecked(True) self.raster_radio.setChecked(False) diff --git a/koordinates/gui/filter_widgets/resolution_filter_widget.py b/koordinates/gui/filter_widgets/resolution_filter_widget.py index d8ea825..81bc349 100644 --- a/koordinates/gui/filter_widgets/resolution_filter_widget.py +++ b/koordinates/gui/filter_widgets/resolution_filter_widget.py @@ -1,3 +1,4 @@ +from typing import Optional import math from qgis.PyQt.QtWidgets import ( @@ -17,7 +18,7 @@ class ResolutionFilterWidget(FilterWidgetComboBase): Custom widget for resolution selection """ - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.drop_down_widget = QWidget() @@ -44,7 +45,8 @@ def __init__(self, parent): self._range = (0.03, 2000) self.slider.setRangeLimits(0, 100000) - self.clear() + self.slider.setRange(self.slider.minimum(), self.slider.maximum()) + self._update_labels() @staticmethod def scale(value, domain, range): @@ -99,7 +101,13 @@ def _update_labels(self): self.changed.emit() def clear(self): + if self.slider.lowerValue() == self.slider.minimum() and \ + self.slider.upperValue() == self.slider.maximum(): + return + + self._block_changes += 1 self.slider.setRange(self.slider.minimum(), self.slider.maximum()) + self._block_changes -= 1 self._update_labels() def should_show_clear(self): @@ -110,26 +118,31 @@ def should_show_clear(self): return super().should_show_clear() def apply_constraints_to_query(self, query: DataBrowserQuery): - if self.map_slider_value_to_resolution(self.slider.lowerValue()) \ - != self.map_slider_value_to_resolution(self.slider.minimum()): - query.minimum_resolution = self.map_slider_value_to_resolution( - self.slider.lowerValue() - ) - if self.map_slider_value_to_resolution(self.slider.upperValue()) != \ - self.map_slider_value_to_resolution(self.slider.maximum()): - query.maximum_resolution = self.map_slider_value_to_resolution( - self.slider.upperValue() - ) + mapped_lower_value =self.map_slider_value_to_resolution(self.slider.lowerValue()) + mapped_minimum = self.map_slider_value_to_resolution(self.slider.minimum()) + if mapped_lower_value != mapped_minimum: + query.minimum_resolution = mapped_lower_value + else: + query.minimum_resolution = None + mapped_upper_value = self.map_slider_value_to_resolution(self.slider.upperValue()) + mapped_maximum = self.map_slider_value_to_resolution(self.slider.maximum()) + if mapped_upper_value != mapped_maximum: + query.maximum_resolution = mapped_upper_value + else: + query.maximum_resolution = None def set_from_query(self, query: DataBrowserQuery): self._block_changes += 1 if query.minimum_resolution is not None: - self.slider.setLowerValue(int(query.minimum_resolution)) + self.slider.setLowerValue( + self.map_value_to_slider(query.minimum_resolution) + ) else: self.slider.setLowerValue(self.slider.minimum()) if query.maximum_resolution is not None: - self.slider.setUpperValue(int(query.maximum_resolution)) + self.slider.setUpperValue( + self.map_value_to_slider(query.maximum_resolution)) else: self.slider.setUpperValue(self.slider.maximum()) @@ -140,17 +153,31 @@ def set_facets(self, facets: dict): min_res = facets.get('raster_resolution', {}).get('min') max_res = facets.get('raster_resolution', {}).get('max') - prev_range = self.current_range() + prev_range = list(self.current_range()) + if prev_range[0] == self._range[0]: + prev_range[0] = None + if prev_range[1] == self._range[1]: + prev_range[1] = None if min_res is not None and max_res is not None: self._range = (min_res, max_res) - new_range = (max(prev_range[0], min_res), min(prev_range[1], max_res)) + new_range = prev_range[:] + if prev_range[0] is not None: + new_range[0] = max(prev_range[0], min_res) + else: + new_range[0] = min_res + if prev_range[1] is not None: + new_range[1] = min(prev_range[1], max_res) + else: + new_range[1] = max_res else: self._range = (0.03, 2000) new_range = self._range self._block_changes += 1 - self.slider.setRange(self.map_value_to_slider(new_range[0]), - self.map_value_to_slider(new_range[1])) + slider_min = self.map_value_to_slider(new_range[0]) + slider_max = self.map_value_to_slider(new_range[1]) + self.slider.setRange(slider_min, slider_max) + self._update_labels() self._block_changes -= 1 diff --git a/koordinates/test/test_filter_widgets.py b/koordinates/test/test_filter_widgets.py index f43fdef..19a1f92 100644 --- a/koordinates/test/test_filter_widgets.py +++ b/koordinates/test/test_filter_widgets.py @@ -17,15 +17,21 @@ from .utilities import get_qgis_app from ..api import ( DataBrowserQuery, + DataType, Publisher, CreativeCommonLicenseVersions, - AccessType + AccessType, + VectorFilter +) +from ..gui.filter_widgets import ( + AccessFilterWidget, + DataTypeFilterWidget, + DateFilterWidget, + GroupFilterWidget, + LicenseFilterWidget, + PublisherFilterWidget, + ResolutionFilterWidget ) -from ..gui.filter_widgets.access_filter_widget import AccessFilterWidget -from ..gui.filter_widgets.date_filter_widget import DateFilterWidget -from ..gui.filter_widgets.group_filter_widget import GroupFilterWidget -from ..gui.filter_widgets.license_filter_widget import LicenseFilterWidget -from ..gui.filter_widgets.publisher_filter_widget import PublisherFilterWidget QGIS_APP = get_qgis_app() @@ -560,6 +566,293 @@ def test_publisher_widget(self): self.assertEqual(query.publisher.id(), 'site:1') self.assertEqual(w.current_text(), 'Site 1') + def test_data_type_widget(self): + w = DataTypeFilterWidget() + self.assertEqual(w.current_text(), 'Data type') + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertEqual(query.data_types, set()) + self.assertEqual(query.vector_filters, set()) + self.assertEqual(query.raster_filters, set()) + self.assertEqual(query.raster_filter_options, set()) + self.assertEqual(query.raster_band_filters, set()) + self.assertEqual(query.grid_filter_options, set()) + + w.apply_constraints_to_query(query) + self.assertEqual(query.data_types, { + DataType.Rasters, + DataType.Vectors, + DataType.Grids}) + self.assertEqual(query.vector_filters, set()) + self.assertEqual(query.raster_filters, set()) + self.assertEqual(query.raster_filter_options, set()) + self.assertEqual(query.raster_band_filters, set()) + self.assertEqual(query.grid_filter_options, set()) + self.assertEqual(w.current_text(), 'Data type') + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Data type') + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertEqual(query.data_types, { + DataType.Rasters, + DataType.Vectors, + DataType.Grids}) + self.assertEqual(query.vector_filters, set()) + self.assertEqual(query.raster_filters, set()) + self.assertEqual(query.raster_filter_options, set()) + self.assertEqual(query.raster_band_filters, set()) + self.assertEqual(query.grid_filter_options, set()) + self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Data type') + + w.vector_radio.click() + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.data_types = set() + query.vector_filters = set() + query.raster_filters = set() + query.raster_filter_options = set() + query.raster_band_filters = set() + query.grid_filter_options = set() + w.apply_constraints_to_query(query) + self.assertEqual(query.data_types, { + DataType.Vectors + }) + self.assertEqual(query.vector_filters, set()) + self.assertEqual(query.raster_filters, set()) + self.assertEqual(query.raster_filter_options, set()) + self.assertEqual(query.raster_band_filters, set()) + self.assertEqual(query.grid_filter_options, set()) + self.assertEqual(w.current_text(), 'Vectors') + + # reapply same, should be no signal + w.set_from_query(query) + self.assertEqual(len(spy), 1) + self.assertEqual(w.current_text(), 'Vectors') + + w.point_checkbox.setChecked(False) + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.data_types = set() + query.vector_filters = set() + query.raster_filters = set() + query.raster_filter_options = set() + query.raster_band_filters = set() + query.grid_filter_options = set() + w.apply_constraints_to_query(query) + self.assertEqual(query.data_types, { + DataType.Vectors + }) + self.assertEqual(query.vector_filters, { + VectorFilter.Polygon, + VectorFilter.Line + }) + self.assertEqual(query.raster_filters, set()) + self.assertEqual(query.raster_filter_options, set()) + self.assertEqual(query.raster_band_filters, set()) + self.assertEqual(query.grid_filter_options, set()) + w.set_from_query(query) + self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), 'Vector: Line, Polygon') + + # clear using query params + # this should never raise signals + query.vector_filters = set() + w.set_from_query(query) + self.assertEqual(len(spy), 2) + query.vector_filters = { + VectorFilter.Point, + } + w.apply_constraints_to_query(query) + self.assertEqual(query.data_types, { + DataType.Vectors + }) + self.assertEqual(query.vector_filters, { + VectorFilter.Point + }) + self.assertEqual(query.raster_filters, set()) + self.assertEqual(query.raster_filter_options, set()) + self.assertEqual(query.raster_band_filters, set()) + self.assertEqual(query.grid_filter_options, set()) + self.assertEqual(w.current_text(), 'Vectors') + + # clear using clear button + w.polygon_checkbox.setChecked(False) + self.assertEqual(len(spy), 3) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 4) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertFalse(query.cc_license_versions) + self.assertFalse(query.cc_license_allow_derivates) + self.assertFalse(query.cc_license_allow_commercial) + self.assertFalse(query.cc_license_changes_must_be_shared) + self.assertEqual(w.current_text(), 'Data type') + + w.clear() + self.assertEqual(len(spy), 4) + self.assertEqual(w.current_text(), 'Data type') + + def test_resolution_widget(self): + w = ResolutionFilterWidget() + self.assertEqual(w.current_text(), 'Resolution') + + # should start cleared + self.assertFalse(w.should_show_clear()) + query = DataBrowserQuery() + self.assertIsNone(query.minimum_resolution) + self.assertIsNone(query.maximum_resolution) + w.apply_constraints_to_query(query) + self.assertIsNone(query.minimum_resolution) + self.assertIsNone(query.maximum_resolution) + self.assertEqual(w.current_text(), 'Resolution') + + spy = QSignalSpy(w.changed) + # re-clearing already cleared should not emit signals + w.clear() + self.assertEqual(len(spy), 0) + self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Resolution') + + # apply same query to widget, should be no signals + w.set_from_query(query) + self.assertEqual(len(spy), 0) + w.apply_constraints_to_query(query) + self.assertIsNone(query.minimum_resolution) + self.assertIsNone(query.maximum_resolution) + self.assertFalse(w.should_show_clear()) + self.assertEqual(w.current_text(), 'Resolution') + + # set facets to ranges + w.set_facets({ + 'raster_resolution': { + 'min': 10, + 'max': 20000}, + }) + self.assertEqual( + w.map_slider_value_to_resolution(w.slider.lowerValue()), + 10) + self.assertEqual(w.slider.upperValue(), 100000) + self.assertEqual( + w.map_slider_value_to_resolution(w.slider.upperValue()), + 20000) + + w.slider.setLowerValue( + w.map_value_to_slider(150) + ) + self.assertEqual(len(spy), 1) + self.assertTrue(w.should_show_clear()) + query.minimum_resolution = 15.09 + w.apply_constraints_to_query(query) + self.assertEqual(query.minimum_resolution, 149.99) + self.assertIsNone(query.maximum_resolution) + self.assertEqual(w.current_text(), 'Resolution 149.99 m - 20000.0 m') + + # reapply same, should be no signal + w.slider.setLowerValue( + w.map_value_to_slider(150) + ) + self.assertEqual(len(spy), 1) + w.set_from_query(query) + self.assertEqual(len(spy), 1) + self.assertEqual(w.current_text(), 'Resolution 149.97 m - 20000.0 m') + + w.slider.setUpperValue( + w.map_value_to_slider(10500) + ) + self.assertEqual(len(spy), 2) + self.assertTrue(w.should_show_clear()) + query.maximum_resolution = None + w.apply_constraints_to_query(query) + self.assertEqual(query.minimum_resolution, 149.97) + self.assertEqual(query.maximum_resolution, 10499.98) + w.set_from_query(query) + self.assertEqual(len(spy), 2) + self.assertEqual(w.current_text(), 'Resolution 149.95 m - 10499.23 m') + + # widen range using facets + w.set_facets({ + 'raster_resolution': { + 'min': 5, + 'max': 50000}, + }) + self.assertEqual(len(spy), 2) + self.assertEqual( + w.map_slider_value_to_resolution(w.slider.minimum()), + 5) + self.assertEqual( + w.map_slider_value_to_resolution(w.slider.maximum()), + 50000) + # same query range should be kept + query.minimum_resolution = None + query.maximum_resolution = None + w.apply_constraints_to_query(query) + self.assertEqual(query.minimum_resolution, 149.93) + self.assertEqual(query.maximum_resolution, 10498.79) + self.assertEqual(w.current_text(), 'Resolution 149.93 m - 10498.79 m') + + # clear using query params + # this should never raise signals + query.minimum_resolution = None + query.maximum_resolution = None + w.set_from_query(query) + self.assertEqual(len(spy), 2) + w.apply_constraints_to_query(query) + self.assertIsNone(query.minimum_resolution) + self.assertIsNone(query.maximum_resolution) + query.minimum_resolution = 5 + query.maximum_resolution = 1000 + w.apply_constraints_to_query(query) + self.assertIsNone(query.minimum_resolution) + self.assertIsNone(query.maximum_resolution) + self.assertEqual(w.current_text(), 'Resolution') + + # clear using clear button + w.slider.setLowerValue( + w.map_value_to_slider(150) + ) + self.assertEqual(len(spy), 3) + self.assertTrue(w.should_show_clear()) + w.clear() + self.assertEqual(len(spy), 4) + self.assertFalse(w.should_show_clear()) + w.apply_constraints_to_query(query) + self.assertIsNone(query.minimum_resolution) + self.assertIsNone(query.maximum_resolution) + self.assertEqual(w.current_text(), 'Resolution') + + w.clear() + self.assertEqual(len(spy), 4) + + w.slider.setUpperValue( + w.map_value_to_slider(20000) + ) + self.assertEqual(len(spy), 5) + # reapply same facet -- should not change setting, or raise signal + w.set_facets({ + 'raster_resolution': { + 'min': 5, + 'max': 50000}, + }) + self.assertEqual(len(spy), 5) + query.minimum_resolution = None + query.maximum_resolution = None + w.apply_constraints_to_query(query) + self.assertIsNone(query.minimum_resolution) + self.assertEqual(query.maximum_resolution, 19998.92) + self.assertEqual(w.current_text(), 'Resolution 5.0 m - 19998.92 m') + if __name__ == '__main__': unittest.main() From 77d943d05549f4139eeb2d44450af6aeb4ea3125 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 11:02:31 +1000 Subject: [PATCH 5/8] Add tests for advanced filter widget --- koordinates/gui/filter_widget.py | 2 +- koordinates/gui/filter_widgets/__init__.py | 2 + .../advanced_filter_widget.py | 27 ++++----- koordinates/test/test_filter_widgets.py | 57 ++++++++++++++++++- 4 files changed, 71 insertions(+), 17 deletions(-) rename koordinates/gui/{ => filter_widgets}/advanced_filter_widget.py (92%) diff --git a/koordinates/gui/filter_widget.py b/koordinates/gui/filter_widget.py index 3299980..b51a802 100644 --- a/koordinates/gui/filter_widget.py +++ b/koordinates/gui/filter_widget.py @@ -20,7 +20,7 @@ QgsFilterLineEdit ) -from .advanced_filter_widget import AdvancedFilterWidget +from .filter_widgets import AdvancedFilterWidget from .enums import ( TabStyle, FilterWidgetAppearance, diff --git a/koordinates/gui/filter_widgets/__init__.py b/koordinates/gui/filter_widgets/__init__.py index 71894bc..25ad14b 100644 --- a/koordinates/gui/filter_widgets/__init__.py +++ b/koordinates/gui/filter_widgets/__init__.py @@ -7,3 +7,5 @@ from .publisher_filter_widget import PublisherFilterWidget # NOQA from .publisher_filter_widget import PublisherSelectionWidget # NOQA from .resolution_filter_widget import ResolutionFilterWidget # NOQA +from .advanced_filter_widget import AdvancedFilterWidget # NOQA + diff --git a/koordinates/gui/advanced_filter_widget.py b/koordinates/gui/filter_widgets/advanced_filter_widget.py similarity index 92% rename from koordinates/gui/advanced_filter_widget.py rename to koordinates/gui/filter_widgets/advanced_filter_widget.py index a54a7ca..d26df49 100644 --- a/koordinates/gui/advanced_filter_widget.py +++ b/koordinates/gui/filter_widgets/advanced_filter_widget.py @@ -19,19 +19,16 @@ QStyleOption ) -from .filter_widgets import ( - AccessFilterWidget, - # CategoryFilterWidget, - DataTypeFilterWidget, - DateFilterWidget, - PublisherFilterWidget, - GroupFilterWidget, - LicenseFilterWidget, - ResolutionFilterWidget -) -from .enums import FilterWidgetAppearance -from .flow_layout import FlowLayout -from ..api import ( +from .access_filter_widget import AccessFilterWidget +from .data_type_filter_widget import DataTypeFilterWidget +from .date_filter_widget import DateFilterWidget +from .publisher_filter_widget import PublisherFilterWidget +from .group_filter_widget import GroupFilterWidget +from .license_filter_widget import LicenseFilterWidget +from .resolution_filter_widget import ResolutionFilterWidget +from ..enums import FilterWidgetAppearance +from ..flow_layout import FlowLayout +from ...api import ( DataBrowserQuery, DataType, Publisher @@ -45,7 +42,7 @@ class AdvancedFilterWidget(QWidget): CORNER_RADIUS = 4 - def __init__(self, parent): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.should_show = False @@ -201,7 +198,7 @@ def apply_constraints_to_query(self, query: DataBrowserQuery): Updates a query to reflect the current widget state """ for w in self.filter_widgets: - if w.isVisible(): + if not w.isHidden(): w.apply_constraints_to_query(query) def set_from_query(self, query: DataBrowserQuery): diff --git a/koordinates/test/test_filter_widgets.py b/koordinates/test/test_filter_widgets.py index 19a1f92..412b101 100644 --- a/koordinates/test/test_filter_widgets.py +++ b/koordinates/test/test_filter_widgets.py @@ -30,7 +30,8 @@ GroupFilterWidget, LicenseFilterWidget, PublisherFilterWidget, - ResolutionFilterWidget + ResolutionFilterWidget, + AdvancedFilterWidget ) QGIS_APP = get_qgis_app() @@ -853,6 +854,60 @@ def test_resolution_widget(self): self.assertEqual(query.maximum_resolution, 19998.92) self.assertEqual(w.current_text(), 'Resolution 5.0 m - 19998.92 m') + def test_advanced_filter_widget(self): + w = AdvancedFilterWidget() + spy = QSignalSpy(w.filters_changed) + + # should start cleared + w.clear_all() + self.assertFalse(spy.wait(1)) + self.assertEqual(len(spy), 0) + + # change access filter + spy2 = QSignalSpy(w.access_widget.changed) + w.access_widget.public_radio.click() + spy.wait() + self.assertEqual(len(spy2), 1) + self.assertEqual(len(spy), 1) + self.assertFalse(spy.wait(1)) + + # change license filter + spy3 = QSignalSpy(w.license_widget.changed) + w.license_widget.cc_3_checkbox.setChecked(True) + spy.wait() + self.assertEqual(len(spy2), 1) + self.assertEqual(len(spy3), 1) + self.assertEqual(len(spy), 2) + self.assertFalse(spy.wait(1)) + + query = DataBrowserQuery() + w.apply_constraints_to_query(query) + self.assertEqual(query.cc_license_versions, + {CreativeCommonLicenseVersions.Version3}) + self.assertEqual(query.access_type, AccessType.Public) + + query.access_type = AccessType.Private + query.cc_license_versions = {CreativeCommonLicenseVersions.Version4} + w.set_from_query(query) + # should be NO signals when setting from query + self.assertFalse(spy.wait(1)) + self.assertEqual(len(spy), 2) + query.access_type = None + query.cc_license_versions = set() + w.apply_constraints_to_query(query) + self.assertEqual(query.cc_license_versions, + {CreativeCommonLicenseVersions.Version4}) + self.assertEqual(query.access_type, AccessType.Private) + + # clear all + w.clear_all() + self.assertEqual(len(spy2), 2) + self.assertEqual(len(spy3), 2) + spy.wait() + # should only be ONE clear signal for the composite widget + self.assertEqual(len(spy), 3) + self.assertFalse(spy.wait(1)) + if __name__ == '__main__': unittest.main() From ee9b7aa102204663a525f9d4393350ee4e0bb4d0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 11:05:05 +1000 Subject: [PATCH 6/8] Lint --- koordinates/gui/filter_widgets/__init__.py | 1 - koordinates/gui/filter_widgets/access_filter_widget.py | 2 +- koordinates/gui/filter_widgets/resolution_filter_widget.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/koordinates/gui/filter_widgets/__init__.py b/koordinates/gui/filter_widgets/__init__.py index 25ad14b..da1f390 100644 --- a/koordinates/gui/filter_widgets/__init__.py +++ b/koordinates/gui/filter_widgets/__init__.py @@ -8,4 +8,3 @@ from .publisher_filter_widget import PublisherSelectionWidget # NOQA from .resolution_filter_widget import ResolutionFilterWidget # NOQA from .advanced_filter_widget import AdvancedFilterWidget # NOQA - diff --git a/koordinates/gui/filter_widgets/access_filter_widget.py b/koordinates/gui/filter_widgets/access_filter_widget.py index a8bc7d1..14ae7d5 100644 --- a/koordinates/gui/filter_widgets/access_filter_widget.py +++ b/koordinates/gui/filter_widgets/access_filter_widget.py @@ -18,7 +18,7 @@ class AccessFilterWidget(FilterWidgetComboBase): Custom widget for access based filtering """ - def __init__(self, parent: Optional[QWidget]=None): + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.drop_down_widget = QWidget() diff --git a/koordinates/gui/filter_widgets/resolution_filter_widget.py b/koordinates/gui/filter_widgets/resolution_filter_widget.py index 81bc349..14c691c 100644 --- a/koordinates/gui/filter_widgets/resolution_filter_widget.py +++ b/koordinates/gui/filter_widgets/resolution_filter_widget.py @@ -118,7 +118,7 @@ def should_show_clear(self): return super().should_show_clear() def apply_constraints_to_query(self, query: DataBrowserQuery): - mapped_lower_value =self.map_slider_value_to_resolution(self.slider.lowerValue()) + mapped_lower_value = self.map_slider_value_to_resolution(self.slider.lowerValue()) mapped_minimum = self.map_slider_value_to_resolution(self.slider.minimum()) if mapped_lower_value != mapped_minimum: query.minimum_resolution = mapped_lower_value From bee32c9a222730331a234a1ce45e5c43b42206e4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 11:58:29 +1000 Subject: [PATCH 7/8] Fix exception --- koordinates/gui/filter_widgets/publisher_filter_widget.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/koordinates/gui/filter_widgets/publisher_filter_widget.py b/koordinates/gui/filter_widgets/publisher_filter_widget.py index dcb3355..fe4a22a 100644 --- a/koordinates/gui/filter_widgets/publisher_filter_widget.py +++ b/koordinates/gui/filter_widgets/publisher_filter_widget.py @@ -617,6 +617,7 @@ class PublisherFilterWidget(FilterWidgetComboBase): """ def __init__(self, parent: Optional[QWidget] = None): + self._current_publisher: Optional[Publisher] = None super().__init__(parent) self.drop_down_widget = PublisherSelectionWidget() @@ -625,9 +626,6 @@ def __init__(self, parent: Optional[QWidget] = None): ) self.set_contents_widget(self.drop_down_widget) - - self._current_publisher: Optional[Publisher] = None - self._update_value() def current_publisher(self) -> Optional[Publisher]: From 6ec7cd1682611794ef1e65dea3beb94f244d4809 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 14 Dec 2023 12:05:50 +1000 Subject: [PATCH 8/8] Fix initial flash after login --- koordinates/gui/koordinates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/koordinates/gui/koordinates.py b/koordinates/gui/koordinates.py index 84fd2b6..f6abaab 100644 --- a/koordinates/gui/koordinates.py +++ b/koordinates/gui/koordinates.py @@ -450,6 +450,8 @@ def __init__(self, parent): self.context_tab_container.setFixedHeight(self.context_tab.sizeHint().height()) self.context_frame = ColoredFrame() + self.context_frame.set_color(QColor()) + self.context_frame.setObjectName('context_frame') context_frame_layout = QVBoxLayout() context_frame_layout.setContentsMargins(0, 0, 0, 0) @@ -642,7 +644,6 @@ def __init__(self, parent): self.button_user.setMenu(self.user_menu) - self._context_tab_changed(self.TAB_EXPLORE_INDEX) self.context_tab_container.setFixedWidth(self.context_container.width()) KoordinatesClient.instance().loginChanged.connect(self._loginChanged)