Skip to content

Commit 25796cd

Browse files
authored
fix: punctuation map tab/click blur due to rerender (related bd223d0) (#374)
1 parent b3c4d6e commit 25796cd

8 files changed

Lines changed: 128 additions & 22 deletions

File tree

appium/conftest.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,21 @@ def check_appium_server() -> bool:
3030
return False
3131

3232

33-
def launch_app(driver: WebDriver, session_config_dir: str, test_name: str) -> str:
33+
def launch_app(driver: WebDriver, session_base_dir: str, test_name: str) -> str:
3434
"""Launch the test app."""
35-
config_home = os.path.join(session_config_dir, test_name)
36-
os.makedirs(config_home, exist_ok=True)
35+
config_home = os.path.join(session_base_dir, test_name, "config")
36+
os.makedirs(config_home)
37+
data_home = os.path.join(session_base_dir, test_name, "data")
38+
os.makedirs(data_home)
39+
40+
# Too many entries. Reduce to accelerate.
41+
if test_name == "test_punctuation_map":
42+
punctuation = os.path.join(data_home, "punctuation")
43+
mb = os.path.join(punctuation, "punc.mb.zh_CN")
44+
os.makedirs(punctuation)
45+
with open(mb, "w") as f:
46+
f.write(". 。\n, ,")
47+
3748
profile_src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "profile")
3849
shutil.copy2(profile_src, os.path.join(config_home, "profile"))
3950
app_path = os.path.join(
@@ -46,6 +57,7 @@ def launch_app(driver: WebDriver, session_config_dir: str, test_name: str) -> st
4657
"arguments": [],
4758
"environment": {
4859
"FCITX_CONFIG_HOME": config_home,
60+
"FCITX_DATA_HOME": data_home,
4961
},
5062
},
5163
)
@@ -95,7 +107,7 @@ def driver(appium_server: str) -> Generator[WebDriver, None, None]:
95107

96108

97109
@pytest.fixture(scope="session")
98-
def session_config_dir() -> Generator[str, None, None]:
110+
def session_base_dir() -> Generator[str, None, None]:
99111
"""Create a unique base config directory for this test session."""
100112
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
101113
base_dir = os.path.join(project_root, "build/appium", timestamp)
@@ -107,11 +119,11 @@ def session_config_dir() -> Generator[str, None, None]:
107119
def app(
108120
request: pytest.FixtureRequest,
109121
driver: WebDriver,
110-
session_config_dir: str,
122+
session_base_dir: str,
111123
) -> Generator[str, None, None]:
112124
"""Manage test app lifecycle for a single test case."""
113125
# Launch fresh app
114-
config_home = launch_app(driver, session_config_dir, request.node.name)
126+
config_home = launch_app(driver, session_base_dir, request.node.name)
115127
yield config_home
116128
# Clean up after test
117129
terminate_app(driver)

appium/test_entry.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import os
2+
3+
from appium.webdriver.webdriver import WebDriver
4+
from util.button import get_undo_redo
5+
from util.key import press
6+
from util.message import CHANGE_NOT_SAVED, UI_NOT_UPDATED, UI_WRONGLY_UPDATED
7+
from util.string import get_string_value, is_focused
8+
from util.window import (
9+
find_element_by_id,
10+
find_elements_by_id,
11+
open_input_method_config,
12+
scroll,
13+
)
14+
15+
KEY = "Key"
16+
MAPPING = "Mapping"
17+
ALT_MAPPING = "AltMapping"
18+
19+
20+
def test_punctuation_map(driver: WebDriver, app: str) -> None:
21+
open_input_method_config(driver, "pinyin")
22+
scroll(
23+
find_element_by_id(driver, "detailScrollView"),
24+
find_element_by_id(driver, "Punctuation"),
25+
find_element_by_id(driver, "PageSize"),
26+
)
27+
punctuation = find_element_by_id(driver, "Punctuation")
28+
punctuation.click()
29+
30+
def read_config_value() -> str:
31+
punc_path = os.path.join(app, r"../data/punctuation/punc.mb.zh_CN")
32+
with open(punc_path, "r") as f:
33+
return f.readline().strip()
34+
35+
undo, _ = get_undo_redo(driver)
36+
key = find_elements_by_id(driver, KEY)[0]
37+
assert is_focused(key), UI_NOT_UPDATED
38+
39+
key.send_keys("*")
40+
press(driver, ["\t"])
41+
mapping = find_elements_by_id(driver, MAPPING)[0]
42+
assert is_focused(mapping), UI_WRONGLY_UPDATED
43+
# Shouldn't trigger rerender so reuse key.
44+
assert get_string_value(key) == "*", UI_WRONGLY_UPDATED
45+
assert read_config_value() == "* 。", CHANGE_NOT_SAVED
46+
47+
mapping.send_keys(r"\times")
48+
alt_mapping = find_elements_by_id(driver, ALT_MAPPING)[0]
49+
alt_mapping.click()
50+
assert get_string_value(key) == "*", UI_WRONGLY_UPDATED
51+
assert get_string_value(mapping) == r"\times", UI_WRONGLY_UPDATED
52+
assert read_config_value() == r"* \times", CHANGE_NOT_SAVED
53+
assert is_focused(alt_mapping), UI_WRONGLY_UPDATED
54+
55+
alt_mapping.send_keys(r"\dot")
56+
press(driver, ["\n"])
57+
assert get_string_value(key) == "*", UI_WRONGLY_UPDATED
58+
assert get_string_value(mapping) == r"\times", UI_WRONGLY_UPDATED
59+
assert get_string_value(alt_mapping) == r"\dot", UI_WRONGLY_UPDATED
60+
assert read_config_value() == r"* \times \dot", CHANGE_NOT_SAVED
61+
62+
undo.click()
63+
assert get_string_value(find_elements_by_id(driver, KEY)[0]) == "*", (
64+
UI_WRONGLY_UPDATED
65+
)
66+
assert get_string_value(find_elements_by_id(driver, MAPPING)[0]) == r"\times", (
67+
UI_WRONGLY_UPDATED
68+
)
69+
assert get_string_value(find_elements_by_id(driver, ALT_MAPPING)[0]) == "", (
70+
UI_NOT_UPDATED
71+
)
72+
assert read_config_value() == r"* \times", CHANGE_NOT_SAVED

appium/test_group.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
UI_NOT_UPDATED,
88
UI_WRONGLY_UPDATED,
99
)
10-
from util.window import find_element_by_id, open_input_method_config, reset_option
10+
from util.window import (
11+
find_element_by_id,
12+
open_input_method_config,
13+
reset_option,
14+
scroll,
15+
)
1116

1217
SWITCH_IDS = ["VAsQuickphrase", "VE_UE", "NG_GN"]
1318

@@ -23,16 +28,10 @@ def read_config_values() -> list[str]:
2328
cfg["Fuzzy"][SWITCH_IDS[2]],
2429
]
2530

26-
page_size = find_element_by_id(driver, "PageSize")
27-
scroll = find_element_by_id(driver, "detailScrollView")
28-
delta_y = page_size.rect["y"] - find_element_by_id(driver, SWITCH_IDS[0]).rect["y"]
29-
driver.execute_script(
30-
"macos: scroll",
31-
{
32-
"elementId": scroll.id,
33-
"deltaX": 0,
34-
"deltaY": delta_y,
35-
},
31+
scroll(
32+
find_element_by_id(driver, "detailScrollView"),
33+
find_element_by_id(driver, SWITCH_IDS[0]),
34+
find_element_by_id(driver, "PageSize"),
3635
)
3736

3837
for switch_id in SWITCH_IDS:

appium/util/button.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from appium.webdriver.webdriver import WebDriver
22
from selenium.webdriver.remote.webelement import WebElement
33
from util.message import BUTTON_SHOULD_BE_DISABLED
4-
from util.window import find_element_by_id
4+
from util.window import find_elements_by_id
55

66

77
def get_undo_redo(driver: WebDriver) -> tuple[WebElement, WebElement]:
88
"""Get undo and redo buttons, asserting they are initially disabled."""
9-
undo = find_element_by_id(driver, "arrow.uturn.left")
9+
undo = find_elements_by_id(driver, "arrow.uturn.left")[0]
1010
assert undo.is_enabled() is False, BUTTON_SHOULD_BE_DISABLED
1111

12-
redo = find_element_by_id(driver, "arrow.uturn.right")
12+
redo = find_elements_by_id(driver, "arrow.uturn.right")[0]
1313
assert redo.is_enabled() is False, BUTTON_SHOULD_BE_DISABLED
1414

1515
return undo, redo

appium/util/string.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@
44
def get_string_value(element: WebElement) -> str:
55
"""Get the current string value from a text field."""
66
return element.get_attribute("value")
7+
8+
9+
def is_focused(element: WebElement) -> bool:
10+
"""Check if the element is focused."""
11+
return element.get_attribute("focused") == "true"

appium/util/window.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ def open_input_method_config(driver: WebDriver, im: str):
3838
find_element_by_id(driver, im).click()
3939

4040

41+
def scroll(container: WebElement, target: WebElement, first: WebElement):
42+
"""Scroll the container to show the target element relative to the first element."""
43+
delta_y = first.rect["y"] - target.rect["y"]
44+
container.parent.execute_script(
45+
"macos: scroll",
46+
{
47+
"elementId": container.id,
48+
"deltaX": 0,
49+
"deltaY": delta_y,
50+
},
51+
)
52+
53+
4154
def reset_option(driver: WebDriver, option_id: str):
4255
label = find_element_by_id(driver, f"{option_id}_label")
4356
driver.execute_script(

src/config/ExternalView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct ExternalView: OptionViewProtocol {
9595
} label: {
9696
Image(systemName: "gear")
9797
}
98+
.accessibilityIdentifier(option ?? "")
9899
.sheet(isPresented: $showDialog) {
99100
VStack {
100101
ScrollView([.vertical]) { // ScrollView is useful for punctuation map.

src/config/ListView.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,12 @@ struct ListView: OptionViewProtocol {
114114
.accessibilityIdentifier("\(optionId)_plus")
115115
}
116116
.onChange(of: value as? NSDictionary) { newValue in
117-
Task {
118-
list = deserialize(newValue ?? [:])
117+
let newList = deserialize(newValue ?? [:])
118+
// Avoid reset focus on press tab for punctuation map.
119+
if "\(serialize(list))" != "\(serialize(newList))" {
120+
Task {
121+
list = newList
122+
}
119123
}
120124
}
121125
}

0 commit comments

Comments
 (0)