Searchable tree widget from a mapping (#158)

* Crude searchable tree widget with example

* Add logging and fix hiding bug

* style: [pre-commit.ci] auto fixes [...]

* Add factory method

* Use regular expression instead

* Reduce API

* Make setData public

* Clear filter when setting data

* Visible instead of hidden

* Show item when parent is visible

* Add docs

* Empty commit to [skip ci]

* style: [pre-commit.ci] auto fixes [...]

* Empty commit to [skip ci]

* Add test coverage

* Improve readability of tests

* Use python not json names

* Simplify example

* Some optimizations

* Clean up tests

* Fix visible siblings

* Modify test to cover visible sibling

* style: [pre-commit.ci] auto fixes [...]

* fix lint

* Update src/superqt/selection/_searchable_tree_widget.py

Co-authored-by: Talley Lambert <talley.lambert@gmail.com>

* Search by value too

* Remove optimizations

* Clean up formatting

* style: [pre-commit.ci] auto fixes [...]

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Talley Lambert <talley.lambert@gmail.com>
This commit is contained in:
Andy Sweet
2023-04-20 16:15:26 -07:00
committed by GitHub
parent 09c76a0bfa
commit bb43cd7fad
5 changed files with 298 additions and 2 deletions

View File

@@ -0,0 +1,151 @@
from typing import List, Tuple
import pytest
from pytestqt.qtbot import QtBot
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QTreeWidget, QTreeWidgetItem
from superqt import QSearchableTreeWidget
@pytest.fixture
def data() -> dict:
return {
"none": None,
"str": "test",
"int": 42,
"list": [2, 3, 5],
"dict": {
"float": 0.5,
"tuple": (22, 99),
"bool": False,
},
}
@pytest.fixture
def widget(qtbot: QtBot, data: dict) -> QSearchableTreeWidget:
widget = QSearchableTreeWidget.fromData(data)
qtbot.addWidget(widget)
return widget
def columns(item: QTreeWidgetItem) -> Tuple[str, str]:
return item.text(0), item.text(1)
def all_items(tree: QTreeWidget) -> List[QTreeWidgetItem]:
return tree.findItems("", Qt.MatchContains | Qt.MatchRecursive)
def shown_items(tree: QTreeWidget) -> List[QTreeWidgetItem]:
items = all_items(tree)
return [item for item in items if not item.isHidden()]
def test_init(qtbot: QtBot):
widget = QSearchableTreeWidget()
qtbot.addWidget(widget)
assert widget.tree.topLevelItemCount() == 0
def test_from_data(qtbot: QtBot, data: dict):
widget = QSearchableTreeWidget.fromData(data)
qtbot.addWidget(widget)
tree = widget.tree
assert tree.topLevelItemCount() == 5
none_item = tree.topLevelItem(0)
assert columns(none_item) == ("none", "None")
assert none_item.childCount() == 0
str_item = tree.topLevelItem(1)
assert columns(str_item) == ("str", "test")
assert str_item.childCount() == 0
int_item = tree.topLevelItem(2)
assert columns(int_item) == ("int", "42")
assert int_item.childCount() == 0
list_item = tree.topLevelItem(3)
assert columns(list_item) == ("list", "list")
assert list_item.childCount() == 3
assert columns(list_item.child(0)) == ("0", "2")
assert columns(list_item.child(1)) == ("1", "3")
assert columns(list_item.child(2)) == ("2", "5")
dict_item = tree.topLevelItem(4)
assert columns(dict_item) == ("dict", "dict")
assert dict_item.childCount() == 3
assert columns(dict_item.child(0)) == ("float", "0.5")
tuple_item = dict_item.child(1)
assert columns(tuple_item) == ("tuple", "tuple")
assert tuple_item.childCount() == 2
assert columns(tuple_item.child(0)) == ("0", "22")
assert columns(tuple_item.child(1)) == ("1", "99")
assert columns(dict_item.child(2)) == ("bool", "False")
def test_set_data(widget: QSearchableTreeWidget):
tree = widget.tree
assert tree.topLevelItemCount() != 1
widget.setData({"test": "reset"})
assert tree.topLevelItemCount() == 1
assert columns(tree.topLevelItem(0)) == ("test", "reset")
def test_search_no_match(widget: QSearchableTreeWidget):
widget.filter.setText("no match here")
items = shown_items(widget.tree)
assert len(items) == 0
def test_search_all_match(widget: QSearchableTreeWidget):
widget.filter.setText("")
tree = widget.tree
assert all_items(tree) == shown_items(tree)
def test_search_match_one_key(widget: QSearchableTreeWidget):
widget.filter.setText("int")
items = shown_items(widget.tree)
assert len(items) == 1
assert columns(items[0]) == ("int", "42")
def test_search_match_one_value(widget: QSearchableTreeWidget):
widget.filter.setText("test")
items = shown_items(widget.tree)
assert len(items) == 1
assert columns(items[0]) == ("str", "test")
def test_search_match_many_keys(widget: QSearchableTreeWidget):
widget.filter.setText("n")
items = shown_items(widget.tree)
assert len(items) == 2
assert columns(items[0]) == ("none", "None")
assert columns(items[1]) == ("int", "42")
def test_search_match_one_show_unmatched_descendants(widget: QSearchableTreeWidget):
widget.filter.setText("list")
items = shown_items(widget.tree)
assert len(items) == 4
assert columns(items[0]) == ("list", "list")
assert columns(items[1]) == ("0", "2")
assert columns(items[2]) == ("1", "3")
assert columns(items[3]) == ("2", "5")
def test_search_match_one_show_unmatched_ancestors(widget: QSearchableTreeWidget):
widget.filter.setText("tuple")
items = shown_items(widget.tree)
assert len(items) == 4
assert columns(items[0]) == ("dict", "dict")
assert columns(items[1]) == ("tuple", "tuple")
assert columns(items[2]) == ("0", "22")
assert columns(items[3]) == ("1", "99")