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")