fix(webui, tests): 修复硬编码路径与组件默认值问题并新增测试

替换音频设置模块的硬编码本地路径为资源目录工具函数,提升跨平台兼容性
修正字幕预览方向选择组件的默认值处理逻辑,移除冗余的默认参数
新增Streamlit组件会话状态合规性测试,确保带key的组件不会同时声明default参数
更新.gitignore以允许提交新增的测试文件
This commit is contained in:
viccy 2026-07-13 03:48:05 +08:00
parent 2fbd3d0c25
commit 990fb36e98
4 changed files with 88 additions and 5 deletions

1
.gitignore vendored
View File

@ -53,6 +53,7 @@ tests/*
!tests/test_script_service_documentary_unittest.py
!tests/test_generate_narration_script_documentary_unittest.py
!tests/test_generate_script_docu_unittest.py
!tests/test_streamlit_widget_session_state.py
docs/reddit-community
docs/wechat-0.8

View File

@ -0,0 +1,84 @@
"""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)

View File

@ -9,7 +9,7 @@ from app.models.schema import AudioVolumeDefaults
from app.utils import utils
INDEXTTS_REFERENCE_AUDIO_SOURCE_DIR = "/Users/viccy/Downloads/tts-mp3-clone/mp3"
INDEXTTS_REFERENCE_AUDIO_SOURCE_DIR = utils.resource_dir("tts_reference_audio")
INDEXTTS_REFERENCE_AUDIO_COPY_SUBDIR = "indextts_refs"
INDEXTTS_REFERENCE_AUDIO_MAP = [
("yingshijieshuo-zh-male.mp3", "影视解说", "Film Narration"),
@ -36,7 +36,7 @@ INDEXTTS_REFERENCE_AUDIO_MAP = [
("sarah-en-female.mp3", "莎拉", "Sarah"),
]
INDEXTTS_REFERENCE_AUDIO_EXTENSIONS = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg")
BGM_RESOURCE_DIR = "/Users/viccy/Downloads/tts-mp3-clone/bgms-safe"
BGM_RESOURCE_DIR = utils.resource_dir("bgm")
BGM_TRACKS_JSON = os.path.join(BGM_RESOURCE_DIR, "tracks.json")
BGM_UPLOAD_SUBDIR = "uploaded_bgms"
BGM_AUDIO_EXTENSIONS = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg")
@ -1965,7 +1965,6 @@ def render_bgm_settings(tr):
tr("Background Music Source"),
options=list(bgm_source_labels.keys()),
selection_mode="single",
default=default_bgm_source,
key="bgm_source_selection",
format_func=lambda source: tr(bgm_source_labels[source]),
help=tr("Background Music Source Help"),

View File

@ -788,11 +788,10 @@ def _render_subtitle_preview_image(tr):
orientation = st.pills(
tr("Subtitle Preview Orientation"),
options=SUBTITLE_PREVIEW_ORIENTATIONS,
default=orientation_default,
format_func=lambda value: tr(SUBTITLE_PREVIEW_ORIENTATION_LABELS[value]),
key="subtitle_preview_orientation",
width="stretch",
) or "portrait"
) or orientation_default
_render_subtitle_position_controls(tr, orientation)
preview_text = tr("Subtitle Preview Sample Text")