summaryrefslogtreecommitdiff
path: root/tests/unit/mainwindow/test_treetabbedbrowser.py
blob: 3ae2b3b00c4bc637d21624da29752b3441e0995b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
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