import os import shutil import tempfile from pathlib import Path from django.conf import settings from django.template import Context, Template from django.template.loader import render_to_string from django.test import RequestFactory, TestCase from django_hosts.resolvers import reverse as reverse_with_host from releases.models import Release from ..models import Document, DocumentRelease from ..templatetags.docs import ( code_links, generate_scroll_to_text_fragment, get_all_doc_versions, ) class TemplateTagTests(TestCase): fixtures = ["doc_test_fixtures"] def test_get_all_doc_versions_empty(self): with self.assertNumQueries(1): self.assertEqual(get_all_doc_versions({}), ["dev"]) def test_get_all_doc_versions(self): tmp_docs_build_root = Path(tempfile.mkdtemp()) self.addCleanup(shutil.rmtree, tmp_docs_build_root) os.makedirs( tmp_docs_build_root / settings.DEFAULT_LANGUAGE_CODE / "1.8" / "_built" / "json" ) os.makedirs( tmp_docs_build_root / settings.DEFAULT_LANGUAGE_CODE / "1.11" / "_built" / "json" ) with self.settings(DOCS_BUILD_ROOT=tmp_docs_build_root): self.assertEqual(get_all_doc_versions({}), ["1.8", "1.11", "dev"]) def test_pygments_template_tag(self): template = Template( ''' {% load docs %} {% pygment 'python' %} def band_listing(request): """A view of all bands.""" bands = models.Band.objects.all() return render(request, 'bands/band_listing.html', {'bands': bands}) {% endpygment %} ''' ) self.assertHTMLEqual( template.render(Context()), """

def 
band_listing
(request
):
    
"""A view of all bands."""
bands =
models.
Band.
objects.
all()
return render
(request
,
'bands/band_listing.html'
, {
'bands':
bands})
""", ) def test_fragment_template_tag(self): highlighted_text = ( "testing frameworks section of Advanced testing" " topics .\nWriting and running tests\n" "Testing tools\nAdvanced testing\ntests ¶" ) template = Template( "{% load docs %}" "https://docs.djangoproject.com/en/5.1/topics/testing/" "{{ highlighted_text|fragment }}" ) self.assertHTMLEqual( template.render(Context({"highlighted_text": highlighted_text})), "https://docs.djangoproject.com/en/5.1/topics/testing/" "#:~:text=testing%20frameworks%20section%20of%20Advanced%20testing" "%20topics.", ) def test_generate_scroll_to_text_fragment(self): stacked_inline = """"StackedInline [source] ¶ The admin interface has the ability to edit models""" text_choices = """TextChoices ): FRESHMAN = "FR" , _ ( "Freshman" ) SOPHOMORE = "SO" , _ ( "Sophomore" ) JUNIOR""" nested_text_choices = ( "TextChoices ( "Medal" , "GOLD " "SILVER BRONZE" )\n\n SPORT_CHOICES = [\n( "" "Martial Arts" , [( "judo\n" ) cases = [ ( stacked_inline, "#:~:text=%22StackedInline%5Bsource%5D", ), ( text_choices, "#:~:text=TextChoices%29%3A", ), ( nested_text_choices, "#:~:text=TextChoices%28%22Medal%22%2C%20%22GOLD%20SILVER%20" "BRONZE%22%29", ), ( """TextChoices , IntegerChoices , and Choices are now available as a way to define Field.choices . TextChoices and IntegerChoices""", "#:~:text=TextChoices%2C%20IntegerChoices%2C%20and%20Choices", ), ( """TextChoices or IntegerChoices ) instances. Any Django field Any function or method reference (e.g. datetime.datetime.today ) (must""", "#:~:text=TextChoices%20or%20IntegerChoices%29%20instances.", ), ( """database configuration in DATABASES : settings.py ¶ DATABASES = { "default" : { "ENGINE" : "django.db.backends.postgresql" , "OPTIONS""", "#:~:text=database%20configuration%20in%20DATABASES%3A", ), ( """ Generic views ¶ See Built-in class-based views API . """, "#:~:text=Generic%20views", ), ] for text, url_text_fragment in cases: with self.subTest(url_text_fragment=url_text_fragment): self.assertEqual( generate_scroll_to_text_fragment(text), url_text_fragment, ) def test_code_links(self): python_objects = { "Layer": "django.contrib.gis.gdal.Layer", "Migration.initial": "django.db.migrations.Migration.initial", "db_for_write": "db_for_write", } for searched_python_objects, expected in [ (None, {}), ("", {}), ("Layer initial db_for_write", {}), ( "Layer initial db_for_write", {"db_for_write": {"full_path": "db_for_write", "module_path": None}}, ), ( "Layer initial db_for_write", { "db_for_write": {"full_path": "db_for_write", "module_path": None}, "Layer": { "full_path": "django.contrib.gis.gdal.Layer", "module_path": "django.contrib.gis.gdal", }, "Migration.initial": { "full_path": "django.db.migrations.Migration.initial", "module_path": "django.db.migrations", }, }, ), ]: with self.subTest(searched_python_objects=searched_python_objects): self.assertEqual( code_links(searched_python_objects, python_objects), expected, ) class TemplateTestCase(TestCase): def _assertOGTitleEqual(self, doc, expected): output = render_to_string( "docs/doc.html", {"doc": doc, "lang": settings.DEFAULT_LANGUAGE_CODE, "version": "5.0"}, request=RequestFactory().get("/"), ) self.assertInHTML(f'', output) def test_opengraph_title(self): doc = Document.objects.create( metadata={"body": "test body"}, release=DocumentRelease.objects.create( lang=settings.DEFAULT_LANGUAGE_CODE, release=Release.objects.create(version="5.0"), ), ) for title, expected in [ ("test title", "test title"), ("test & title", "test & title"), ('test "title"', "test "title""), ("test title", "test title"), ]: doc.title = title with self.subTest(title=title): self._assertOGTitleEqual(doc, f"{expected} | Django documentation") class TemplateTagTestCase(TestCase): def test_search_form_renders_without_request_in_template(self): """ Ensures the tag doesn't crash when rendered inside a template that lacks a 'request' variable e.g. during Django's built-in error views. """ template = Template("{% load docs %}{% search_form %}") rendered = template.render(Context({})) self.assertIn( '', rendered, ) docs_search_url = reverse_with_host( "document-search", host="docs", kwargs={"lang": settings.DEFAULT_LANGUAGE_CODE, "version": "dev"}, ) self.assertIn(f'
', rendered)