diff --git a/tests/pilot_recorder.py b/tests/pilot_recorder.py index 295004a..5247e1d 100644 --- a/tests/pilot_recorder.py +++ b/tests/pilot_recorder.py @@ -1,20 +1,31 @@ -import re -from rich.text import Text +import os from textual.css.query import NoMatches, TooManyMatches from textual.dom import DOMNode from textual.events import Event, Key, MouseDown, MouseMove, MouseUp from textual.screen import Screen from textual_paint.paint import PaintApp -OUTPUT_FILE = "tests/test_paint_something.py" +def unique_file(path: str) -> str: + filename, extension = os.path.splitext(path) + counter = 1 -steps: list[tuple[Event, str]] = [] + while os.path.exists(path): + # path = f"{filename} ({counter}){extension}" + path = f"{filename}_{counter}{extension}" + counter += 1 -def get_selector(target: DOMNode) -> str: + return path + +OUTPUT_FILE = unique_file("tests/test_paint_something.py") + +steps: list[tuple[Event, str, int|None]] = [] + +def get_selector(target: DOMNode) -> tuple[str, int|None]: """Return a selector that can be used to find the widget.""" + assert app is not None, "app should be set by now" widget = target if widget.id: - return f"#{widget.id}" + return f"#{widget.id}", None selector = widget.css_identifier while widget.parent and not isinstance(widget.parent, Screen): widget = widget.parent @@ -26,20 +37,23 @@ def get_selector(target: DOMNode) -> str: try: query_result = app.query_one(selector) except TooManyMatches: - raise Exception(f"Selector {selector!r} matches more than one widget ({app.query(selector).nodes!r})") + # raise Exception(f"Selector {selector!r} matches more than one widget ({app.query(selector).nodes!r})") + return selector, app.query(selector).nodes.index(target) + # smarter differentiators would be nice, like tooltip or text content, + # but at least with indices, you'll know when you changed the tab order except NoMatches: raise Exception(f"Selector {selector!r} didn't match the target widget ({target!r})") if query_result is not target: raise Exception(f"Selector {selector!r} matched a different widget than the target ({query_result!r} rather than {target!r})") - return selector + return selector, None original_on_event = PaintApp.on_event async def on_event(self: PaintApp, event: Event) -> None: await original_on_event(self, event) if isinstance(event, (MouseDown, MouseMove, MouseUp)): widget, _ = self.get_widget_at(event.x, event.y) - steps.append((event, get_selector(widget))) + steps.append((event, *get_selector(widget))) elif isinstance(event, Key): if event.key == "ctrl+z": steps.pop() @@ -57,20 +71,27 @@ def replay() -> None: app.on_event = on_event.__get__(app) async def replay_steps() -> None: assert app is not None, "app should be set by now" - for event in steps: + for event, selector, index in steps: await app.on_event(event) app.call_later(replay_steps) app.run() # blocking def indent(text: str, spaces: int) -> str: - return re.sub(r"^", " " * spaces, text, flags=re.MULTILINE) + return "\n".join(" " * spaces + line for line in text.splitlines()) def save_replay() -> None: + assert app is not None, "app should be set by now" helpers_code = "" steps_code = "" - for event, selector in steps: + for event, selector, index in steps: if isinstance(event, MouseDown): - steps_code += f"await pilot.click({selector!r}, offset=Offset({event.x}, {event.y}))\n" + if index is None: + steps_code += f"await pilot.click({selector!r}, offset=Offset({event.x}, {event.y}))\n" + else: + steps_code += f"widget = pilot.app.query({selector!r})[{index!r}]\n" + # steps_code += f"await pilot.click(widget, offset=Offset({event.x}, {event.y}))\n" # would be nice + steps_code += f"await pilot.click(offset=Offset({event.x}, {event.y}) + widget.region.offset)\n" + script = f"""\ from pathlib import Path, PurePath @@ -103,7 +124,7 @@ def test_paint_something(snap_compare: SnapCompareType): {indent(helpers_code, 8)} {indent(steps_code, 8)} - assert snap_compare(PAINT, run_before=test_paint_something_steps, size=({app.size.width}, {app.size.height})) + assert snap_compare(PAINT, run_before=test_paint_something_steps, terminal_size=({app.size.width}, {app.size.height})) """ with open(OUTPUT_FILE, "w") as f: f.write(script)