toga/cocoa/tests_backend/app.py
2024-04-30 11:59:30 +08:00

231 lines
7.8 KiB
Python

from pathlib import Path
import PIL.Image
from rubicon.objc import NSPoint, ObjCClass, objc_id, send_message
import toga
from toga_cocoa.keys import cocoa_key, toga_key
from toga_cocoa.libs import (
NSApplication,
NSEvent,
NSEventModifierFlagShift,
NSEventType,
NSWindow,
)
from .probe import BaseProbe
NSPanel = ObjCClass("NSPanel")
class AppProbe(BaseProbe):
supports_key = True
supports_key_mod3 = True
def __init__(self, app):
super().__init__()
self.app = app
# Prevents erroneous test fails from secondary windows opening as tabs
NSWindow.allowsAutomaticWindowTabbing = False
assert isinstance(self.app._impl.native, NSApplication)
@property
def config_path(self):
return Path.home() / "Library/Preferences/org.beeware.toga.testbed"
@property
def data_path(self):
return Path.home() / "Library/Application Support/org.beeware.toga.testbed"
@property
def cache_path(self):
return Path.home() / "Library/Caches/org.beeware.toga.testbed"
@property
def logs_path(self):
return Path.home() / "Library/Logs/org.beeware.toga.testbed"
@property
def is_cursor_visible(self):
# There's no API level mechanism to detect cursor visibility;
# fall back to the implementation's proxy variable.
return self.app._impl._cursor_visible
def is_full_screen(self, window):
return window.content._impl.native.isInFullScreenMode()
def content_size(self, window):
return (
window.content._impl.native.frame.size.width,
window.content._impl.native.frame.size.height,
)
def assert_app_icon(self, icon):
# We have no real way to check we've got the right icon; use pixel peeping as a
# guess. Construct a PIL image from the current icon.
img = toga.Image(
NSApplication.sharedApplication.applicationIconImage
).as_format(PIL.Image.Image)
# Due to icon resizing and colorspace issues, the exact pixel colors are
# inconsistent, so multiple values must be provided for test purposes.
if icon:
# The explicit alt icon has blue background, with green at a point 1/3 into
# the image
assert img.getpixel((5, 5)) in {
(205, 226, 243, 255),
(211, 226, 243, 255),
(211, 230, 245, 255),
}
mid_color = img.getpixel((img.size[0] // 3, img.size[1] // 3))
assert mid_color in {
(0, 204, 9, 255),
(6, 204, 8, 255),
(14, 197, 8, 255),
(105, 192, 32, 255),
}
else:
# The default icon is transparent background, and brown in the center.
assert img.getpixel((5, 5))[3] == 0
mid_color = img.getpixel((img.size[0] // 2, img.size[1] // 2))
assert mid_color in {
(130, 100, 57, 255),
(130, 109, 66, 255),
(138, 107, 64, 255),
(138, 108, 64, 255),
(149, 119, 73, 255),
}
def _menu_item(self, path):
main_menu = self.app._impl.native.mainMenu
menu = main_menu
orig_path = path.copy()
while True:
label, path = path[0], path[1:]
item = menu.itemWithTitle(label)
if item is None:
raise AssertionError(
f"Menu {' > '.join(orig_path)} not found; "
f"no item named {label!r}; options are: "
+ ",".join(f"{str(item.title)!r}" for item in menu.itemArray)
)
if path:
menu = item.submenu
if menu is None:
raise AssertionError(
f"Menu {' > '.join(orig_path)} not found; "
f"{str(item.title)} does not have a submenu"
)
else:
# No more path segments; we've found the full path.
break
return item
def _activate_menu_item(self, path):
item = self._menu_item(path)
send_message(
self.app._impl.native.delegate,
item.action,
item,
restype=None,
argtypes=[objc_id],
)
def activate_menu_exit(self):
self._activate_menu_item(["*", "Quit Toga Testbed"])
def activate_menu_about(self):
self._activate_menu_item(["*", "About Toga Testbed"])
async def close_about_dialog(self):
about_dialog = self.app._impl.native.keyWindow
if isinstance(about_dialog, NSPanel):
about_dialog.close()
def activate_menu_visit_homepage(self):
self._activate_menu_item(["Help", "Visit homepage"])
def assert_system_menus(self):
self.assert_menu_item(["*", "About Toga Testbed"], enabled=True)
self.assert_menu_item(["*", "Settings\u2026"], enabled=False)
self.assert_menu_item(["*", "Hide Toga Testbed"], enabled=True)
self.assert_menu_item(["*", "Hide Others"], enabled=True)
self.assert_menu_item(["*", "Show All"], enabled=True)
self.assert_menu_item(["*", "Quit Toga Testbed"], enabled=True)
self.assert_menu_item(["File", "Close"], enabled=True)
self.assert_menu_item(["File", "Close All"], enabled=True)
self.assert_menu_item(["Edit", "Undo"], enabled=True)
self.assert_menu_item(["Edit", "Redo"], enabled=True)
self.assert_menu_item(["Edit", "Cut"], enabled=True)
self.assert_menu_item(["Edit", "Copy"], enabled=True)
self.assert_menu_item(["Edit", "Paste"], enabled=True)
self.assert_menu_item(["Edit", "Paste and Match Style"], enabled=True)
self.assert_menu_item(["Edit", "Delete"], enabled=True)
self.assert_menu_item(["Edit", "Select All"], enabled=True)
self.assert_menu_item(["Window", "Minimize"], enabled=True)
self.assert_menu_item(["Help", "Visit homepage"], enabled=True)
def activate_menu_close_window(self):
self._activate_menu_item(["File", "Close"])
def activate_menu_close_all_windows(self):
self._activate_menu_item(["File", "Close All"])
def activate_menu_minimize(self):
self._activate_menu_item(["Window", "Minimize"])
def assert_menu_item(self, path, enabled):
item = self._menu_item(path)
assert item.isEnabled() == enabled
def assert_menu_order(self, path, expected):
menu = self._menu_item(path).submenu
assert menu.numberOfItems == len(expected)
for item, title in zip(menu.itemArray, expected):
if title == "---":
assert item.isSeparatorItem
else:
assert item.title == title
def keystroke(self, combination):
key, modifiers = cocoa_key(combination)
key_code = {
"a": 0,
"A": 0,
"1": 18,
"!": 18,
"'": 39,
";": 41,
"|": 42,
" ": 49,
chr(0xF708): 96, # F5
chr(0x2196): 115, # Home
# This only works because we're *not* testing the numeric 5
"5": 87,
}[key]
# Add the shift modifier to disambiguate shifted keys from non-shifted
if key in {"!", "|"}:
modifiers |= NSEventModifierFlagShift
event = NSEvent.keyEventWithType(
NSEventType.KeyDown,
location=NSPoint(0, 0), # key presses don't have a location.
modifierFlags=modifiers,
timestamp=0,
windowNumber=self.app.main_window._impl.native.windowNumber,
context=None,
characters="?",
charactersIgnoringModifiers="?",
isARepeat=False,
keyCode=key_code,
)
return toga_key(event)