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
|