diff options
author | toofar <toofar@spalge.com> | 2024-02-08 19:33:34 +1300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-08 19:33:34 +1300 |
commit | 8637347641012a68482310bf6274d6cbbc4fa38c (patch) | |
tree | 087abc0f384803ccb11a8ff7c600d3b3cfd2b2e7 | |
parent | 0d91c22acc7cd6bf822a5f4e9ab398baeaf74a80 (diff) | |
parent | 273345bff8d6369fcaaebe4ec4bd8a6951f4c618 (diff) | |
download | qutebrowser-8637347641012a68482310bf6274d6cbbc4fa38c.tar.gz qutebrowser-8637347641012a68482310bf6274d6cbbc4fa38c.zip |
Merge pull request #8083 from pylbrecht/tree-tabs-e2e
Add end2end tests for tree tabs feature
-rw-r--r-- | qutebrowser/misc/sessions.py | 50 | ||||
-rw-r--r-- | tests/end2end/features/conftest.py | 214 | ||||
-rw-r--r-- | tests/end2end/features/test_treetabs_bdd.py | 6 | ||||
-rw-r--r-- | tests/end2end/features/treetabs.feature | 27 |
4 files changed, 194 insertions, 103 deletions
diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index dac43d238..fe8e7ba24 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -121,6 +121,42 @@ class TabHistoryItem: last_visited=self.last_visited) +def reconstruct_tree_data(window_data): + """Return a dict usable as a tree from a window. + + Returns a dict like: + { + 1: {'children': [2]}, + 2: { + ...tab, + "treetab_node_data": { + "children": [], + "collapsed": False, + "parent": 1, + "uid": 2, + } + } + } + + Which you can traverse by starting at the node with no "treetab_node_data" + attribute (the root) and pulling successive levels of children from the + dict using their `uid`s as keys. + + The ...tab part represents the usual attributes for a tab when saved in a + session. + """ + tree_data = {} + root = window_data['treetab_root'] + tree_data[root['uid']] = { + 'children': root['children'], + 'tab': {}, + 'collapsed': False + } + for tab in window_data['tabs']: + tree_data[tab['treetab_node_data']['uid']] = tab + return tree_data + + class SessionManager(QObject): """Manager for sessions. @@ -474,18 +510,6 @@ class SessionManager(QObject): except ValueError as e: raise SessionError(e) - def _reconstruct_tree_data(self, window_data): - tree_data = {} - root = window_data['treetab_root'] - tree_data[root['uid']] = { - 'children': root['children'], - 'tab': {}, - 'collapsed': False - } - for tab in window_data['tabs']: - tree_data[tab['treetab_node_data']['uid']] = tab - return tree_data - def _load_tree(self, tabbed_browser, tree_data): tree_keys = list(tree_data.keys()) if not tree_keys: @@ -540,7 +564,7 @@ class SessionManager(QObject): load_tree_tabs = 'treetab_root' in win.keys() and \ tabbed_browser.is_treetabbedbrowser if load_tree_tabs: - tree_data = self._reconstruct_tree_data(win) + tree_data = reconstruct_tree_data(win) self._load_tree(tabbed_browser, tree_data) else: for i, tab in enumerate(tabs): diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 70a5fc205..6511bdd96 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -20,6 +20,7 @@ import pytest import pytest_bdd as bdd import qutebrowser +from qutebrowser.misc import sessions from qutebrowser.utils import log, utils, docutils, version from qutebrowser.browser import pdfjs from end2end.fixtures import testprocess @@ -125,14 +126,14 @@ def set_setting_given(quteproc, server, opt, value): @bdd.given(bdd.parsers.parse("I open {path}")) -def open_path_given(quteproc, path): +def open_path_given(quteproc, server, path): """Open a URL. This is available as "Given:" step so it can be used as "Background:". It always opens a new tab, unlike "When I open ..." """ - quteproc.open_path(path, new_tab=True) + open_path(quteproc, server, path, default_kwargs={"new_tab": True}) @bdd.given(bdd.parsers.parse("I run {command}")) @@ -188,7 +189,7 @@ def clear_log_lines(quteproc): @bdd.when(bdd.parsers.parse("I open {path}")) -def open_path(quteproc, server, path): +def open_path(quteproc, server, path, default_kwargs: dict = None): """Open a URL. - If used like "When I open ... in a new tab", the URL is opened in a new @@ -200,56 +201,40 @@ def open_path(quteproc, server, path): path = path.replace('(port)', str(server.port)) path = testutils.substitute_testdata(path) - new_tab = False - related_tab = False - new_bg_tab = False - new_window = False - private = False - as_url = False - wait = True - - related_tab_suffix = ' in a new related tab' - related_background_tab_suffix = ' in a new related background tab' - new_tab_suffix = ' in a new tab' - new_bg_tab_suffix = ' in a new background tab' - new_window_suffix = ' in a new window' - private_suffix = ' in a private window' - do_not_wait_suffix = ' without waiting' - as_url_suffix = ' as a URL' + suffixes = { + "in a new tab": "new_tab", + "in a new related tab": ("new_tab", "related_tab"), + "in a new related background tab": ("new_bg_tab", "related_tab"), + "in a new background tab": "new_bg_tab", + "in a new window": "new_window", + "in a private window": "private", + "without waiting": {"wait": False}, + "as a URL": "as_url", + } + + def update_from_value(value, kwargs): + if isinstance(value, str): + kwargs[value] = True + elif isinstance(value, (tuple, list)): + for i in value: + update_from_value(i, kwargs) + elif isinstance(value, dict): + kwargs.update(value) + kwargs = {} while True: - if path.endswith(new_tab_suffix): - path = path[:-len(new_tab_suffix)] - new_tab = True - elif path.endswith(related_tab_suffix): - path = path[:-len(related_tab_suffix)] - new_tab = True - related_tab = True - elif path.endswith(related_background_tab_suffix): - path = path[:-len(related_background_tab_suffix)] - new_bg_tab = True - related_tab = True - elif path.endswith(new_bg_tab_suffix): - path = path[:-len(new_bg_tab_suffix)] - new_bg_tab = True - elif path.endswith(new_window_suffix): - path = path[:-len(new_window_suffix)] - new_window = True - elif path.endswith(private_suffix): - path = path[:-len(private_suffix)] - private = True - elif path.endswith(as_url_suffix): - path = path[:-len(as_url_suffix)] - as_url = True - elif path.endswith(do_not_wait_suffix): - path = path[:-len(do_not_wait_suffix)] - wait = False + for suffix, value in suffixes.items(): + if path.endswith(suffix): + path = path[:-len(suffix) - 1] + update_from_value(value, kwargs) + break else: break - quteproc.open_path(path, related_tab=related_tab, new_tab=new_tab, - new_bg_tab=new_bg_tab, new_window=new_window, - private=private, as_url=as_url, wait=wait) + if not kwargs and default_kwargs: + kwargs.update(default_kwargs) + + quteproc.open_path(path, **kwargs) @bdd.when(bdd.parsers.parse("I set {opt} to {value}")) @@ -620,55 +605,104 @@ def check_contents_json(quteproc, text): assert actual == expected -@bdd.then(bdd.parsers.parse("the following tabs should be open:\n{tabs}")) -def check_open_tabs(quteproc, request, tabs): - """Check the list of open tabs in the session. +@bdd.then(bdd.parsers.parse("the following tabs should be open:\n{expected_tabs}")) +def check_open_tabs(quteproc, request, expected_tabs): + """Check the list of open tabs in a one window session. This is a lightweight alternative for "The session should look like: ...". - It expects a list of URLs, with an optional "(active)" suffix. + It expects a tree of URLs in the form: + - data/numbers/1.txt + - data/numbers/2.txt (active) + + Where the indentation is optional (but if present the indent should be two + spaces) and the suffix can be one or more of: + + (active) + (pinned) + (collapsed) """ session = quteproc.get_session() + expected_tabs = expected_tabs.splitlines() + assert len(session['windows']) == 1 + window = session['windows'][0] + assert len(window['tabs']) == len(expected_tabs) + active_suffix = ' (active)' pinned_suffix = ' (pinned)' - tabs = tabs.splitlines() - assert len(session['windows']) == 1 - assert len(session['windows'][0]['tabs']) == len(tabs) - - # If we don't have (active) anywhere, don't check it - has_active = any(active_suffix in line for line in tabs) - has_pinned = any(pinned_suffix in line for line in tabs) - - for i, line in enumerate(tabs): - line = line.strip() - assert line.startswith('- ') - line = line[2:] # remove "- " prefix - - active = False - pinned = False - - while line.endswith(active_suffix) or line.endswith(pinned_suffix): - if line.endswith(active_suffix): - # active - line = line[:-len(active_suffix)] - active = True - else: - # pinned - line = line[:-len(pinned_suffix)] - pinned = True - - session_tab = session['windows'][0]['tabs'][i] - current_page = session_tab['history'][-1] - assert current_page['url'] == quteproc.path_to_url(line) - if active: - assert session_tab['active'] - elif has_active: - assert 'active' not in session_tab - - if pinned: - assert current_page['pinned'] - elif has_pinned: - assert not current_page['pinned'] + collapsed_suffix = ' (collapsed)' + # Don't check for states in the session if they aren't in the expected + # text. + has_active = any(active_suffix in line for line in expected_tabs) + has_pinned = any(pinned_suffix in line for line in expected_tabs) + has_collapsed = any(collapsed_suffix in line for line in expected_tabs) + + def tab_to_str(tab, prefix="", collapsed=False): + """Convert a tab from a session file into a one line string.""" + current = [ + entry + for entry in tab["history"] + if entry.get("active") + ][0] + text = f"{prefix}- {current['url']}" + for suffix, state in { + active_suffix: tab.get("active") and has_active, + collapsed_suffix: collapsed and has_collapsed, + pinned_suffix: current["pinned"] and has_pinned, + }.items(): + if state: + text += suffix + return text + + def tree_to_str(node, tree_data, indentation=-1): + """Traverse a tree turning each node into an indented string.""" + tree_node = node.get("treetab_node_data") + if tree_node: # root node doesn't have treetab_node_data + yield tab_to_str( + node, + prefix=" " * indentation, + collapsed=tree_node["collapsed"], + ) + else: + tree_node = node + + for uid in tree_node["children"]: + yield from tree_to_str(tree_data[uid], tree_data, indentation + 1) + + is_tree_tab_window = "treetab_root" in window + if is_tree_tab_window: + tree_data = sessions.reconstruct_tree_data(window) + root = [node for node in tree_data.values() if "treetab_node_data" not in node][0] + actual = list(tree_to_str(root, tree_data)) + else: + actual = [tab_to_str(tab) for tab in window["tabs"]] + + def normalize(line): + """Normalize expected lines to match session lines. + + Turn paths into URLs and sort suffixes. + """ + prefix, rest = line.split("- ", maxsplit=1) + path = rest.split(" ", maxsplit=1) + path[0] = quteproc.path_to_url(path[0]) + if len(path) == 2: + suffixes = path[1].split() + for s in suffixes: + assert s[0] == "(" + assert s[-1] == ")" + path[1] = " ".join(sorted(suffixes)) + return "- ".join((prefix, " ".join(path))) + + expected_tabs = [ + normalize(line) + for line in expected_tabs + ] + # Removed the hyphens from the start of lines so they don't get mixed in + # with the diff markers. + expected_tabs = [line.replace("- ", "") for line in expected_tabs] + actual = [line.replace("- ", "") for line in actual] + for idx, expected in enumerate(expected_tabs): + assert expected == actual[idx] @bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should ' diff --git a/tests/end2end/features/test_treetabs_bdd.py b/tests/end2end/features/test_treetabs_bdd.py new file mode 100644 index 000000000..9cbb315d7 --- /dev/null +++ b/tests/end2end/features/test_treetabs_bdd.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) <mail@qutebrowser.org> +# +# SPDX-License-Identifier: GPL-3.0-or-later + +import pytest_bdd as bdd +bdd.scenarios("treetabs.feature") diff --git a/tests/end2end/features/treetabs.feature b/tests/end2end/features/treetabs.feature new file mode 100644 index 000000000..f32ff3d07 --- /dev/null +++ b/tests/end2end/features/treetabs.feature @@ -0,0 +1,27 @@ +Feature: Tree tab management + Tests for various :tree-tab-* commands. + + Background: + # Open a new tree tab enabled window, close everything else + Given I set tabs.tabs_are_windows to false + And I set tabs.tree_tabs to true + And I open about:blank?starting%20page in a new window + And I clean up open tabs + And I clear the log + + Scenario: :tab-close --recursive + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new related tab + And I open data/numbers/3.txt in a new related tab + And I open data/numbers/4.txt in a new tab + And I run :tab-focus 1 + And I run :tab-close --recursive + Then the following tabs should be open: + - data/numbers/4.txt + + Scenario: Open a child tab + When I open data/numbers/1.txt + And I open data/numbers/2.txt in a new related tab + Then the following tabs should be open: + - data/numbers/1.txt + - data/numbers/2.txt (active) |