Commit Graph

39 Commits

Author SHA1 Message Date
Isaiah Odhner
6f13a5a302 Clean up 2023-09-12 21:23:36 -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
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
85d601b262 Refactor tests that click a tool button 2023-09-11 21:16:13 -04:00
Isaiah Odhner
95d89a6e7f Move Pilot test helpers to a file 2023-09-11 19:40:20 -04:00
Isaiah Odhner
0d9f62a4c3 Skip polygon test for now 2023-09-11 19:24:41 -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
5426ed14a3 Use semantic key synonym 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
86a403eb80 WIP: test polygon tool prematurely closing polygon 2023-09-11 17:56:03 -04:00
Isaiah Odhner
1ae3cbe37b WIP: test polygon tool 2023-09-11 17:56:02 -04:00
Isaiah Odhner
9f12ba7833 Guard against future usage of unique class at runtime 2023-09-08 23:49:52 -04:00
Isaiah Odhner
cc6b0d65c0 Unify import style 2023-09-08 23:47:09 -04:00
Isaiah Odhner
d2300c96a3 Annotate tests with type hints 2023-09-08 23:38:37 -04:00
Isaiah Odhner
ad9ff303f1 Update fixture docstring 2023-09-08 22:53:58 -04:00
Isaiah Odhner
3cc70b6e4d Test About Paint dialog 2023-09-08 22:46:52 -04:00
Isaiah Odhner
ea5ca0c90f Fix flaky test due to pressed style of Show Details button 2023-09-08 22:40:49 -04:00
Isaiah Odhner
c5353f4e7b Test custom zoom dialog 2023-09-08 22:32:53 -04:00
Isaiah Odhner
628170d213 Test error dialog
It's pretty ridiculous in the expanded state, but better to show it.
Also, the button text isn't switching to "Hide Details".
2023-09-08 22:32:53 -04:00
Isaiah Odhner
faa9cefe85 Test expand canvas dialog 2023-09-08 21:40:29 -04:00
Isaiah Odhner
1b9cfe51b3 Fix dark mode tests failing to open dialogs
This was kind of mind-boggling, narrowing it down to dark mode,
and then to dark mode *but not CSS*.
I kept narrowing it down, and looked into how dark mode was implemented,
and finally figured this out.
`app.call_later(app.refresh_css)` in `App.watch_dark` causes a timing issue.
2023-09-08 21:29:12 -04:00
Isaiah Odhner
90f52f0655 It's not flaky... it's really the dark mode tests that are failing 2023-09-08 21:21:58 -04:00
Isaiah Odhner
278490f7d5 WIP: Add more tests 2023-09-08 20:03:30 -04:00
Isaiah Odhner
b9822a8290 Fix flaky tests due to cursor blinking 2023-09-08 20:03:17 -04:00
Isaiah Odhner
57ae2e8d44 Clean up 2023-09-08 20:03:17 -04:00
Isaiah Odhner
18c38fd3e0 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 14:44:56 -04:00
Isaiah Odhner
f09f5fbf55 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 02:18:48 -04:00
Isaiah Odhner
11939b6603 Resize screenshots for some tests 2023-09-07 17:46:48 -04:00
Isaiah Odhner
7d88cbe44f Rename test to match 2023-09-07 17:23:35 -04:00
Isaiah Odhner
f8f2d5ea01 Add snapshot tests for more keyboard-accessible dialogs 2023-09-07 17:22:34 -04:00
Isaiah Odhner
ed97f0afb0 WIP: set up snapshot testing 2023-09-07 15:19:10 -04:00