1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
import itertools
import sys
import pytest
from pydantic import BaseModel
from app_model.types import (
KeyBinding,
KeyBindingRule,
KeyCode,
KeyMod,
SimpleKeyBinding,
)
from app_model.types._constants import OperatingSystem
from app_model.types._keys import KeyChord, KeyCombo, StandardKeyBinding
MAC = sys.platform == "darwin"
@pytest.mark.parametrize("use_symbols", [True, False])
@pytest.mark.parametrize(
("os", "joinchar", "expected_use_symbols", "expected_non_use_symbols"),
[
(OperatingSystem.WINDOWS, "+", "⊞+A", "Win+A"),
(OperatingSystem.LINUX, "-", "Super-A", "Super-A"),
(OperatingSystem.MACOS, "", "⌘A", "CmdA"),
],
)
def test_simple_keybinding_to_text(
use_symbols: bool,
os: OperatingSystem,
joinchar: str,
expected_use_symbols: str,
expected_non_use_symbols: str,
) -> None:
kb = SimpleKeyBinding.from_str("Meta+A")
expected = expected_non_use_symbols
if use_symbols:
expected = expected_use_symbols
assert kb.to_text(os=os, use_symbols=use_symbols, joinchar=joinchar) == expected
@pytest.mark.parametrize("use_symbols", [True, False])
@pytest.mark.parametrize(
("os", "joinchar", "expected_use_symbols", "expected_non_use_symbols"),
[
(
OperatingSystem.WINDOWS,
"+",
"Ctrl+A ⇧+[ Alt+/ ⊞+9",
"Ctrl+A Shift+[ Alt+/ Win+9",
),
(
OperatingSystem.LINUX,
"-",
"Ctrl-A ⇧-[ Alt-/ Super-9",
"Ctrl-A Shift-[ Alt-/ Super-9",
),
(OperatingSystem.MACOS, "", "⌃A ⇧[ ⌥/ ⌘9", "ControlA Shift[ Option/ Cmd9"),
],
)
def test_keybinding_to_text(
use_symbols: bool,
os: OperatingSystem,
joinchar: str,
expected_use_symbols: str,
expected_non_use_symbols: str,
) -> None:
kb = KeyBinding.from_str("Ctrl+A Shift+[ Alt+/ Meta+9")
expected = expected_non_use_symbols
if use_symbols:
expected = expected_use_symbols
assert kb.to_text(os=os, use_symbols=use_symbols, joinchar=joinchar) == expected
@pytest.mark.parametrize("key", list("ADgf`]/,"))
@pytest.mark.parametrize("mod", ["ctrl", "shift", "alt", "meta", None])
def test_simple_keybinding_single_mod(mod: str, key: str) -> None:
_mod = f"{mod}+" if mod else ""
kb = SimpleKeyBinding.from_str(f"{_mod}{key}")
assert str(kb).lower() == f"{_mod}{key}".lower()
assert not kb.is_modifier_key()
# we can compare it with another SimpleKeyBinding
# using validate method just for test coverage... will pass to from_str
assert kb == SimpleKeyBinding._parse_input(f"{_mod}{key}")
# or with a string
assert kb == f"{_mod}{key}"
assert kb != ["A", "B"] # check type error during comparison
# round trip to int
assert isinstance(kb.to_int(), KeyCombo)
# using validate method just for test coverage... will pass to from_int
assert SimpleKeyBinding._parse_input(int(kb)) == kb
assert SimpleKeyBinding._parse_input(kb) == kb
# first part of a Keybinding is a simple keybinding
as_full_kb = KeyBinding.validate(kb)
assert as_full_kb.part0 == kb
assert KeyBinding.validate(int(kb)).part0 == kb
assert int(as_full_kb) == int(kb)
def test_simple_keybinding_multi_mod() -> None:
# here we're also testing that cmd and win get cast to 'KeyMod.CtrlCmd'
kb = SimpleKeyBinding.from_str("cmd+shift+A")
assert not kb.is_modifier_key()
assert int(kb) & KeyMod.CtrlCmd | KeyMod.Shift
kb = SimpleKeyBinding.from_str("win+shift+A")
assert not kb.is_modifier_key()
assert int(kb) & KeyMod.CtrlCmd | KeyMod.Shift
kb = SimpleKeyBinding.from_str("win") # just a modifier
assert kb.is_modifier_key()
controls = ["ctrl", "control", "ctl", "⌃", "^"]
shifts = ["shift", "⇧"]
alts = ["alt", "opt", "option", "⌥"]
metas = ["meta", "super", "cmd", "command", "⌘", "win", "windows", "⊞"]
delimiters = ["+", "-"]
key = ["A"]
combos = [
delim.join(x)
for delim, *x in itertools.product(delimiters, controls, shifts, alts, metas, key)
]
@pytest.mark.parametrize("key", combos)
def test_keybinding_parser(key: str) -> None:
# Test all the different ways to write the modifiers
assert str(KeyBinding.from_str(key)) == "Ctrl+Shift+Alt+Meta+A"
def test_chord_keybinding() -> None:
kb = KeyBinding.from_str("Shift+A Cmd+9")
assert len(kb) == 2
assert kb != "Shift+A Cmd+9" # comparison with string considered anti-pattern
assert kb == KeyBinding.from_str("Shift+A Cmd+9")
assert kb.part0 == SimpleKeyBinding(shift=True, key=KeyCode.KeyA)
assert kb.part0 == "Shift+A"
assert str(kb) in repr(kb)
# round trip to int
assert isinstance(kb.to_int(), KeyChord)
# using validate method just for test coverage... will pass to from_int
assert KeyBinding.validate(int(kb)) == kb
assert KeyBinding.validate(kb) == kb
def test_in_dict() -> None:
a = SimpleKeyBinding.from_str("Shift+A")
b = KeyBinding.from_str("Shift+B")
try:
kbs = {
a: 0,
b: 1,
}
except TypeError as e:
if str(e).startswith("unhashable type"):
pytest.fail(f"keybinds not hashable: {e}")
else:
raise e
assert kbs[a] == 0
assert kbs[b] == 1
new_a = KeyBinding.from_str("Shift+A")
with pytest.raises(KeyError):
kbs[new_a]
def test_in_model() -> None:
class M(BaseModel):
key: KeyBinding
m = M(key="Shift+A B")
assert m.model_dump_json() == '{"key":"Shift+A B"}'
def test_standard_keybindings() -> None:
class M(BaseModel):
key: KeyBindingRule
m = M(key=StandardKeyBinding.Copy)
assert m.key.primary == KeyMod.CtrlCmd | KeyCode.KeyC
|