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.
This commit is contained in:
Isaiah Odhner 2023-09-12 18:38:08 -04:00
parent 370b497cc6
commit 7f91138a63
4 changed files with 47 additions and 4 deletions

View File

@ -118,6 +118,7 @@
"psutil",
"pybabel",
"pycache",
"pyfakefs",
"pyfiglet",
"Pylance",
"pyobjc",

View File

@ -14,3 +14,4 @@ types-Pillow==10.0.0.1 # for development
types-psutil==5.9.5.15 # for development
pytest==7.4.1 # for development
pytest-textual-snapshot==0.4.0 # for development
pyfakefs==5.2.4 # for development

View File

@ -163,7 +163,11 @@ class EnhancedDirectoryTree(DirectoryTree):
if callback is None:
callback = lambda: None
self._expand_matching_child(self.root, target_path.parts[1:], callback)
remaining_parts = target_path.parts[1:]
# True unless target_path is the root (edge case which occurs when testing with pyfakefs).
if len(remaining_parts) > 1:
self._expand_matching_child(self.root, remaining_parts, callback)
def render_label(
self, node: TreeNode[DirEntry], base_style: Style, style: Style

View File

@ -1,12 +1,17 @@
from pathlib import Path, PurePath
from typing import TYPE_CHECKING, Awaitable, Callable, Iterable, Protocol
from typing import (TYPE_CHECKING, Awaitable, Callable, Generator, Iterable,
Protocol)
import pyfakefs
import pytest
from pyfakefs.fake_file import FakeDirectory
from pyfakefs.fake_filesystem import FakeFilesystem
from textual.geometry import Offset
from textual.pilot import Pilot
from textual.widgets import Input
from tests.pilot_helpers import click_by_attr, click_by_index, drag
from textual_paint.figlet_font_writer import FIGletFontWriter
if TYPE_CHECKING:
# When tests are run, paint.py is re-evaluated,
@ -58,6 +63,38 @@ def each_theme(request: pytest.FixtureRequest):
args.theme = "light"
args.ascii_only = False
APPS_DIR_ABSOLUTE = (Path(__file__).parent / APPS_DIR).resolve()
TESTS_DIR_ABSOLUTE = Path(__file__).parent.resolve()
@pytest.fixture
def my_fs(fs: FakeFilesystem) -> Generator[FakeFilesystem, None, None]:
print("adding real directory", APPS_DIR_ABSOLUTE)
fs.add_real_directory(APPS_DIR_ABSOLUTE)
# Without this, pytest-textual-snapshot will show "No history for this test"
print("adding real directory", TESTS_DIR_ABSOLUTE)
fs.add_real_directory(TESTS_DIR_ABSOLUTE)
# TODO: use proper mocking or figure out how to get FigletFont to find the real font files.
# 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
def test_paint_app(snap_compare: SnapCompareType, each_theme: None):
assert snap_compare(PAINT, terminal_size=LARGER)
@ -71,10 +108,10 @@ def test_paint_flip_rotate_dialog(snap_compare: SnapCompareType, each_theme: Non
def test_paint_image_attributes_dialog(snap_compare: SnapCompareType, each_theme: None):
assert snap_compare(PAINT, press=["ctrl+e"])
def test_paint_open_dialog(snap_compare: SnapCompareType, each_theme: None):
def test_paint_open_dialog(snap_compare: SnapCompareType, each_theme: None, my_fs: None):
assert snap_compare(PAINT, press=["ctrl+o"], terminal_size=LARGER)
def test_paint_save_dialog(snap_compare: SnapCompareType, each_theme: None):
def test_paint_save_dialog(snap_compare: SnapCompareType, each_theme: None, my_fs: None):
assert snap_compare(PAINT, press=["ctrl+s"], terminal_size=LARGER)
def test_paint_help_dialog(snap_compare: SnapCompareType, each_theme: None):