Skip to content

Commit 5c5afc0

Browse files
authored
Merge pull request #65 from NHSDigital/dtoss-9144-link-radio-errors-to-first-choice
Correctly link errors to the matching form field
2 parents 1a86768 + 4e55159 commit 5c5afc0

6 files changed

Lines changed: 139 additions & 37 deletions

File tree

manage_breast_screening/config/system_test_setup.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
5-
from playwright.sync_api import sync_playwright, expect
5+
from playwright.sync_api import expect, sync_playwright
66

77

88
@pytest.mark.system
@@ -27,11 +27,28 @@ def setUp(self):
2727
def tearDown(self):
2828
self.page.close()
2929

30-
def expect_validation_error(self, id: str, fieldset_legend: str, error_text: str):
30+
def expect_validation_error(
31+
self,
32+
error_text: str,
33+
fieldset_legend: str,
34+
field_label: str,
35+
field_name: str | None = "",
36+
):
3137
summary_box = self.page.locator(".nhsuk-error-summary")
32-
error_link = summary_box.locator(f"a[href='#{id}']")
33-
expect(error_link).to_have_text(error_text)
38+
expect(summary_box).to_contain_text(error_text)
3439

35-
fieldset = self.page.locator('fieldset').filter(has_text=fieldset_legend)
40+
error_link = summary_box.get_by_text(error_text)
41+
error_link.click()
42+
43+
fieldset = self.page.locator("fieldset").filter(has_text=fieldset_legend)
3644
error_span = fieldset.locator("span").filter(has_text=error_text)
37-
expect(error_span).to_have_id(id)
45+
expect(error_span).to_contain_text(error_text)
46+
47+
if field_name:
48+
field = fieldset.get_by_label(field_label).and_(
49+
fieldset.locator(f"[name='{field_name}']")
50+
)
51+
else:
52+
field = fieldset.get_by_label(field_label)
53+
54+
expect(field).to_be_focused()

manage_breast_screening/record_a_mammogram/forms.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ def __init__(self, *args, **kwargs):
6868
for field_name, _ in self.STOPPED_REASON_CHOICES:
6969
self.fields[f"{field_name}_details"] = forms.CharField(required=False)
7070

71+
# Ensure that the field order matches the order we want to render in
72+
details_fields = [
73+
f"{field_name}_details" for field_name, _ in self.STOPPED_REASON_CHOICES
74+
]
75+
self.order_fields(["stopped_reasons"] + details_fields + ["decision"])
76+
7177
stopped_reasons = forms.MultipleChoiceField(
7278
choices=STOPPED_REASON_CHOICES,
7379
required=True,

manage_breast_screening/record_a_mammogram/tests/system/test_recording_a_mammogram.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def before(self):
2020
self.appointment = AppointmentFactory(screening_episode=self.screening_episode)
2121

2222
def test_recording_a_mammogram_without_capturing_medical_information(self):
23+
"""
24+
I can record a mammogram without entering any relevant medical information.
25+
"""
2326
self.given_i_am_on_the_start_screening_page()
2427
self.then_i_should_see_the_demographic_banner()
2528
self.and_i_should_see_the_participant_details()
@@ -28,9 +31,30 @@ def test_recording_a_mammogram_without_capturing_medical_information(self):
2831
self.then_i_should_be_on_the_medical_information_page()
2932
self.and_i_should_be_prompted_to_ask_about_relevant_medical_information()
3033

31-
self.when_the_participant_shares_no_relevant_medical_information()
34+
self.when_i_mark_that_the_participant_shared_no_medical_information()
3235
self.then_the_screen_should_show_that_it_is_awaiting_images_from_the_PACS()
3336

37+
def test_filling_out_forms_incorrectly(self):
38+
"""
39+
At each step in the flow, when I fill out the forms incorrectly,
40+
then I should see the errors so I can fix them.
41+
"""
42+
self.given_i_am_on_the_start_screening_page()
43+
self.when_i_submit_the_form()
44+
self.then_i_am_prompted_to_answer_can_the_screening_go_ahead()
45+
46+
self.when_i_check_the_participants_identity_and_confirm_the_last_mammogram_date()
47+
self.then_i_should_be_on_the_medical_information_page()
48+
49+
self.when_i_submit_the_form()
50+
self.then_i_am_prompted_to_answer_has_the_participant_shared_medical_info()
51+
52+
self.when_i_mark_that_the_participant_shared_medical_information()
53+
self.then_i_should_be_on_the_record_medical_information_page()
54+
55+
self.when_i_submit_the_form()
56+
self.then_i_am_prompted_to_confirm_whether_imaging_can_go_ahead()
57+
3458
def given_i_am_on_the_start_screening_page(self):
3559
self.page.goto(
3660
self.live_server_url
@@ -54,27 +78,62 @@ def when_i_check_the_participants_identity_and_confirm_the_last_mammogram_date(
5478
self.page.get_by_label("Yes, go to medical information").check()
5579
self.page.get_by_role("button", name="Continue").click()
5680

81+
def when_i_submit_the_form(self):
82+
self.page.get_by_role("button", name="Continue").click()
83+
5784
def then_i_should_be_on_the_medical_information_page(self):
5885
path = reverse(
5986
"record_a_mammogram:ask_for_medical_information",
6087
kwargs={"id": self.appointment.pk},
6188
)
6289
expect(self.page).to_have_url(re.compile(path))
6390

91+
def then_i_should_be_on_the_record_medical_information_page(self):
92+
path = reverse(
93+
"record_a_mammogram:record_medical_information",
94+
kwargs={"id": self.appointment.pk},
95+
)
96+
expect(self.page).to_have_url(re.compile(path))
97+
6498
def and_i_should_be_prompted_to_ask_about_relevant_medical_information(self):
6599
expect(
66100
self.page.get_by_text(
67101
"Has the participant shared any relevant medical information?"
68102
)
69103
).to_be_visible()
70104

71-
def when_the_participant_shares_no_relevant_medical_information(self):
105+
def when_i_mark_that_the_participant_shared_no_medical_information(self):
72106
self.page.get_by_label("No - proceed to imaging").check()
73107
self.page.get_by_role("button", name="Continue").click()
74108

109+
def when_i_mark_that_the_participant_shared_medical_information(self):
110+
self.page.get_by_label("Yes").check()
111+
self.page.get_by_role("button", name="Continue").click()
112+
75113
def then_the_screen_should_show_that_it_is_awaiting_images_from_the_PACS(self):
76114
path = reverse(
77115
"record_a_mammogram:awaiting_images",
78116
kwargs={"id": self.appointment.pk},
79117
)
80118
expect(self.page).to_have_url(re.compile(path))
119+
120+
def then_i_am_prompted_to_answer_can_the_screening_go_ahead(self):
121+
self.expect_validation_error(
122+
error_text="This field is required.",
123+
fieldset_legend="Can the appointment go ahead?",
124+
field_label="Yes, go to medical information",
125+
)
126+
127+
def then_i_am_prompted_to_answer_has_the_participant_shared_medical_info(self):
128+
self.expect_validation_error(
129+
error_text="This field is required.",
130+
fieldset_legend="Has the participant shared any relevant medical information?",
131+
field_label="Yes",
132+
)
133+
134+
def then_i_am_prompted_to_confirm_whether_imaging_can_go_ahead(self):
135+
self.expect_validation_error(
136+
error_text="This field is required.",
137+
fieldset_legend="Can imaging go ahead?",
138+
field_label="Yes, mark incomplete sections as ‘none’ or ‘no’",
139+
)
Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import re
2+
23
import pytest
34
from django.urls import reverse
45
from playwright.sync_api import expect
56

7+
from manage_breast_screening.clinics.models import Appointment
8+
from manage_breast_screening.clinics.tests.factories import (
9+
AppointmentFactory,
10+
ScreeningEpisodeFactory,
11+
)
612
from manage_breast_screening.config.system_test_setup import SystemTestCase
7-
from manage_breast_screening.clinics.tests.factories import AppointmentFactory, ScreeningEpisodeFactory
813
from manage_breast_screening.participants.tests.factories import ParticipantFactory
9-
from manage_breast_screening.clinics.models import Appointment
14+
1015

1116
class TestUserSubmitsCannotGoAheadForm(SystemTestCase):
1217
@pytest.fixture(autouse=True)
@@ -31,11 +36,12 @@ def test_user_submits_cannot_go_ahead_form(self):
3136
self.then_i_see_the_clinics_page()
3237
self.and_the_appointment_is_updated()
3338

34-
3539
def given_i_am_on_the_cannot_go_ahead_form(self):
3640
self.page.goto(
37-
self.live_server_url + reverse(
38-
"record_a_mammogram:appointment_cannot_go_ahead", kwargs={"id": self.appointment.pk}
41+
self.live_server_url
42+
+ reverse(
43+
"record_a_mammogram:appointment_cannot_go_ahead",
44+
kwargs={"id": self.appointment.pk},
3945
)
4046
)
4147

@@ -44,14 +50,14 @@ def when_i_submit_the_form(self):
4450

4551
def then_i_should_see_validation_errors(self):
4652
self.expect_validation_error(
47-
id="stopped_reasons-error",
48-
fieldset_legend="Why has this appointment been stopped?",
4953
error_text="A reason for why this appointment cannot continue must be provided",
54+
fieldset_legend="Why has this appointment been stopped?",
55+
field_label="Participant did not attend",
5056
)
5157
self.expect_validation_error(
52-
id="decision-error",
58+
error_text="Select whether the participant needs to be invited for another appointment",
5359
fieldset_legend="Does the appointment need to be rescheduled?",
54-
error_text="Select whether the participant needs to be invited for another appointment"
60+
field_label="Yes, add participant to reinvite list",
5561
)
5662

5763
def when_i_select_a_reason_for_the_appointment_being_stopped(self):
@@ -62,28 +68,34 @@ def and_i_select_other_as_a_reason(self):
6268

6369
def and_i_choose_to_add_the_participant_to_the_reinvite_list(self):
6470
self.page.get_by_label("Yes, add participant to reinvite list").click()
65-
expect(self.page.get_by_label("Yes, add participant to reinvite list")).to_be_checked()
71+
expect(
72+
self.page.get_by_label("Yes, add participant to reinvite list")
73+
).to_be_checked()
6674

6775
def then_i_see_an_error_for_other_details(self):
6876
self.expect_validation_error(
69-
id="other_details-error",
70-
fieldset_legend="Why has this appointment been stopped?",
7177
error_text="Explain why this appointment cannot proceed",
78+
fieldset_legend="Why has this appointment been stopped?",
79+
field_label="Provide details",
80+
field_name="other_details",
7281
)
7382

7483
def when_i_fill_in_other_details(self):
75-
self.page.locator("#other_details").fill("Explain other choice")
84+
self.page.locator("#id_other_details").fill("Explain other choice")
7685

7786
def then_i_see_the_clinics_page(self):
78-
expect(self.page).to_have_url(
79-
re.compile(reverse("clinics:index"))
80-
)
87+
expect(self.page).to_have_url(re.compile(reverse("clinics:index")))
8188

8289
def and_the_appointment_is_updated(self):
8390
self.appointment.refresh_from_db()
84-
self.assertEqual(self.appointment.status, Appointment.Status.ATTENDED_NOT_SCREENED)
91+
self.assertEqual(
92+
self.appointment.status, Appointment.Status.ATTENDED_NOT_SCREENED
93+
)
8594
self.assertEqual(self.appointment.reinvite, True)
86-
self.assertEqual(self.appointment.stopped_reasons, {
87-
"stopped_reasons": ["failed_identity_check", "other"],
88-
"other_details": "Explain other choice"
89-
})
95+
self.assertEqual(
96+
self.appointment.stopped_reasons,
97+
{
98+
"stopped_reasons": ["failed_identity_check", "other"],
99+
"other_details": "Explain other choice",
100+
},
101+
)

manage_breast_screening/templates/record_a_mammogram/appointment_cannot_go_ahead.jinja

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
{% block form %}
77
{% macro conditionalCheckboxInput(params) %}
88
{% set details_field_name = params.field_name + "_details" %}
9+
{% set details_field_id = params.details_field.auto_id %}
10+
{% set details_field_value = params.details_field.value() %}
911
{% set input_params = {
1012
"label": {
1113
"text": "Provide details"
1214
},
13-
"id": details_field_name,
15+
"id": details_field_id,
1416
"name": details_field_name,
15-
"value": params.details_value
17+
"value": details_field_value
1618
} %}
17-
{% if form[details_field_name].errors %}
19+
{% if params.details_field.errors %}
1820
{% set error_params = {
1921
"errorMessage": {
2022
"text": form[details_field_name].errors | first
@@ -31,20 +33,21 @@
3133
{% for field_name, label in form.stopped_reasons.field.choices %}
3234
{% do checkboxItems.append({
3335
"text": label,
36+
"id": form.stopped_reasons.auto_id if loop.first,
3437
"value": field_name,
3538
"checked": field_name in (form.stopped_reasons.value() or []),
3639
"conditional": {
3740
"html": conditionalCheckboxInput({
3841
"field_name": field_name,
39-
"details_value": form[field_name + "_details"].value()
42+
"details_field": form[field_name + "_details"],
4043
})
4144
}
4245
}) %}
4346
{% endfor %}
4447

4548
{% set checkbox_params = {
46-
"idPrefix": "stopped_reasons",
47-
"name": "stopped_reasons",
49+
"idPrefix": form.stopped_reasons.auto_id,
50+
"name": form.stopped_reasons.html_name,
4851
"fieldset": {
4952
"legend": {
5053
"text": "Why has this appointment been stopped?",

manage_breast_screening/templates/wizard_step.jinja

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
{% if form.errors %}
1818
{% set ns = namespace(errors=[]) %}
1919

20-
{% for field, messages in form.errors | items %}
21-
{% set ns.errors = ns.errors + [{"text": ",".join(messages), "href": "#" ~ field ~ "-error"}] %}
20+
{% for field in form %}
21+
{% set ns.errors = ns.errors + [{"text": ",".join(field.errors), "href": "#" ~ field.auto_id}] %}
22+
{% endfor %}
23+
{% for error_list in form.non_field_errors() %}
24+
{% set ns.errors = ns.errors + [{"text": ",".join(error_list)}] %}
2225
{% endfor %}
2326

2427
{{ errorSummary({
@@ -56,6 +59,7 @@
5659

5760
{{ radios({
5861
"name": form.decision.html_name,
62+
"idPrefix": form.decision.auto_id,
5963
"fieldset": {
6064
"legend": {
6165
"text": decision_legend,
@@ -69,6 +73,7 @@
6973
} if decision_hint,
7074
"items": [
7175
{
76+
"id": form.decision.auto_id,
7277
"value": form.decision.field.choices[0][0],
7378
"text": form.decision[0].choice_label,
7479
"checked": form.decision.value() == form.decision.field.choices[0][0]

0 commit comments

Comments
 (0)