1# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: 2 3# Copyright 2015-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org> 4# Copyright 2015-2018 Daniel Schadt 5# 6# This file is part of qutebrowser. 7# 8# qutebrowser is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 3 of the License, or 11# (at your option) any later version. 12# 13# qutebrowser is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. 20 21"""Test the built-in directory browser.""" 22 23import pathlib 24import dataclasses 25from typing import List 26 27import pytest 28import bs4 29 30from PyQt5.QtCore import QUrl 31 32from qutebrowser.utils import urlutils 33from helpers import testutils 34 35 36pytestmark = pytest.mark.qtwebengine_skip("Title is empty when parsing for " 37 "some reason?") 38 39 40class DirLayout: 41 42 """Provide a fake directory layout to test dirbrowser.""" 43 44 LAYOUT = [ 45 'folder0/file00', 46 'folder0/file01', 47 'folder1/folder10/file100', 48 'folder1/file10', 49 'folder1/file11', 50 'file0', 51 'file1', 52 ] 53 54 @classmethod 55 def layout_folders(cls): 56 """Return all folders in the root directory of the layout.""" 57 folders = set() 58 for path in cls.LAYOUT: 59 parts = path.split('/') 60 if len(parts) > 1: 61 folders.add(parts[0]) 62 folders = list(folders) 63 folders.sort() 64 return folders 65 66 @classmethod 67 def get_folder_content(cls, name): 68 """Return (folders, files) for the given folder in the root dir.""" 69 folders = set() 70 files = set() 71 for path in cls.LAYOUT: 72 if not path.startswith(name + '/'): 73 continue 74 parts = path.split('/') 75 if len(parts) == 2: 76 files.add(parts[1]) 77 else: 78 folders.add(parts[1]) 79 folders = list(folders) 80 folders.sort() 81 files = list(files) 82 files.sort() 83 return (folders, files) 84 85 def __init__(self, factory): 86 self._factory = factory 87 self.base = factory.getbasetemp() 88 self.layout = factory.mktemp('layout') 89 self._mklayout() 90 91 def _mklayout(self): 92 for filename in self.LAYOUT: 93 path = self.layout / filename 94 path.parent.mkdir(exist_ok=True, parents=True) 95 path.touch() 96 97 def file_url(self): 98 """Return a file:// link to the directory.""" 99 return urlutils.file_url(str(self.layout)) 100 101 def path(self, *parts): 102 """Return the path to the given file inside the layout folder.""" 103 return self.layout.joinpath(*parts) 104 105 def base_path(self): 106 """Return the path of the base temporary folder.""" 107 return self.base 108 109 110@dataclasses.dataclass 111class Parsed: 112 113 path: str 114 parent: str 115 folders: List[str] 116 files: List[str] 117 118 119@dataclasses.dataclass 120class Item: 121 122 path: str 123 link: str 124 text: str 125 126 127def parse(quteproc): 128 """Parse the dirbrowser content from the given quteproc. 129 130 Args: 131 quteproc: The quteproc fixture. 132 """ 133 html = quteproc.get_content(plain=False) 134 soup = bs4.BeautifulSoup(html, 'html.parser') 135 136 with testutils.ignore_bs4_warning(): 137 print(soup.prettify()) 138 139 title_prefix = 'Browse directory: ' 140 # Strip off the title prefix to obtain the path of the folder that 141 # we're browsing 142 path = pathlib.Path(soup.title.string[len(title_prefix):]) 143 144 container = soup('div', id='dirbrowserContainer')[0] 145 146 parent_elem = container('ul', class_='parent') 147 if not parent_elem: 148 parent = None 149 else: 150 parent = pathlib.Path(QUrl(parent_elem[0].li.a['href']).toLocalFile()) 151 152 folders = [] 153 files = [] 154 155 for css_class, list_ in [('folders', folders), ('files', files)]: 156 for li in container('ul', class_=css_class)[0]('li'): 157 item_path = pathlib.Path(QUrl(li.a['href']).toLocalFile()) 158 list_.append(Item(path=item_path, link=li.a['href'], 159 text=str(li.a.string))) 160 161 return Parsed(path=path, parent=parent, folders=folders, files=files) 162 163 164@pytest.fixture(scope='module') 165def dir_layout(tmp_path_factory): 166 return DirLayout(tmp_path_factory) 167 168 169def test_parent_folder(dir_layout, quteproc): 170 quteproc.open_url(dir_layout.file_url()) 171 page = parse(quteproc) 172 assert page.parent == dir_layout.base_path() 173 174 175def test_parent_with_slash(dir_layout, quteproc): 176 """Test the parent link with a URL that has a trailing slash.""" 177 quteproc.open_url(dir_layout.file_url() + '/') 178 page = parse(quteproc) 179 assert page.parent == dir_layout.base_path() 180 181 182def test_parent_in_root_dir(dir_layout, quteproc): 183 # This actually works on windows 184 urlstr = urlutils.file_url(str(pathlib.Path('/'))) 185 quteproc.open_url(urlstr) 186 page = parse(quteproc) 187 assert page.parent is None 188 189 190def test_enter_folder_smoke(dir_layout, quteproc): 191 quteproc.open_url(dir_layout.file_url()) 192 quteproc.send_cmd(':hint all normal') 193 # a is the parent link, s is the first listed folder/file 194 quteproc.send_cmd(':hint-follow s') 195 expected_url = urlutils.file_url(str(dir_layout.path('folder0'))) 196 quteproc.wait_for_load_finished_url(expected_url) 197 page = parse(quteproc) 198 assert page.path == dir_layout.path('folder0') 199 200 201@pytest.mark.parametrize('folder', DirLayout.layout_folders()) 202def test_enter_folder(dir_layout, quteproc, folder): 203 quteproc.open_url(dir_layout.file_url()) 204 quteproc.click_element_by_text(text=folder) 205 expected_url = urlutils.file_url(str(dir_layout.path(folder))) 206 quteproc.wait_for_load_finished_url(expected_url) 207 page = parse(quteproc) 208 assert page.path == dir_layout.path(folder) 209 assert page.parent == dir_layout.path() 210 folders, files = DirLayout.get_folder_content(folder) 211 foldernames = [item.text for item in page.folders] 212 assert foldernames == folders 213 filenames = [item.text for item in page.files] 214 assert filenames == files 215