From 9cd6d2f7b470fc1d8e72502085e9bf9766656a55 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:13:56 -0700 Subject: [PATCH 01/33] Add basic icon with tooltip --- src/registrar/models/domain.py | 25 +++++++++++++++++++++++++ src/registrar/templates/home.html | 9 +++++++++ 2 files changed, 34 insertions(+) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 27a8364bc..e84221a3f 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1397,6 +1397,31 @@ def dns_needed(self): logger.info("Changing to DNS_NEEDED state") logger.info("able to transition to DNS_NEEDED state") + def get_state_help_text(self) -> str: + """Returns a str containing additional information about a given state""" + help_text = "" + print(f"self state is {self.state}") + match self.state: + case self.State.DNS_NEEDED | self.State.UNKNOWN: + help_text = ( + "Before this domain can be used, " + "you’ll need to add name server addresses." + ) + case self.State.READY: + help_text = "This domain has name servers and is ready for use." + case self.State.ON_HOLD: + help_text = ( + "This domain is administratively paused, " + "so it can’t be edited and won’t resolve in DNS. " + "Contact help@get.gov for details." + ) + case self.State.DELETED: + help_text = ( + "This domain has been removed and " + "is no longer registered to your organization." + ) + return help_text + def _disclose_fields(self, contact: PublicContact): """creates a disclose object that can be added to a contact Create using .disclose= on the command before sending. diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index c7a005f97..0b0553d8b 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -53,6 +53,15 @@

Domains

{% else %} {{ domain.state|capfirst }} {% endif %} + + + From 6f891d82883d3ab2cdf6312546673a5dd7d1a0df Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:26:53 -0700 Subject: [PATCH 02/33] Increase hover area --- src/registrar/templates/home.html | 40 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 0b0553d8b..1afb167cf 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -45,23 +45,29 @@

Domains

{{ domain.expiration_date|date }} - {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} - {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} - Expired - {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} - DNS needed - {% else %} - {{ domain.state|capfirst }} - {% endif %} - - - + + {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} + {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} + Expired + {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} + DNS needed + {% else %} + {{ domain.state|capfirst }} + {% endif %} + {# TODO: this tooltip should trigger on click, not hover. Better for access and works better button wise #} + + + +
From 60d1c2f1ed76cf68bff7a0d972354c5396a9f200 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:53:46 -0700 Subject: [PATCH 03/33] CSS changes --- src/registrar/assets/sass/_theme/_buttons.scss | 8 ++++++++ src/registrar/templates/home.html | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 02089ec6d..6aa2995fd 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -132,3 +132,11 @@ a.usa-button--unstyled:visited { margin-left: units(2); } } + +// TODO: find another file for this +.info-button span.usa-tooltip svg.usa-icon { + svg.usa-icon{ + transform: translateY(2px) !important; + background: transparent; + } +} diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 1afb167cf..60aa1cd55 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -45,7 +45,7 @@

Domains

{{ domain.expiration_date|date }} - Date: Wed, 31 Jan 2024 10:31:43 -0700 Subject: [PATCH 04/33] Change style --- .../assets/sass/_theme/_buttons.scss | 11 +++++----- src/registrar/templates/home.html | 22 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 6aa2995fd..81ae2f0d7 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -133,10 +133,9 @@ a.usa-button--unstyled:visited { } } -// TODO: find another file for this -.info-button span.usa-tooltip svg.usa-icon { - svg.usa-icon{ - transform: translateY(2px) !important; - background: transparent; - } +// TODO: find another file for this +.info-tooltip { + transform: translateY(4px) !important; + display: inline-block; + background: transparent; } diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 60aa1cd55..dd7e70fbb 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -45,7 +45,7 @@

Domains

{{ domain.expiration_date|date }} - Domains {% else %} {{ domain.state|capfirst }} {% endif %} - {# TODO: this tooltip should trigger on click, not hover. Better for access and works better button wise #} - - - + + + + + From e2e4ad9d02075ae2f268d5c52d8f281598f6ef78 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:29:11 -0700 Subject: [PATCH 05/33] Align icon --- .../assets/sass/_theme/_buttons.scss | 7 ------- src/registrar/templates/home.html | 20 +++++++++---------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 81ae2f0d7..02089ec6d 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -132,10 +132,3 @@ a.usa-button--unstyled:visited { margin-left: units(2); } } - -// TODO: find another file for this -.info-tooltip { - transform: translateY(4px) !important; - display: inline-block; - background: transparent; -} diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index dd7e70fbb..f8d1332ef 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -58,17 +58,15 @@

Domains

{% else %} {{ domain.state|capfirst }} {% endif %} - - - - - + + +
From 5debd67a897c6e050cf0d3bdf568466d6ba9e638 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:46:14 -0700 Subject: [PATCH 06/33] Add color --- src/registrar/templates/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index f8d1332ef..50fba8cc5 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -59,7 +59,7 @@

Domains

{{ domain.state|capfirst }} {% endif %} Date: Wed, 31 Jan 2024 11:55:27 -0700 Subject: [PATCH 07/33] Styles --- src/registrar/assets/sass/_theme/_buttons.scss | 4 ++-- src/registrar/assets/sass/_theme/_tables.scss | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 2f4121399..ef8635b95 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -53,8 +53,8 @@ a.usa-button--unstyled.disabled-link:focus { } .usa-button--unstyled.disabled-button, -.usa-button--unstyled.disabled-link:hover, -.usa-button--unstyled.disabled-link:focus { +.usa-button--unstyled.disabled-button:hover, +.usa-button--unstyled.disabled-button:focus { cursor: not-allowed !important; outline: none !important; text-decoration: none !important; diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 84c4791e5..1ba24baba 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -26,6 +26,13 @@ padding-bottom: units(2px); } + td { + .no-click-outline, .no-click-outline:hover, .no-click-outline:focus{ + cursor: default !important; + outline: none !important; + } + } + // Ticket #1510 // @include at-media('desktop') { // th:first-child { From 340e395c0013325c34173dcf5b3407f07237c33e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:54:32 -0700 Subject: [PATCH 08/33] Outline styling, refactor help text logic --- src/registrar/assets/sass/_theme/_tables.scss | 6 ++- src/registrar/models/domain.py | 49 +++++++++++-------- src/registrar/templates/home.html | 4 +- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 1ba24baba..45e47cee3 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -28,8 +28,10 @@ td { .no-click-outline, .no-click-outline:hover, .no-click-outline:focus{ - cursor: default !important; - outline: none !important; + outline: none; + } + .cursor-help{ + cursor: help; } } diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index e84221a3f..542d063ed 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -139,6 +139,33 @@ class State(models.TextChoices): # previously existed but has been deleted from the registry DELETED = "deleted", "Deleted" + @classmethod + def get_help_text(cls, state) -> str: + """Returns a help message for a desired state. If none is found, an empty string is returned""" + help_texts = { + # For now, unknown has the same message as DNS_NEEDED + cls.UNKNOWN:( + "Before this domain can be used, " + "you’ll need to add name server addresses." + ), + cls.DNS_NEEDED: ( + "Before this domain can be used, " + "you’ll need to add name server addresses." + ), + cls.READY: "This domain has name servers and is ready for use.", + cls.ON_HOLD: ( + "This domain is administratively paused, " + "so it can’t be edited and won’t resolve in DNS. " + "Contact help@get.gov for details." + ), + cls.DELETED: ( + "This domain has been removed and " + "is no longer registered to your organization." + ) + } + + return help_texts.get(state, "") + class Cache(property): """ Python descriptor to turn class methods into properties. @@ -1399,27 +1426,7 @@ def dns_needed(self): def get_state_help_text(self) -> str: """Returns a str containing additional information about a given state""" - help_text = "" - print(f"self state is {self.state}") - match self.state: - case self.State.DNS_NEEDED | self.State.UNKNOWN: - help_text = ( - "Before this domain can be used, " - "you’ll need to add name server addresses." - ) - case self.State.READY: - help_text = "This domain has name servers and is ready for use." - case self.State.ON_HOLD: - help_text = ( - "This domain is administratively paused, " - "so it can’t be edited and won’t resolve in DNS. " - "Contact help@get.gov for details." - ) - case self.State.DELETED: - help_text = ( - "This domain has been removed and " - "is no longer registered to your organization." - ) + help_text = Domain.State.get_help_text(self.state) return help_text def _disclose_fields(self, contact: PublicContact): diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 8f77748a0..9a1d550a4 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -48,7 +48,7 @@

Domains

{{ domain.expiration_date|date }} - Domains {{ domain.state|capfirst }} {% endif %} Date: Wed, 31 Jan 2024 13:31:54 -0700 Subject: [PATCH 09/33] Add max width to tooltip --- src/registrar/assets/sass/_theme/_base.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index b6d13cee3..afaf0c1df 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -129,3 +129,8 @@ abbr[title] { .flex-end { align-items: flex-end; } + +.usa-tooltip__body { + max-width: 320px; + white-space: normal; +} From 6572ee8d0ccd4fc56578331bcd55019d919e0038 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:48:38 -0700 Subject: [PATCH 10/33] Test --- src/registrar/assets/sass/_theme/_base.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index afaf0c1df..6bd690188 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -131,6 +131,7 @@ abbr[title] { } .usa-tooltip__body { - max-width: 320px; + min-width: 320px; + max-width: 350px; white-space: normal; } From 4de78042471cc2577b98a81f21c7cced51d8e674 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:12:49 -0700 Subject: [PATCH 11/33] Disable pointer events on svg --- src/registrar/assets/sass/_theme/_base.scss | 6 ++++++ src/registrar/templates/home.html | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 6bd690188..fa1f0e210 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -135,3 +135,9 @@ abbr[title] { max-width: 350px; white-space: normal; } + +// USWDS has weird interactions with SVGs regarding tooltips, +// and other components. In this event, we need to disable pointer interactions. +.disable-pointer-events { + pointer-events: none; +} diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 9a1d550a4..566a2d102 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -68,7 +68,7 @@

Domains

title="{{domain.get_state_help_text}}" focusable="true" > - +
From 7b60b9d5d4ecc5d24d30310a8b26ceeda890e68e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 09:41:52 -0700 Subject: [PATCH 12/33] Unit test --- src/registrar/tests/test_views.py | 58 ++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index dcb6ba9e0..a030b4404 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.contrib.auth import get_user_model -from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore +from .common import AuditedAdminMockData, MockEppLib, MockSESClient, completed_application, create_user, generic_domain_object # type: ignore from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -105,6 +105,62 @@ def test_home_lists_domain_applications(self): # clean up application.delete() + + def test_state_help_text(self): + """Tests if each domain state has help text""" + + # Get the expected text content of each state + deleted_text = ( + "Before this domain can be used, " + "you’ll need to add name server addresses." + ) + dns_needed_text = ( + "Before this domain can be used, " + "you’ll need to add name server addresses." + ) + ready_text = "This domain has name servers and is ready for use." + on_hold_text = ( + "This domain is administratively paused, " + "so it can’t be edited and won’t resolve in DNS. " + "Contact help@get.gov for details." + ) + deleted_text = ( + "This domain has been removed and " + "is no longer registered to your organization." + ) + # Generate a mapping of domain names, the state, and expected messages for the subtest + test_cases = [ + ("deleted.gov", Domain.State.DELETED, deleted_text), + ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), + ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), + ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), + ("ready.gov", Domain.State.READY, ready_text), + ] + for domain_name, state, expected_message in test_cases: + with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): + + # Create a domain and a UserRole with the given params + test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) + user_role, _ = UserDomainRole.objects.get_or_create( + user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER + ) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, domain_name, count=2) + + # Check that we have the right text content. + # We expect two instances because of SR content. + self.assertContains(response, expected_message, count=2) + + # Check that its nested in the right html element + + # Delete the role and domain to ensure we're testing in isolation + user_role.delete() + test_domain.delete() def test_home_deletes_withdrawn_domain_application(self): """Tests if the user can delete a DomainApplication in the 'withdrawn' status""" From 9e33539af267d52be1485fb1e5cd023e4a1fe51a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 09:45:16 -0700 Subject: [PATCH 13/33] Change test case --- src/registrar/templates/home.html | 1 - src/registrar/tests/test_views.py | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 566a2d102..b5dab701b 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -50,7 +50,6 @@

Domains

{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index a030b4404..0531d81d5 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -153,11 +153,8 @@ def test_state_help_text(self): self.assertContains(response, domain_name, count=2) # Check that we have the right text content. - # We expect two instances because of SR content. - self.assertContains(response, expected_message, count=2) + self.assertContains(response, expected_message, count=1) - # Check that its nested in the right html element - # Delete the role and domain to ensure we're testing in isolation user_role.delete() test_domain.delete() From 24954ebdce62e02e7d8c8d263722d01f12523f2e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 10:57:11 -0700 Subject: [PATCH 14/33] Add text for expired --- src/registrar/models/domain.py | 13 +++++++++++-- src/registrar/templates/domain_detail.html | 7 ++++++- src/registrar/tests/test_views.py | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 542d063ed..70b3ac2e5 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1425,8 +1425,17 @@ def dns_needed(self): logger.info("able to transition to DNS_NEEDED state") def get_state_help_text(self) -> str: - """Returns a str containing additional information about a given state""" - help_text = Domain.State.get_help_text(self.state) + """Returns a str containing additional information about a given state. + Returns custom content for when the domain itself is expired.""" + if not self.is_expired(): + help_text = Domain.State.get_help_text(self.state) + else: + # Given expired is not a physical state, but it is displayed as such, + # We need custom logic to determine this message. + help_text = ( + "This domain has expired, but it is still online. " + "To renew this domain, contact help@get.gov." + ) return help_text def _disclose_fields(self, contact: PublicContact): diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 09fc189e4..fe9062a23 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -6,7 +6,7 @@
@@ -25,6 +25,11 @@ {% else %} {{ domain.state|title }} {% endif %} + {% if domain.get_state_help_text %} +
+ {{ domain.get_state_help_text }} +
+ {% endif %}

diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 0531d81d5..ba28a7a56 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -128,6 +128,10 @@ def test_state_help_text(self): "This domain has been removed and " "is no longer registered to your organization." ) + expired_text = ( + "This domain has expired, but it is still online. " + "To renew this domain, contact help@get.gov." + ) # Generate a mapping of domain names, the state, and expected messages for the subtest test_cases = [ ("deleted.gov", Domain.State.DELETED, deleted_text), @@ -135,12 +139,16 @@ def test_state_help_text(self): ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), ("ready.gov", Domain.State.READY, ready_text), + ("expired.gov", Domain.State.READY, expired_text) ] for domain_name, state, expected_message in test_cases: with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): # Create a domain and a UserRole with the given params test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) + if domain_name == "expired.gov": + test_domain.expiration_date = date(2011, 10, 10) + test_domain.save() user_role, _ = UserDomainRole.objects.get_or_create( user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER ) From c0e866011b2e39c483a64a63eda481c331e2a766 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:52:08 -0700 Subject: [PATCH 15/33] Changes --- src/registrar/public/sass/_theme/_base.scss | 141 ++++++++++++++++++++ src/registrar/templates/home.html | 1 - 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 src/registrar/public/sass/_theme/_base.scss diff --git a/src/registrar/public/sass/_theme/_base.scss b/src/registrar/public/sass/_theme/_base.scss new file mode 100644 index 000000000..a7a4bf8a9 --- /dev/null +++ b/src/registrar/public/sass/_theme/_base.scss @@ -0,0 +1,141 @@ +@use "uswds-core" as *; + +/* Styles for making visible to screen reader / AT users only. */ +.sr-only { + @include sr-only; +} + +.clear-both { + clear: both; +} + +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +#wrapper { + flex-grow: 1; + padding-top: units(3); + padding-bottom: units(6) * 2 ; //Workaround because USWDS units jump from 10 to 15 +} + +#wrapper.dashboard { + background-color: color('primary-lightest'); + padding-top: units(5); +} + +.usa-logo { + @include at-media(desktop) { + margin-top: units(2); + } +} + +.usa-logo__text { + @include typeset('sans', 'xl', 2); + color: color('primary-darker'); +} + +.usa-nav__primary { + margin-top:units(1); +} + +.section--outlined { + background-color: color('white'); + border: 1px solid color('base-lighter'); + border-radius: 4px; + padding: 0 units(2) units(3); + margin-top: units(3); + + h2 { + color: color('primary-dark'); + margin-top: units(2); + margin-bottom: units(2); + } + + p { + margin-bottom: 0; + } + + @include at-media(mobile-lg) { + margin-top: units(5); + + h2 { + margin-bottom: 0; + } + } +} + +.break-word { + word-break: break-word; +} + +.dotgov-status-box { + background-color: color('primary-lightest'); + border-color: color('accent-cool-lighter'); +} + +.dotgov-status-box--action-need { + background-color: color('warning-lighter'); + border-color: color('warning'); +} + +footer { + border-top: 1px solid color('primary-darker'); +} + +.usa-footer__secondary-section { + background-color: color('primary-lightest'); +} + +.usa-footer__secondary-section a { + color: color('primary'); +} + +.usa-identifier__logo { + height: units(7); +} + +abbr[title] { + // workaround for underlining abbr element + border-bottom: none; + text-decoration: none; +} + +@include at-media(tablet) { + .float-right-tablet { + float: right; + } + .float-left-tablet { + float: left; + } +} + +@include at-media(desktop) { + .float-right-desktop { + float: right; + } + .float-left-desktop { + float: left; + } +} + +.flex-end { + align-items: flex-end; +} + +.usa-tooltip__body { + min-width: 320px; +} + +// USWDS has weird interactions with SVGs regarding tooltips, +// and other components. In this event, we need to disable pointer interactions. +.disable-pointer-events { + pointer-events: none; +} diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index b5dab701b..fecb05540 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -49,7 +49,6 @@

Domains

{{ domain.expiration_date|date }} {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} From a50462353fccc90983a864fdea6629def69a84e0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:53:40 -0700 Subject: [PATCH 16/33] Remove accidental addition --- src/registrar/assets/sass/_theme/_base.scss | 2 - src/registrar/public/sass/_theme/_base.scss | 141 -------------------- 2 files changed, 143 deletions(-) delete mode 100644 src/registrar/public/sass/_theme/_base.scss diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index fa1f0e210..a7a4bf8a9 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -132,8 +132,6 @@ abbr[title] { .usa-tooltip__body { min-width: 320px; - max-width: 350px; - white-space: normal; } // USWDS has weird interactions with SVGs regarding tooltips, diff --git a/src/registrar/public/sass/_theme/_base.scss b/src/registrar/public/sass/_theme/_base.scss deleted file mode 100644 index a7a4bf8a9..000000000 --- a/src/registrar/public/sass/_theme/_base.scss +++ /dev/null @@ -1,141 +0,0 @@ -@use "uswds-core" as *; - -/* Styles for making visible to screen reader / AT users only. */ -.sr-only { - @include sr-only; -} - -.clear-both { - clear: both; -} - -* { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -body { - display: flex; - flex-direction: column; - min-height: 100vh; -} - -#wrapper { - flex-grow: 1; - padding-top: units(3); - padding-bottom: units(6) * 2 ; //Workaround because USWDS units jump from 10 to 15 -} - -#wrapper.dashboard { - background-color: color('primary-lightest'); - padding-top: units(5); -} - -.usa-logo { - @include at-media(desktop) { - margin-top: units(2); - } -} - -.usa-logo__text { - @include typeset('sans', 'xl', 2); - color: color('primary-darker'); -} - -.usa-nav__primary { - margin-top:units(1); -} - -.section--outlined { - background-color: color('white'); - border: 1px solid color('base-lighter'); - border-radius: 4px; - padding: 0 units(2) units(3); - margin-top: units(3); - - h2 { - color: color('primary-dark'); - margin-top: units(2); - margin-bottom: units(2); - } - - p { - margin-bottom: 0; - } - - @include at-media(mobile-lg) { - margin-top: units(5); - - h2 { - margin-bottom: 0; - } - } -} - -.break-word { - word-break: break-word; -} - -.dotgov-status-box { - background-color: color('primary-lightest'); - border-color: color('accent-cool-lighter'); -} - -.dotgov-status-box--action-need { - background-color: color('warning-lighter'); - border-color: color('warning'); -} - -footer { - border-top: 1px solid color('primary-darker'); -} - -.usa-footer__secondary-section { - background-color: color('primary-lightest'); -} - -.usa-footer__secondary-section a { - color: color('primary'); -} - -.usa-identifier__logo { - height: units(7); -} - -abbr[title] { - // workaround for underlining abbr element - border-bottom: none; - text-decoration: none; -} - -@include at-media(tablet) { - .float-right-tablet { - float: right; - } - .float-left-tablet { - float: left; - } -} - -@include at-media(desktop) { - .float-right-desktop { - float: right; - } - .float-left-desktop { - float: left; - } -} - -.flex-end { - align-items: flex-end; -} - -.usa-tooltip__body { - min-width: 320px; -} - -// USWDS has weird interactions with SVGs regarding tooltips, -// and other components. In this event, we need to disable pointer interactions. -.disable-pointer-events { - pointer-events: none; -} From bb3e71380fd46da032f6f53a62e0b9e5072c5b66 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:23:32 -0700 Subject: [PATCH 17/33] Fix test cases --- src/registrar/assets/sass/_theme/_base.scss | 10 ++++-- src/registrar/templates/home.html | 4 +-- src/registrar/tests/test_views.py | 35 ++++++++++++++++----- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index a7a4bf8a9..ca4c03de6 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -130,10 +130,14 @@ abbr[title] { align-items: flex-end; } -.usa-tooltip__body { - min-width: 320px; +// Only apply this custom wrapping to desktop +@media (min-width: 768px) { + .usa-tooltip__body { + min-width: 320px; + max-width: 350px; + white-space: normal; + } } - // USWDS has weird interactions with SVGs regarding tooltips, // and other components. In this event, we need to disable pointer interactions. .disable-pointer-events { diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index fecb05540..44023fc7d 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -48,9 +48,7 @@

Domains

{{ domain.expiration_date|date }} - + {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} Expired diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index ba28a7a56..7dc11f9ec 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -128,10 +128,6 @@ def test_state_help_text(self): "This domain has been removed and " "is no longer registered to your organization." ) - expired_text = ( - "This domain has expired, but it is still online. " - "To renew this domain, contact help@get.gov." - ) # Generate a mapping of domain names, the state, and expected messages for the subtest test_cases = [ ("deleted.gov", Domain.State.DELETED, deleted_text), @@ -139,16 +135,15 @@ def test_state_help_text(self): ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), ("ready.gov", Domain.State.READY, ready_text), - ("expired.gov", Domain.State.READY, expired_text) ] for domain_name, state, expected_message in test_cases: with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): # Create a domain and a UserRole with the given params test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) - if domain_name == "expired.gov": - test_domain.expiration_date = date(2011, 10, 10) - test_domain.save() + test_domain.expiration_date = date.today() + test_domain.save() + user_role, _ = UserDomainRole.objects.get_or_create( user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER ) @@ -167,6 +162,30 @@ def test_state_help_text(self): user_role.delete() test_domain.delete() + def test_state_help_text_expired(self): + """Tests if each domain state has help text when expired""" + expired_text = ( + "This domain has expired, but it is still online. " + "To renew this domain, contact help@get.gov." + ) + test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) + test_domain.expiration_date = date(2011, 10, 10) + test_domain.save() + + UserDomainRole.objects.get_or_create( + user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER + ) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "expired.gov", count=2) + + # Check that we have the right text content. + self.assertContains(response, expired_text, count=1) + def test_home_deletes_withdrawn_domain_application(self): """Tests if the user can delete a DomainApplication in the 'withdrawn' status""" From 6dd846f8b68c73898d3e19e875816d8c0039f5f3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:30:52 -0700 Subject: [PATCH 18/33] Linting --- src/registrar/models/domain.py | 20 +++++-------------- src/registrar/tests/test_views.py | 33 +++++++++---------------------- 2 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 70b3ac2e5..9fd5d5490 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -144,24 +144,15 @@ def get_help_text(cls, state) -> str: """Returns a help message for a desired state. If none is found, an empty string is returned""" help_texts = { # For now, unknown has the same message as DNS_NEEDED - cls.UNKNOWN:( - "Before this domain can be used, " - "you’ll need to add name server addresses." - ), - cls.DNS_NEEDED: ( - "Before this domain can be used, " - "you’ll need to add name server addresses." - ), + cls.UNKNOWN: ("Before this domain can be used, " "you’ll need to add name server addresses."), + cls.DNS_NEEDED: ("Before this domain can be used, " "you’ll need to add name server addresses."), cls.READY: "This domain has name servers and is ready for use.", cls.ON_HOLD: ( - "This domain is administratively paused, " + "This domain is administratively paused, " "so it can’t be edited and won’t resolve in DNS. " "Contact help@get.gov for details." ), - cls.DELETED: ( - "This domain has been removed and " - "is no longer registered to your organization." - ) + cls.DELETED: ("This domain has been removed and " "is no longer registered to your organization."), } return help_texts.get(state, "") @@ -1433,8 +1424,7 @@ def get_state_help_text(self) -> str: # Given expired is not a physical state, but it is displayed as such, # We need custom logic to determine this message. help_text = ( - "This domain has expired, but it is still online. " - "To renew this domain, contact help@get.gov." + "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." ) return help_text diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 7dc11f9ec..ad7c54bd7 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -6,7 +6,7 @@ from django.urls import reverse from django.contrib.auth import get_user_model -from .common import AuditedAdminMockData, MockEppLib, MockSESClient, completed_application, create_user, generic_domain_object # type: ignore +from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -105,29 +105,20 @@ def test_home_lists_domain_applications(self): # clean up application.delete() - + def test_state_help_text(self): """Tests if each domain state has help text""" - + # Get the expected text content of each state - deleted_text = ( - "Before this domain can be used, " - "you’ll need to add name server addresses." - ) - dns_needed_text = ( - "Before this domain can be used, " - "you’ll need to add name server addresses." - ) + deleted_text = "Before this domain can be used, " "you’ll need to add name server addresses." + dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses." ready_text = "This domain has name servers and is ready for use." on_hold_text = ( - "This domain is administratively paused, " + "This domain is administratively paused, " "so it can’t be edited and won’t resolve in DNS. " "Contact help@get.gov for details." ) - deleted_text = ( - "This domain has been removed and " - "is no longer registered to your organization." - ) + deleted_text = "This domain has been removed and " "is no longer registered to your organization." # Generate a mapping of domain names, the state, and expected messages for the subtest test_cases = [ ("deleted.gov", Domain.State.DELETED, deleted_text), @@ -138,7 +129,6 @@ def test_state_help_text(self): ] for domain_name, state, expected_message in test_cases: with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): - # Create a domain and a UserRole with the given params test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) test_domain.expiration_date = date.today() @@ -164,17 +154,12 @@ def test_state_help_text(self): def test_state_help_text_expired(self): """Tests if each domain state has help text when expired""" - expired_text = ( - "This domain has expired, but it is still online. " - "To renew this domain, contact help@get.gov." - ) + expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) test_domain.expiration_date = date(2011, 10, 10) test_domain.save() - UserDomainRole.objects.get_or_create( - user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER - ) + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) # Grab the home page response = self.client.get("/") From e27054cd259b5246891faa9655eaae04c3ecdbcc Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:02:52 -0700 Subject: [PATCH 19/33] Add styles for desktop and mobile --- src/registrar/assets/sass/_theme/_base.scss | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index ca4c03de6..b5f4c5c6f 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -131,11 +131,21 @@ abbr[title] { } // Only apply this custom wrapping to desktop -@media (min-width: 768px) { +@include at-media(desktop) { .usa-tooltip__body { - min-width: 320px; + min-width: 350px; max-width: 350px; white-space: normal; + text-align: center; + } +} + +@media (min-width: 768px) { + .usa-tooltip__body { + min-width: 250px; + max-width: 250px; + white-space: normal; + text-align: center; } } // USWDS has weird interactions with SVGs regarding tooltips, From 0a921e14424fa91f30ad435275b5568bb167eb13 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:00:31 -0700 Subject: [PATCH 20/33] Fix bug with unknown and add test for it --- src/registrar/models/domain.py | 2 +- src/registrar/tests/test_views.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 9fd5d5490..233e619b3 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1418,7 +1418,7 @@ def dns_needed(self): def get_state_help_text(self) -> str: """Returns a str containing additional information about a given state. Returns custom content for when the domain itself is expired.""" - if not self.is_expired(): + if not self.is_expired() and self.state != self.State.UNKNOWN: help_text = Domain.State.get_help_text(self.state) else: # Given expired is not a physical state, but it is displayed as such, diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index ad7c54bd7..ab1dfc87e 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -170,6 +170,41 @@ def test_state_help_text_expired(self): # Check that we have the right text content. self.assertContains(response, expired_text, count=1) + + def test_state_help_text_no_expiration_date(self): + """Tests if each domain state has help text when expiration date is None""" + + # == Test a expiration of None for state ready. This should be expired. == # + expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." + test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "expired.gov", count=2) + + # Check that we have the right text content. + self.assertContains(response, expired_text, count=1) + + # == Test a expiration of None for state unknown. This should not display expired text. == # + unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses." + test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN) + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "notexpired.gov", count=2) + + # Check that we have the right text content. + self.assertContains(response, unknown_text, count=1) def test_home_deletes_withdrawn_domain_application(self): """Tests if the user can delete a DomainApplication in the 'withdrawn' status""" From 3888b24af8a3a5ff023bcd85ae8457abf941f06d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:02:25 -0700 Subject: [PATCH 21/33] Modify test slightly --- src/registrar/tests/test_views.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index ab1dfc87e..6afe834a1 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -176,7 +176,9 @@ def test_state_help_text_no_expiration_date(self): # == Test a expiration of None for state ready. This should be expired. == # expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." - test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) + test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY) + test_domain.expiration_date = None + test_domain.save() UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) @@ -185,7 +187,10 @@ def test_state_help_text_no_expiration_date(self): # Make sure the user can actually see the domain. # We expect two instances because of SR content. - self.assertContains(response, "expired.gov", count=2) + self.assertContains(response, "imexpired.gov", count=2) + + # Make sure the expiration date is None + self.assertEqual(test_domain.expiration_date, None) # Check that we have the right text content. self.assertContains(response, expired_text, count=1) @@ -193,6 +198,8 @@ def test_state_help_text_no_expiration_date(self): # == Test a expiration of None for state unknown. This should not display expired text. == # unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses." test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN) + test_domain_2.expiration_date = None + test_domain_2.save() UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER) @@ -203,6 +210,9 @@ def test_state_help_text_no_expiration_date(self): # We expect two instances because of SR content. self.assertContains(response, "notexpired.gov", count=2) + # Make sure the expiration date is None + self.assertEqual(test_domain_2.expiration_date, None) + # Check that we have the right text content. self.assertContains(response, unknown_text, count=1) From 19af42ca49ce8fd668743c0068453bd05630ee30 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:06:46 -0700 Subject: [PATCH 22/33] Fix unit test --- src/registrar/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 6afe834a1..666b3c962 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -110,7 +110,7 @@ def test_state_help_text(self): """Tests if each domain state has help text""" # Get the expected text content of each state - deleted_text = "Before this domain can be used, " "you’ll need to add name server addresses." + deleted_text = "This domain has been removed and " "is no longer registered to your organization." dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses." ready_text = "This domain has name servers and is ready for use." on_hold_text = ( From 4110b37865f5cf80b64348aca5c1923281e55b8f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:25:19 -0700 Subject: [PATCH 23/33] Linting, fix domain.py --- src/registrar/models/domain.py | 8 +++++--- src/registrar/tests/test_views.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 233e619b3..ca1ba541a 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1418,14 +1418,16 @@ def dns_needed(self): def get_state_help_text(self) -> str: """Returns a str containing additional information about a given state. Returns custom content for when the domain itself is expired.""" - if not self.is_expired() and self.state != self.State.UNKNOWN: - help_text = Domain.State.get_help_text(self.state) - else: + + if self.is_expired() and self.state != self.State.UNKNOWN: # Given expired is not a physical state, but it is displayed as such, # We need custom logic to determine this message. help_text = ( "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." ) + else: + help_text = Domain.State.get_help_text(self.state) + return help_text def _disclose_fields(self, contact: PublicContact): diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 666b3c962..2ba762b0e 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -170,7 +170,7 @@ def test_state_help_text_expired(self): # Check that we have the right text content. self.assertContains(response, expired_text, count=1) - + def test_state_help_text_no_expiration_date(self): """Tests if each domain state has help text when expiration date is None""" From 4437b315b32eca484a024fb3bf1145595edbd0e0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Feb 2024 12:43:27 -0700 Subject: [PATCH 24/33] Consolidate css --- src/registrar/assets/sass/_theme/_base.scss | 13 +++---------- src/registrar/assets/sass/_theme/_tables.scss | 9 ++++++--- src/registrar/templates/home.html | 4 ++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index b5f4c5c6f..347b132a3 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -133,23 +133,16 @@ abbr[title] { // Only apply this custom wrapping to desktop @include at-media(desktop) { .usa-tooltip__body { - min-width: 350px; - max-width: 350px; + width: 350px; white-space: normal; text-align: center; } } -@media (min-width: 768px) { +@media (tablet) { .usa-tooltip__body { - min-width: 250px; - max-width: 250px; + width: 250px; white-space: normal; text-align: center; } } -// USWDS has weird interactions with SVGs regarding tooltips, -// and other components. In this event, we need to disable pointer interactions. -.disable-pointer-events { - pointer-events: none; -} diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 45e47cee3..7b13656e7 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -27,11 +27,14 @@ } td { - .no-click-outline, .no-click-outline:hover, .no-click-outline:focus{ + .no-click-outline-and-cursor-help{ outline: none; - } - .cursor-help{ cursor: help; + use { + // USWDS has weird interactions with SVGs regarding tooltips, + // and other components. In this event, we need to disable pointer interactions. + pointer-events: none; + } } } diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 44023fc7d..699a82c61 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -58,13 +58,13 @@

Domains

{{ domain.state|capfirst }} {% endif %} - +
From c8c5fc8134a07b80e85f117a67cc0de874a81d84 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:19:39 -0700 Subject: [PATCH 25/33] CSS change --- src/registrar/assets/sass/_theme/_base.scss | 2 +- src/registrar/templates/home.html | 36 ++++++++++----------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 347b132a3..0a62009e7 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -139,7 +139,7 @@ abbr[title] { } } -@media (tablet) { +@include at-media(tablet) { .usa-tooltip__body { width: 250px; white-space: normal; diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 699a82c61..0747c9985 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -48,25 +48,23 @@

Domains

{{ domain.expiration_date|date }} - - {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} - {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} - Expired - {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} - DNS needed - {% else %} - {{ domain.state|capfirst }} - {% endif %} - - - - + {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} + {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} + Expired + {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} + DNS needed + {% else %} + {{ domain.state|capfirst }} + {% endif %} + + +
From 8e4e41ce35c2855d515ce5860b1aa82f118df039 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:32:07 -0700 Subject: [PATCH 26/33] Change import order on tests test_views seems is bugging out for some reason --- src/registrar/tests/test_views.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 2ba762b0e..d4e7fbcb7 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1,14 +1,15 @@ +import logging +import boto3_mocking # type: ignore from unittest import skip from unittest.mock import MagicMock, ANY, patch +from datetime import date, datetime, timedelta +from django.utils import timezone from django.conf import settings from django.test import Client, TestCase from django.urls import reverse from django.contrib.auth import get_user_model - -from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore from django_webtest import WebTest # type: ignore -import boto3_mocking # type: ignore from registrar.utility.errors import ( NameserverError, @@ -22,8 +23,8 @@ ) from registrar.models import ( - DomainApplication, Domain, + DomainApplication, DomainInformation, DraftDomain, DomainInvitation, @@ -36,11 +37,8 @@ User, ) from registrar.views.application import ApplicationWizard, Step -from datetime import date, datetime, timedelta -from django.utils import timezone +from .common import MockEppLib, MockSESClient, completed_application, create_user, less_console_noise # type: ignore -from .common import less_console_noise -import logging logger = logging.getLogger(__name__) @@ -119,13 +117,18 @@ def test_state_help_text(self): "Contact help@get.gov for details." ) deleted_text = "This domain has been removed and " "is no longer registered to your organization." + + # Seperated here for the linter. This works locally but fails through github actions? + # No idea why. Linter and test cases think this doesn't exist. + domain_state = Domain.State # noqa: F821 + # Generate a mapping of domain names, the state, and expected messages for the subtest test_cases = [ - ("deleted.gov", Domain.State.DELETED, deleted_text), - ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), - ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), - ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), - ("ready.gov", Domain.State.READY, ready_text), + ("deleted.gov", domain_state.DELETED, deleted_text), + ("dnsneeded.gov", domain_state.DNS_NEEDED, dns_needed_text), + ("unknown.gov", domain_state.UNKNOWN, dns_needed_text), + ("onhold.gov", domain_state.ON_HOLD, on_hold_text), + ("ready.gov", domain_state.READY, ready_text), ] for domain_name, state, expected_message in test_cases: with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): From 980864f48eab407a52e9e569268bd52dd92bbcd3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:32:42 -0700 Subject: [PATCH 27/33] Revert "Change import order on tests" This reverts commit 8e4e41ce35c2855d515ce5860b1aa82f118df039. --- src/registrar/tests/test_views.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index d4e7fbcb7..2ba762b0e 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1,15 +1,14 @@ -import logging -import boto3_mocking # type: ignore from unittest import skip from unittest.mock import MagicMock, ANY, patch -from datetime import date, datetime, timedelta -from django.utils import timezone from django.conf import settings from django.test import Client, TestCase from django.urls import reverse from django.contrib.auth import get_user_model + +from .common import MockEppLib, MockSESClient, completed_application, create_user # type: ignore from django_webtest import WebTest # type: ignore +import boto3_mocking # type: ignore from registrar.utility.errors import ( NameserverError, @@ -23,8 +22,8 @@ ) from registrar.models import ( - Domain, DomainApplication, + Domain, DomainInformation, DraftDomain, DomainInvitation, @@ -37,8 +36,11 @@ User, ) from registrar.views.application import ApplicationWizard, Step -from .common import MockEppLib, MockSESClient, completed_application, create_user, less_console_noise # type: ignore +from datetime import date, datetime, timedelta +from django.utils import timezone +from .common import less_console_noise +import logging logger = logging.getLogger(__name__) @@ -117,18 +119,13 @@ def test_state_help_text(self): "Contact help@get.gov for details." ) deleted_text = "This domain has been removed and " "is no longer registered to your organization." - - # Seperated here for the linter. This works locally but fails through github actions? - # No idea why. Linter and test cases think this doesn't exist. - domain_state = Domain.State # noqa: F821 - # Generate a mapping of domain names, the state, and expected messages for the subtest test_cases = [ - ("deleted.gov", domain_state.DELETED, deleted_text), - ("dnsneeded.gov", domain_state.DNS_NEEDED, dns_needed_text), - ("unknown.gov", domain_state.UNKNOWN, dns_needed_text), - ("onhold.gov", domain_state.ON_HOLD, on_hold_text), - ("ready.gov", domain_state.READY, ready_text), + ("deleted.gov", Domain.State.DELETED, deleted_text), + ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), + ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), + ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), + ("ready.gov", Domain.State.READY, ready_text), ] for domain_name, state, expected_message in test_cases: with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): From 70bdfd4589a881ae9533bef75465219f997a1dfe Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:49:20 -0700 Subject: [PATCH 28/33] Fix test cases --- src/registrar/tests/test_views.py | 359 ----------------- src/registrar/tests/test_views_application.py | 364 ++++++++++++++++++ 2 files changed, 364 insertions(+), 359 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 194b4a8e5..3cfeeeedb 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -55,362 +55,3 @@ def tearDown(self): DomainApplication.objects.all().delete() DomainInformation.objects.all().delete() self.user.delete() - - -class LoggedInTests(TestWithUser): - def setUp(self): - super().setUp() - self.client.force_login(self.user) - - def tearDown(self): - super().tearDown() - Contact.objects.all().delete() - - def test_home_lists_domain_applications(self): - response = self.client.get("/") - self.assertNotContains(response, "igorville.gov") - site = DraftDomain.objects.create(name="igorville.gov") - application = DomainApplication.objects.create(creator=self.user, requested_domain=site) - response = self.client.get("/") - - # count = 7 because of screenreader content - self.assertContains(response, "igorville.gov", count=7) - - # clean up - application.delete() - - def test_state_help_text(self): - """Tests if each domain state has help text""" - - # Get the expected text content of each state - deleted_text = "This domain has been removed and " "is no longer registered to your organization." - dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses." - ready_text = "This domain has name servers and is ready for use." - on_hold_text = ( - "This domain is administratively paused, " - "so it can’t be edited and won’t resolve in DNS. " - "Contact help@get.gov for details." - ) - deleted_text = "This domain has been removed and " "is no longer registered to your organization." - # Generate a mapping of domain names, the state, and expected messages for the subtest - test_cases = [ - ("deleted.gov", Domain.State.DELETED, deleted_text), - ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), - ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), - ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), - ("ready.gov", Domain.State.READY, ready_text), - ] - for domain_name, state, expected_message in test_cases: - with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): - # Create a domain and a UserRole with the given params - test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) - test_domain.expiration_date = date.today() - test_domain.save() - - user_role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER - ) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, domain_name, count=2) - - # Check that we have the right text content. - self.assertContains(response, expected_message, count=1) - - # Delete the role and domain to ensure we're testing in isolation - user_role.delete() - test_domain.delete() - - def test_state_help_text_expired(self): - """Tests if each domain state has help text when expired""" - expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." - test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) - test_domain.expiration_date = date(2011, 10, 10) - test_domain.save() - - UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, "expired.gov", count=2) - - # Check that we have the right text content. - self.assertContains(response, expired_text, count=1) - - def test_state_help_text_no_expiration_date(self): - """Tests if each domain state has help text when expiration date is None""" - - # == Test a expiration of None for state ready. This should be expired. == # - expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." - test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY) - test_domain.expiration_date = None - test_domain.save() - - UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, "imexpired.gov", count=2) - - # Make sure the expiration date is None - self.assertEqual(test_domain.expiration_date, None) - - # Check that we have the right text content. - self.assertContains(response, expired_text, count=1) - - # == Test a expiration of None for state unknown. This should not display expired text. == # - unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses." - test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN) - test_domain_2.expiration_date = None - test_domain_2.save() - - UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, "notexpired.gov", count=2) - - # Make sure the expiration date is None - self.assertEqual(test_domain_2.expiration_date, None) - - # Check that we have the right text content. - self.assertContains(response, unknown_text, count=1) - - def test_home_deletes_withdrawn_domain_application(self): - """Tests if the user can delete a DomainApplication in the 'withdrawn' status""" - - site = DraftDomain.objects.create(name="igorville.gov") - application = DomainApplication.objects.create( - creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN - ) - - # Ensure that igorville.gov exists on the page - home_page = self.client.get("/") - self.assertContains(home_page, "igorville.gov") - - # Check if the delete button exists. We can do this by checking for its id and text content. - self.assertContains(home_page, "Delete") - self.assertContains(home_page, "button-toggle-delete-domain-alert-1") - - # Trigger the delete logic - response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) - - self.assertNotContains(response, "igorville.gov") - - # clean up - application.delete() - - def test_home_deletes_started_domain_application(self): - """Tests if the user can delete a DomainApplication in the 'started' status""" - - site = DraftDomain.objects.create(name="igorville.gov") - application = DomainApplication.objects.create( - creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.STARTED - ) - - # Ensure that igorville.gov exists on the page - home_page = self.client.get("/") - self.assertContains(home_page, "igorville.gov") - - # Check if the delete button exists. We can do this by checking for its id and text content. - self.assertContains(home_page, "Delete") - self.assertContains(home_page, "button-toggle-delete-domain-alert-1") - - # Trigger the delete logic - response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) - - self.assertNotContains(response, "igorville.gov") - - # clean up - application.delete() - - def test_home_doesnt_delete_other_domain_applications(self): - """Tests to ensure the user can't delete Applications not in the status of STARTED or WITHDRAWN""" - - # Given that we are including a subset of items that can be deleted while excluding the rest, - # subTest is appropriate here as otherwise we would need many duplicate tests for the same reason. - with less_console_noise(): - draft_domain = DraftDomain.objects.create(name="igorville.gov") - for status in DomainApplication.ApplicationStatus: - if status not in [ - DomainApplication.ApplicationStatus.STARTED, - DomainApplication.ApplicationStatus.WITHDRAWN, - ]: - with self.subTest(status=status): - application = DomainApplication.objects.create( - creator=self.user, requested_domain=draft_domain, status=status - ) - - # Trigger the delete logic - response = self.client.post( - reverse("application-delete", kwargs={"pk": application.pk}), follow=True - ) - - # Check for a 403 error - the end user should not be allowed to do this - self.assertEqual(response.status_code, 403) - - desired_application = DomainApplication.objects.filter(requested_domain=draft_domain) - - # Make sure the DomainApplication wasn't deleted - self.assertEqual(desired_application.count(), 1) - - # clean up - application.delete() - - def test_home_deletes_domain_application_and_orphans(self): - """Tests if delete for DomainApplication deletes orphaned Contact objects""" - - # Create the site and contacts to delete (orphaned) - contact = Contact.objects.create( - first_name="Henry", - last_name="Mcfakerson", - ) - contact_shared = Contact.objects.create( - first_name="Relative", - last_name="Aether", - ) - - # Create two non-orphaned contacts - contact_2 = Contact.objects.create( - first_name="Saturn", - last_name="Mars", - ) - - # Attach a user object to a contact (should not be deleted) - contact_user, _ = Contact.objects.get_or_create(user=self.user) - - site = DraftDomain.objects.create(name="igorville.gov") - application = DomainApplication.objects.create( - creator=self.user, - requested_domain=site, - status=DomainApplication.ApplicationStatus.WITHDRAWN, - authorizing_official=contact, - submitter=contact_user, - ) - application.other_contacts.set([contact_2]) - - # Create a second application to attach contacts to - site_2 = DraftDomain.objects.create(name="teaville.gov") - application_2 = DomainApplication.objects.create( - creator=self.user, - requested_domain=site_2, - status=DomainApplication.ApplicationStatus.STARTED, - authorizing_official=contact_2, - submitter=contact_shared, - ) - application_2.other_contacts.set([contact_shared]) - - # Ensure that igorville.gov exists on the page - home_page = self.client.get("/") - self.assertContains(home_page, "igorville.gov") - - # Trigger the delete logic - response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) - - # igorville is now deleted - self.assertNotContains(response, "igorville.gov") - - # Check if the orphaned contact was deleted - orphan = Contact.objects.filter(id=contact.id) - self.assertFalse(orphan.exists()) - - # All non-orphan contacts should still exist and are unaltered - try: - current_user = Contact.objects.filter(id=contact_user.id).get() - except Contact.DoesNotExist: - self.fail("contact_user (a non-orphaned contact) was deleted") - - self.assertEqual(current_user, contact_user) - try: - edge_case = Contact.objects.filter(id=contact_2.id).get() - except Contact.DoesNotExist: - self.fail("contact_2 (a non-orphaned contact) was deleted") - - self.assertEqual(edge_case, contact_2) - - def test_home_deletes_domain_application_and_shared_orphans(self): - """Test the edge case for an object that will become orphaned after a delete - (but is not an orphan at the time of deletion)""" - - # Create the site and contacts to delete (orphaned) - contact = Contact.objects.create( - first_name="Henry", - last_name="Mcfakerson", - ) - contact_shared = Contact.objects.create( - first_name="Relative", - last_name="Aether", - ) - - # Create two non-orphaned contacts - contact_2 = Contact.objects.create( - first_name="Saturn", - last_name="Mars", - ) - - # Attach a user object to a contact (should not be deleted) - contact_user, _ = Contact.objects.get_or_create(user=self.user) - - site = DraftDomain.objects.create(name="igorville.gov") - application = DomainApplication.objects.create( - creator=self.user, - requested_domain=site, - status=DomainApplication.ApplicationStatus.WITHDRAWN, - authorizing_official=contact, - submitter=contact_user, - ) - application.other_contacts.set([contact_2]) - - # Create a second application to attach contacts to - site_2 = DraftDomain.objects.create(name="teaville.gov") - application_2 = DomainApplication.objects.create( - creator=self.user, - requested_domain=site_2, - status=DomainApplication.ApplicationStatus.STARTED, - authorizing_official=contact_2, - submitter=contact_shared, - ) - application_2.other_contacts.set([contact_shared]) - - home_page = self.client.get("/") - self.assertContains(home_page, "teaville.gov") - - # Trigger the delete logic - response = self.client.post(reverse("application-delete", kwargs={"pk": application_2.pk}), follow=True) - - self.assertNotContains(response, "teaville.gov") - - # Check if the orphaned contact was deleted - orphan = Contact.objects.filter(id=contact_shared.id) - self.assertFalse(orphan.exists()) - - def test_application_form_view(self): - response = self.client.get("/request/", follow=True) - self.assertContains( - response, - "You’re about to start your .gov domain request.", - ) - - def test_domain_application_form_with_ineligible_user(self): - """Application form not accessible for an ineligible user. - This test should be solid enough since all application wizard - views share the same permissions class""" - self.user.status = User.RESTRICTED - self.user.save() - - with less_console_noise(): - response = self.client.get("/request/", follow=True) - self.assertEqual(response.status_code, 403) diff --git a/src/registrar/tests/test_views_application.py b/src/registrar/tests/test_views_application.py index 62485fa86..733eaf578 100644 --- a/src/registrar/tests/test_views_application.py +++ b/src/registrar/tests/test_views_application.py @@ -2,6 +2,7 @@ from django.conf import settings from django.urls import reverse +from datetime import date from .common import MockSESClient, completed_application # type: ignore from django_webtest import WebTest # type: ignore @@ -14,6 +15,8 @@ Contact, User, Website, + UserDomainRole, + DraftDomain, ) from registrar.views.application import ApplicationWizard, Step @@ -2197,3 +2200,364 @@ def test_approved_application_not_in_active_requests(self): # domain object, so we do not expect to see 'city.gov' # in either the Domains or Requests tables. self.assertNotContains(home_page, "city.gov") + + +class HomeTests(TestWithUser): + """A series of tests that target the two tables on home.html""" + + def setUp(self): + super().setUp() + self.client.force_login(self.user) + + def tearDown(self): + super().tearDown() + Contact.objects.all().delete() + + def test_home_lists_domain_applications(self): + response = self.client.get("/") + self.assertNotContains(response, "igorville.gov") + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create(creator=self.user, requested_domain=site) + response = self.client.get("/") + + # count = 7 because of screenreader content + self.assertContains(response, "igorville.gov", count=7) + + # clean up + application.delete() + + def test_state_help_text(self): + """Tests if each domain state has help text""" + + # Get the expected text content of each state + deleted_text = "This domain has been removed and " "is no longer registered to your organization." + dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses." + ready_text = "This domain has name servers and is ready for use." + on_hold_text = ( + "This domain is administratively paused, " + "so it can’t be edited and won’t resolve in DNS. " + "Contact help@get.gov for details." + ) + deleted_text = "This domain has been removed and " "is no longer registered to your organization." + # Generate a mapping of domain names, the state, and expected messages for the subtest + test_cases = [ + ("deleted.gov", Domain.State.DELETED, deleted_text), + ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), + ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), + ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), + ("ready.gov", Domain.State.READY, ready_text), + ] + for domain_name, state, expected_message in test_cases: + with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): + # Create a domain and a UserRole with the given params + test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) + test_domain.expiration_date = date.today() + test_domain.save() + + user_role, _ = UserDomainRole.objects.get_or_create( + user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER + ) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, domain_name, count=2) + + # Check that we have the right text content. + self.assertContains(response, expected_message, count=1) + + # Delete the role and domain to ensure we're testing in isolation + user_role.delete() + test_domain.delete() + + def test_state_help_text_expired(self): + """Tests if each domain state has help text when expired""" + expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." + test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) + test_domain.expiration_date = date(2011, 10, 10) + test_domain.save() + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "expired.gov", count=2) + + # Check that we have the right text content. + self.assertContains(response, expired_text, count=1) + + def test_state_help_text_no_expiration_date(self): + """Tests if each domain state has help text when expiration date is None""" + + # == Test a expiration of None for state ready. This should be expired. == # + expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." + test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY) + test_domain.expiration_date = None + test_domain.save() + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "imexpired.gov", count=2) + + # Make sure the expiration date is None + self.assertEqual(test_domain.expiration_date, None) + + # Check that we have the right text content. + self.assertContains(response, expired_text, count=1) + + # == Test a expiration of None for state unknown. This should not display expired text. == # + unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses." + test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN) + test_domain_2.expiration_date = None + test_domain_2.save() + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "notexpired.gov", count=2) + + # Make sure the expiration date is None + self.assertEqual(test_domain_2.expiration_date, None) + + # Check that we have the right text content. + self.assertContains(response, unknown_text, count=1) + + def test_home_deletes_withdrawn_domain_application(self): + """Tests if the user can delete a DomainApplication in the 'withdrawn' status""" + + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN + ) + + # Ensure that igorville.gov exists on the page + home_page = self.client.get("/") + self.assertContains(home_page, "igorville.gov") + + # Check if the delete button exists. We can do this by checking for its id and text content. + self.assertContains(home_page, "Delete") + self.assertContains(home_page, "button-toggle-delete-domain-alert-1") + + # Trigger the delete logic + response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) + + self.assertNotContains(response, "igorville.gov") + + # clean up + application.delete() + + def test_home_deletes_started_domain_application(self): + """Tests if the user can delete a DomainApplication in the 'started' status""" + + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.STARTED + ) + + # Ensure that igorville.gov exists on the page + home_page = self.client.get("/") + self.assertContains(home_page, "igorville.gov") + + # Check if the delete button exists. We can do this by checking for its id and text content. + self.assertContains(home_page, "Delete") + self.assertContains(home_page, "button-toggle-delete-domain-alert-1") + + # Trigger the delete logic + response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) + + self.assertNotContains(response, "igorville.gov") + + # clean up + application.delete() + + def test_home_doesnt_delete_other_domain_applications(self): + """Tests to ensure the user can't delete Applications not in the status of STARTED or WITHDRAWN""" + + # Given that we are including a subset of items that can be deleted while excluding the rest, + # subTest is appropriate here as otherwise we would need many duplicate tests for the same reason. + with less_console_noise(): + draft_domain = DraftDomain.objects.create(name="igorville.gov") + for status in DomainApplication.ApplicationStatus: + if status not in [ + DomainApplication.ApplicationStatus.STARTED, + DomainApplication.ApplicationStatus.WITHDRAWN, + ]: + with self.subTest(status=status): + application = DomainApplication.objects.create( + creator=self.user, requested_domain=draft_domain, status=status + ) + + # Trigger the delete logic + response = self.client.post( + reverse("application-delete", kwargs={"pk": application.pk}), follow=True + ) + + # Check for a 403 error - the end user should not be allowed to do this + self.assertEqual(response.status_code, 403) + + desired_application = DomainApplication.objects.filter(requested_domain=draft_domain) + + # Make sure the DomainApplication wasn't deleted + self.assertEqual(desired_application.count(), 1) + + # clean up + application.delete() + + def test_home_deletes_domain_application_and_orphans(self): + """Tests if delete for DomainApplication deletes orphaned Contact objects""" + + # Create the site and contacts to delete (orphaned) + contact = Contact.objects.create( + first_name="Henry", + last_name="Mcfakerson", + ) + contact_shared = Contact.objects.create( + first_name="Relative", + last_name="Aether", + ) + + # Create two non-orphaned contacts + contact_2 = Contact.objects.create( + first_name="Saturn", + last_name="Mars", + ) + + # Attach a user object to a contact (should not be deleted) + contact_user, _ = Contact.objects.get_or_create(user=self.user) + + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=self.user, + requested_domain=site, + status=DomainApplication.ApplicationStatus.WITHDRAWN, + authorizing_official=contact, + submitter=contact_user, + ) + application.other_contacts.set([contact_2]) + + # Create a second application to attach contacts to + site_2 = DraftDomain.objects.create(name="teaville.gov") + application_2 = DomainApplication.objects.create( + creator=self.user, + requested_domain=site_2, + status=DomainApplication.ApplicationStatus.STARTED, + authorizing_official=contact_2, + submitter=contact_shared, + ) + application_2.other_contacts.set([contact_shared]) + + # Ensure that igorville.gov exists on the page + home_page = self.client.get("/") + self.assertContains(home_page, "igorville.gov") + + # Trigger the delete logic + response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) + + # igorville is now deleted + self.assertNotContains(response, "igorville.gov") + + # Check if the orphaned contact was deleted + orphan = Contact.objects.filter(id=contact.id) + self.assertFalse(orphan.exists()) + + # All non-orphan contacts should still exist and are unaltered + try: + current_user = Contact.objects.filter(id=contact_user.id).get() + except Contact.DoesNotExist: + self.fail("contact_user (a non-orphaned contact) was deleted") + + self.assertEqual(current_user, contact_user) + try: + edge_case = Contact.objects.filter(id=contact_2.id).get() + except Contact.DoesNotExist: + self.fail("contact_2 (a non-orphaned contact) was deleted") + + self.assertEqual(edge_case, contact_2) + + def test_home_deletes_domain_application_and_shared_orphans(self): + """Test the edge case for an object that will become orphaned after a delete + (but is not an orphan at the time of deletion)""" + + # Create the site and contacts to delete (orphaned) + contact = Contact.objects.create( + first_name="Henry", + last_name="Mcfakerson", + ) + contact_shared = Contact.objects.create( + first_name="Relative", + last_name="Aether", + ) + + # Create two non-orphaned contacts + contact_2 = Contact.objects.create( + first_name="Saturn", + last_name="Mars", + ) + + # Attach a user object to a contact (should not be deleted) + contact_user, _ = Contact.objects.get_or_create(user=self.user) + + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=self.user, + requested_domain=site, + status=DomainApplication.ApplicationStatus.WITHDRAWN, + authorizing_official=contact, + submitter=contact_user, + ) + application.other_contacts.set([contact_2]) + + # Create a second application to attach contacts to + site_2 = DraftDomain.objects.create(name="teaville.gov") + application_2 = DomainApplication.objects.create( + creator=self.user, + requested_domain=site_2, + status=DomainApplication.ApplicationStatus.STARTED, + authorizing_official=contact_2, + submitter=contact_shared, + ) + application_2.other_contacts.set([contact_shared]) + + home_page = self.client.get("/") + self.assertContains(home_page, "teaville.gov") + + # Trigger the delete logic + response = self.client.post(reverse("application-delete", kwargs={"pk": application_2.pk}), follow=True) + + self.assertNotContains(response, "teaville.gov") + + # Check if the orphaned contact was deleted + orphan = Contact.objects.filter(id=contact_shared.id) + self.assertFalse(orphan.exists()) + + def test_application_form_view(self): + response = self.client.get("/request/", follow=True) + self.assertContains( + response, + "You’re about to start your .gov domain request.", + ) + + def test_domain_application_form_with_ineligible_user(self): + """Application form not accessible for an ineligible user. + This test should be solid enough since all application wizard + views share the same permissions class""" + self.user.status = User.RESTRICTED + self.user.save() + + with less_console_noise(): + response = self.client.get("/request/", follow=True) + self.assertEqual(response.status_code, 403) From 76809a75a8438154b3b03296dfb716b32c15dac9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:57:46 -0700 Subject: [PATCH 29/33] Linting --- src/registrar/assets/sass/_theme/_base.scss | 2 +- src/registrar/assets/sass/_theme/_tables.scss | 16 +++++++--------- src/registrar/tests/test_views.py | 5 ----- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 0a62009e7..983af3a01 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -139,7 +139,7 @@ abbr[title] { } } -@include at-media(tablet) { +@media (min-width: 768px) { .usa-tooltip__body { width: 250px; white-space: normal; diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 7b13656e7..0d58b5878 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -26,15 +26,13 @@ padding-bottom: units(2px); } - td { - .no-click-outline-and-cursor-help{ - outline: none; - cursor: help; - use { - // USWDS has weird interactions with SVGs regarding tooltips, - // and other components. In this event, we need to disable pointer interactions. - pointer-events: none; - } + td .no-click-outline-and-cursor-help { + outline: none; + cursor: help; + use { + // USWDS has weird interactions with SVGs regarding tooltips, + // and other components. In this event, we need to disable pointer interactions. + pointer-events: none; } } diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 3cfeeeedb..8469071f8 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1,5 +1,4 @@ from django.test import Client, TestCase -from django.urls import reverse from django.contrib.auth import get_user_model from .common import MockEppLib # type: ignore @@ -8,11 +7,7 @@ from registrar.models import ( DomainApplication, DomainInformation, - DraftDomain, - Contact, - User, ) -from .common import less_console_noise import logging logger = logging.getLogger(__name__) From 515c3711b38a92f203b3e0e07f7d45a5e154c909 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 7 Feb 2024 13:30:13 -0700 Subject: [PATCH 30/33] Add aria information --- src/registrar/assets/sass/_theme/_base.scss | 16 ++++++++++++---- src/registrar/templates/home.html | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 983af3a01..127db5589 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -139,10 +139,18 @@ abbr[title] { } } -@media (min-width: 768px) { +@include at-media(tablet) { .usa-tooltip__body { - width: 250px; - white-space: normal; - text-align: center; + width: 250px !important; + white-space: normal !important; + text-align: center !important; + } +} + +@include at-media(mobile) { + .usa-tooltip__body { + width: 250px !important; + white-space: normal !important; + text-align: center !important; } } diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 0747c9985..b5e8ca5a4 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -58,10 +58,10 @@

Domains

{% endif %} From b3abe0b031568d1b1915c3cbe11b640ff7f9d581 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:00:20 -0700 Subject: [PATCH 31/33] PR suggestions --- src/registrar/templates/domain_detail.html | 4 +++- src/registrar/templates/home.html | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index ba891da57..2b2d45695 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -17,6 +17,7 @@ Status: + {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} Expired @@ -25,8 +26,9 @@ {% else %} {{ domain.state|title }} {% endif %} + {% if domain.get_state_help_text %} -
+
{{ domain.get_state_help_text }}
{% endif %} diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index b5e8ca5a4..b4f3b99f8 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -62,6 +62,7 @@

Domains

title="{{domain.get_state_help_text}}" focusable="true" aria-label="Status Information" + labelledby="Status Information" > From 3a595cf01ff419042ddf61d22912a2fa30c01a80 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:34:44 -0700 Subject: [PATCH 32/33] VO changes --- src/registrar/templates/home.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index b4f3b99f8..a79065f50 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -62,9 +62,9 @@

Domains

title="{{domain.get_state_help_text}}" focusable="true" aria-label="Status Information" - labelledby="Status Information" + role="tooltip" > - + From 144140c2fdb18925030b53675560d5f34497ac6a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:03:17 -0700 Subject: [PATCH 33/33] Linting --- src/registrar/tests/test_views_application.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/tests/test_views_application.py b/src/registrar/tests/test_views_application.py index 0a596a148..593b8d31e 100644 --- a/src/registrar/tests/test_views_application.py +++ b/src/registrar/tests/test_views_application.py @@ -18,7 +18,6 @@ User, Website, UserDomainRole, - DraftDomain, ) from registrar.views.application import ApplicationWizard, Step