102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
"""
|
|
Regression test for: AttributeError: module 'tkinter' has no attribute 'filedialog'
|
|
|
|
On some Python installs (e.g., embedded distributions, or installs where
|
|
the Tcl/Tk runtime is missing), the `tkinter` package imports cleanly but
|
|
the `tkinter.filedialog` sub-module fails to load. The original `_LazyModule`
|
|
in src/gui_2.py used `getattr(tkinter, 'filedialog')` which raises a
|
|
confusing AttributeError at the call site. With 14 call sites in
|
|
render_projects_panel, render_workspace_settings_hub, render_fonts_panel,
|
|
and render_gemini_cli_settings, this AttributeError spammed the GUI's
|
|
stderr at 60fps whenever the Project Settings window was open.
|
|
|
|
The fix must make `_LazyModule` fall back to a stub that mimics
|
|
`tkinter.filedialog`'s public API (askopenfilename, askdirectory,
|
|
asksaveasfilename, askopenfilenames) so the GUI does not crash.
|
|
|
|
This test uses a deliberately-missing sub-module to exercise the fallback
|
|
path, making it deterministic across Python installs.
|
|
"""
|
|
import pytest
|
|
import importlib
|
|
|
|
from src.gui_2 import _LazyModule
|
|
|
|
|
|
def test_lazymodule_falls_back_to_stub_on_attribute_error() -> None:
|
|
"""
|
|
Resolution must NOT raise AttributeError when the sub-module is
|
|
missing. Instead, _resolve() must return a stub that exposes the
|
|
public filedialog API. Before the fix, this test fails with
|
|
AttributeError: module 'os' has no attribute 'this_submodule_does_not_exist'.
|
|
"""
|
|
bad = _LazyModule("os", "this_submodule_does_not_exist")
|
|
resolved = bad._resolve()
|
|
assert resolved is not None
|
|
assert hasattr(resolved, "askopenfilename")
|
|
assert hasattr(resolved, "askdirectory")
|
|
assert hasattr(resolved, "asksaveasfilename")
|
|
assert hasattr(resolved, "askopenfilenames")
|
|
|
|
|
|
def test_lazymodule_stub_returns_empty_strings() -> None:
|
|
"""
|
|
The stub functions must return safe empty values:
|
|
- askopenfilename, askdirectory, asksaveasfilename: empty string ""
|
|
- askopenfilenames: empty tuple ()
|
|
This ensures downstream code that does `if p and p not in app.x:`
|
|
or `if paths:` treats the missing-dialog as a no-op.
|
|
"""
|
|
bad = _LazyModule("os", "this_submodule_does_not_exist")
|
|
resolved = bad._resolve()
|
|
assert resolved.askopenfilename() == ""
|
|
assert resolved.askdirectory() == ""
|
|
assert resolved.asksaveasfilename() == ""
|
|
assert resolved.askopenfilenames() == ()
|
|
|
|
|
|
def test_lazymodule_stub_ignores_kwargs() -> None:
|
|
"""
|
|
The stub must accept the same kwargs the real tkinter.filedialog
|
|
accepts (title, filetypes, defaultextension, initialdir) and return
|
|
the empty sentinel. This prevents TypeError if a call site passes
|
|
kwargs that the stub does not know about.
|
|
"""
|
|
bad = _LazyModule("os", "this_submodule_does_not_exist")
|
|
resolved = bad._resolve()
|
|
assert resolved.askopenfilename(title="x", filetypes=[("All", "*.*")]) == ""
|
|
assert resolved.askdirectory(title="y", initialdir="/") == ""
|
|
assert resolved.asksaveasfilename(title="z", defaultextension=".toml", filetypes=[("TOML", "*.toml")]) == ""
|
|
assert resolved.askopenfilenames(filetypes=[("Image", "*.png")]) == ()
|
|
|
|
|
|
def test_lazymodule_real_filedialog_resolves_when_tkinter_works() -> None:
|
|
"""
|
|
On a working tkinter install (with Tcl/Tk runtime), the
|
|
`_LazyModule("tkinter", "filedialog")` instance must resolve to the
|
|
real tkinter.filedialog module. This is the smoke test: if tkinter
|
|
is healthy, the lazy import works as before.
|
|
"""
|
|
import tkinter as tk_root
|
|
try:
|
|
import tkinter.filedialog as real_filedialog
|
|
except (ImportError, AttributeError, tk_root.TclError):
|
|
pytest.skip("tkinter.filedialog not available in this Python install")
|
|
lazy = _LazyModule("tkinter", "filedialog")
|
|
resolved = lazy._resolve()
|
|
assert resolved is real_filedialog
|
|
|
|
|
|
def test_lazymodule_real_filedialog_does_not_raise_attribute_error() -> None:
|
|
"""
|
|
On a working tkinter install, calling .askopenfilename() through the
|
|
lazy module must not raise AttributeError. (Tests the call path
|
|
used by 14 call sites in render_projects_panel etc.)
|
|
"""
|
|
lazy = _LazyModule("tkinter", "filedialog")
|
|
resolved = lazy._resolve()
|
|
assert hasattr(resolved, "askopenfilename")
|
|
assert hasattr(resolved, "askdirectory")
|
|
assert hasattr(resolved, "asksaveasfilename")
|
|
assert hasattr(resolved, "askopenfilenames")
|