style(overlays): redesign create-overlay modal
Reorders fields to Name → Type → System-wide. Drops the legacy fieldset border and the now-stale "path is generated automatically" hint. Type radios use the new .radio-row vocabulary with always-visible descriptions; the admin-only system-wide checkbox becomes a .switch-row toggle. Form field names are unchanged, so the overlay-creation handler is untouched. Plan deviation: live_server e2e fixture now also sets LEFT4ME_ROOT. This is required because the new test creates an overlay end-to-end via the UI, and create_overlay_directory() writes under $LEFT4ME_ROOT, which defaults to /var/lib/left4me (unwritable on dev machines). The two existing live_server consumers (test_editor, test_smoke) only visit /blueprints/<id> routes that don't touch the filesystem, so this change is safe for them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6cce8b7be7
commit
34b65fcbbe
3 changed files with 91 additions and 9 deletions
|
|
@ -34,17 +34,48 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}">
|
<input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}">
|
||||||
<fieldset class="overlay-type-radio">
|
|
||||||
<legend>Type</legend>
|
<div class="field">
|
||||||
<label><input type="radio" name="type" value="workshop" checked> Workshop (downloads from Steam)</label>
|
<label class="field-label" for="create-overlay-name">Name</label>
|
||||||
<label><input type="radio" name="type" value="script"> Script (runs sandboxed bash)</label>
|
<input id="create-overlay-name" name="name" required>
|
||||||
<label><input type="radio" name="type" value="files"> Files (upload / edit text files online)</label>
|
</div>
|
||||||
</fieldset>
|
|
||||||
<label>Name <input name="name" required></label>
|
<div class="field">
|
||||||
|
<span class="field-label">Type</span>
|
||||||
|
<div class="radio-list">
|
||||||
|
<label class="radio-row">
|
||||||
|
<input type="radio" name="type" value="workshop" checked>
|
||||||
|
<span class="radio-row-text">
|
||||||
|
<strong>Workshop</strong>
|
||||||
|
<span>Downloads from Steam</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-row">
|
||||||
|
<input type="radio" name="type" value="script">
|
||||||
|
<span class="radio-row-text">
|
||||||
|
<strong>Script</strong>
|
||||||
|
<span>Runs sandboxed bash</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-row">
|
||||||
|
<input type="radio" name="type" value="files">
|
||||||
|
<span class="radio-row-text">
|
||||||
|
<strong>Files</strong>
|
||||||
|
<span>Upload / edit text files online</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if g.user and g.user.admin %}
|
{% if g.user and g.user.admin %}
|
||||||
<label><input type="checkbox" name="system_wide" value="1"> System-wide (visible to all users)</label>
|
<label class="switch-row">
|
||||||
|
<input type="checkbox" name="system_wide" value="1">
|
||||||
|
<span class="switch-row-text">
|
||||||
|
<strong>System-wide</strong>
|
||||||
|
<span>Visible to all users</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="muted">The path is generated automatically.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="button-secondary" data-inline-modal-close>Cancel</button>
|
<button type="button" class="button-secondary" data-inline-modal-close>Cancel</button>
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@ def login(page, base_url: str, username: str = "alice", password: str = "secret"
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def live_server(tmp_path, monkeypatch):
|
def live_server(tmp_path, monkeypatch):
|
||||||
|
# Some routes (e.g. POST /overlays via create_overlay_directory) write
|
||||||
|
# under $LEFT4ME_ROOT. Point it at tmp_path so the prod default
|
||||||
|
# /var/lib/left4me doesn't kick in on dev machines.
|
||||||
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
||||||
app = _boot_app(tmp_path, monkeypatch)
|
app = _boot_app(tmp_path, monkeypatch)
|
||||||
|
|
||||||
with session_scope() as session:
|
with session_scope() as session:
|
||||||
|
|
|
||||||
47
l4d2web/tests/e2e/test_overlays_create.py
Normal file
47
l4d2web/tests/e2e/test_overlays_create.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
"""E2E test for the redesigned create-overlay modal."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
from .conftest import login
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.e2e
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_overlay_modal_field_order_and_submission(page: Page, live_server) -> None:
|
||||||
|
base_url = live_server["base_url"]
|
||||||
|
login(page, base_url)
|
||||||
|
page.goto(f"{base_url}/overlays")
|
||||||
|
page.click('button[data-inline-modal-open="create-overlay-modal"]')
|
||||||
|
|
||||||
|
modal = page.locator("#create-overlay-modal")
|
||||||
|
expect(modal).to_be_visible()
|
||||||
|
|
||||||
|
# Field order: Name input must appear before Type radios in DOM/visual order.
|
||||||
|
name_input = modal.locator('input[name="name"]')
|
||||||
|
type_workshop = modal.locator('input[name="type"][value="workshop"]')
|
||||||
|
expect(name_input).to_be_visible()
|
||||||
|
expect(type_workshop).to_be_visible()
|
||||||
|
name_box = name_input.bounding_box()
|
||||||
|
type_box = type_workshop.bounding_box()
|
||||||
|
assert name_box is not None and type_box is not None
|
||||||
|
assert name_box["y"] < type_box["y"], "Name should sit above Type"
|
||||||
|
|
||||||
|
# No legacy fieldset border around Type group.
|
||||||
|
expect(modal.locator("fieldset.overlay-type-radio")).to_have_count(0)
|
||||||
|
|
||||||
|
# No legacy "path is generated automatically" copy anywhere.
|
||||||
|
expect(modal).not_to_contain_text("path is generated automatically")
|
||||||
|
|
||||||
|
# Default workshop selection is checked.
|
||||||
|
expect(type_workshop).to_be_checked()
|
||||||
|
|
||||||
|
# Submit with a unique name and the script type.
|
||||||
|
name_input.fill("e2e-test-overlay")
|
||||||
|
modal.locator('input[name="type"][value="script"]').check()
|
||||||
|
modal.locator('button[type="submit"]:has-text("Create")').click()
|
||||||
|
|
||||||
|
# Handler redirects to /overlays/<id> on success.
|
||||||
|
page.wait_for_url("**/overlays/*", timeout=5000)
|
||||||
|
expect(page.locator("h1")).to_contain_text("e2e-test-overlay")
|
||||||
Loading…
Reference in a new issue