mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-07-17 11:38:09 +00:00
替换音频设置模块的硬编码本地路径为资源目录工具函数,提升跨平台兼容性 修正字幕预览方向选择组件的默认值处理逻辑,移除冗余的默认参数 新增Streamlit组件会话状态合规性测试,确保带key的组件不会同时声明default参数 更新.gitignore以允许提交新增的测试文件
85 lines
2.5 KiB
Python
85 lines
2.5 KiB
Python
"""Regression coverage for Streamlit widget state initialization."""
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).parents[1]
|
|
|
|
|
|
def _attribute_name(node: ast.expr) -> str | None:
|
|
if isinstance(node, ast.Name):
|
|
return node.id
|
|
if isinstance(node, ast.Attribute):
|
|
parent = _attribute_name(node.value)
|
|
return f"{parent}.{node.attr}" if parent else node.attr
|
|
return None
|
|
|
|
|
|
def _session_state_key(target: ast.expr) -> str | None:
|
|
if not isinstance(target, ast.Subscript):
|
|
return None
|
|
if _attribute_name(target.value) != "st.session_state":
|
|
return None
|
|
if isinstance(target.slice, ast.Constant) and isinstance(target.slice.value, str):
|
|
return target.slice.value
|
|
return None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("source_path", "function_name", "widget_key"),
|
|
[
|
|
(
|
|
"webui/components/audio_settings.py",
|
|
"render_bgm_settings",
|
|
"bgm_source_selection",
|
|
),
|
|
(
|
|
"webui/components/subtitle_settings.py",
|
|
"_render_subtitle_preview_image",
|
|
"subtitle_preview_orientation",
|
|
),
|
|
],
|
|
)
|
|
def test_state_initialized_widgets_do_not_also_declare_defaults(
|
|
source_path: str,
|
|
function_name: str,
|
|
widget_key: str,
|
|
):
|
|
"""Streamlit warns when a keyed widget has both a default and state value."""
|
|
module = ast.parse((PROJECT_ROOT / source_path).read_text(encoding="utf-8"))
|
|
function = next(
|
|
node
|
|
for node in module.body
|
|
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
|
)
|
|
|
|
assigned_keys = {
|
|
key
|
|
for node in ast.walk(function)
|
|
if isinstance(node, ast.Assign)
|
|
for target in node.targets
|
|
if (key := _session_state_key(target)) is not None
|
|
}
|
|
assert widget_key in assigned_keys
|
|
|
|
widget_calls = []
|
|
for node in ast.walk(function):
|
|
if not isinstance(node, ast.Call):
|
|
continue
|
|
widget_name = _attribute_name(node.func)
|
|
keywords = {keyword.arg: keyword.value for keyword in node.keywords}
|
|
key_node = keywords.get("key")
|
|
if (
|
|
widget_name is not None
|
|
and widget_name.startswith("st.")
|
|
and isinstance(key_node, ast.Constant)
|
|
and key_node.value == widget_key
|
|
):
|
|
widget_calls.append(keywords)
|
|
|
|
assert widget_calls
|
|
assert all("default" not in keywords for keywords in widget_calls)
|