110 lines
5.1 KiB
Python
110 lines
5.1 KiB
Python
"""Display geometry and native UI metrics without real accounts or listeners."""
|
|
import os
|
|
os.environ.setdefault('QT_QPA_PLATFORM', 'offscreen')
|
|
from types import SimpleNamespace
|
|
from unittest import TestCase, mock
|
|
from PySide6.QtCore import QRect
|
|
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout
|
|
import wechat_gui_qt as ui
|
|
|
|
|
|
class DisplayHost(QMainWindow):
|
|
_set_initial_geometry = ui.MainWindow._set_initial_geometry
|
|
_display_metrics_changed = ui.MainWindow._display_metrics_changed
|
|
_screen_changed = ui.MainWindow._screen_changed
|
|
_apply_window_ui_scale = ui.MainWindow._apply_window_ui_scale
|
|
|
|
def __init__(self, area):
|
|
super().__init__()
|
|
self.test_screen = SimpleNamespace(availableGeometry=lambda: area,
|
|
availableGeometryChanged=mock.Mock(), logicalDotsPerInchChanged=mock.Mock())
|
|
self._scale_timer = mock.Mock()
|
|
self.runtime_settings = {'scale_ratio': '自动'}
|
|
|
|
def screen(self):
|
|
return self.test_screen
|
|
|
|
|
|
class DisplayTest(TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.app = QApplication.instance() or QApplication([])
|
|
|
|
def tearDown(self):
|
|
ui.sync_ui_scale(1630, 920, factor=1)
|
|
|
|
def test_initial_window_stays_inside_small_and_offset_displays(self):
|
|
for area in [QRect(0,0,960,540), QRect(-1280,0,1280,680), QRect(1920,0,640,360), QRect(0,0,320,240)]:
|
|
with self.subTest(area=area):
|
|
host=DisplayHost(area)
|
|
host._set_initial_geometry()
|
|
self.assertTrue(area.contains(host.geometry()), (area,host.geometry()))
|
|
host.deleteLater()
|
|
|
|
def test_cross_monitor_clamps_to_current_available_geometry(self):
|
|
area=QRect(-960,100,960,500);host=DisplayHost(area)
|
|
host.setGeometry(0,0,1630,920)
|
|
host.console_shell=SimpleNamespace(page_ready=True,page=lambda:SimpleNamespace(runJavaScript=mock.Mock()))
|
|
host._display_metrics_changed()
|
|
self.assertTrue(area.contains(host.geometry()))
|
|
self.assertLessEqual(host.minimumWidth(),area.width())
|
|
self.assertLessEqual(host.minimumHeight(),area.height())
|
|
host._scale_timer.start.assert_called_once()
|
|
host.deleteLater()
|
|
|
|
def test_screen_notifications_are_not_duplicated(self):
|
|
host=DisplayHost(QRect(0,0,960,540));first=host.screen()
|
|
with mock.patch.object(ui.QTimer,'singleShot'):
|
|
host._screen_changed(first);host._screen_changed(first)
|
|
first.availableGeometryChanged.connect.assert_called_once()
|
|
first.logicalDotsPerInchChanged.connect.assert_called_once()
|
|
second=SimpleNamespace(availableGeometryChanged=mock.Mock(),logicalDotsPerInchChanged=mock.Mock())
|
|
host._screen_changed(second)
|
|
first.availableGeometryChanged.disconnect.assert_called_once()
|
|
second.availableGeometryChanged.connect.assert_called_once()
|
|
host.deleteLater()
|
|
|
|
def test_normal_window_resize_does_not_reduce_font_size(self):
|
|
host=DisplayHost(QRect(0,0,1920,1080))
|
|
for width,height in [(1630,920),(960,500),(640,330)]:
|
|
host.resize(width,height);host._apply_window_ui_scale()
|
|
self.assertEqual(ui.ui_scale_factor(),1)
|
|
host.runtime_settings['scale_ratio']='300%'
|
|
host._apply_window_ui_scale();self.assertEqual(ui.ui_scale_factor(),3)
|
|
host.deleteLater()
|
|
|
|
def test_native_dimensions_font_and_spacing_scale_together_without_compounding(self):
|
|
ui.sync_ui_scale(1630,920,factor=1)
|
|
panel=QWidget();layout=QVBoxLayout(panel);layout.setContentsMargins(10,10,10,10);layout.setSpacing(8)
|
|
button=QPushButton('测试',panel);button.setFixedSize(120,40);button.setStyleSheet('font-size:14px;')
|
|
layout.addWidget(button)
|
|
for scale in (2,3,1,2,1):
|
|
ui.sync_ui_scale(640,330,panel,factor=scale)
|
|
self.assertEqual(button.width(),120*scale)
|
|
self.assertEqual(button.height(),40*scale)
|
|
self.assertEqual(layout.spacing(),8*scale)
|
|
self.assertEqual(layout.contentsMargins().left(),10*scale)
|
|
self.assertIn(f'font-size:{14*scale}px',button.styleSheet())
|
|
panel.deleteLater()
|
|
|
|
def test_automatic_and_invalid_preferences_follow_system(self):
|
|
for preference in ('自动', '', None, 'nan', '999%', '2'):
|
|
self.assertEqual(ui.interface_scale(preference),1)
|
|
self.assertEqual(ui.interface_scale('125%'),1.25)
|
|
self.assertEqual(ui.interface_scale('200%'),2)
|
|
self.assertEqual(ui.interface_scale('300%'),3)
|
|
|
|
def test_saved_preference_does_not_set_process_dpi_multiplier(self):
|
|
import inspect
|
|
self.assertNotIn('QT_SCALE_FACTOR',inspect.getsource(ui))
|
|
|
|
def test_monitor_removal_tolerates_a_deleted_screen_object(self):
|
|
class DeletedScreen:
|
|
def __getattr__(self, name):
|
|
raise RuntimeError("Internal C++ object already deleted")
|
|
host=DisplayHost(QRect(0,0,960,540));host._watched_screen=DeletedScreen()
|
|
with mock.patch.object(ui.QTimer,'singleShot'):
|
|
host._screen_changed(host.screen())
|
|
self.assertIs(host._watched_screen,host.screen())
|
|
host.deleteLater()
|