Align the project hero with the showcase design: owner cover, logo, repo link
The hero now matches the showcase reference: the title block, tagline (first description line), type/platform chips, author row and started date render OVERLAID on the cover banner behind a bottom scrim, with the optional project logo as a framed tile beside them and the Visit Website CTA on the right. The section tab bar switches to the underline style with Overview active. Owners control the missing pieces from the create and edit modals, which are now multipart: cover_image and logo_image file uploads (stored as bare uploaded filenames via save_inline_image, the posts.image pattern; a new upload replaces the previous one) plus a repo_url sibling of website_url (same normalization/validation). The cover feeds og:image and the schema image, the logo becomes schema thumbnailUrl, and sameAs now carries website + repository. repo_url rides ProjectOut, the Devii create/edit actions and the API docs; tests cover repo normalization, the multipart upload round-trip and the hero render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -355,6 +355,48 @@ def test_project_page_cover_and_screenshots_from_image_attachments(app_server):
|
||||
assert '"screenshot"' in html, "Expected screenshot urls in the JSON-LD"
|
||||
|
||||
|
||||
def test_owner_uploaded_cover_and_logo_render_in_hero(app_server):
|
||||
"""Multipart cover_image/logo_image uploads land on the row and in the hero."""
|
||||
import io
|
||||
from PIL import Image
|
||||
|
||||
session, _ = _member()
|
||||
|
||||
def png(color):
|
||||
buf = io.BytesIO()
|
||||
Image.new("RGB", (12, 6), color).save(buf, "PNG")
|
||||
return buf.getvalue()
|
||||
|
||||
r = session.post(
|
||||
f"{BASE_URL}/projects/create",
|
||||
headers=JSON,
|
||||
data={
|
||||
"title": _unique("dlhero"),
|
||||
"description": "Hero art test project",
|
||||
"project_type": "game",
|
||||
"status": "In Development",
|
||||
"platforms": "PC",
|
||||
},
|
||||
files={
|
||||
"cover_image": ("cover.png", png((10, 20, 90)), "image/png"),
|
||||
"logo_image": ("logo.png", png((90, 20, 10)), "image/png"),
|
||||
},
|
||||
)
|
||||
assert r.status_code == 200, r.text[:300]
|
||||
slug = r.json()["data"]["slug"]
|
||||
|
||||
refresh_snapshot()
|
||||
row = get_table("projects").find_one(slug=slug)
|
||||
assert row["cover_image"], "cover_image filename expected on the row"
|
||||
assert row["logo_image"], "logo_image filename expected on the row"
|
||||
|
||||
html = session.get(f"{BASE_URL}/projects/{slug}").text
|
||||
assert f"/static/uploads/{row['cover_image']}" in html
|
||||
assert f"/static/uploads/{row['logo_image']}" in html
|
||||
assert 'class="project-logo"' in html
|
||||
assert '"thumbnailUrl"' in html, "Expected the logo as schema thumbnailUrl"
|
||||
|
||||
|
||||
def test_project_page_json_ld_rating_from_stars(app_server):
|
||||
"""Stars surface as an aggregateRating; zero stars emit none."""
|
||||
session, _ = _member()
|
||||
|
||||
@@ -115,12 +115,14 @@ def test_owner_can_set_and_clear_website_url(app_server):
|
||||
"title": "Website Via Api",
|
||||
"description": "has a website now",
|
||||
"website_url": "myproject.dev/docs",
|
||||
"repo_url": "github.com/me/website-via-api",
|
||||
},
|
||||
allow_redirects=False,
|
||||
)
|
||||
assert r.status_code == 200 and r.json()["ok"] is True
|
||||
row = get_table("projects").find_one(slug=slug)
|
||||
assert row["website_url"] == "https://myproject.dev/docs"
|
||||
assert row["repo_url"] == "https://github.com/me/website-via-api"
|
||||
|
||||
html = requests.get(f"{BASE_URL}/projects/{slug}").text
|
||||
assert "Visit Website" in html
|
||||
|
||||
@@ -57,6 +57,7 @@ def test_project_form_website_url_normalizes_and_validates():
|
||||
ProjectForm(**base, website_url="http://x.dev/a?b=1").website_url
|
||||
== "http://x.dev/a?b=1"
|
||||
)
|
||||
assert ProjectForm(**base, repo_url="github.com/me/x").repo_url == "https://github.com/me/x"
|
||||
assert normalize_website_url(" ") == ""
|
||||
with pytest.raises(ValidationError):
|
||||
ProjectForm(**base, website_url="javascript:alert(1)")
|
||||
|
||||
Reference in New Issue
Block a user