release: public alpha 0.6.0 baseline
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
import unittest
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
from PIL import Image
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
MODULE_PATH = PROJECT_ROOT / "wechat_adaptive_gui_bridge.py"
|
||||
SPEC = importlib.util.spec_from_file_location("wechat_adaptive_gui_bridge", MODULE_PATH)
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC.loader is not None
|
||||
sys.modules[SPEC.name] = MODULE
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
class CoordinateTests(unittest.TestCase):
|
||||
def test_baseline_coordinates_are_unchanged(self):
|
||||
self.assertEqual(
|
||||
MODULE.scaled_point(
|
||||
MODULE.BASE_DRAFT_POINT,
|
||||
width=MODULE.BASE_WIDTH,
|
||||
height=MODULE.BASE_HEIGHT,
|
||||
),
|
||||
MODULE.BASE_DRAFT_POINT,
|
||||
)
|
||||
|
||||
def test_current_vnc_coordinates_are_scaled(self):
|
||||
self.assertEqual(
|
||||
MODULE.scaled_point(
|
||||
MODULE.BASE_DRAFT_POINT,
|
||||
width=1470,
|
||||
height=694,
|
||||
),
|
||||
(450, 625),
|
||||
)
|
||||
self.assertEqual(
|
||||
MODULE.scaled_point(
|
||||
MODULE.BASE_SEND_POINT,
|
||||
width=1470,
|
||||
height=694,
|
||||
),
|
||||
(1419, 637),
|
||||
)
|
||||
self.assertEqual(
|
||||
MODULE.scaled_box(
|
||||
MODULE.BASE_INPUT_BOX,
|
||||
width=1470,
|
||||
height=694,
|
||||
),
|
||||
(306, 582, 1469, 693),
|
||||
)
|
||||
self.assertEqual(
|
||||
MODULE.scaled_region(
|
||||
MODULE.BASE_INPUT_TEXT_ROI,
|
||||
width=1470,
|
||||
height=694,
|
||||
),
|
||||
(327, 595, 653, 60),
|
||||
)
|
||||
self.assertEqual(MODULE.INPUT_TEXT_ROI_NAME, "input_text_core_v2")
|
||||
|
||||
|
||||
class VncKeyboardTests(unittest.TestCase):
|
||||
def test_vnc_key_event_has_the_expected_wire_format(self):
|
||||
connection = mock.Mock()
|
||||
MODULE.vnc_key_event(connection, down=True, keysym=ord("v"))
|
||||
connection.sendall.assert_called_once_with(
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, ord("v"))
|
||||
)
|
||||
|
||||
def test_x11_clipboard_rejects_a_failed_owner(self):
|
||||
process = mock.Mock()
|
||||
process.returncode = 1
|
||||
with mock.patch.object(
|
||||
MODULE.subprocess,
|
||||
"Popen",
|
||||
return_value=process,
|
||||
):
|
||||
with self.assertRaisesRegex(RuntimeError, "clipboard operation failed"):
|
||||
MODULE.set_x_clipboard("测试")
|
||||
|
||||
def test_vnc_pointer_event_has_the_expected_wire_format(self):
|
||||
connection = mock.Mock()
|
||||
MODULE.vnc_pointer_event(connection, buttons=1, x=450, y=652)
|
||||
connection.sendall.assert_called_once_with(
|
||||
MODULE.struct.pack(">BBHH", 5, 1, 450, 652)
|
||||
)
|
||||
|
||||
def test_vnc_select_all_and_clear_uses_vnc_key_events(self):
|
||||
connection = mock.Mock()
|
||||
MODULE.vnc_select_all_and_clear(connection)
|
||||
payloads = [call.args[0] for call in connection.sendall.call_args_list]
|
||||
self.assertEqual(
|
||||
payloads,
|
||||
[
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, 0xFFE3),
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, ord("a")),
|
||||
MODULE.struct.pack(">BBHI", 4, 0, 0, ord("a")),
|
||||
MODULE.struct.pack(">BBHI", 4, 0, 0, 0xFFE3),
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, 0xFF08),
|
||||
MODULE.struct.pack(">BBHI", 4, 0, 0, 0xFF08),
|
||||
],
|
||||
)
|
||||
|
||||
def test_vnc_return_key_uses_the_standard_enter_keysym(self):
|
||||
connection = mock.Mock()
|
||||
MODULE.vnc_key_press(connection, 0xFF0D)
|
||||
self.assertEqual(
|
||||
[call.args[0] for call in connection.sendall.call_args_list],
|
||||
[
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, 0xFF0D),
|
||||
MODULE.struct.pack(">BBHI", 4, 0, 0, 0xFF0D),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class InputVerificationTests(unittest.TestCase):
|
||||
def test_changed_pixels_ignores_a_small_cursor_change(self):
|
||||
before = Image.new("RGB", (20, 20), "white")
|
||||
after = before.copy()
|
||||
after.putpixel((1, 1), (0, 0, 0))
|
||||
self.assertEqual(MODULE.changed_pixels(before, after), 1)
|
||||
|
||||
def test_changed_pixels_detects_pasted_text_region(self):
|
||||
before = Image.new("RGB", (20, 20), "white")
|
||||
after = before.copy()
|
||||
for x in range(10):
|
||||
for y in range(10):
|
||||
after.putpixel((x, y), (0, 0, 0))
|
||||
self.assertGreaterEqual(
|
||||
MODULE.changed_pixels(before, after),
|
||||
MODULE.MIN_DRAFT_CHANGED_PIXELS,
|
||||
)
|
||||
|
||||
def test_changed_pixels_rejects_an_unchanged_send_region(self):
|
||||
image = Image.new("RGB", (20, 20), "white")
|
||||
self.assertLess(
|
||||
MODULE.changed_pixels(image, image.copy()),
|
||||
MODULE.MIN_DRAFT_CHANGED_PIXELS,
|
||||
)
|
||||
|
||||
def test_ink_pixels_detects_text_and_blank_roi(self):
|
||||
blank = Image.new("RGB", (20, 20), "white")
|
||||
text = blank.copy()
|
||||
for x in range(8):
|
||||
for y in range(8):
|
||||
text.putpixel((x, y), (0, 0, 0))
|
||||
|
||||
self.assertLessEqual(MODULE.ink_pixels(blank), MODULE.MAX_EMPTY_ROI_INK_PIXELS)
|
||||
self.assertGreater(MODULE.ink_pixels(text), MODULE.MAX_EMPTY_ROI_INK_PIXELS)
|
||||
|
||||
def test_wait_for_input_empty_reports_poll_count(self):
|
||||
dirty = Image.new("RGB", (20, 20), "white")
|
||||
for x in range(8):
|
||||
for y in range(8):
|
||||
dirty.putpixel((x, y), (0, 0, 0))
|
||||
blank = Image.new("RGB", (20, 20), "white")
|
||||
monotonic_values = iter((1.0, 1.0, 1.05, 1.05))
|
||||
with mock.patch.object(
|
||||
MODULE,
|
||||
"input_image",
|
||||
side_effect=[dirty, blank],
|
||||
), mock.patch.object(
|
||||
MODULE.time,
|
||||
"monotonic",
|
||||
side_effect=lambda: next(monotonic_values),
|
||||
), mock.patch.object(MODULE.time, "sleep"):
|
||||
_, ink, polls, elapsed_ms = MODULE.wait_for_input_empty(
|
||||
"window",
|
||||
width=1470,
|
||||
height=694,
|
||||
timeout_seconds=MODULE.CLEAR_CONFIRM_TIMEOUT_SECONDS,
|
||||
)
|
||||
|
||||
self.assertLessEqual(ink, MODULE.MAX_EMPTY_ROI_INK_PIXELS)
|
||||
self.assertEqual(polls, 2)
|
||||
self.assertEqual(elapsed_ms, 50.0)
|
||||
|
||||
def test_wait_for_input_change_reports_poll_count_and_duration(self):
|
||||
before = Image.new("RGB", (20, 20), "white")
|
||||
after = before.copy()
|
||||
for x in range(10):
|
||||
for y in range(10):
|
||||
after.putpixel((x, y), (0, 0, 0))
|
||||
monotonic_values = iter((1.0, 1.0, 1.05))
|
||||
with mock.patch.object(
|
||||
MODULE,
|
||||
"input_image",
|
||||
return_value=after,
|
||||
), mock.patch.object(
|
||||
MODULE,
|
||||
"changed_pixels",
|
||||
return_value=MODULE.MIN_SEND_CHANGED_PIXELS,
|
||||
), mock.patch.object(
|
||||
MODULE.time,
|
||||
"monotonic",
|
||||
side_effect=lambda: next(monotonic_values),
|
||||
):
|
||||
_, changed, polls, elapsed_ms = MODULE.wait_for_input_change(
|
||||
"window",
|
||||
before,
|
||||
width=1470,
|
||||
height=694,
|
||||
threshold=MODULE.MIN_SEND_CHANGED_PIXELS,
|
||||
timeout_seconds=MODULE.SEND_CONFIRM_TIMEOUT_SECONDS,
|
||||
)
|
||||
|
||||
self.assertEqual(changed, MODULE.MIN_SEND_CHANGED_PIXELS)
|
||||
self.assertEqual(polls, 1)
|
||||
self.assertEqual(elapsed_ms, 50.0)
|
||||
|
||||
|
||||
class SendTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.pending_text = Path(self.temporary.name) / "pending.txt"
|
||||
self.pending_token = Path(self.temporary.name) / "pending.token"
|
||||
self.pending_text.write_text("reply", encoding="utf-8")
|
||||
self.pending_token.write_text("token\n", encoding="utf-8")
|
||||
self.patches = [
|
||||
mock.patch.object(MODULE, "PENDING_TEXT", self.pending_text),
|
||||
mock.patch.object(MODULE, "PENDING_TOKEN", self.pending_token),
|
||||
mock.patch.object(MODULE, "window_id", return_value="window"),
|
||||
mock.patch.object(MODULE, "geometry", return_value=(1470, 694)),
|
||||
mock.patch.object(MODULE, "position", return_value=(0, 0)),
|
||||
mock.patch.object(MODULE.time, "sleep"),
|
||||
]
|
||||
for patch in self.patches:
|
||||
patch.start()
|
||||
|
||||
def tearDown(self):
|
||||
for patch in reversed(self.patches):
|
||||
patch.stop()
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_send_presses_return_first_after_roi_flag(self):
|
||||
connection = mock.Mock()
|
||||
before = Image.new("RGB", (20, 20), "white")
|
||||
after = before.copy()
|
||||
with mock.patch.object(
|
||||
MODULE,
|
||||
"vnc_focus_input",
|
||||
return_value=connection,
|
||||
) as focus, mock.patch.object(
|
||||
MODULE,
|
||||
"input_image",
|
||||
return_value=before,
|
||||
), mock.patch.object(
|
||||
MODULE,
|
||||
"wait_for_input_change",
|
||||
return_value=(after, MODULE.MIN_SEND_CHANGED_PIXELS, 1, 50.0),
|
||||
) as wait:
|
||||
MODULE.send("token")
|
||||
|
||||
focus.assert_called_once_with("window", (450, 625), origin=(0, 0))
|
||||
connection.sendall.assert_any_call(
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, 0xFF0D)
|
||||
)
|
||||
self.assertEqual(wait.call_args.kwargs["box"], MODULE.BASE_INPUT_TEXT_ROI)
|
||||
self.assertFalse(self.pending_text.exists())
|
||||
self.assertFalse(self.pending_token.exists())
|
||||
|
||||
def test_send_uses_button_when_return_is_not_confirmed(self):
|
||||
first_connection = mock.Mock()
|
||||
second_connection = mock.Mock()
|
||||
before = Image.new("RGB", (20, 20), "white")
|
||||
after_return = before.copy()
|
||||
after_click = before.copy()
|
||||
with mock.patch.object(
|
||||
MODULE,
|
||||
"vnc_focus_input",
|
||||
side_effect=[first_connection, second_connection],
|
||||
) as focus, mock.patch.object(
|
||||
MODULE,
|
||||
"input_image",
|
||||
return_value=before,
|
||||
), mock.patch.object(
|
||||
MODULE,
|
||||
"wait_for_input_change",
|
||||
side_effect=[
|
||||
(after_click, 0, 18, 900.0),
|
||||
(after_return, MODULE.MIN_SEND_CHANGED_PIXELS, 1, 50.0),
|
||||
],
|
||||
):
|
||||
MODULE.send("token")
|
||||
|
||||
self.assertEqual(
|
||||
[call.args for call in focus.call_args_list],
|
||||
[("window", (450, 625)), ("window", (1419, 637))],
|
||||
)
|
||||
first_connection.sendall.assert_any_call(
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, 0xFF0D)
|
||||
)
|
||||
self.assertFalse(self.pending_text.exists())
|
||||
self.assertFalse(self.pending_token.exists())
|
||||
|
||||
def test_deliver_text_confirms_clear_before_paste(self):
|
||||
connection = mock.Mock()
|
||||
blank = Image.new("RGB", (20, 20), "white")
|
||||
after_paste = blank.copy()
|
||||
after_send = blank.copy()
|
||||
with mock.patch.object(
|
||||
MODULE,
|
||||
"vnc_focus_input",
|
||||
return_value=connection,
|
||||
), mock.patch.object(
|
||||
MODULE,
|
||||
"wait_for_input_empty",
|
||||
return_value=(blank, 0, 1, 50.0),
|
||||
) as empty, mock.patch.object(
|
||||
MODULE,
|
||||
"vnc_paste",
|
||||
) as paste, mock.patch.object(
|
||||
MODULE,
|
||||
"input_image",
|
||||
return_value=after_paste,
|
||||
), mock.patch.object(
|
||||
MODULE,
|
||||
"wait_for_input_change",
|
||||
side_effect=[
|
||||
(after_paste, MODULE.MIN_DRAFT_CHANGED_PIXELS, 1, 50.0),
|
||||
(after_send, MODULE.MIN_SEND_CHANGED_PIXELS, 1, 50.0),
|
||||
],
|
||||
) as wait:
|
||||
payload = MODULE.deliver_text("reply")
|
||||
|
||||
self.assertEqual(payload["state"], "sent")
|
||||
empty.assert_called_once()
|
||||
paste.assert_called_once_with(connection, "reply")
|
||||
self.assertEqual(len(wait.call_args_list), 2)
|
||||
connection.sendall.assert_any_call(
|
||||
MODULE.struct.pack(">BBHI", 4, 1, 0, 0xFF0D)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user