From 505ded049374f073f2df8f79096f2fe5936e07cc Mon Sep 17 00:00:00 2001 From: "Schmidt Till (CSS TO PME DSI PAO MUC)" Date: Thu, 10 Sep 2026 23:28:59 +0200 Subject: [PATCH] feat: build single-page web UI in static/index.html (#4) --- static/index.html | 892 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_ui.py | 155 ++++++++ 2 files changed, 1047 insertions(+) create mode 100644 static/index.html create mode 100644 tests/test_ui.py diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..c746aaa --- /dev/null +++ b/static/index.html @@ -0,0 +1,892 @@ + + + + + + unwrap-clothes | Garment Photo Cleaner + + + +
+
+
+ unwrap-clothes + Studio v1.0 +
+

Wrinkle-free garment photo cleaning and catalog backdrop standardization

+
+ +
+
+ +
+
+ Garment Photo +
+
+ + +
+
📷
+

Drag & drop garment photo here

+

or click to browse from your computer (PNG, JPEG, WebP)

+
+ +
+
+ + +
+
+ Editing Prompt & Model Options +
+ +
+ +
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + Upscales Muse output back to original dimensions using Pillow Lanczos resampling +
+
+
+ +
+ + + + + +
+
+ + + +
+ + + + diff --git a/tests/test_ui.py b/tests/test_ui.py new file mode 100644 index 0000000..9c46113 --- /dev/null +++ b/tests/test_ui.py @@ -0,0 +1,155 @@ +"""Tests for single-page web UI in static/index.html.""" + +from bs4 import BeautifulSoup +from fastapi.testclient import TestClient + +from app import app +from cleaner import BACKGROUND_PRESETS, DEFAULT_MODEL, build_prompt + + +def test_get_root_serves_html(): + """GET / returns 200 OK and text/html.""" + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200 + assert "text/html" in response.headers.get("content-type", "") + + +def test_get_static_index_html_serves_html(): + """GET /static/index.html returns 200 OK and text/html.""" + client = TestClient(app) + response = client.get("/static/index.html") + assert response.status_code == 200 + assert "text/html" in response.headers.get("content-type", "") + + +def test_html_contains_upload_elements(): + """HTML contains file input, drop zone, and preview image element.""" + client = TestClient(app) + response = client.get("/") + soup = BeautifulSoup(response.text, "html.parser") + + file_input = soup.find("input", {"type": "file"}) + assert file_input is not None, "File input element missing" + assert file_input.get("id") or file_input.get("name") + + drop_zone = soup.find(id="drop-zone") or soup.find(class_="drop-zone") + assert drop_zone is not None, "Drop zone container missing" + + # Preview elements for original image, name, and dimensions + before_preview = soup.find(id="before-image") or soup.find(id="preview-img") or soup.find(id="before-preview") + assert before_preview is not None, "Before preview image element missing" + + file_info = soup.find(id="file-info") or soup.find(class_="file-info") + assert file_info is not None, "File info container for name and dimensions missing" + + +def test_html_contains_prompt_controls(): + """HTML contains textarea prefilled with beige prompt and preset buttons.""" + client = TestClient(app) + response = client.get("/") + soup = BeautifulSoup(response.text, "html.parser") + + prompt_textarea = soup.find("textarea") + assert prompt_textarea is not None, "Prompt textarea missing" + expected_default = build_prompt("beige") + assert expected_default in prompt_textarea.text or expected_default in prompt_textarea.get("value", "") + + # Preset buttons + preset_beige = soup.find(id="preset-beige") or soup.find("button", {"data-preset": "beige"}) + assert preset_beige is not None, "Warm beige preset button missing" + + preset_white = soup.find(id="preset-white") or soup.find("button", {"data-preset": "white"}) + assert preset_white is not None, "Bright white preset button missing" + + +def test_html_contains_model_and_option_inputs(): + """HTML contains model input, API key input, and restore_resolution checkbox.""" + client = TestClient(app) + response = client.get("/") + soup = BeautifulSoup(response.text, "html.parser") + + model_input = soup.find("input", {"name": "model"}) or soup.find(id="model") + assert model_input is not None, "Model input missing" + assert model_input.get("value") == DEFAULT_MODEL or model_input.get("placeholder") == DEFAULT_MODEL + + api_key_input = ( + soup.find("input", {"type": "password", "name": "api_key"}) + or soup.find("input", {"name": "api_key"}) + or soup.find(id="api-key") + ) + assert api_key_input is not None, "API key input missing" + placeholder = api_key_input.get("placeholder", "") + assert "OPENROUTER_API_KEY" in placeholder + + restore_checkbox = ( + soup.find("input", {"type": "checkbox", "name": "restore_resolution"}) + or soup.find("input", {"type": "checkbox", "id": "restore-resolution"}) + ) + assert restore_checkbox is not None, "Restore resolution checkbox missing" + assert restore_checkbox.has_attr("checked"), "Restore resolution should be checked by default" + + +def test_html_contains_processing_and_error_elements(): + """HTML contains submit button, loading spinner/message with timer, and error banner.""" + client = TestClient(app) + response = client.get("/") + soup = BeautifulSoup(response.text, "html.parser") + + submit_btn = soup.find("button", {"type": "submit"}) or soup.find(id="submit-btn") + assert submit_btn is not None, "Submit button missing" + + loading_container = soup.find(id="loading-container") or soup.find(id="loading") or soup.find(class_="loading") + assert loading_container is not None, "Loading container missing" + loading_text = loading_container.get_text() + assert "Editing with Muse, usually takes ~15-20s" in loading_text or "15-20s" in loading_text + + timer = soup.find(id="elapsed-timer") or soup.find(class_="elapsed-timer") + assert timer is not None, "Elapsed timer display missing" + + error_banner = soup.find(id="error-banner") or soup.find(class_="error-banner") or soup.find(id="error-container") + assert error_banner is not None, "Error banner missing" + + +def test_html_contains_results_and_comparison_elements(): + """HTML contains before/after comparison, metrics (cost, dimensions), and download button.""" + client = TestClient(app) + response = client.get("/") + soup = BeautifulSoup(response.text, "html.parser") + + results_container = soup.find(id="results-container") or soup.find(id="results") or soup.find(class_="results") + assert results_container is not None, "Results container missing" + + after_image = soup.find(id="after-image") or soup.find(id="after-preview") + assert after_image is not None, "After image display element missing" + + cost_display = soup.find(id="cost-display") or soup.find(class_="cost-display") + assert cost_display is not None, "Cost display missing" + + res_display = soup.find(id="resolution-display") or soup.find(class_="resolution-display") + assert res_display is not None, "Resolution display missing" + + download_btn = soup.find(id="download-btn") or soup.find(id="download-link") + assert download_btn is not None, "Download button missing" + + +def test_html_javascript_logic(): + """HTML script contains drag-drop, FileReader, presets, /api/clean call, and download handler.""" + client = TestClient(app) + response = client.get("/") + html = response.text + + # Script tag present + assert "