diff --git a/tests/main/test_main_views.py b/tests/main/test_main_views.py index 997a2928..5568c2a0 100644 --- a/tests/main/test_main_views.py +++ b/tests/main/test_main_views.py @@ -7,6 +7,7 @@ import json from http import HTTPStatus +from urllib import parse as parseUrl import pytest from django.db.models import QuerySet @@ -567,10 +568,80 @@ def _get_url(self): return reverse("licensing") -class TestViewSkillProfilePageView(TemplateOkMixin): +class TestViewSkillProfilePageView(TemplateOkMixin, BS4Mixin): """Test suite for the ViewSkillProfilePageView.""" _template_name = "main/shared-skills-profile.html" - def _get_url(self): - return reverse("view_skill_profile") + def _example_chart_data(self, user_skill): + return [ + { + "user_id": "root", + "user_data": [ + { + "skill": user_skill.skill.name, + "category": user_skill.skill.competency.competency_domain.name, + "subcategory": user_skill.skill.competency.name, + "skill_level": user_skill.skill_level.level, + } + ], + } + ] + + def _get_url(self, chart_data=None): + """Construct the URL for the view skill profile page with query parameters.""" + skill_levels = json.dumps(list(SkillLevel.objects.values("level", "name"))) + chart_data_str = json.dumps(chart_data) + url = f"{reverse('view_skill_profile')}" + params = parseUrl.urlencode( + { + "skill_levels": skill_levels, + "chart_data": chart_data_str, + } + ) + url = f"{url}?{params}" + return url + + def test_provides_required_context(self, client, user_skill): + """Test that the view skill profile view provides the correct context.""" + url = self._get_url(self._example_chart_data(user_skill)) + response = client.get(url) + assert response.status_code == HTTPStatus.OK + assert "chart_data" in response.context + assert isinstance(response.context["chart_data"], list) + assert response.context["chart_data"] == self._example_chart_data(user_skill) + assert "skill_levels" in response.context + assert isinstance(response.context["skill_levels"], list) + assert response.context["skill_levels"] == list( + SkillLevel.objects.values("level", "name") + ) + + def test_skill_wheel_script(self, soup_factory, user_skill): + """Test that the skill profile view contains the correct script.""" + soup = soup_factory(chart_data=self._example_chart_data(user_skill)) + card = soup.find("div", class_="card-body") + + assert card.find(tag_with_text_filter("h1", "Skills profile")) + assert card.find("div", id="dataviz_root") + + skill_level_list = list(SkillLevel.objects.values("level", "name")) + user_skill_dict = { + "skill": user_skill.skill.name, + "category": user_skill.skill.competency.competency_domain.name, + "subcategory": user_skill.skill.competency.name, + "skill_level": user_skill.skill_level.level, + } + chart_data = [{"user_id": "root", "user_data": [user_skill_dict]}] + + assert card.find( + tag_with_text_filter("script", f"const skillLevels = {skill_level_list};") + ) + assert card.find( + tag_with_text_filter("script", f"const charts = {chart_data};") + ) + assert card.find( + tag_with_text_filter( + "script", + "renderRadialBarChart(target, charts[i].user_data, skillLevels);", + ) + ) diff --git a/tests/main/view_utils.py b/tests/main/view_utils.py index e1876ea2..95dda4de 100644 --- a/tests/main/view_utils.py +++ b/tests/main/view_utils.py @@ -1,6 +1,7 @@ """Utility module for view tests.""" from abc import ABC, abstractmethod +from collections.abc import Callable from http import HTTPStatus import pytest @@ -13,12 +14,16 @@ class TemplateOkMixin(ABC): """Mixin for tests that verify the correct template usage. Note: Using this requires the test class to define: - - A `_get_url` method + - A `_get_url` method that can be called with no arguments - A `_template_name` variable """ _template_name: str + @abstractmethod + def _get_url(self, **kwargs) -> str: + return NotImplemented + def test_template_used(self, admin_client): """Test the correct template is used by the GET request.""" with assertTemplateUsed(template_name=self._template_name): @@ -30,13 +35,13 @@ class LoginRequiredMixin(ABC): """Mixin for tests that require a user to be logged in. Note: Using this requires the test class to define: - - A `_get_url` method + - A `_get_url` method that can be called with no arguments """ _template_name: str @abstractmethod - def _get_url(self) -> str: + def _get_url(self, **kwargs) -> str: return NotImplemented def test_login_required(self, client): @@ -54,27 +59,46 @@ class BS4Mixin(ABC): """ @abstractmethod - def _get_url(self) -> str: + def _get_url(self, **kwargs) -> str: return NotImplemented @pytest.fixture - def soup(self, client) -> BeautifulSoup: + def soup_factory(self, client, admin_client, user) -> Callable[..., BeautifulSoup]: + """A fixture factory for the BeautifulSoup4 object of the requested page. + + Returns a function that can be called with kwargs provided if the get_url method + requires them. Possible kwargs: + - authenticated: `True` if user should be logged-in + - admin: `True` if user should be an admin + - Any other kwargs: passed to `get_url` method + """ + + def get_soup(admin=False, authenticated=False, **kwargs) -> BeautifulSoup: + _client = admin_client if admin else client + if authenticated: + _client.force_login(user) + if kwargs: + response = _client.get(self._get_url(**kwargs)) + else: + response = _client.get(self._get_url()) + return BeautifulSoup(response.content, "html.parser") + + return get_soup + + @pytest.fixture + def soup(self, soup_factory) -> BeautifulSoup: """A fixture of the BeautifulSoup4 object of the requested page.""" - response = client.get(self._get_url()) - return BeautifulSoup(response.content, "html.parser") + return soup_factory() @pytest.fixture - def auth_soup(self, client, user) -> BeautifulSoup: + def auth_soup(self, soup_factory) -> BeautifulSoup: """A BeautifulSoup4 object of the requested page viewed by a logged-in user.""" - client.force_login(user) - response = client.get(self._get_url()) - return BeautifulSoup(response.content, "html.parser") + return soup_factory(authenticated=True) @pytest.fixture - def admin_soup(self, admin_client) -> BeautifulSoup: + def admin_soup(self, soup_factory) -> BeautifulSoup: """A BeautifulSoup4 object of the requested page viewed by an admin user.""" - response = admin_client.get(self._get_url()) - return BeautifulSoup(response.content, "html.parser") + return soup_factory(admin=True) def tag_with_text_filter(tag_name: str, text: str):