From 6403aca2a296e083b0eb43292cd29e633f4c54b6 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Sun, 28 Aug 2022 12:57:10 +0100 Subject: [PATCH] Add tests for search page --- website/search/factories.py | 8 +++ website/search/models.py | 10 +-- website/search/tests.py | 129 ++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 website/search/factories.py create mode 100644 website/search/tests.py diff --git a/website/search/factories.py b/website/search/factories.py new file mode 100644 index 0000000..f39ce1a --- /dev/null +++ b/website/search/factories.py @@ -0,0 +1,8 @@ +from website.common.factories import BaseContentFactory + +from .models import SearchPage + + +class SearchPageFactory(BaseContentFactory): + class Meta: + model = SearchPage diff --git a/website/search/models.py b/website/search/models.py index 9d4f1ea..8b7589f 100644 --- a/website/search/models.py +++ b/website/search/models.py @@ -1,7 +1,7 @@ from django.core.paginator import EmptyPage, Paginator from django.http.request import HttpRequest -from django.http.response import HttpResponse, HttpResponseBadRequest -from django.shortcuts import render +from django.http.response import Http404, HttpResponse, HttpResponseBadRequest +from django.template.response import TemplateResponse from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.views.decorators.http import require_GET @@ -46,7 +46,7 @@ class SearchPage(RoutablePageMixin, BaseContentPage): serializer = SearchParamsSerializer(data=request.GET) if not serializer.is_valid(): - return render( + return TemplateResponse( request, "search/enter-search-term.html", {"MIN_SEARCH_LENGTH": MIN_SEARCH_LENGTH}, @@ -82,8 +82,8 @@ class SearchPage(RoutablePageMixin, BaseContentPage): ) except EmptyPage: - results = [] + raise Http404 context["results"] = results - return render(request, "search/search_results.html", context) + return TemplateResponse(request, "search/search_results.html", context) diff --git a/website/search/tests.py b/website/search/tests.py new file mode 100644 index 0000000..4e4174e --- /dev/null +++ b/website/search/tests.py @@ -0,0 +1,129 @@ +from bs4 import BeautifulSoup +from django.test import TestCase + +from website.common.factories import ContentPageFactory +from website.home.models import HomePage + +from .factories import SearchPageFactory +from .models import SearchPage + + +class SearchPageTestCase(TestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.home_page = HomePage.objects.get() + cls.page = SearchPageFactory(parent=cls.home_page) + + def test_accessible(self) -> None: + response = self.client.get(self.page.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["search_url"], "results/") + self.assertEqual(response.context["MIN_SEARCH_LENGTH"], 3) + + def test_initial_query(self) -> None: + response = self.client.get(self.page.url, {"q": "post 1"}) + self.assertEqual(response.context["search_query"], "post 1") + self.assertTemplateNotUsed(response, "search/enter-search-term.html") + + search_input = BeautifulSoup(response.content, "lxml").find("input") + self.assertEqual(search_input.attrs["value"], "post 1") + + def test_search_input(self) -> None: + response = self.client.get(self.page.url) + self.assertEqual(response.status_code, 200) + + soup = BeautifulSoup(response.content, "lxml") + search_input = soup.find("input") + + self.assertEqual(search_input.attrs["name"], "q") + self.assertEqual(search_input.attrs["hx-get"], "results/") + self.assertEqual(search_input.attrs["value"], "") + + self.assertEqual(len(soup.select(search_input.attrs["hx-target"])), 1) + self.assertEqual(len(soup.select(search_input.attrs["hx-indicator"])), 1) + + +class SearchPageResultsTestCase(TestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.home_page = HomePage.objects.get() + cls.page = SearchPageFactory(parent=cls.home_page) + + for i in range(SearchPage.PAGE_SIZE + 1): + ContentPageFactory(parent=cls.home_page, title=f"Post {i}") + + cls.url = cls.page.url + cls.page.reverse_subpage("results") + + def test_returns_results(self) -> None: + with self.assertNumQueries(11): + response = self.client.get(self.url, {"q": "post"}, HTTP_HX_REQUEST="true") + self.assertEqual(response.status_code, 200) + + self.assertEqual(len(response.context["results"]), SearchPage.PAGE_SIZE) + self.assertEqual(response.context["paginator"].count, SearchPage.PAGE_SIZE + 1) + self.assertEqual(response.context["search_query"], "post") + self.assertEqual(response.context["page_num"], 1) + + def test_page_trigger(self) -> None: + response = self.client.get(self.url, {"q": "post"}, HTTP_HX_REQUEST="true") + self.assertEqual(response.status_code, 200) + trigger = BeautifulSoup(response.content, "lxml").find( + "span", attrs={"hx-trigger": "revealed"} + ) + self.assertEqual(trigger.attrs["hx-swap"], "outerHTML") + self.assertEqual(trigger.attrs["hx-get"], "results/?q=post&page=2") + + def test_pagination(self) -> None: + response = self.client.get( + self.url, {"q": "post", "page": 2}, HTTP_HX_REQUEST="true" + ) + self.assertEqual(response.status_code, 200) + + self.assertEqual(response.context["page_num"], 2) + self.assertEqual(len(response.context["results"]), 1) + + self.assertIsNone( + BeautifulSoup(response.content, "lxml").find( + "span", attrs={"hx-trigger": "revealed"} + ) + ) + + def test_too_high_page(self) -> None: + with self.assertNumQueries(46): + response = self.client.get( + self.url, {"q": "post", "page": 3}, HTTP_HX_REQUEST="true" + ) + self.assertEqual(response.status_code, 404) + + def test_returns_result(self) -> None: + response = self.client.get(self.url, {"q": "post 1"}, HTTP_HX_REQUEST="true") + self.assertEqual(response.status_code, 200) + + self.assertEqual(len(response.context["results"]), 1) + self.assertEqual(list(response.context["results"])[0].title, "Post 1") + + def test_no_results(self) -> None: + response = self.client.get(self.url, {"q": "nothing"}, HTTP_HX_REQUEST="true") + self.assertEqual(response.status_code, 200) + + self.assertEqual(len(response.context["results"]), 0) + self.assertContains(response, "No results found") + + def test_no_query(self) -> None: + with self.assertNumQueries(7): + response = self.client.get(self.url, HTTP_HX_REQUEST="true") + self.assertEqual(response.status_code, 200) + + self.assertTemplateUsed(response, "search/enter-search-term.html") + + def test_empty_query(self) -> None: + with self.assertNumQueries(7): + response = self.client.get(self.url, {"q": ""}, HTTP_HX_REQUEST="true") + self.assertEqual(response.status_code, 200) + + self.assertTemplateUsed(response, "search/enter-search-term.html") + + def test_not_htmx(self) -> None: + with self.assertNumQueries(7): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 400)