2023-09-13 04:22:22 +03:00
|
|
|
"""Visual regression tests, using pytest-textual-snapshot. Run with `pytest`."""
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
from pathlib import Path, PurePath
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
from typing import (TYPE_CHECKING, Awaitable, Callable, Generator, Iterable,
|
|
|
|
Protocol)
|
2023-09-07 22:14:07 +03:00
|
|
|
|
|
|
|
import pytest
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
from pyfakefs.fake_filesystem import FakeFilesystem
|
2023-09-09 08:12:21 +03:00
|
|
|
from textual.geometry import Offset
|
2023-09-09 02:59:36 +03:00
|
|
|
from textual.pilot import Pilot
|
2023-09-08 22:03:50 +03:00
|
|
|
from textual.widgets import Input
|
2023-09-07 22:14:07 +03:00
|
|
|
|
2023-09-12 03:07:36 +03:00
|
|
|
from tests.pilot_helpers import click_by_attr, click_by_index, drag
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
from textual_paint.figlet_font_writer import FIGletFontWriter
|
2023-09-12 02:28:33 +03:00
|
|
|
|
2023-09-09 06:49:52 +03:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
# When tests are run, paint.py is re-evaluated,
|
|
|
|
# leading to a different class of the same name at runtime.
|
|
|
|
from textual_paint.paint import PaintApp
|
2023-09-09 02:59:36 +03:00
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
|
|
|
|
class SnapCompareType(Protocol):
|
|
|
|
"""Type of the function returned by the snap_compare fixture."""
|
|
|
|
def __call__(
|
|
|
|
self,
|
|
|
|
app_path: str | PurePath,
|
|
|
|
press: Iterable[str] = (),
|
|
|
|
terminal_size: tuple[int, int] = (80, 24),
|
|
|
|
run_before: Callable[[Pilot], Awaitable[None] | None] | None = None, # type: ignore
|
|
|
|
) -> bool:
|
|
|
|
...
|
|
|
|
|
2023-09-07 22:14:07 +03:00
|
|
|
# These paths are treated as relative to this file.
|
|
|
|
APPS_DIR = Path("../src/textual_paint")
|
|
|
|
PAINT = APPS_DIR / "paint.py"
|
|
|
|
GALLERY = APPS_DIR / "gallery.py"
|
|
|
|
|
2023-09-08 00:43:14 +03:00
|
|
|
LARGER = (81, 38)
|
2023-09-09 04:40:29 +03:00
|
|
|
"""Large enough to show the Textual Paint app's main UI and most dialogs comfortably."""
|
2023-09-09 02:59:36 +03:00
|
|
|
LARGEST = (107, 42)
|
|
|
|
"""Large enough to show the Edit Colors dialog, which is a bit oversized."""
|
2023-09-08 00:43:14 +03:00
|
|
|
|
2023-09-09 02:59:36 +03:00
|
|
|
# Prevent flaky tests due to timing issues.
|
2023-09-09 03:20:07 +03:00
|
|
|
Input.cursor_blink = False # type: ignore
|
Test light and dark theme variations with a pytest fixture
First I tried setting PYTEST_TEXTUAL_PAINT_ARGS as an environment variable, to be interpreted by args.py, but it turns out args.py is only executed once, not once per test. It's not using subprocesses, only importing and reimporting the app code, and instantiating new App instances, so parts of the code that are at the top level of modules is only evaluated once.
So I found a new strategy, of importing the `args` object in the test fixture and modifying it directly.
I also realized the --ascii-only option permanently modifies Textual's widgets and borders, and my own widgets, for the life of the process, so I'm holding off on that one. I should be able to make --ascii-only mode more dynamic, and could even target it as a runtime toggle, as a goal, since that's basically what I'll need to achieve to get it working for the tests, but thinking of it as a feature is more fun.
2023-09-08 01:40:16 +03:00
|
|
|
|
|
|
|
@pytest.fixture(params=[
|
|
|
|
{"theme": "light", "ascii_only": False},
|
|
|
|
{"theme": "dark", "ascii_only": False},
|
Merge snapshot results for ASCII-only and Unicode UI tests
I'm basically doing TDD to snapshot testing!
I'm creating tests that don't pass yet, setting up an expectation
that the app match the given screenshots, which is funny in a nice
"improper hierarchy" sort of way, but it's possible because I do
actually have the app rendering how I want, just only in isolation.
If I run the ascii_only tests by themselves, I can get good results
from them, but running them interwoven with default Unicode-using UI
tests doesn't work yet, since the ASCII-only mode permanently changes
how certain widgets render, for the life of the process, so that's
what I'm applying TDD to: making it toggleable at runtime.
I commented out the Unicode tests, and uncommented the ASCII-only tests,
renamed test_snapshots.ambr to test_snapshots_ascii.ambr,
reverted the changes to test_snapshots.ambr to get the Unicode version,
ran my new merge_ambr.py script to join the sets of snapshots,
then replaced test_snapshots.ambr with test_snapshots_merged.ambr
Finally, I uncommented both sets of tests, and I'm ready to do TDD!
2023-09-08 18:24:57 +03:00
|
|
|
{"theme": "light", "ascii_only": True},
|
|
|
|
{"theme": "dark", "ascii_only": True},
|
Test light and dark theme variations with a pytest fixture
First I tried setting PYTEST_TEXTUAL_PAINT_ARGS as an environment variable, to be interpreted by args.py, but it turns out args.py is only executed once, not once per test. It's not using subprocesses, only importing and reimporting the app code, and instantiating new App instances, so parts of the code that are at the top level of modules is only evaluated once.
So I found a new strategy, of importing the `args` object in the test fixture and modifying it directly.
I also realized the --ascii-only option permanently modifies Textual's widgets and borders, and my own widgets, for the life of the process, so I'm holding off on that one. I should be able to make --ascii-only mode more dynamic, and could even target it as a runtime toggle, as a goal, since that's basically what I'll need to achieve to get it working for the tests, but thinking of it as a feature is more fun.
2023-09-08 01:40:16 +03:00
|
|
|
], ids=lambda param: f"{param['theme']}_{'ascii' if param['ascii_only'] else 'unicode'}")
|
2023-09-09 06:38:37 +03:00
|
|
|
def each_theme(request: pytest.FixtureRequest):
|
2023-09-09 05:53:58 +03:00
|
|
|
"""Fixture to test each combination of UI styles."""
|
Test light and dark theme variations with a pytest fixture
First I tried setting PYTEST_TEXTUAL_PAINT_ARGS as an environment variable, to be interpreted by args.py, but it turns out args.py is only executed once, not once per test. It's not using subprocesses, only importing and reimporting the app code, and instantiating new App instances, so parts of the code that are at the top level of modules is only evaluated once.
So I found a new strategy, of importing the `args` object in the test fixture and modifying it directly.
I also realized the --ascii-only option permanently modifies Textual's widgets and borders, and my own widgets, for the life of the process, so I'm holding off on that one. I should be able to make --ascii-only mode more dynamic, and could even target it as a runtime toggle, as a goal, since that's basically what I'll need to achieve to get it working for the tests, but thinking of it as a feature is more fun.
2023-09-08 01:40:16 +03:00
|
|
|
theme = request.param.get("theme")
|
|
|
|
ascii_only = request.param.get("ascii_only")
|
|
|
|
from textual_paint.args import args
|
|
|
|
args.theme = theme
|
|
|
|
args.ascii_only = ascii_only
|
2023-09-13 04:22:22 +03:00
|
|
|
|
|
|
|
yield # run the test
|
|
|
|
|
Test light and dark theme variations with a pytest fixture
First I tried setting PYTEST_TEXTUAL_PAINT_ARGS as an environment variable, to be interpreted by args.py, but it turns out args.py is only executed once, not once per test. It's not using subprocesses, only importing and reimporting the app code, and instantiating new App instances, so parts of the code that are at the top level of modules is only evaluated once.
So I found a new strategy, of importing the `args` object in the test fixture and modifying it directly.
I also realized the --ascii-only option permanently modifies Textual's widgets and borders, and my own widgets, for the life of the process, so I'm holding off on that one. I should be able to make --ascii-only mode more dynamic, and could even target it as a runtime toggle, as a goal, since that's basically what I'll need to achieve to get it working for the tests, but thinking of it as a feature is more fun.
2023-09-08 01:40:16 +03:00
|
|
|
args.theme = "light"
|
|
|
|
args.ascii_only = False
|
|
|
|
|
2023-09-13 01:19:40 +03:00
|
|
|
REPO_DIR_ABSOLUTE = Path(__file__).parent.parent.resolve()
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def my_fs(fs: FakeFilesystem) -> Generator[FakeFilesystem, None, None]:
|
2023-09-13 04:22:22 +03:00
|
|
|
"""Fixture to fake the filesystem, except for the repo directory."""
|
2023-09-13 01:19:40 +03:00
|
|
|
|
2023-09-13 04:22:22 +03:00
|
|
|
# Without the repo dir, textual paint will fail to load FIGlet fonts or dialog icons.
|
|
|
|
# Without the __snapshots__ dir, pytest-textual-snapshot will show "No history for this test" in the report.
|
2023-09-13 01:19:40 +03:00
|
|
|
print("adding real directory", REPO_DIR_ABSOLUTE)
|
|
|
|
fs.add_real_directory(REPO_DIR_ABSOLUTE)
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
|
Fix DirectoryTree retaining real pathlib access
This fixes the following error:
----------------------------------------------- Captured stderr call -----------------------------------------------
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│ /home/io/Projects/textual-paint/src/textual_paint/file_dialogs.py:86 in │
│ on_mount │
│ │
│ 83 │ def on_mount(self) -> None: │
│ 84 │ │ """Called when the window is mounted.""" │
│ 85 │ │ self.content.mount( │
│ ❱ 86 │ │ │ EnhancedDirectoryTree(path="/"), │
│ 87 │ │ │ Horizontal( │
│ 88 │ │ │ │ Label(_("File name:")), │
│ 89 │ │ │ │ Input(classes="filename_input autofocus", value=self._ │
│ │
│ ╭──────────────────── locals ────────────────────╮ │
│ │ self = OpenDialogWindow(id='window_auto_id_0') │ │
│ ╰────────────────────────────────────────────────╯ │
│ │
│ in __init__:119 │
│ │
│ 116 │ │ self._load_queue: Queue[TreeNode[DirEntry]] = Queue() │
│ 117 │ │ super().__init__( │
│ 118 │ │ │ str(path), │
│ ❱ 119 │ │ │ data=DirEntry(self.PATH(path)), │
│ 120 │ │ │ name=name, │
│ 121 │ │ │ id=id, │
│ 122 │ │ │ classes=classes, │
│ │
│ ╭───────────────── locals ──────────────────╮ │
│ │ classes = None │ │
│ │ disabled = False │ │
│ │ id = None │ │
│ │ name = None │ │
│ │ path = '/' │ │
│ │ self = EnhancedDirectoryTree( │ │
│ │ │ id='some fake shit', │ │
│ │ │ name='some more fake shit' │ │
│ │ ) │ │
│ ╰───────────────────────────────────────────╯ │
│ │
│ in __new__:960 │
│ │
│ 957 │ def __new__(cls, *args, **kwargs): │
│ 958 │ │ if cls is Path: │
│ 959 │ │ │ cls = WindowsPath if os.name == 'nt' else PosixPath │
│ ❱ 960 │ │ self = cls._from_parts(args) │
│ 961 │ │ if not self._flavour.is_supported: │
│ 962 │ │ │ raise NotImplementedError("cannot instantiate %r on your │
│ 963 │ │ │ │ │ │ │ │ │ % (cls.__name__,)) │
│ │
│ ╭──────────── locals ─────────────╮ │
│ │ args = ('/',) │ │
│ │ cls = <class 'pathlib.Path'> │ │
│ │ kwargs = {} │ │
│ ╰─────────────────────────────────╯ │
│ │
│ in _from_parts:594 │
│ │
│ 591 │ │ # We need to call _parse_args on the instance, so as to get t │
│ 592 │ │ # right flavour. │
│ 593 │ │ self = object.__new__(cls) │
│ ❱ 594 │ │ drv, root, parts = self._parse_args(args) │
│ 595 │ │ self._drv = drv │
│ 596 │ │ self._root = root │
│ 597 │ │ self._parts = parts │
│ │
│ ╭──────────────────────────── locals ─────────────────────────────╮ │
│ │ args = ('/',) │ │
│ │ cls = <class 'pathlib.Path'> │ │
│ │ self = <repr-error "'Path' object has no attribute '_flavour'"> │ │
│ ╰─────────────────────────────────────────────────────────────────╯ │
│ │
│ in _parse_args:587 │
│ │
│ 584 │ │ │ │ │ │ "argument should be a str object or an os.Pat │
│ 585 │ │ │ │ │ │ "object returning str, not %r" │
│ 586 │ │ │ │ │ │ % type(a)) │
│ ❱ 587 │ │ return cls._flavour.parse_parts(parts) │
│ 588 │ │
│ 589 │ @classmethod │
│ 590 │ def _from_parts(cls, args): │
│ │
│ ╭──────────── locals ────────────╮ │
│ │ a = '/' │ │
│ │ args = ('/',) │ │
│ │ cls = <class 'pathlib.Path'> │ │
│ │ parts = ['/'] │ │
│ ╰────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────╯
AttributeError: type object 'Path' has no attribute '_flavour'
...which was itself masked by an error:
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/_doc.py:136: in take_svg_screenshot
app.run(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1207: in run
asyncio.run(run_app())
/usr/lib/python3.10/asyncio/runners.py:44: in run
return loop.run_until_complete(main)
/usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
return future.result()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1196: in run_app
await self.run_async(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1168: in run_async
await app._shutdown()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:2186: in _shutdown
await self._close_all()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:2166: in _close_all
await self._prune_node(stack_screen)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:2656: in _prune_node
await asyncio.gather(*close_messages)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/message_pump.py:453: in _close_messages
await self._task
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/message_pump.py:469: in _process_messages
await self._pre_process()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/message_pump.py:490: in _pre_process
self.app._handle_exception(error)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1822: in _handle_exception
self._fatal_error()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1827: in _fatal_error
traceback = Traceback(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/traceback.py:264: in __init__
trace = self.extract(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/traceback.py:449: in extract
locals={
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/traceback.py:450: in <dictcomp>
key: pretty.traverse(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/pretty.py:852: in traverse
node = _traverse(_object, root=True)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/pretty.py:647: in _traverse
args = list(iter_rich_args(rich_repr_result))
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/pretty.py:614: in iter_rich_args
for arg in rich_args:
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/widget.py:2516: in __rich_repr__
yield "id", self.id, None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'EnhancedDirectoryTree' object has no attribute '_id'") raised in repr()] EnhancedDirectoryTree object at 0x7f06b38c89d0>
@property
def id(self) -> str | None:
"""The ID of this node, or None if the node has no ID."""
> return self._id
E AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/dom.py:483: AttributeError
...which I worked around by adding class vars to EnhancedDirectoryTree:
_id = "some fake shit"
_name = "some more fake shit"
It was also a heisenbug. I was able to run the tests, even update snapshots, running in debug mode in VS Code.
2023-09-13 02:20:36 +03:00
|
|
|
# DirectoryTree stores a reference to Path for some reason, making my life more difficult.
|
|
|
|
from textual.widgets._directory_tree import DirectoryTree
|
|
|
|
orig_PATH = DirectoryTree.PATH
|
|
|
|
DirectoryTree.PATH = Path
|
|
|
|
|
2023-09-13 04:22:22 +03:00
|
|
|
# TODO: use proper(?) mocking or figure out how to get FigletFont to find the real font files.
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
# This folder doesn't actually exist on my system, so it's not getting them from there.
|
|
|
|
# from pyfiglet import SHARED_DIRECTORY
|
|
|
|
# fs.add_real_directory(SHARED_DIRECTORY)
|
|
|
|
|
|
|
|
# Don't fail trying to load the default font "standard", we don't need it!
|
|
|
|
# `pkg_resources` doesn't seem to work with pyfakefs.
|
|
|
|
from pyfiglet import FigletFont
|
|
|
|
def preloadFont(self: FigletFont, font: str):
|
|
|
|
dumb_font = FIGletFontWriter(commentLines=["Stupid font for testing"])
|
|
|
|
for ordinal in dumb_font.charOrder:
|
|
|
|
dumb_font.figChars[ordinal] = "fallback font for testing"
|
|
|
|
return dumb_font.createFigFileData()
|
|
|
|
FigletFont.preloadFont = preloadFont
|
|
|
|
|
|
|
|
# Add an extra file to show how a file looks in the EnhancedDirectoryTree widget.
|
|
|
|
fs.create_file("/pyfakefs_added_file.txt", contents="pyfakefs ate ur FS")
|
|
|
|
|
|
|
|
yield fs
|
|
|
|
|
Fix DirectoryTree retaining real pathlib access
This fixes the following error:
----------------------------------------------- Captured stderr call -----------------------------------------------
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│ /home/io/Projects/textual-paint/src/textual_paint/file_dialogs.py:86 in │
│ on_mount │
│ │
│ 83 │ def on_mount(self) -> None: │
│ 84 │ │ """Called when the window is mounted.""" │
│ 85 │ │ self.content.mount( │
│ ❱ 86 │ │ │ EnhancedDirectoryTree(path="/"), │
│ 87 │ │ │ Horizontal( │
│ 88 │ │ │ │ Label(_("File name:")), │
│ 89 │ │ │ │ Input(classes="filename_input autofocus", value=self._ │
│ │
│ ╭──────────────────── locals ────────────────────╮ │
│ │ self = OpenDialogWindow(id='window_auto_id_0') │ │
│ ╰────────────────────────────────────────────────╯ │
│ │
│ in __init__:119 │
│ │
│ 116 │ │ self._load_queue: Queue[TreeNode[DirEntry]] = Queue() │
│ 117 │ │ super().__init__( │
│ 118 │ │ │ str(path), │
│ ❱ 119 │ │ │ data=DirEntry(self.PATH(path)), │
│ 120 │ │ │ name=name, │
│ 121 │ │ │ id=id, │
│ 122 │ │ │ classes=classes, │
│ │
│ ╭───────────────── locals ──────────────────╮ │
│ │ classes = None │ │
│ │ disabled = False │ │
│ │ id = None │ │
│ │ name = None │ │
│ │ path = '/' │ │
│ │ self = EnhancedDirectoryTree( │ │
│ │ │ id='some fake shit', │ │
│ │ │ name='some more fake shit' │ │
│ │ ) │ │
│ ╰───────────────────────────────────────────╯ │
│ │
│ in __new__:960 │
│ │
│ 957 │ def __new__(cls, *args, **kwargs): │
│ 958 │ │ if cls is Path: │
│ 959 │ │ │ cls = WindowsPath if os.name == 'nt' else PosixPath │
│ ❱ 960 │ │ self = cls._from_parts(args) │
│ 961 │ │ if not self._flavour.is_supported: │
│ 962 │ │ │ raise NotImplementedError("cannot instantiate %r on your │
│ 963 │ │ │ │ │ │ │ │ │ % (cls.__name__,)) │
│ │
│ ╭──────────── locals ─────────────╮ │
│ │ args = ('/',) │ │
│ │ cls = <class 'pathlib.Path'> │ │
│ │ kwargs = {} │ │
│ ╰─────────────────────────────────╯ │
│ │
│ in _from_parts:594 │
│ │
│ 591 │ │ # We need to call _parse_args on the instance, so as to get t │
│ 592 │ │ # right flavour. │
│ 593 │ │ self = object.__new__(cls) │
│ ❱ 594 │ │ drv, root, parts = self._parse_args(args) │
│ 595 │ │ self._drv = drv │
│ 596 │ │ self._root = root │
│ 597 │ │ self._parts = parts │
│ │
│ ╭──────────────────────────── locals ─────────────────────────────╮ │
│ │ args = ('/',) │ │
│ │ cls = <class 'pathlib.Path'> │ │
│ │ self = <repr-error "'Path' object has no attribute '_flavour'"> │ │
│ ╰─────────────────────────────────────────────────────────────────╯ │
│ │
│ in _parse_args:587 │
│ │
│ 584 │ │ │ │ │ │ "argument should be a str object or an os.Pat │
│ 585 │ │ │ │ │ │ "object returning str, not %r" │
│ 586 │ │ │ │ │ │ % type(a)) │
│ ❱ 587 │ │ return cls._flavour.parse_parts(parts) │
│ 588 │ │
│ 589 │ @classmethod │
│ 590 │ def _from_parts(cls, args): │
│ │
│ ╭──────────── locals ────────────╮ │
│ │ a = '/' │ │
│ │ args = ('/',) │ │
│ │ cls = <class 'pathlib.Path'> │ │
│ │ parts = ['/'] │ │
│ ╰────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────╯
AttributeError: type object 'Path' has no attribute '_flavour'
...which was itself masked by an error:
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/_doc.py:136: in take_svg_screenshot
app.run(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1207: in run
asyncio.run(run_app())
/usr/lib/python3.10/asyncio/runners.py:44: in run
return loop.run_until_complete(main)
/usr/lib/python3.10/asyncio/base_events.py:649: in run_until_complete
return future.result()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1196: in run_app
await self.run_async(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1168: in run_async
await app._shutdown()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:2186: in _shutdown
await self._close_all()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:2166: in _close_all
await self._prune_node(stack_screen)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:2656: in _prune_node
await asyncio.gather(*close_messages)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/message_pump.py:453: in _close_messages
await self._task
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/message_pump.py:469: in _process_messages
await self._pre_process()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/message_pump.py:490: in _pre_process
self.app._handle_exception(error)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1822: in _handle_exception
self._fatal_error()
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/app.py:1827: in _fatal_error
traceback = Traceback(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/traceback.py:264: in __init__
trace = self.extract(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/traceback.py:449: in extract
locals={
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/traceback.py:450: in <dictcomp>
key: pretty.traverse(
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/pretty.py:852: in traverse
node = _traverse(_object, root=True)
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/pretty.py:647: in _traverse
args = list(iter_rich_args(rich_repr_result))
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/rich/pretty.py:614: in iter_rich_args
for arg in rich_args:
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/widget.py:2516: in __rich_repr__
yield "id", self.id, None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'EnhancedDirectoryTree' object has no attribute '_id'") raised in repr()] EnhancedDirectoryTree object at 0x7f06b38c89d0>
@property
def id(self) -> str | None:
"""The ID of this node, or None if the node has no ID."""
> return self._id
E AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
/home/io/Projects/textual-paint/.venv/lib/python3.10/site-packages/textual/dom.py:483: AttributeError
...which I worked around by adding class vars to EnhancedDirectoryTree:
_id = "some fake shit"
_name = "some more fake shit"
It was also a heisenbug. I was able to run the tests, even update snapshots, running in debug mode in VS Code.
2023-09-13 02:20:36 +03:00
|
|
|
# probably don't need to actually clean up, but whatever
|
|
|
|
DirectoryTree.PATH = orig_PATH
|
|
|
|
|
Test light and dark theme variations with a pytest fixture
First I tried setting PYTEST_TEXTUAL_PAINT_ARGS as an environment variable, to be interpreted by args.py, but it turns out args.py is only executed once, not once per test. It's not using subprocesses, only importing and reimporting the app code, and instantiating new App instances, so parts of the code that are at the top level of modules is only evaluated once.
So I found a new strategy, of importing the `args` object in the test fixture and modifying it directly.
I also realized the --ascii-only option permanently modifies Textual's widgets and borders, and my own widgets, for the life of the process, so I'm holding off on that one. I should be able to make --ascii-only mode more dynamic, and could even target it as a runtime toggle, as a goal, since that's basically what I'll need to achieve to get it working for the tests, but thinking of it as a feature is more fun.
2023-09-08 01:40:16 +03:00
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_app(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-08 00:43:14 +03:00
|
|
|
assert snap_compare(PAINT, terminal_size=LARGER)
|
2023-09-07 22:14:07 +03:00
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_stretch_skew_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-07 22:14:07 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+w"])
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_flip_rotate_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-08 00:21:20 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+r"])
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_image_attributes_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-08 00:21:20 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+e"])
|
|
|
|
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
def test_paint_open_dialog(snap_compare: SnapCompareType, each_theme: None, my_fs: None):
|
2023-09-08 00:43:14 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+o"], terminal_size=LARGER)
|
2023-09-08 00:21:20 +03:00
|
|
|
|
Use pyfakefs for file dialog tests
- The app's directory structure is not a constant, and shouldn't play into this test. Aside from codebase restructuring, directories like `__pycache__` can come and go.
- Even if a temporary directory were created with files enough to fill the view, the scrollbar would still change based on the folder structure outside of the temporary folder.
- pyfakefs is one way to ensure a consistent view of a folder structure for testing. It allows adding real folders in a readonly way. It's more complicated than I thought it would be going in, since I had to add workarounds for pyfiglet and pytest-textual-snapshot, and handle an edge case in my EnhancedDirectoryTree (which got an error which seems to be swallowed?), not to mention pyfakefs raises an error saying "No such file or directory in the fake filesystem" when actually it's the real directory not existing when trying to add it to the fake filesystem, and VS Code was hiding stack frames and refusing to step into library code, and it turned out that I was resolving the absolute path wrong, but it looked right to me because the only part that was missing was "textual-paint", when, at a glance it seemed present, since the "textual_paint" part was present. Ay-ay-ay!
- I don't know if this will fix the problem I saw where these tests' snapshots all changed with no visual or even structural changes, just the IDs of elements changing. I don't know what caused that.
Oh yeah and this is still actually a problem:
============================================= short test summary info ==============================================
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_open_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_unicode] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[light_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
FAILED tests/test_snapshots.py::test_paint_save_dialog[dark_ascii] - AttributeError: 'EnhancedDirectoryTree' object has no attribute '_id'. Did you mean: 'id'?
========================== 8 failed, 56 passed, 1 xfailed, 1 warning in 152.86s (0:02:32) ==========================
It worked when running in debug, but not when running normally.
2023-09-13 01:38:08 +03:00
|
|
|
def test_paint_save_dialog(snap_compare: SnapCompareType, each_theme: None, my_fs: None):
|
2023-09-08 00:43:14 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+s"], terminal_size=LARGER)
|
2023-09-08 00:21:20 +03:00
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_help_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-08 00:43:14 +03:00
|
|
|
assert snap_compare(PAINT, press=["f1"], terminal_size=LARGER)
|
2023-09-08 00:21:20 +03:00
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_view_bitmap(snap_compare: SnapCompareType):
|
2023-09-08 00:21:20 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+f"])
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_invert_and_exit(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-08 00:21:20 +03:00
|
|
|
assert snap_compare(PAINT, press=["ctrl+i", "ctrl+q"])
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_swap_selected_colors(snap_compare: SnapCompareType):
|
|
|
|
async def swap_selected_colors(pilot: Pilot[None]):
|
2023-09-09 02:59:36 +03:00
|
|
|
await pilot.click("CharInput", control=True)
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=swap_selected_colors)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_character_picker_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-09 04:09:08 +03:00
|
|
|
async def open_character_picker(pilot: Pilot[None]):
|
|
|
|
# app.dark = True caused it to fail to open the dialog in the dark theme,
|
|
|
|
# due to `self.call_later(self.refresh_css)` in `watch_dark` in `App`
|
|
|
|
# (verified by replacing `app.dark = args.theme == "dark"` with `app.call_later(app.refresh_css)`)
|
|
|
|
# Adding a delay works around this.
|
|
|
|
await pilot.pause(1.0)
|
2023-09-09 02:59:36 +03:00
|
|
|
await pilot.click("CharInput")
|
|
|
|
await pilot.click("CharInput")
|
2023-09-09 04:09:08 +03:00
|
|
|
assert pilot.app.query_one("CharacterSelectorDialogWindow")
|
2023-09-09 02:59:36 +03:00
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=open_character_picker, terminal_size=LARGER)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_edit_colors_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-09 04:09:08 +03:00
|
|
|
async def open_edit_colors(pilot: Pilot[None]):
|
|
|
|
await pilot.pause(1.0) # see comment in test_paint_character_picker_dialog
|
2023-09-09 02:59:36 +03:00
|
|
|
pilot.app.query("ColorsBox Button")[0].id = "a_color_button"
|
|
|
|
await pilot.click("#a_color_button")
|
|
|
|
await pilot.click("#a_color_button")
|
2023-09-09 04:09:08 +03:00
|
|
|
assert pilot.app.query_one("EditColorsDialogWindow")
|
2023-09-09 02:59:36 +03:00
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=open_edit_colors, terminal_size=LARGEST)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_expand_canvas_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-09 04:40:29 +03:00
|
|
|
async def paste_large_content(pilot: Pilot[None]):
|
2023-09-09 05:12:29 +03:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
# Will be a different class at runtime, per test, due to re-evaluating the module.
|
2023-09-09 06:47:09 +03:00
|
|
|
assert isinstance(pilot.app, PaintApp)
|
2023-09-09 04:40:29 +03:00
|
|
|
pilot.app.paste("a" * 1000)
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=paste_large_content, terminal_size=LARGER)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_error_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-09 05:12:29 +03:00
|
|
|
async def show_error(pilot: Pilot[None]):
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
# Will be a different class at runtime, per test, due to re-evaluating the module.
|
2023-09-09 06:47:09 +03:00
|
|
|
assert isinstance(pilot.app, PaintApp)
|
2023-09-09 05:12:29 +03:00
|
|
|
pilot.app.message_box("EMIT", "Error Message Itself Test", "ok", error=Exception("Error Message Itself Test"))
|
|
|
|
assert pilot.app.query_one("MessageBox")
|
|
|
|
await pilot.pause(1.0)
|
|
|
|
assert pilot.app.query_one("MessageBox .details_button")
|
|
|
|
await pilot.click("MessageBox .details_button")
|
2023-09-09 05:31:44 +03:00
|
|
|
await pilot.pause(0.5) # avoid pressed state
|
2023-09-09 05:12:29 +03:00
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=show_error)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_custom_zoom_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-09 05:21:08 +03:00
|
|
|
async def show_custom_zoom(pilot: Pilot[None]):
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
# Will be a different class at runtime, per test, due to re-evaluating the module.
|
2023-09-09 06:47:09 +03:00
|
|
|
assert isinstance(pilot.app, PaintApp)
|
2023-09-09 05:21:08 +03:00
|
|
|
pilot.app.action_custom_zoom()
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=show_custom_zoom)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_paint_about_paint_dialog(snap_compare: SnapCompareType, each_theme: None):
|
2023-09-09 05:33:44 +03:00
|
|
|
async def show_about_paint(pilot: Pilot[None]):
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
# Will be a different class at runtime, per test, due to re-evaluating the module.
|
2023-09-09 06:47:09 +03:00
|
|
|
assert isinstance(pilot.app, PaintApp)
|
2023-09-09 05:33:44 +03:00
|
|
|
pilot.app.action_about_paint()
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=show_about_paint)
|
|
|
|
|
2023-09-13 04:22:22 +03:00
|
|
|
# TODO: test changing color of in-progress polygon when selecting a color from the palette
|
|
|
|
# TODO: test dragging to define polygon; in particular, dragging can define the first two points at once
|
2023-09-09 08:12:21 +03:00
|
|
|
def test_paint_polygon_tool(snap_compare: SnapCompareType):
|
|
|
|
async def draw_polygon(pilot: Pilot[None]):
|
2023-09-13 04:22:22 +03:00
|
|
|
# TODO: fix polygon closing prematurely, interpreting clicks as double clicks despite the distance,
|
|
|
|
# and then remove as many of these pause() calls as possible
|
2023-09-12 05:23:17 +03:00
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Polygon")
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(3, 2))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(19, 2))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(29, 7))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(11, 7))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(3, 2))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
# first shape (defined above) should be closed by returning to start point
|
|
|
|
|
|
|
|
await click_by_index(pilot, '#available_colors Button', 16) # red
|
|
|
|
await pilot.click('#canvas', offset=Offset(17, 10))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(30, 16))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(49, 16))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(35, 10))
|
2023-09-12 05:23:17 +03:00
|
|
|
# no pause — double click on purpose
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(35, 10))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
# second shape (defined above) should be closed by double clicking
|
|
|
|
|
|
|
|
await click_by_index(pilot, '#available_colors Button', 17) # yellow
|
|
|
|
await pilot.click('#canvas', offset=Offset(33, 2))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(58, 16))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(58, 2))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(44, 2))
|
2023-09-12 05:23:17 +03:00
|
|
|
await pilot.pause(0.3)
|
2023-09-12 04:51:27 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(52, 7))
|
|
|
|
# third shape (defined above) should be left open as a polyline
|
2023-09-09 08:12:21 +03:00
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=draw_polygon, terminal_size=LARGER)
|
|
|
|
|
2023-09-11 20:24:44 +03:00
|
|
|
def test_text_tool_wrapping(snap_compare: SnapCompareType):
|
|
|
|
async def automate_app(pilot: Pilot[None]):
|
2023-09-12 03:07:36 +03:00
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Text")
|
2023-09-12 05:36:31 +03:00
|
|
|
await drag(pilot, '#canvas', [Offset(5, 8), Offset(24, 16)])
|
2023-09-11 21:56:13 +03:00
|
|
|
for key in ('T', 'e', 'x', 't', 'space', 'T', 'o', 'o', 'l', 'space', 'T', 'e', 's', 't', 'space', 'left_parenthesis', 'T', 'T', 'T', 'right_parenthesis', 'n', 'e', 'w', 'space', 'l', 'i', 'n', 'e', 'space', 's', 't', 'a', 'r', 't', 's', 'space', 'h', 'e', 'r', 'e', 'a', 'n', 'd', 'space', 'h', 'e', 'r', 'e', 'space', 'a', 'u', 't', 'o', 'm', 'a', 't', 'i', 'c', 'a', 'l', 'hyphen', 'l', 'y'):
|
2023-09-11 20:24:44 +03:00
|
|
|
await pilot.press(key)
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=automate_app, terminal_size=LARGER)
|
|
|
|
|
|
|
|
def test_text_tool_cursor_keys_and_color(snap_compare: SnapCompareType):
|
|
|
|
async def automate_app(pilot: Pilot[None]):
|
2023-09-12 03:07:36 +03:00
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Text")
|
2023-09-12 05:36:31 +03:00
|
|
|
await drag(pilot, '#canvas', [Offset(8, 5), Offset(21, 10)])
|
2023-09-12 01:55:14 +03:00
|
|
|
for key in ('s', 'end', 'pagedown', '1', 'home', '2', 'pageup', '3', 'end', '4', 'pageup', 'home', 'right', 'right', 'c', 'r', 'e', 't', 'backspace', 'backspace', 'backspace', 'backspace', 'v', '3', 'n'):
|
|
|
|
await pilot.press(key)
|
2023-09-11 20:24:44 +03:00
|
|
|
await pilot.click('#canvas', offset=Offset(9, 10))
|
2023-09-12 01:55:14 +03:00
|
|
|
for key in ('e', 'r', 'o'):
|
|
|
|
await pilot.press(key)
|
2023-09-12 02:28:33 +03:00
|
|
|
await click_by_index(pilot, '#available_colors Button', 9)
|
|
|
|
await click_by_index(pilot, '#available_colors Button', 18, control=True)
|
2023-09-11 20:24:44 +03:00
|
|
|
|
2023-09-12 01:55:14 +03:00
|
|
|
assert snap_compare(PAINT, run_before=automate_app, terminal_size=LARGER)
|
2023-09-11 20:24:44 +03:00
|
|
|
|
2023-09-12 05:37:30 +03:00
|
|
|
def test_free_form_select(snap_compare: SnapCompareType):
|
|
|
|
async def automate_app(pilot: Pilot[None]):
|
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Free-Form Select")
|
|
|
|
await drag(pilot, '#canvas', [Offset(8, 3), Offset(9, 4), Offset(10, 4), Offset(13, 5), Offset(17, 7), Offset(21, 8), Offset(26, 10), Offset(30, 12), Offset(35, 13), Offset(39, 14), Offset(40, 14), Offset(40, 15), Offset(39, 15), Offset(37, 15), Offset(33, 16), Offset(30, 16), Offset(25, 16), Offset(21, 16), Offset(16, 15), Offset(11, 14), Offset(4, 13), Offset(0, 13), Offset(2, 0), Offset(3, 0), Offset(4, 0), Offset(0, 9), Offset(7, 8), Offset(13, 7), Offset(20, 5), Offset(27, 4), Offset(28, 4), Offset(27, 4), Offset(27, 5), Offset(25, 6), Offset(20, 10), Offset(15, 13), Offset(12, 16), Offset(11, 17), Offset(10, 18), Offset(10, 17), Offset(10, 17)])
|
|
|
|
await pilot.press('ctrl+i')
|
|
|
|
await drag(pilot, '#canvas', [Offset(21, 14), Offset(21, 14), Offset(22, 14), Offset(23, 14), Offset(24, 14), Offset(25, 14), Offset(26, 14), Offset(27, 14), Offset(28, 14), Offset(29, 14), Offset(30, 14), Offset(31, 14), Offset(32, 14), Offset(33, 14), Offset(34, 14), Offset(34, 14)])
|
|
|
|
await drag(pilot, '#canvas', [Offset(9, 10), Offset(9, 10), Offset(8, 10), Offset(7, 10), Offset(6, 10), Offset(5, 11), Offset(4, 11), Offset(3, 11), Offset(4, 11), Offset(5, 11), Offset(6, 11), Offset(7, 11), Offset(8, 11), Offset(8, 12), Offset(9, 12), Offset(10, 12), Offset(11, 12), Offset(12, 12), Offset(13, 12), Offset(13, 11), Offset(13, 10), Offset(14, 10), Offset(14, 9), Offset(13, 9), Offset(12, 9), Offset(11, 9), Offset(11, 10), Offset(10, 10), Offset(10, 10)])
|
|
|
|
await pilot.press('ctrl+i')
|
|
|
|
await drag(pilot, '#canvas', [Offset(12, 6), Offset(12, 6), Offset(13, 6), Offset(14, 6), Offset(14, 5), Offset(15, 5), Offset(16, 5), Offset(16, 4), Offset(17, 4), Offset(18, 4), Offset(18, 3), Offset(19, 3), Offset(19, 2), Offset(20, 2), Offset(20, 1), Offset(19, 0), Offset(18, 0), Offset(17, 0), Offset(16, 0), Offset(15, 0), Offset(14, 0), Offset(13, 0), Offset(12, 0), Offset(12, 1), Offset(11, 1), Offset(10, 1), Offset(9, 2), Offset(8, 2), Offset(7, 3), Offset(6, 3), Offset(6, 3)])
|
|
|
|
await pilot.press('delete')
|
|
|
|
await drag(pilot, '#canvas', [Offset(47, 10), Offset(47, 10), Offset(46, 10), Offset(46, 11), Offset(45, 12), Offset(45, 13), Offset(45, 14), Offset(45, 15), Offset(45, 16), Offset(46, 17), Offset(47, 18), Offset(48, 18), Offset(49, 18), Offset(50, 18), Offset(50, 19), Offset(51, 19), Offset(52, 19), Offset(53, 19), Offset(54, 19), Offset(55, 19), Offset(56, 19), Offset(57, 18), Offset(58, 18), Offset(59, 17), Offset(60, 17), Offset(60, 16), Offset(61, 16), Offset(61, 15), Offset(61, 14), Offset(60, 14), Offset(60, 13), Offset(59, 12), Offset(58, 11), Offset(57, 11), Offset(57, 10), Offset(56, 10), Offset(55, 10), Offset(55, 10)])
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=automate_app, terminal_size=LARGER)
|
2023-09-13 01:46:53 +03:00
|
|
|
|
|
|
|
def test_free_form_select_meld_negative_coords(snap_compare: SnapCompareType):
|
|
|
|
async def automate_app(pilot: Pilot[None]):
|
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Fill With Color")
|
|
|
|
await click_by_index(pilot, '#available_colors Button', 17) # yellow
|
|
|
|
await pilot.click('#canvas', offset=Offset(19, 8))
|
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Free-Form Select")
|
|
|
|
await drag(pilot, '#editing_area', [Offset(19, 1), Offset(19, 1), Offset(18, 2), Offset(17, 2), Offset(15, 3), Offset(13, 4), Offset(6, 6), Offset(2, 8), Offset(0, 10), Offset(3, 2), Offset(2, 0), Offset(2, 1), Offset(2, 2), Offset(3, 2), Offset(5, 2), Offset(14, 14), Offset(1, 14), Offset(1, 13), Offset(4, 13), Offset(8, 12), Offset(12, 11), Offset(16, 11), Offset(20, 10), Offset(22, 10), Offset(23, 9), Offset(24, 9), Offset(25, 9), Offset(26, 9), Offset(26, 8), Offset(25, 8), Offset(23, 7), Offset(19, 6), Offset(15, 6), Offset(11, 5), Offset(6, 3), Offset(3, 2), Offset(2, 1), Offset(2, 0), Offset(3, 0), Offset(3, 0)])
|
|
|
|
await drag(pilot, '#canvas', [Offset(13, 8), Offset(13, 8), Offset(12, 8), Offset(12, 7), Offset(12, 6), Offset(11, 6), Offset(11, 5), Offset(10, 5), Offset(10, 4), Offset(9, 4), Offset(8, 3), Offset(8, 3)])
|
|
|
|
await pilot.press('ctrl+i')
|
2023-09-13 03:16:55 +03:00
|
|
|
await pilot.pause(0.5)
|
2023-09-13 01:46:53 +03:00
|
|
|
await pilot.click('#editing_area', offset=Offset(0, 20)) # deselect
|
2023-09-13 03:16:55 +03:00
|
|
|
await pilot.pause(0.5)
|
2023-09-13 01:46:53 +03:00
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=automate_app, terminal_size=LARGER)
|
2023-09-12 05:37:30 +03:00
|
|
|
|
2023-09-12 05:42:10 +03:00
|
|
|
def test_select(snap_compare: SnapCompareType):
|
|
|
|
async def automate_app(pilot: Pilot[None]):
|
|
|
|
await click_by_attr(pilot, "ToolsBox Button", "tooltip", "Select")
|
|
|
|
await drag(pilot, '#canvas', [Offset(5, 3), Offset(5, 3), Offset(6, 4), Offset(8, 5), Offset(9, 6), Offset(11, 7), Offset(13, 8), Offset(14, 10), Offset(15, 10), Offset(16, 11), Offset(17, 11), Offset(18, 12), Offset(19, 12), Offset(19, 13), Offset(20, 13), Offset(21, 13), Offset(21, 14), Offset(22, 14), Offset(22, 14)])
|
|
|
|
await pilot.press('ctrl+i')
|
|
|
|
await drag(pilot, '#canvas', [Offset(12, 8), Offset(13, 8), Offset(13, 9), Offset(14, 10), Offset(15, 12), Offset(16, 13), Offset(18, 14), Offset(19, 15), Offset(20, 15), Offset(20, 16), Offset(21, 16), Offset(22, 17), Offset(23, 17), Offset(24, 17), Offset(24, 16), Offset(24, 15), Offset(24, 14), Offset(25, 13), Offset(25, 13)])
|
|
|
|
await drag(pilot, '#canvas', [Offset(7, 13), Offset(7, 13), Offset(8, 13), Offset(9, 14), Offset(10, 15), Offset(11, 15), Offset(12, 16), Offset(13, 17), Offset(15, 17), Offset(16, 18), Offset(17, 19), Offset(18, 19), Offset(19, 19), Offset(19, 20), Offset(20, 20), Offset(21, 20), Offset(22, 21), Offset(23, 21), Offset(24, 21), Offset(24, 22), Offset(25, 22), Offset(25, 22)])
|
|
|
|
await pilot.press('ctrl+i')
|
|
|
|
await drag(pilot, '#canvas', [Offset(12, 3), Offset(12, 3), Offset(13, 4), Offset(14, 5), Offset(14, 6), Offset(15, 7), Offset(16, 7), Offset(17, 8), Offset(18, 9), Offset(19, 9), Offset(20, 10), Offset(21, 10), Offset(22, 10), Offset(23, 11), Offset(24, 12), Offset(25, 12), Offset(26, 12), Offset(26, 13), Offset(27, 13), Offset(28, 14), Offset(29, 14), Offset(30, 14), Offset(30, 15), Offset(31, 15), Offset(32, 15), Offset(32, 16), Offset(33, 16), Offset(34, 16), Offset(35, 16), Offset(36, 16), Offset(37, 16), Offset(38, 16), Offset(38, 15), Offset(39, 15), Offset(40, 15), Offset(41, 15), Offset(42, 16), Offset(41, 16), Offset(41, 16)])
|
|
|
|
await drag(pilot, '#canvas', [Offset(34, 13), Offset(34, 13), Offset(34, 12), Offset(35, 12), Offset(35, 11), Offset(35, 10), Offset(35, 9), Offset(36, 9), Offset(37, 9), Offset(38, 9), Offset(39, 9), Offset(40, 9), Offset(39, 9), Offset(38, 9), Offset(38, 9)],
|
|
|
|
control=True) # duplicate selection with Ctrl
|
|
|
|
await drag(pilot, '#canvas', [Offset(2, 6), Offset(2, 6), Offset(3, 6), Offset(4, 6), Offset(4, 7), Offset(5, 7), Offset(6, 7), Offset(7, 8), Offset(8, 8), Offset(8, 9), Offset(10, 9), Offset(11, 10), Offset(12, 11), Offset(13, 11), Offset(13, 12), Offset(15, 12), Offset(16, 13), Offset(17, 13), Offset(18, 14), Offset(19, 14), Offset(20, 15), Offset(21, 16), Offset(23, 16), Offset(24, 17), Offset(25, 17), Offset(25, 18), Offset(26, 18), Offset(27, 18), Offset(28, 18), Offset(29, 18), Offset(30, 18), Offset(31, 18), Offset(32, 18), Offset(33, 18), Offset(33, 18)])
|
|
|
|
|
|
|
|
assert snap_compare(PAINT, run_before=automate_app, terminal_size=LARGER)
|
|
|
|
|
2023-09-09 06:38:37 +03:00
|
|
|
def test_gallery_app(snap_compare: SnapCompareType):
|
2023-09-07 22:14:07 +03:00
|
|
|
assert snap_compare(GALLERY)
|
|
|
|
|