diff --git a/requirements/dev.in b/requirements/dev.in index f92244c..d742ad8 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -12,3 +12,4 @@ types-requests mypy curlylint djhtml +wagtail-factories diff --git a/requirements/dev.txt b/requirements/dev.txt index 5478d58..fd5a07e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -33,6 +33,8 @@ djangorestframework==3.13.1 # via -r requirements/base.txt, wagtail djhtml==1.5.1 # via -r requirements/dev.in draftjs-exporter==2.1.7 # via -r requirements/base.txt, wagtail et-xmlfile==1.1.0 # via -r requirements/base.txt, openpyxl +factory-boy==3.2.1 # via wagtail-factories +faker==14.1.0 # via factory-boy flake8==4.0.1 # via -r requirements/dev.in gunicorn==20.1.0 # via -r requirements/base.txt honcho==1.1.0 # via -r requirements/dev.in @@ -58,11 +60,12 @@ pycodestyle==2.8.0 # via flake8 pyflakes==2.4.0 # via flake8 pygments==2.12.0 # via -r requirements/base.txt pyparsing==3.0.9 # via -r requirements/base.txt, packaging +python-dateutil==2.8.2 # via faker pytz==2022.1 # via -r requirements/base.txt, django-modelcluster, djangorestframework, l18n redis==4.3.4 # via -r requirements/base.txt, django-redis, django-rq, rq requests==2.28.1 # via -r requirements/base.txt, wagtail, wagtail-generic-chooser rq==1.10.1 # via -r requirements/base.txt, django-rq -six==1.16.0 # via -r requirements/base.txt, html5lib, l18n +six==1.16.0 # via -r requirements/base.txt, html5lib, l18n, python-dateutil soupsieve==2.3.2.post1 # via -r requirements/base.txt, beautifulsoup4 sqlparse==0.4.2 # via -r requirements/base.txt, django, django-debug-toolbar tablib[xls,xlsx]==3.2.1 # via -r requirements/base.txt, wagtail @@ -73,8 +76,9 @@ types-requests==2.28.5 # via -r requirements/dev.in types-urllib3==1.26.17 # via types-requests typing-extensions==4.3.0 # via mypy urllib3==1.26.11 # via -r requirements/base.txt, requests -wagtail==3.0.1 # via -r requirements/base.txt, wagtail-draftail-snippet +wagtail==3.0.1 # via -r requirements/base.txt, wagtail-draftail-snippet, wagtail-factories wagtail-draftail-snippet==0.4.1 # via -r requirements/base.txt +wagtail-factories==3.1.0 # via -r requirements/dev.in wagtail-generic-chooser==0.4.1 # via -r requirements/base.txt webencodings==0.5.1 # via -r requirements/base.txt, html5lib wheel==0.37.1 # via pip-tools diff --git a/website/common/factories.py b/website/common/factories.py new file mode 100644 index 0000000..3879417 --- /dev/null +++ b/website/common/factories.py @@ -0,0 +1,19 @@ +import factory +import wagtail_factories + +from . import models + + +class BaseContentFactory(wagtail_factories.PageFactory): + title = factory.Faker("catch_phrase") + subtitle = factory.Faker("bs") + + +class ContentPageFactory(BaseContentFactory): + class Meta: + model = models.ContentPage + + +class ListingPageFactory(BaseContentFactory): + class Meta: + model = models.ListingPage diff --git a/website/common/tests/__init__.py b/website/common/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/common/tests/test_pages.py b/website/common/tests/test_pages.py new file mode 100644 index 0000000..e8407df --- /dev/null +++ b/website/common/tests/test_pages.py @@ -0,0 +1,69 @@ +from django.template.loader import get_template +from django.test import SimpleTestCase, TestCase + +from website.common.factories import ContentPageFactory, ListingPageFactory +from website.common.models import BasePage +from website.common.utils import get_page_models +from website.home.models import HomePage + + +class BasePageTestCase(SimpleTestCase): + def test_unique_body_classes(self) -> None: + body_classes = [page.body_class for page in get_page_models()] + self.assertEqual(len(body_classes), len(set(body_classes))) + + def test_pages_inherit_base_page(self) -> None: + for page_model in get_page_models(): + self.assertTrue( + issubclass(page_model, BasePage), + f"{page_model} does not inherit from {BasePage}.", + ) + + def test_pages_have_template(self) -> None: + for page in get_page_models(): + get_template(page.template) + + +class ContentPageTestCase(TestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.home_page = HomePage.objects.get() + cls.page = ContentPageFactory(parent=cls.home_page) + + def test_accessible(self) -> None: + response = self.client.get(self.page.url) + self.assertEqual(response.status_code, 200) + + def test_queries(self) -> None: + with self.assertNumQueries(15): + self.client.get(self.page.url) + + +class ListingPageTestCase(TestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.home_page = HomePage.objects.get() + cls.page = ListingPageFactory(parent=cls.home_page) + + # Orphaned content page, shouldn't show up on lists + ContentPageFactory() + + ContentPageFactory(parent=cls.page) + ContentPageFactory(parent=cls.page) + + def test_accessible(self) -> None: + response = self.client.get(self.page.url) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context["child_pages"]), 2) + + def test_queries(self) -> None: + expected_queries = 18 + + with self.assertNumQueries(expected_queries): + self.client.get(self.page.url) + + # Add another page, and check queries don't change + ContentPageFactory(parent=self.page) + + with self.assertNumQueries(expected_queries): + self.client.get(self.page.url) diff --git a/website/common/tests/test_streamfield.py b/website/common/tests/test_streamfield.py new file mode 100644 index 0000000..7d5eee4 --- /dev/null +++ b/website/common/tests/test_streamfield.py @@ -0,0 +1,21 @@ +from django.test import SimpleTestCase + +from website.common.streamfield import ( + IGNORE_HEADING_BLOCKS, + IGNORE_PLAINTEXT_BLOCKS, + get_blocks, +) + + +class StreamFieldBlocksTestCase(SimpleTestCase): + def test_ignored_plaintext_blocks(self) -> None: + plaintext_block_classes = [c[1].__class__ for c in get_blocks()] + + for block_class in IGNORE_PLAINTEXT_BLOCKS: + self.assertIn(block_class, plaintext_block_classes) + + def test_ignored_heading_blocks(self) -> None: + heading_block_classes = [c[1].__class__ for c in get_blocks()] + + for block_class in IGNORE_HEADING_BLOCKS: + self.assertIn(block_class, heading_block_classes) diff --git a/website/common/tests.py b/website/common/tests/test_utils.py similarity index 74% rename from website/common/tests.py rename to website/common/tests/test_utils.py index 08aa449..495897b 100644 --- a/website/common/tests.py +++ b/website/common/tests/test_utils.py @@ -1,33 +1,13 @@ -from django.template.loader import get_template from django.test import SimpleTestCase from wagtail.rich_text import features as richtext_feature_registry -from .embed import YouTubeLiteEmbedFinder -from .models import BasePage -from .rich_text import ( +from website.common.embed import YouTubeLiteEmbedFinder +from website.common.rich_text import ( RICH_TEXT_FEATURES, RICH_TEXT_FEATURES_PLAIN, RICH_TEXT_FEATURES_SIMPLE, ) -from .streamfield import IGNORE_HEADING_BLOCKS, IGNORE_PLAINTEXT_BLOCKS, get_blocks -from .utils import count_words, extract_text, get_page_models, get_table_of_contents - - -class BasePageTestCase(SimpleTestCase): - def test_unique_body_classes(self) -> None: - body_classes = [page.body_class for page in get_page_models()] - self.assertEqual(len(body_classes), len(set(body_classes))) - - def test_pages_inherit_base_page(self) -> None: - for page_model in get_page_models(): - self.assertTrue( - issubclass(page_model, BasePage), - f"{page_model} does not inherit from {BasePage}.", - ) - - def test_pages_have_template(self) -> None: - for page in get_page_models(): - get_template(page.template) +from website.common.utils import count_words, extract_text, get_table_of_contents class YouTubeLiteEmbedFinderTestCase(SimpleTestCase): @@ -127,20 +107,6 @@ class CountWordsTestCase(SimpleTestCase): self.assertEqual(count_words("Hello there! How are you?"), 5) -class StreamFieldBlocksTestCase(SimpleTestCase): - def test_ignored_plaintext_blocks(self) -> None: - plaintext_block_classes = [c[1].__class__ for c in get_blocks()] - - for block_class in IGNORE_PLAINTEXT_BLOCKS: - self.assertIn(block_class, plaintext_block_classes) - - def test_ignored_heading_blocks(self) -> None: - heading_block_classes = [c[1].__class__ for c in get_blocks()] - - for block_class in IGNORE_HEADING_BLOCKS: - self.assertIn(block_class, heading_block_classes) - - class RichTextFeaturesTestCase(SimpleTestCase): def test_features_exist(self) -> None: for feature in RICH_TEXT_FEATURES: