Add tests for SEO meta tags rendering and improve meta_tags.html formatting
All checks were successful
Deploy to Server / deploy (push) Successful in 11s
All checks were successful
Deploy to Server / deploy (push) Successful in 11s
This commit is contained in:
parent
70298fdd1e
commit
f8236034a3
2 changed files with 110 additions and 18 deletions
106
config/tests/test_seo.py
Normal file
106
config/tests/test_seo.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.test import RequestFactory
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
|
||||
|
||||
def _render_meta_tags(context: dict | None = None, path: str = "/test/") -> str:
|
||||
"""Helper function to render meta tags template with a test request and given context.
|
||||
|
||||
Args:
|
||||
context: Optional dictionary of context variables to pass to the template.
|
||||
path: The URL path for the test request.
|
||||
|
||||
Returns:
|
||||
The rendered HTML content of the meta tags.
|
||||
"""
|
||||
request: WSGIRequest = RequestFactory().get(path)
|
||||
return render_to_string(
|
||||
"includes/meta_tags.html",
|
||||
context=context or {},
|
||||
request=request,
|
||||
)
|
||||
|
||||
|
||||
def _extract_meta_content(content: str, key: str) -> str:
|
||||
"""Extract the content of a meta tag by its name or property.
|
||||
|
||||
Args:
|
||||
content: The full HTML content to search within.
|
||||
key: The value of the name or property attribute to match.
|
||||
|
||||
Returns:
|
||||
The content attribute value of the matched meta tag.
|
||||
"""
|
||||
pattern: re.Pattern[str] = re.compile(
|
||||
rf'<meta\s+(?:name|property)="{re.escape(key)}"\s+content="([^"]*)"\s*/?>',
|
||||
)
|
||||
match: re.Match[str] | None = pattern.search(content)
|
||||
assert match is not None, f"Expected meta tag for {key!r} in rendered output."
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def test_meta_tags_use_request_absolute_url_for_og_url_and_canonical() -> None:
|
||||
"""Test that without page_url in context, og:url and canonical tags use request.build_absolute_uri."""
|
||||
content: str = _render_meta_tags(path="/drops/")
|
||||
|
||||
assert _extract_meta_content(content, "og:url") == "http://testserver/drops/"
|
||||
assert '<link rel="canonical" href="http://testserver/drops/" />' in content
|
||||
|
||||
|
||||
def test_meta_tags_use_explicit_page_url_for_og_url_and_canonical() -> None:
|
||||
"""Test that providing page_url in context results in correct og:url and canonical tags."""
|
||||
content: str = _render_meta_tags(
|
||||
{
|
||||
"page_url": "https://example.com/custom-page/",
|
||||
},
|
||||
path="/ignored/",
|
||||
)
|
||||
|
||||
assert (
|
||||
_extract_meta_content(content, "og:url") == "https://example.com/custom-page/"
|
||||
)
|
||||
assert '<link rel="canonical" href="https://example.com/custom-page/" />' in content
|
||||
|
||||
|
||||
def test_meta_tags_twitter_card_is_summary_without_image() -> None:
|
||||
"""Test that without page_image in context, twitter:card is summary and no twitter:image tag is present."""
|
||||
content: str = _render_meta_tags()
|
||||
|
||||
assert _extract_meta_content(content, "twitter:card") == "summary"
|
||||
assert 'name="twitter:image"' not in content
|
||||
|
||||
|
||||
def test_meta_tags_twitter_card_is_summary_large_image_with_page_image() -> None:
|
||||
"""Test that providing page_image in context results in twitter:card being summary_large_image and correct og:image and twitter:image tags."""
|
||||
content: str = _render_meta_tags({
|
||||
"page_image": "https://example.com/image.png",
|
||||
"page_image_width": 1200,
|
||||
"page_image_height": 630,
|
||||
})
|
||||
|
||||
assert _extract_meta_content(content, "twitter:card") == "summary_large_image"
|
||||
assert _extract_meta_content(content, "og:image") == "https://example.com/image.png"
|
||||
assert (
|
||||
_extract_meta_content(content, "twitter:image")
|
||||
== "https://example.com/image.png"
|
||||
)
|
||||
assert _extract_meta_content(content, "og:image:width") == "1200"
|
||||
assert _extract_meta_content(content, "og:image:height") == "630"
|
||||
|
||||
|
||||
def test_meta_tags_render_pagination_links() -> None:
|
||||
"""Test that pagination_info in context results in correct prev/next link tags in output."""
|
||||
content: str = _render_meta_tags({
|
||||
"pagination_info": [
|
||||
{"rel": "prev", "url": "https://example.com/page/1/"},
|
||||
{"rel": "next", "url": "https://example.com/page/3/"},
|
||||
],
|
||||
})
|
||||
|
||||
assert '<link rel="prev" href="https://example.com/page/1/" />' in content
|
||||
assert '<link rel="next" href="https://example.com/page/3/" />' in content
|
||||
|
|
@ -41,12 +41,7 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
{# Twitter Card tags for rich previews #}
|
||||
<meta name="twitter:card"
|
||||
content="{% if page_image %}
|
||||
summary_large_image
|
||||
{% else %}
|
||||
summary
|
||||
{% endif %}" />
|
||||
<meta name="twitter:card" content="{% if page_image %}summary_large_image{% else %}summary{% endif %}" />
|
||||
<meta name="twitter:title" content="{% firstof page_title 'ttvdrops' %}" />
|
||||
<meta name="twitter:description"
|
||||
content="{% firstof page_description 'ttvdrops - Twitch and Kick drops.' %}" />
|
||||
|
|
@ -55,8 +50,7 @@
|
|||
{% if published_date %}<meta property="article:published_time" content="{{ published_date }}" />{% endif %}
|
||||
{% if modified_date %}<meta property="article:modified_time" content="{{ modified_date }}" />{% endif %}
|
||||
{# Canonical tag #}
|
||||
<link rel="canonical"
|
||||
href="{% firstof page_url request.build_absolute_uri %}" />
|
||||
<link rel="canonical" href="{% firstof page_url request.build_absolute_uri %}" />
|
||||
{# Pagination links (for crawler efficiency) #}
|
||||
{% if pagination_info %}
|
||||
{% for link in pagination_info %}<link rel="{{ link.rel }}" href="{{ link.url }}" />{% endfor %}
|
||||
|
|
@ -64,20 +58,12 @@
|
|||
{# Schema.org JSON-LD structured data #}
|
||||
{% if schema_data %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
{
|
||||
schema_data | safe
|
||||
}
|
||||
}
|
||||
{{ schema_data|safe }}
|
||||
</script>
|
||||
{% endif %}
|
||||
{# Breadcrumb schema #}
|
||||
{% if breadcrumb_schema %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
{
|
||||
breadcrumb_schema | safe
|
||||
}
|
||||
}
|
||||
{{ breadcrumb_schema|safe }}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue