From 990fb36e98e605a43cd0bde25b7f018f823f7231 Mon Sep 17 00:00:00 2001 From: viccy Date: Mon, 13 Jul 2026 03:48:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(webui,=20tests):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=A1=AC=E7=BC=96=E7=A0=81=E8=B7=AF=E5=BE=84=E4=B8=8E=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E9=BB=98=E8=AE=A4=E5=80=BC=E9=97=AE=E9=A2=98=E5=B9=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 替换音频设置模块的硬编码本地路径为资源目录工具函数,提升跨平台兼容性 修正字幕预览方向选择组件的默认值处理逻辑,移除冗余的默认参数 新增Streamlit组件会话状态合规性测试,确保带key的组件不会同时声明default参数 更新.gitignore以允许提交新增的测试文件 --- .gitignore | 1 + tests/test_streamlit_widget_session_state.py | 84 ++++++++++++++++++++ webui/components/audio_settings.py | 5 +- webui/components/subtitle_settings.py | 3 +- 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 tests/test_streamlit_widget_session_state.py diff --git a/.gitignore b/.gitignore index f790c04..4cbb810 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/tests/test_streamlit_widget_session_state.py b/tests/test_streamlit_widget_session_state.py new file mode 100644 index 0000000..cd8b0c9 --- /dev/null +++ b/tests/test_streamlit_widget_session_state.py @@ -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) diff --git a/webui/components/audio_settings.py b/webui/components/audio_settings.py index c9c2332..f5e606c 100644 --- a/webui/components/audio_settings.py +++ b/webui/components/audio_settings.py @@ -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"), diff --git a/webui/components/subtitle_settings.py b/webui/components/subtitle_settings.py index 22e88e8..7d1c81b 100644 --- a/webui/components/subtitle_settings.py +++ b/webui/components/subtitle_settings.py @@ -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")