diff --git a/config/tests/test_seo.py b/config/tests/test_seo.py new file mode 100644 index 0000000..3574eb1 --- /dev/null +++ b/config/tests/test_seo.py @@ -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'', + ) + 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 '' 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 '' 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 '' in content + assert '' in content diff --git a/templates/includes/meta_tags.html b/templates/includes/meta_tags.html index a8e1797..d90c294 100644 --- a/templates/includes/meta_tags.html +++ b/templates/includes/meta_tags.html @@ -41,12 +41,7 @@ {% endif %} {% endif %} {# Twitter Card tags for rich previews #} - + @@ -55,8 +50,7 @@ {% if published_date %}{% endif %} {% if modified_date %}{% endif %} {# Canonical tag #} - + {# Pagination links (for crawler efficiency) #} {% if pagination_info %} {% for link in pagination_info %}{% endfor %} @@ -64,20 +58,12 @@ {# Schema.org JSON-LD structured data #} {% if schema_data %} {% endif %} {# Breadcrumb schema #} {% if breadcrumb_schema %} {% endif %}