Commit Graph

123 Commits

Author SHA1 Message Date
Isaiah Odhner
06344fb8de Silence type checker warnings (reportOptionalMemberAccess) 2023-09-16 23:31:08 -04:00
Isaiah Odhner
0791b1c080 Satisfy the type checker
`PYRIGHT_PYTHON_FORCE_VERSION=1.1.327 pyright` now gives 0 errors

(before this commit it was 16 errors)
2023-09-16 23:31:08 -04:00
Isaiah Odhner
a6b5cb31be Clean up mocked method FigletFont.preloadFont 2023-09-16 23:31:08 -04:00
Isaiah Odhner
0f617dd8c4 Dynamically theme message box icons 2023-09-15 22:03:34 -04:00
Isaiah Odhner
7a13659d48 Fix "Show Details" not changing to "Hide Details" when expanding error 2023-09-15 20:44:40 -04:00
Isaiah Odhner
fc1baa9815 Move imports so that Organize Imports doesn't break it
Before, if I ran Organize Imports in VS Code, it reordered things such that `sys.path.insert` was after the import that needed it.
Now it's stable.
2023-09-15 19:56:27 -04:00
Isaiah Odhner
857e459b2d Get tests running, if not passing, on Windows 2023-09-15 17:08:13 -04:00
Isaiah Odhner
40291f29fd Add fake folders to mask OS differences in snapshot tests
Now the tests pass on macOS, not just Ubuntu. Not sure about Windows.
2023-09-15 17:08:13 -04:00
Isaiah Odhner
7fa80b853c Fix flaky test due to pressed button style 2023-09-14 20:22:48 -04:00
Isaiah Odhner
91b2c285fe Re-color in-progress polygon/curve immediately 2023-09-14 20:04:31 -04:00
Isaiah Odhner
ad39f34084 Add Polygon test with dragging 2023-09-14 16:58:47 -04:00
Isaiah Odhner
3f1c6c964a Fix Polygon tool detecting double clicks despite distance 2023-09-14 16:32:40 -04:00
Isaiah Odhner
b6d0d2cc7b Delete debug scratchpad for polygon test 2023-09-14 15:09:22 -04:00
Isaiah Odhner
bf8e445f69 Make palette state local to the app instance 2023-09-14 02:31:49 -04:00
Isaiah Odhner
faa9c76ba8 Test file drag-and-drop handling 2023-09-14 02:03:52 -04:00
Isaiah Odhner
070e253394 Move fixtures to conftest.py 2023-09-14 01:17:01 -04:00
Isaiah Odhner
5a7ba995c0 Accept super trivial change to About dialog snapshots
The scrollbar handle is slightly taller now because I removed the `--recode-samples` option.
2023-09-13 02:37:37 -04:00
Isaiah Odhner
c538787ff2 Recurse into subfolders for re-encoding round trip test
There are many more failures now, which I should probably take a look at
at some point, but I don't know how to best inspect the changes yet.
I'll mark these as xfail (expected to fail) for now.
2023-09-13 02:24:25 -04:00
Isaiah Odhner
bb047c04e7 Move --recode-samples to an automated test 2023-09-12 21:54:19 -04:00
Isaiah Odhner
6f13a5a302 Clean up 2023-09-12 21:23:36 -04:00
Isaiah Odhner
1f1bf577f2 Disable tool icon swaps when running in pytest 2023-09-12 21:08:24 -04:00
Isaiah Odhner
08847ca91b Fix Free-Form Select behavior when melding with negative coordinates 2023-09-12 20:16:55 -04:00
Isaiah Odhner
96fe28269d Accept changes to file dialog snapshots
...but NOT the Free-Form Select test! I'm noticing that when marked
as skipped, running with --snapshot-update deletes the snapshot.
So neither xfail or skip (or xfail with run=False) is a good solution.
It's almost as if people don't normally do TDD with snapshots.

Anyways, HOPEFULLY I can move on now, to actually fixing things,
like the Polygon tool, and new bugs I've found while adding tests.
2023-09-12 19:45:41 -04:00
Isaiah Odhner
d6705912b3 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-12 19:45:41 -04:00
Isaiah Odhner
dce55cd833 Fix further errors with pyfakefs 2023-09-12 19:45:41 -04:00
Isaiah Odhner
7f91138a63 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-12 19:45:41 -04:00
Isaiah Odhner
bb8e968f6c Test Free-Form Select off-screen melding bug 2023-09-12 19:45:41 -04:00
Isaiah Odhner
48b15319da Test Select tool 2023-09-11 22:42:10 -04:00
Isaiah Odhner
2206df20bd Test Free-Form Select tool 2023-09-11 22:37:30 -04:00
Isaiah Odhner
ad86491ada Shorten Offset arguments 2023-09-11 22:36:31 -04:00
Isaiah Odhner
42e3104c7e Tighten Polygon test timing
and find the tool button by tooltip text
2023-09-11 22:25:38 -04:00
Isaiah Odhner
ec774c264a Test Polygon tool! 2023-09-11 22:11:02 -04:00
Isaiah Odhner
e849af1dcd Add to todos 2023-09-11 21:42:09 -04:00
Isaiah Odhner
304f18c3f4 Import helpers instead of including them in generated code 2023-09-11 21:42:09 -04:00
Isaiah Odhner
85d601b262 Refactor tests that click a tool button 2023-09-11 21:16:13 -04:00
Isaiah Odhner
6e7a3445e0 DRY widget clicking helpers (use one in the other) 2023-09-11 20:23:15 -04:00
Isaiah Odhner
95d89a6e7f Move Pilot test helpers to a file 2023-09-11 19:40:20 -04:00
Isaiah Odhner
7f379103a6 Accept new Text tool test snapshots 2023-09-11 19:35:04 -04:00
Isaiah Odhner
0d9f62a4c3 Skip polygon test for now 2023-09-11 19:24:41 -04:00
Isaiah Odhner
6c108294b8 Add modifier options to click_by_index (not recorded yet) 2023-09-11 19:12:26 -04:00
Isaiah Odhner
d6399022ee Fix/cleanup test_text_tool_cursor_keys_and_color
- Fix `APP_PATH` reference error
- Hold Ctrl to set the foreground color (modifiers not recorded yet)
- DRY `pilot.press`
- Use same terminal size as many other tests
- Seven isn't spelt with a 2, but zero is! ;)
2023-09-11 19:10:18 -04:00
Isaiah Odhner
3b76ba0997 Accept mysterious ID changes to file dialog snapshots
WITHOUT accepting new WIP Polygon and Text tool tests
2023-09-11 18:45:21 -04:00
Isaiah Odhner
5426ed14a3 Use semantic key synonym 2023-09-11 18:02:20 -04:00
Isaiah Odhner
155b0e4c99 Wait only at start of playback, not before each drag/click in test 2023-09-11 18:02:20 -04:00
Isaiah Odhner
2b447a06d6 WIP: Polygon and Text tool tests 2023-09-11 18:02:20 -04:00
Isaiah Odhner
58ddc7e856 Add a word in docstring
I would've named the class `TestRecorder` if the file name `test_recorder.py` wouldn't be picked up by pytest as a test file...
2023-09-11 18:02:20 -04:00
Isaiah Odhner
2d03709565 Rename placeholders to sound less hand-wavy 2023-09-11 18:02:20 -04:00
Isaiah Odhner
a844cd7967 Make output_file a parameter; add __init__ docstring 2023-09-11 18:02:20 -04:00
Isaiah Odhner
405cc1bf4e Make test recorder generic to apps
The only mention of Paint is at construction time now.
2023-09-11 18:02:20 -04:00
Isaiah Odhner
c5e96ef580 Add todos to module-level docstring 2023-09-11 18:02:20 -04:00