145 lines
5.1 KiB
Python
145 lines
5.1 KiB
Python
"""The kit's snippets, the chrome, and the rules the estate agreed to.
|
|
|
|
This module used to be copied into every service. It lives here now; a service
|
|
keeps only the part that lists its own pages.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from django.template.loader import render_to_string
|
|
from django.test import Client
|
|
|
|
from yksa_web.states import STATES
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent / "yksa_web"
|
|
TEMPLATES = sorted((ROOT / "templates").rglob("*.html"))
|
|
STYLESHEETS = [ROOT / "static" / "yksa" / "css" / "kit.css"]
|
|
|
|
|
|
# --- the chrome -------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_the_shared_pages_render():
|
|
client = Client()
|
|
for url in ("/preferences/timezone/", "/privacy/", "/cookies/"):
|
|
assert client.get(url).status_code == 200, url
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_the_navbar_and_footer_are_on_every_page():
|
|
"""Both are stated requirements, and both were hand-copied before this."""
|
|
html = Client().get("/privacy/").content.decode()
|
|
assert "custom-navbar" in html
|
|
assert "yksa/img/logo-full-en.svg" in html
|
|
assert 'href="/cookies/"' in html # footer link
|
|
assert "yksa/css/kit.css" in html
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_the_service_cannot_reorder_the_chrome():
|
|
"""navbar, then main, then footer — a service that wants otherwise has to
|
|
stop extending the kit, which is the point."""
|
|
html = Client().get("/privacy/").content.decode()
|
|
assert html.index("<nav") < html.index("<main") < html.index("<footer")
|
|
|
|
|
|
# --- the snippets -----------------------------------------------------------
|
|
|
|
|
|
def test_state_carries_a_colour_an_icon_and_a_word():
|
|
"""Colour is never the only signal: the badge has to survive a colour-blind
|
|
reader and a greyscale print."""
|
|
html = render_to_string("yksa/ui/_state.html", {"state": "failed"})
|
|
assert "yksa-state-failed" in html
|
|
assert "bi-x-lg" in html
|
|
assert "failed" in html
|
|
|
|
|
|
def test_every_state_in_the_vocabulary_renders():
|
|
for name in STATES:
|
|
assert f"yksa-state-{name}" in render_to_string(
|
|
"yksa/ui/_state.html", {"state": name}
|
|
)
|
|
|
|
|
|
def test_an_unknown_state_still_renders():
|
|
"""A status the reader can see beats one that silently disappears."""
|
|
html = render_to_string("yksa/ui/_state.html", {"state": "nonsense"})
|
|
assert "yksa-state-unknown" in html
|
|
|
|
|
|
def test_chip_is_colourless():
|
|
"""A chip is a fact, not a status. If it needs a colour it is a state."""
|
|
html = render_to_string("yksa/ui/_chip.html", {"text": "v3"})
|
|
for variant in ("success", "danger", "warning", "primary"):
|
|
assert variant not in html
|
|
|
|
|
|
def test_page_title_is_always_h1_h3():
|
|
"""Pages cannot drift apart visually if none of them picks its own size."""
|
|
html = render_to_string("yksa/ui/_page_title.html", {"title": "Widgets"})
|
|
assert '<h1 class="h3' in html
|
|
|
|
|
|
def test_page_title_links_its_parent():
|
|
html = render_to_string("yksa/ui/_page_title.html", {
|
|
"title": "W-1", "parent": "Widgets", "parent_url": "/widgets/",
|
|
})
|
|
assert 'href="/widgets/"' in html
|
|
assert "W-1" in html
|
|
|
|
|
|
# --- the estate-wide rules --------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize("banned", ["fw-semibold", "fw-medium", "fw-light", "fw-bolder"])
|
|
def test_no_banned_font_weights_in_templates(banned):
|
|
"""Weights are 400 and 700. Hierarchy comes from weight, colour and size."""
|
|
assert [str(p) for p in TEMPLATES if banned in p.read_text(encoding="utf-8")] == []
|
|
|
|
|
|
@pytest.mark.parametrize("banned", [r"font-weight:\s*[56]00", r"letter-spacing"])
|
|
def test_no_banned_typography_in_css(banned):
|
|
"""The template rule was enforced and the CSS rule never was, which is how
|
|
main.css came to carry font-weight: 500."""
|
|
offenders = [
|
|
str(p) for p in STYLESHEETS
|
|
if re.search(banned, _without_comments(p.read_text(encoding="utf-8")))
|
|
]
|
|
assert offenders == []
|
|
|
|
|
|
def _without_comments(css: str) -> str:
|
|
return re.sub(r"/\*.*?\*/", "", css, flags=re.S)
|
|
|
|
|
|
def test_no_legacy_text_muted():
|
|
"""Deprecated in Bootstrap 5.3. Muted text is text-body-secondary."""
|
|
assert [str(p) for p in TEMPLATES if "text-muted" in p.read_text(encoding="utf-8")] == []
|
|
|
|
|
|
def test_no_display_classes():
|
|
"""`.display-*` is a marketing scale; headings take `.h1`-`.h6`."""
|
|
offenders = [
|
|
str(p) for p in TEMPLATES
|
|
if re.search(r"\bdisplay-[1-6]\b", p.read_text(encoding="utf-8"))
|
|
]
|
|
assert offenders == []
|
|
|
|
|
|
def test_no_django_comment_syntax():
|
|
"""`{# #}` breaks across edits and leaks into rendered output."""
|
|
assert [str(p) for p in TEMPLATES if "{#" in p.read_text(encoding="utf-8")] == []
|
|
|
|
|
|
def test_bootstrap_is_at_least_5_3():
|
|
"""text-body-secondary and --bs-*-bg-subtle arrived in 5.3. On 5.2.3 every
|
|
rule above passes and the page still renders wrong, which is what happened
|
|
across the estate before this package owned the bundle."""
|
|
bundle = ROOT / "static" / "yksa" / "css" / "bootstrap.min.css"
|
|
assert "--bs-success-bg-subtle" in bundle.read_text(encoding="utf-8")
|