mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-08-01 19:05:52 +00:00
fix(webui, tests): 修复硬编码路径与组件默认值问题并新增测试
替换音频设置模块的硬编码本地路径为资源目录工具函数,提升跨平台兼容性 修正字幕预览方向选择组件的默认值处理逻辑,移除冗余的默认参数 新增Streamlit组件会话状态合规性测试,确保带key的组件不会同时声明default参数 更新.gitignore以允许提交新增的测试文件
This commit is contained in:
parent
2fbd3d0c25
commit
990fb36e98
1
.gitignore
vendored
1
.gitignore
vendored
@ -53,6 +53,7 @@ tests/*
|
|||||||
!tests/test_script_service_documentary_unittest.py
|
!tests/test_script_service_documentary_unittest.py
|
||||||
!tests/test_generate_narration_script_documentary_unittest.py
|
!tests/test_generate_narration_script_documentary_unittest.py
|
||||||
!tests/test_generate_script_docu_unittest.py
|
!tests/test_generate_script_docu_unittest.py
|
||||||
|
!tests/test_streamlit_widget_session_state.py
|
||||||
|
|
||||||
docs/reddit-community
|
docs/reddit-community
|
||||||
docs/wechat-0.8
|
docs/wechat-0.8
|
||||||
|
|||||||
84
tests/test_streamlit_widget_session_state.py
Normal file
84
tests/test_streamlit_widget_session_state.py
Normal 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)
|
||||||
@ -9,7 +9,7 @@ from app.models.schema import AudioVolumeDefaults
|
|||||||
from app.utils import utils
|
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_COPY_SUBDIR = "indextts_refs"
|
||||||
INDEXTTS_REFERENCE_AUDIO_MAP = [
|
INDEXTTS_REFERENCE_AUDIO_MAP = [
|
||||||
("yingshijieshuo-zh-male.mp3", "影视解说", "Film Narration"),
|
("yingshijieshuo-zh-male.mp3", "影视解说", "Film Narration"),
|
||||||
@ -36,7 +36,7 @@ INDEXTTS_REFERENCE_AUDIO_MAP = [
|
|||||||
("sarah-en-female.mp3", "莎拉", "Sarah"),
|
("sarah-en-female.mp3", "莎拉", "Sarah"),
|
||||||
]
|
]
|
||||||
INDEXTTS_REFERENCE_AUDIO_EXTENSIONS = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg")
|
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_TRACKS_JSON = os.path.join(BGM_RESOURCE_DIR, "tracks.json")
|
||||||
BGM_UPLOAD_SUBDIR = "uploaded_bgms"
|
BGM_UPLOAD_SUBDIR = "uploaded_bgms"
|
||||||
BGM_AUDIO_EXTENSIONS = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg")
|
BGM_AUDIO_EXTENSIONS = (".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg")
|
||||||
@ -1965,7 +1965,6 @@ def render_bgm_settings(tr):
|
|||||||
tr("Background Music Source"),
|
tr("Background Music Source"),
|
||||||
options=list(bgm_source_labels.keys()),
|
options=list(bgm_source_labels.keys()),
|
||||||
selection_mode="single",
|
selection_mode="single",
|
||||||
default=default_bgm_source,
|
|
||||||
key="bgm_source_selection",
|
key="bgm_source_selection",
|
||||||
format_func=lambda source: tr(bgm_source_labels[source]),
|
format_func=lambda source: tr(bgm_source_labels[source]),
|
||||||
help=tr("Background Music Source Help"),
|
help=tr("Background Music Source Help"),
|
||||||
|
|||||||
@ -788,11 +788,10 @@ def _render_subtitle_preview_image(tr):
|
|||||||
orientation = st.pills(
|
orientation = st.pills(
|
||||||
tr("Subtitle Preview Orientation"),
|
tr("Subtitle Preview Orientation"),
|
||||||
options=SUBTITLE_PREVIEW_ORIENTATIONS,
|
options=SUBTITLE_PREVIEW_ORIENTATIONS,
|
||||||
default=orientation_default,
|
|
||||||
format_func=lambda value: tr(SUBTITLE_PREVIEW_ORIENTATION_LABELS[value]),
|
format_func=lambda value: tr(SUBTITLE_PREVIEW_ORIENTATION_LABELS[value]),
|
||||||
key="subtitle_preview_orientation",
|
key="subtitle_preview_orientation",
|
||||||
width="stretch",
|
width="stretch",
|
||||||
) or "portrait"
|
) or orientation_default
|
||||||
_render_subtitle_position_controls(tr, orientation)
|
_render_subtitle_position_controls(tr, orientation)
|
||||||
|
|
||||||
preview_text = tr("Subtitle Preview Sample Text")
|
preview_text = tr("Subtitle Preview Sample Text")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user