Slide-creation-engine / tests /test_app_ui.py
rigelbar's picture
Harden workflow UI contrast
7797512
Raw
History Blame Contribute Delete
8.07 kB
from __future__ import annotations
from app import (
CSS,
handle_stage_nav,
handle_generate,
handle_grade,
pending_activity_message,
readable_current_artifact_markdown,
render_activity_message,
render_stage_score_gate_panel,
render_stage_score_strip,
stage_score_rows,
workflow_progress_summary,
workspace_visibility_updates,
)
from course_slide_factory.constants import STAGE_IDS
from course_slide_factory.models import IssueSeverity, IssueType
from course_slide_factory.quality import approve_current_artifact, upsert_issue
from course_slide_factory.workflow import build_empty_state, generate_stage, update_setup_from_inputs
def _activity_value(update):
return update["value"] if isinstance(update, dict) else update
def _update_visible(update):
return update["visible"] if isinstance(update, dict) else None
def test_workflow_progress_summary_starts_empty():
state = build_empty_state(deck_title="UI Test", source_url="mock://source")
summary = workflow_progress_summary(state)
rows = stage_score_rows(state, "setup_inputs")
assert summary["complete"] == 0
assert summary["total"] == len(STAGE_IDS)
assert summary["percent"] == 0
assert rows[0]["stage_id"] == "setup_inputs"
assert rows[0]["active"]
assert rows[0]["score"] is None
assert rows[0]["status"] == "pending"
def test_stage_score_rows_track_complete_review_stale_and_blockers():
state = build_empty_state(deck_title="UI Test", source_url="mock://source")
state, _message = generate_stage(state, "setup_inputs")
state.stages["setup_inputs"].score = 92
approve_current_artifact(state, "setup_inputs")
state, _message = generate_stage(state, "text_generation")
state.stages["text_generation"].score = 72
state.stages["title_generation"].is_stale = True
upsert_issue(
state,
IssueType.TECHNICAL_INACCURACY,
IssueSeverity.BLOCKER,
"Blocking issue",
stage_id="technical_review",
)
state.stages["technical_review"].is_stale = False
summary = workflow_progress_summary(state)
rows = {row["stage_id"]: row for row in stage_score_rows(state, "text_generation")}
score_strip = render_stage_score_strip(state, "text_generation")
assert summary["complete"] == 1
assert summary["percent"] == round(1 / len(STAGE_IDS) * 100)
assert summary["unresolved_blockers"] >= 1
assert rows["setup_inputs"]["status"] == "complete"
assert rows["text_generation"]["status"] == "review"
assert rows["text_generation"]["active"]
assert rows["title_generation"]["status"] == "stale"
assert rows["technical_review"]["status"] == "blocked"
assert "Text" in score_strip
assert "72/100" in score_strip
def test_setup_stage_shows_feedback_workspaces():
updates = workspace_visibility_updates("setup_inputs")
assert [update["visible"] for update in updates] == [True, False, False, False, True]
def test_activity_messages_render_distinct_pending_and_complete_states():
pending = pending_activity_message("Creating setup candidate...")
complete = render_activity_message("Setup candidate created.", "complete")
assert "activity-card pending" in pending
assert "Working" in pending
assert "Creating setup candidate..." in pending
assert "activity-card complete" in complete
assert "Done" in complete
def test_light_theme_css_covers_gradio_dataframes_and_action_hierarchy():
assert ":root" in CSS
assert "color-scheme: light" in CSS
assert "--border-color-primary: #d9dee7" in CSS
assert "--body-text-color-subdued: #4b5563" in CSS
assert "--ag-background-color: #ffffff" in CSS
assert ".compact-panel .ag-header" in CSS
assert '.compact-panel [role="gridcell"]' in CSS
assert ".compact-panel .virtual-row" in CSS
assert ".compact-panel .body-cell" in CSS
assert '.gradio-container [data-testid="dataframe"] tbody tr:nth-child(even)' in CSS
assert '.gradio-container [role="gridcell"]' in CSS
assert ".gradio-container .header-table" in CSS
assert ".gradio-container .virtual-row.row-odd" in CSS
assert ".production-action" in CSS
assert ".approval-action" in CSS
assert ".score-gate" in CSS
assert '.score-gate [data-testid="column"]' in CSS
def test_stage_score_gate_panel_renders_pending_and_scored_states():
state = build_empty_state(deck_title="UI Test", source_url="mock://source")
pending = render_stage_score_gate_panel(state, "setup_inputs")
state.stages["setup_inputs"].score = 91
scored = render_stage_score_gate_panel(state, "setup_inputs")
assert "Setup & Inputs score" in pending
assert "Pending" in pending
assert "stage-result-card scored" in scored
assert "91/100" in scored
def test_setup_candidate_button_returns_visible_snapshot_feedback(tmp_path):
instructions = tmp_path / "instructions.txt"
instructions.write_text("Course plan\n- Explain the core course concept.", encoding="utf-8")
generated = handle_generate(
None,
"Course Slide Deck",
"",
"mock://template/course",
"",
True,
"obj_1: Explain the core course concept.",
[str(instructions)],
"",
"",
"instructions_file",
None,
"",
False,
"setup_inputs",
)
assert _update_visible(generated[1]) is True
message = _activity_value(generated[2])
assert "activity-card complete" in message
assert "Done" in message
assert "Setup candidate created as setup_inputs_v1." in message
assert "1 parsed upload(s)" in message
assert "Check Setup Score" in message
assert "### Setup & Inputs output" in generated[3]
assert "Input summary" in generated[3]
assert "Parsed uploads: 1" in generated[3]
assert "instructions.txt" in generated[3]
assert '"input_summary"' not in generated[3]
assert generated[4] == generated[3]
def test_readable_artifact_markdown_uses_human_summary_not_json(tmp_path):
instructions = tmp_path / "instructions.txt"
instructions.write_text("Course plan\n- Explain the core course concept.", encoding="utf-8")
state = build_empty_state(deck_title="Course Slide Deck", source_url="")
update_setup_from_inputs(
state,
deck_title="Course Slide Deck",
source_url="",
template_url="mock://template/course",
output_folder_id="",
dry_run=True,
objectives_text="obj_1: Explain the core course concept.",
material_files=[str(instructions)],
start_mode="instructions_file",
)
state, _message = generate_stage(state, "setup_inputs")
output = readable_current_artifact_markdown(state, "setup_inputs")
assert output.startswith("### Setup & Inputs output")
assert "Input summary" in output
assert "Uploaded materials" in output
assert '"uploaded_materials"' not in output
def test_stage_navigation_clears_activity_message():
generated = handle_generate(
None,
"Course Slide Deck",
"mock://source",
"mock://template/course",
"",
True,
"",
[],
"",
"",
"instructions_file",
None,
"",
False,
"setup_inputs",
)
navigated = handle_stage_nav(generated[0], "source_extraction_objective_mapping")
assert navigated[4]["visible"] is False
assert navigated[5] == ""
def test_setup_grade_button_path_returns_score_feedback():
generated = handle_generate(
None,
"Course Slide Deck",
"mock://source",
"mock://template/course",
"",
True,
"",
[],
"",
"",
"instructions_file",
None,
"",
False,
"setup_inputs",
)
graded = handle_grade(generated[0], "setup_inputs")
message = _activity_value(graded[2])
assert "activity-card complete" in message
assert "Grade for Setup & Inputs:" in message