summaryrefslogtreecommitdiff
path: root/tests/unit/mainwindow/test_treetabbedbrowser.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit/mainwindow/test_treetabbedbrowser.py')
-rw-r--r--tests/unit/mainwindow/test_treetabbedbrowser.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/tests/unit/mainwindow/test_treetabbedbrowser.py b/tests/unit/mainwindow/test_treetabbedbrowser.py
new file mode 100644
index 000000000..3ae2b3b00
--- /dev/null
+++ b/tests/unit/mainwindow/test_treetabbedbrowser.py
@@ -0,0 +1,255 @@
+# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import pytest
+
+from qutebrowser.config.configtypes import NewTabPosition, NewChildPosition
+from qutebrowser.misc.notree import Node
+from qutebrowser.mainwindow import treetabbedbrowser, treetabwidget
+
+
+@pytest.fixture
+def mock_browser(mocker):
+ # Mock browser used as `self` below because we are actually testing mostly
+ # standalone functionality apart from the tab stack related counters.
+ # Which are also only defined in __init__, not on the class, so mock
+ # doesn't see them. Hence specifying them manually here.
+ browser = mocker.Mock(
+ spec=treetabbedbrowser.TreeTabbedBrowser,
+ widget=mocker.Mock(spec=treetabwidget.TreeTabWidget),
+ _tree_tab_child_rel_idx=0,
+ _tree_tab_sibling_rel_idx=0,
+ _tree_tab_toplevel_rel_idx=0,
+ )
+
+ # Sad little workaround to create a bound method on a mock, because
+ # _position_tab calls a method on self but we are using a mock as self to
+ # avoid initializing the whole tabbed browser class.
+ def reset_passthrough():
+ return treetabbedbrowser.TreeTabbedBrowser._reset_stack_counters(
+ browser
+ )
+ browser._reset_stack_counters = reset_passthrough
+
+ return browser
+
+
+class TestPositionTab:
+ """Test TreeTabbedBrowser._position_tab()."""
+
+ @pytest.mark.parametrize(
+ " relation, cur_node, pos, expected", [
+ ("sibling", "three", "first", "one",),
+ ("sibling", "three", "prev", "two",),
+ ("sibling", "three", "next", "three",),
+ ("sibling", "three", "last", "six",),
+ ("sibling", "one", "first", "root",),
+ ("sibling", "one", "prev", "root",),
+ ("sibling", "one", "next", "one",),
+ ("sibling", "one", "last", "seven",),
+
+ ("related", "one", "first", "one",),
+ ("related", "one", "last", "six",),
+ ("related", "two", "first", "two",),
+ ("related", "two", "last", "two",),
+
+ (None, "five", "first", "root",),
+ (None, "five", "prev", "root",),
+ (None, "five", "next", "one",),
+ (None, "five", "last", "seven",),
+ (None, "seven", "prev", "one",),
+ (None, "seven", "next", "seven",),
+ ]
+ )
+ def test_position_tab(
+ self,
+ config_stub,
+ mock_browser,
+ # parameterized
+ relation,
+ cur_node,
+ pos,
+ expected,
+ ):
+ """Test tree tab positioning.
+
+ How to use the parameters above:
+ * refer to the tree structure being passed to create_tree() below, that's
+ our starting state
+ * specify how the new node should be related to the current one
+ * specify cur_node by value, which is the tab currently focused when the
+ new tab is opened and the one the "sibling" and "related" arguments
+ refer to
+ * set "pos" which is the position of the new node in the list of
+ siblings it's going to end up in. It should be one of first, list, prev,
+ next (except the "related" relation doesn't support prev and next)
+ * specify the expected preceding node (the preceding sibling if there is
+ one, otherwise the parent) after the new node is positioned, "root" is
+ a valid value for this
+
+ Having the expectation being the preceding tab (sibling or parent) is
+ a bit limited, in particular if the new tab somehow ends up as a child
+ instead of the next sibling you wouldn't be able to tell those
+ situations apart. But I went this route to avoid having to specify
+ multiple trees in the parameters.
+ """
+ root = self.create_tree(
+ """
+ - one
+ - two
+ - three
+ - four
+ - five
+ - six
+ - seven
+ """,
+ )
+ new_node = Node("new", parent=root)
+
+ config_stub.val.tabs.new_position.stacking = False
+ self.call_position_tab(
+ mock_browser,
+ root,
+ cur_node,
+ new_node,
+ pos,
+ relation,
+ )
+
+ preceding_node = None
+ if new_node.parent.children[0] == new_node:
+ preceding_node = new_node.parent
+ else:
+ for n in new_node.parent.children:
+ if n.value == "new":
+ break
+ preceding_node = n
+ else:
+ pytest.fail("new tab not found")
+
+ assert preceding_node.value == expected
+
+ def call_position_tab(
+ self,
+ mock_browser,
+ root,
+ cur_node,
+ new_node,
+ pos,
+ relation,
+ background=False,
+ ):
+ sibling = related = False
+ if relation == "sibling":
+ sibling = True
+ elif relation == "related":
+ related = True
+ elif relation == "background":
+ background = True
+ elif relation is not None:
+ pytest.fail(
+ "Valid values for relation are: "
+ "sibling, related, background, None"
+ )
+
+ # This relation -> parent mapping is copied from
+ # TreeTabbedBrowser.tabopen().
+ cur_node = next(n for n in root.traverse() if n.value == cur_node)
+ assert not (related and sibling)
+ if related:
+ parent = cur_node
+ NewChildPosition().from_str(pos)
+ elif sibling:
+ parent = cur_node.parent
+ NewTabPosition().from_str(pos)
+ else:
+ parent = root
+ NewTabPosition().from_str(pos)
+
+ treetabbedbrowser.TreeTabbedBrowser._position_tab(
+ mock_browser,
+ cur_node=cur_node,
+ new_node=new_node,
+ pos=pos,
+ parent=parent,
+ sibling=sibling,
+ related=related,
+ background=background,
+ )
+
+ def create_tree(self, tree_str):
+ # Construct a notree.Node tree from the test string.
+ root = Node("root")
+ previous_indent = ''
+ previous_node = root
+ for line in tree_str.splitlines():
+ if not line.strip():
+ continue
+ indent, value = line.split("-")
+ node = Node(value.strip())
+ if len(indent) > len(previous_indent):
+ node.parent = previous_node
+ elif len(indent) == len(previous_indent):
+ node.parent = previous_node.parent
+ else:
+ # TODO: handle going up in jumps of more than one rank
+ node.parent = previous_node.parent.parent
+ previous_indent = indent
+ previous_node = node
+ return root
+
+ @pytest.mark.parametrize(
+ " test_tree, relation, pos, expected", [
+ ("tree_one", "sibling", "next", "one,two,new1,new2,new3",),
+ ("tree_one", "sibling", "prev", "one,new3,new2,new1,two",),
+ ("tree_one", None, "next", "one,two,new1,new2,new3",),
+ ("tree_one", None, "prev", "new3,new2,new1,one,two",),
+ ("tree_one", "related", "first", "one,two,new1,new2,new3",),
+ ("tree_one", "related", "last", "one,two,new1,new2,new3",),
+ ]
+ )
+ def test_position_tab_stacking(
+ self,
+ config_stub,
+ mock_browser,
+ # parameterized
+ test_tree,
+ relation,
+ pos,
+ expected,
+ ):
+ """Test tree tab positioning with tab stacking enabled.
+
+ With tab stacking enabled the first background tab should be opened
+ beside the current one, successive background tabs should be opened on
+ the other side of prior opened tabs, not beside the current tab.
+ This test covers what is currently implemented, I'm not sure all the
+ desired behavior is implemented currently though.
+ """
+ # Simpler tree here to make the assert string a bit simpler.
+ # Tab "two" is hardcoded as cur_tab.
+ root = self.create_tree(
+ """
+ - one
+ - two
+ """,
+ )
+ config_stub.val.tabs.new_position.stacking = True
+
+ for val in ["new1", "new2", "new3"]:
+ new_node = Node(val, parent=root)
+
+ self.call_position_tab(
+ mock_browser,
+ root,
+ "two",
+ new_node,
+ pos,
+ relation,
+ background=True,
+ )
+
+ actual = ",".join([n.value for n in root.traverse()])
+ actual = actual[len("root,"):]
+ assert actual == expected