aboutsummaryrefslogtreecommitdiff
path: root/tools/tests/test_ui_flow.py
blob: 5e359c2ef37788b3551a77b909810b3a2366fb5d (plain) (blame)
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
# Author:  cirakg & claude ai
# Summary: Tests for the UI flow driver's pure parts: key layout, JSON lookup, the app-log reader, barcode reading.
from __future__ import annotations

import json
import threading
import time
from pathlib import Path

import pytest
import segno
import ui_flow
from PIL import Image


def write_sim(tmp_path: Path, config: dict[str, object]) -> Path:
    (tmp_path / "simulator.json").write_text(json.dumps(config), encoding="utf-8")
    return tmp_path


def test_load_keys_gives_button_centres(tmp_path: Path) -> None:
    device = write_sim(
        tmp_path,
        {
            "keys": [
                {"id": "enter", "behavior": "onSelect", "location": {"x": 10, "y": 20, "width": 30, "height": 40}},
                {
                    "id": "menu",
                    "behavior": "onMenu",
                    "isHold": True,
                    "location": {"x": 0, "y": 0, "width": 2, "height": 2},
                },
                {"id": "start", "location": {"x": 5, "y": 5, "width": 10, "height": 10}},
            ]
        },
    )
    keys = ui_flow.load_keys(device)
    assert keys["enter"] == ui_flow.Key(25, 40, "onSelect", False)
    assert keys["menu"].hold
    assert keys["start"].behavior == ""


def test_find_key_searches_nested_structures() -> None:
    data = {"display": {"isTouch": True}, "glance": [{"contentArea": {"x": 1}}]}
    assert ui_flow.find_key(data, "isTouch") is True
    assert ui_flow.find_key(data, "contentArea") == {"x": 1}
    assert ui_flow.find_key(data, "missing") is None


def test_app_log_waits_only_for_lines_after_the_mark(tmp_path: Path) -> None:
    log_path = tmp_path / "app.log"
    log_path.write_text("DIAG shown list\n", encoding="utf-8")
    log = ui_flow.AppLog(log_path)
    assert log.wait_for(r"DIAG shown list\s*$", 1).group(0).startswith("DIAG shown list")
    log.set_mark()

    def append_later() -> None:
        time.sleep(0.2)
        with log_path.open("a", encoding="utf-8") as f:
            f.write("DIAG shown otp\n")

    writer = threading.Thread(target=append_later)
    writer.start()
    assert log.wait_for(r"DIAG shown otp", 3)
    writer.join()
    with pytest.raises(ui_flow.FlowError):
        log.wait_for(r"DIAG shown list", 0.3)  # only before the mark


def test_app_log_latest_returns_the_last_match(tmp_path: Path) -> None:
    log_path = tmp_path / "app.log"
    log_path.write_text("DIAG list_focus y=10\nother\nDIAG list_focus y=42\n", encoding="utf-8")
    match = ui_flow.AppLog(log_path).latest(r"DIAG list_focus y=(\d+)")
    assert match is not None and match.group(1) == "42"
    assert ui_flow.AppLog(tmp_path / "missing.log").latest("x") is None


def test_barcodes_on_reads_a_drawn_code(tmp_path: Path) -> None:
    path = tmp_path / "qr.png"
    segno.make("OTPW stress", error="m").save(str(path), scale=4, border=4)
    screen = Image.open(path).convert("RGB")
    assert ui_flow.barcodes_on(screen) == ["OTPW stress"]
    assert ui_flow.barcodes_on(Image.new("RGB", (100, 100), "white")) == []


def test_vertical_shift_measures_a_scrolled_list() -> None:
    rows = Image.new("L", (60, 400), 0)
    for top in range(0, 400, 50):  # bands of different shades, like menu items
        rows.paste(40 + top // 2, (5, top + 5, 55, top + 40))
    before = rows.crop((0, 0, 60, 200))
    assert ui_flow.vertical_shift(before, rows.crop((0, 37, 60, 237))) == 37
    assert ui_flow.vertical_shift(rows.crop((0, 37, 60, 237)), before) == -37
    assert ui_flow.vertical_shift(before, before) == 0