# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2021 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qutebrowser is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . """Tests for the webelement utils.""" from typing import TYPE_CHECKING from unittest import mock import collections.abc import operator import itertools import dataclasses import pytest from PyQt5.QtCore import QRect, QPoint, QUrl QWebElement = pytest.importorskip('PyQt5.QtWebKit').QWebElement from qutebrowser.browser import browsertab from qutebrowser.browser.webkit import webkitelem from qutebrowser.misc import objects from qutebrowser.utils import usertypes if TYPE_CHECKING: from helpers import stubs def get_webelem(geometry=None, frame=None, *, null=False, style=None, attributes=None, tagname=None, classes=None, parent=None, js_rect_return=None, zoom_text_only=False): """Factory for WebKitElement objects based on a mock. Args: geometry: The geometry of the QWebElement as QRect. frame: The QWebFrame the element is in. null: Whether the element is null or not. style: A dict with the styleAttributes of the element. attributes: Boolean HTML attributes to be added. tagname: The tag name. classes: HTML classes to be added. js_rect_return: If None, what evaluateJavaScript returns is based on geometry. If set, the return value of evaluateJavaScript. zoom_text_only: Whether zoom.text_only is set in the config """ elem = mock.Mock() elem.isNull.return_value = null elem.geometry.return_value = geometry elem.webFrame.return_value = frame elem.tagName.return_value = tagname elem.toOuterXml.return_value = '' elem.toPlainText.return_value = 'text' elem.parent.return_value = parent if geometry is not None: if frame is None: scroll_x = 0 scroll_y = 0 else: scroll_x = frame.scrollPosition().x() scroll_y = frame.scrollPosition().y() if js_rect_return is None: if frame is None or zoom_text_only: zoom = 1.0 else: zoom = frame.zoomFactor() elem.evaluateJavaScript.return_value = { "length": 1, "0": { "left": (geometry.left() - scroll_x) / zoom, "top": (geometry.top() - scroll_y) / zoom, "right": (geometry.right() - scroll_x) / zoom, "bottom": (geometry.bottom() - scroll_y) / zoom, "width": geometry.width() / zoom, "height": geometry.height() / zoom, } } else: elem.evaluateJavaScript.return_value = js_rect_return attribute_dict = {} if attributes is None: pass elif not isinstance(attributes, collections.abc.Mapping): attribute_dict.update({e: None for e in attributes}) else: attribute_dict.update(attributes) elem.hasAttribute.side_effect = lambda k: k in attribute_dict elem.attribute.side_effect = lambda k: attribute_dict.get(k, '') elem.setAttribute.side_effect = (lambda k, v: operator.setitem(attribute_dict, k, v)) elem.removeAttribute.side_effect = attribute_dict.pop elem.attributeNames.return_value = list(attribute_dict) if classes is not None: elem.classes.return_value = classes.split(' ') else: elem.classes.return_value = [] style_dict = { 'visibility': '', 'display': '', 'foo': 'bar', 'opacity': '100' } if style is not None: style_dict.update(style) def _style_property(name, strategy): """Helper function to act as styleProperty method.""" if strategy != QWebElement.ComputedStyle: raise ValueError("styleProperty called with strategy != " "ComputedStyle ({})!".format(strategy)) return style_dict[name] elem.styleProperty.side_effect = _style_property tab = mock.Mock(autospec=browsertab.AbstractTab) tab.is_deleted.return_value = False wrapped = webkitelem.WebKitElement(elem, tab=tab) return wrapped class SelectionAndFilterTests: """Generator for tests for TestSelectionsAndFilters.""" # A mapping of an HTML element to a list of groups where the selectors # (after filtering) should match. # # Based on this, test cases are generated to make sure it matches those # groups and not the others. TESTS = [ ('', []), ('', []), ('', ['url']), ('', ['url']), ('', ['all']), ('', ['all', 'links', 'url']), ('', ['all', 'links', 'url']), ('', ['all']), ('', ['all', 'links', 'url']), ('', ['all']), ('', ['all', 'links', 'url']), ('