feat(booth): wrap verbatim index.html booths with a back-to-booths chip + inherited favicon
Verbatim-index.html booths were served raw (FileResponse) with no base
template, so they had no favicon and no way back to the booth index — the
gap the app-rendered gallery/zoom pages already covered via base.html.
booth_view now reads a small verbatim index.html and, via a pure
wrap_verbatim_html(), injects:
- a fixed-position 'all booths' chip (scoped class, max z-index, hidden
in print), pinned top-right (empty on left-aligned report layouts; a
top-left chip clips the page title) and appended at the END of the
document so it never reorders the page;
- the Booth favicon at the first head-ish seam, only if the page declares
no icon of its own.
Injection is doctype/charset-safe for the compact HTML real booths use
(<!doctype html><meta charset><title><style>…content, no explicit head/
body): nothing is ever placed ahead of a leading <!doctype> (which would
force quirks mode), and the ~250B favicon link keeps the charset <meta>
inside the first-1024-byte detection window. The raw file route
(/b/<name>/index.html) stays byte-for-byte, so assets and ?dl=1 downloads
are unchanged; files over 8 MB serve raw, unwrapped.
Verified live on nh3-dev :8090 across the real booth shapes (compact-HTML
crow-*/jackdaw-*/mimir-favicon, well-formed dcc-summarizer-ab, own-icon
edict-favicon). 10 new tests; suite 38 passing.
This commit is contained in:
@@ -6,6 +6,7 @@ import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from booth.app import (
|
||||
FAVICON_LINK,
|
||||
build_gallery,
|
||||
classify,
|
||||
create_app,
|
||||
@@ -14,6 +15,7 @@ from booth.app import (
|
||||
is_expired,
|
||||
safe_upload_name,
|
||||
sweep_once,
|
||||
wrap_verbatim_html,
|
||||
)
|
||||
|
||||
PICKUP_RE = re.compile(r"^(\d{1,2}-[a-z]+|[a-z]+-\d{1,2})$")
|
||||
@@ -357,3 +359,79 @@ def test_image_view_nonimage_redirects_to_raw(client):
|
||||
r = c.get("/b/run1/view", params={"f": "notes.txt"}, follow_redirects=False)
|
||||
assert r.status_code == 307
|
||||
assert r.headers["location"] == "/b/run1/notes.txt"
|
||||
|
||||
|
||||
# ---- verbatim-index.html wrapper --------------------------------------------
|
||||
|
||||
|
||||
def test_wrap_injects_chip_and_favicon():
|
||||
html = "<html><head><title>Brief</title></head><body><h1>REPORT</h1></body></html>"
|
||||
out = wrap_verbatim_html(html)
|
||||
assert 'class="booth-nav-home"' in out # floating back chip
|
||||
assert 'href="/"' in out # points at the main booth index
|
||||
assert "all booths" in out
|
||||
assert FAVICON_LINK in out # favicon inherited
|
||||
assert "<h1>REPORT</h1>" in out # original content preserved
|
||||
# favicon lands in the head, chip lands in the body
|
||||
assert out.index(FAVICON_LINK) < out.index("</head>")
|
||||
assert out.index("booth-nav-home") > out.index("<body>")
|
||||
|
||||
|
||||
def test_wrap_respects_existing_favicon():
|
||||
html = '<html><head><link rel="icon" href="data:image/png;base64,AAAA"></head><body>x</body></html>'
|
||||
out = wrap_verbatim_html(html)
|
||||
assert FAVICON_LINK not in out # the page's own icon wins
|
||||
assert out.count('rel="icon"') == 1
|
||||
assert 'class="booth-nav-home"' in out # chip is still added
|
||||
|
||||
|
||||
def test_wrap_bare_fragment_appends_chip():
|
||||
out = wrap_verbatim_html("<h1>bare fragment</h1>") # no doctype/head/body
|
||||
assert 'class="booth-nav-home"' in out
|
||||
assert out.rstrip().endswith("</style>") # chip appended at the end
|
||||
assert FAVICON_LINK in out # no doctype -> safe to prepend the icon
|
||||
assert out.index(FAVICON_LINK) < out.index("bare") # icon ahead of content (implied head)
|
||||
|
||||
|
||||
def test_wrap_no_head_injects_favicon():
|
||||
out = wrap_verbatim_html("<body><h1>no head</h1></body>")
|
||||
assert 'class="booth-nav-home"' in out
|
||||
assert FAVICON_LINK in out # injected even without an explicit <head>
|
||||
|
||||
|
||||
def test_wrap_compact_doctype_stays_first():
|
||||
# the real-booth shape: compact HTML, no explicit head/body. The injection must
|
||||
# not push anything ahead of the doctype (quirks mode) or past the charset window.
|
||||
html = "<!doctype html><meta charset=utf-8><title>T</title><style>body{margin:0}</style><h1>REPORT</h1>"
|
||||
out = wrap_verbatim_html(html)
|
||||
assert out.lstrip().lower().startswith("<!doctype") # doctype still first -> standards mode
|
||||
assert FAVICON_LINK in out
|
||||
assert out.index(FAVICON_LINK) < out.index("<h1>") # icon in the implied head, before content
|
||||
assert out.index("charset") < 1024 # charset meta stays in the detection window
|
||||
assert 'class="booth-nav-home"' in out
|
||||
assert out.index("booth-nav-home") > out.index("<h1>REPORT</h1>") # chip appended after content
|
||||
|
||||
|
||||
def test_verbatim_booth_wrapped_with_back_chip(client):
|
||||
c, data = client
|
||||
d = data / "brief"
|
||||
d.mkdir()
|
||||
(d / "index.html").write_text("<html><head></head><body><h1>BRIEF</h1></body></html>")
|
||||
r = c.get("/b/brief/")
|
||||
assert r.status_code == 200
|
||||
assert "BRIEF" in r.text # content preserved
|
||||
assert 'class="booth-nav-home"' in r.text # back chip injected
|
||||
assert 'href="/"' in r.text
|
||||
assert 'rel="icon"' in r.text # favicon inherited
|
||||
|
||||
|
||||
def test_verbatim_index_raw_file_route_unwrapped(client):
|
||||
# the file route (/b/<name>/index.html) still serves the raw bytes — the chip
|
||||
# only rides on the booth view (/b/<name>/), so downloads/assets stay verbatim
|
||||
c, data = client
|
||||
d = data / "brief"
|
||||
d.mkdir()
|
||||
(d / "index.html").write_text("<html><body><h1>BRIEF</h1></body></html>")
|
||||
r = c.get("/b/brief/index.html")
|
||||
assert r.status_code == 200
|
||||
assert "booth-nav-home" not in r.text
|
||||
|
||||
Reference in New Issue
Block a user