+
Error:
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
diff --git a/app/templates/views/send-test.html b/app/templates/views/send-test.html
index fd5eb63db..faa71873b 100644
--- a/app/templates/views/send-test.html
+++ b/app/templates/views/send-test.html
@@ -37,6 +37,8 @@
data_kwargs={'force-focus': True}
) %}
+ {% set extra_class = "extra-tracking" if form.placeholder_value.label.text == "phone number" else "" %}
+ {% set placeholder_id = "phone number" if form.placeholder_value.label.text == "phone number" else "" %}
diff --git a/gulpfile.js b/gulpfile.js
index e1bf8ba5c..f6389f3c9 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -82,7 +82,7 @@ const javascripts = () => {
paths.src + 'javascripts/totalMessagesChart.js',
paths.src + 'javascripts/activityChart.js',
paths.src + 'javascripts/sidenav.js',
-
+ paths.src + 'javascripts/validation.js',
])
.pipe(plugins.prettyerror())
.pipe(
diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py
index 0e781ab82..f04937e9b 100644
--- a/tests/app/main/views/organizations/test_organizations.py
+++ b/tests/app/main/views/organizations/test_organizations.py
@@ -824,7 +824,7 @@ def test_manage_org_users_should_show_live_search_if_more_than_7_users(
assert not page.select_one("[data-force-focus]")
assert textbox["class"] == ["usa-input"]
assert (
- normalize_spaces(page.select_one("label[for=search]").text)
+ normalize_spaces(page.select_one("label[class=usa-label]").text)
== "Search by name or email address"
)
@@ -982,20 +982,13 @@ def test_view_organization_settings(
page = client_request.get(endpoint, org_id=organization_one["id"])
radios = page.select("input[type=radio]")
+ labels = page.select("label.usa-radio__label") # Select all radio labels in order
for index, option in enumerate(expected_options):
option_values = {
"value": radios[index]["value"],
- "label": normalize_spaces(
- page.select_one("label[for={}]".format(radios[index]["id"])).text
- ),
+ "label": normalize_spaces(labels[index].text), # Match labels using index
}
- if "hint" in option:
- option_values["hint"] = normalize_spaces(
- page.select_one(
- "label[for={}] + .usa-hint".format(radios[index]["id"])
- ).text
- )
assert option_values == option
if expected_selected:
@@ -1082,7 +1075,7 @@ def test_update_organization_sector_sends_service_id_data_to_api_client(
client_request.post(
"main.edit_organization_type",
org_id=organization_one["id"],
- _data={"organization_type": "federal"},
+ _data={"organization_type": "state"},
_expected_status=302,
_expected_redirect=url_for(
"main.organization_settings",
@@ -1093,7 +1086,7 @@ def test_update_organization_sector_sends_service_id_data_to_api_client(
mock_update_organization.assert_called_once_with(
organization_one["id"],
cached_service_ids=["12345", "67890", SERVICE_ONE_ID],
- organization_type="federal",
+ organization_type="state",
)
diff --git a/tests/app/main/views/service_settings/test_service_settings.py b/tests/app/main/views/service_settings/test_service_settings.py
index a9895ef99..d7155f709 100644
--- a/tests/app/main/views/service_settings/test_service_settings.py
+++ b/tests/app/main/views/service_settings/test_service_settings.py
@@ -466,14 +466,30 @@ def test_show_switch_service_to_count_as_live_page(
"main.service_switch_count_as_live",
service_id=SERVICE_ONE_ID,
)
- assert page.select_one("[checked]")["value"] == selected
- assert (
- page.select_one(
- "label[for={}]".format(page.select_one("[checked]")["id"])
- ).text.strip()
- == labelled
+
+ client_request.login(platform_admin_user)
+ page = client_request.get(
+ "main.service_switch_count_as_live",
+ service_id=SERVICE_ONE_ID,
)
+ # Find the checked radio button
+ checked_input = page.select_one("[checked]")
+
+ # Ensure we actually found a checked input
+ assert checked_input is not None, "No checked radio button found"
+
+ # Check that the selected value is as expected
+ assert checked_input["value"] == selected
+
+ # Find all labels
+ labels = page.select("label.usa-radio__label")
+
+ # Extract label text and see if it matches the expected label
+ label_texts = [label.text.strip() for label in labels]
+
+ assert labelled in label_texts, f"Expected label '{labelled}' not found. Found labels: {label_texts}"
+
@pytest.mark.parametrize(
("post_data", "expected_persisted_value"),
@@ -1320,7 +1336,7 @@ def test_shows_delete_link_for_error_on_post_request_for_edit_email_reply_to_add
== "Error: Enter a valid email address"
)
assert (
- page.select_one("input#email_address").get("value")
+ page.select_one("input#reply-to-email-address").get("value")
== "not a valid email address"
)
@@ -2372,11 +2388,13 @@ def test_send_files_by_email_contact_details_prefills_the_form_with_the_existing
page = client_request.get(
"main.send_files_by_email_contact_details", service_id=SERVICE_ONE_ID
)
+
assert page.find(
"input", attrs={"name": "contact_details_type", "value": contact_details_type}
).has_attr("checked")
+
assert (
- page.find("input", {"id": contact_details_type}).get("value")
+ page.find("input", {"name": contact_details_type}).get("value")
== contact_details_value
)
diff --git a/tests/app/main/views/test_activity.py b/tests/app/main/views/test_activity.py
index 8d583223e..02bed98fa 100644
--- a/tests/app/main/views/test_activity.py
+++ b/tests/app/main/views/test_activity.py
@@ -388,7 +388,7 @@ def test_search_recipient_form(
query_dict = parse_qs(url.query)
assert query_dict == {}
- assert page.select_one("label[for=to]").text.strip() == expected_search_box_label
+ assert page.select_one("label:contains('Search by')").text.strip() == expected_search_box_label
recipient_inputs = page.select("input[name=to]")
assert len(recipient_inputs) == 2
@@ -421,7 +421,7 @@ def test_api_users_are_told_they_can_search_by_reference_when_service_has_api_ke
service_id=SERVICE_ONE_ID,
message_type=message_type,
)
- assert page.select_one("label[for=to]").text.strip() == expected_search_box_label
+ assert page.select_one("label:contains('Search by')").text.strip() == expected_search_box_label
@pytest.mark.parametrize(
@@ -448,7 +448,8 @@ def test_api_users_are_not_told_they_can_search_by_reference_when_service_has_no
service_id=SERVICE_ONE_ID,
message_type=message_type,
)
- assert page.select_one("label[for=to]").text.strip() == expected_search_box_label
+
+ assert page.select_one("label:contains('Search by')").text.strip() == expected_search_box_label
def test_should_show_notifications_for_a_service_with_next_previous(
diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py
index 2f6b3a226..7b0b1d9e4 100644
--- a/tests/app/main/views/test_manage_users.py
+++ b/tests/app/main/views/test_manage_users.py
@@ -197,7 +197,7 @@ def test_should_show_live_search_if_more_than_7_users(
"usa-input",
]
assert (
- normalize_spaces(page.select_one("label[for=search]").text)
+ normalize_spaces(page.select_one("label:contains('Search by')").text)
== "Search by name or email address"
)
@@ -447,7 +447,7 @@ def test_invite_user_has_correct_email_field(
client_request.login(platform_admin_user)
email_field = client_request.get(
"main.invite_user", service_id=SERVICE_ONE_ID
- ).select_one("#email_address")
+ ).select_one("#email-address")
assert email_field["spellcheck"] == "false"
assert "autocomplete" not in email_field
diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py
index a55307a2b..64ff5bd4c 100644
--- a/tests/app/main/views/test_register.py
+++ b/tests/app/main/views/test_register.py
@@ -17,8 +17,8 @@ def test_render_register_returns_template_with_form(client_request, mocker):
page = client_request.get_url("/register")
assert page.find("input", attrs={"name": "auth_type"}).attrs["value"] == "sms_auth"
- assert page.select_one("#email_address")["spellcheck"] == "false"
- assert page.select_one("#email_address")["autocomplete"] == "email"
+ assert page.select_one("#email-address")["spellcheck"] == "false"
+ assert page.select_one("#email-address")["autocomplete"] == "email"
assert page.select_one("#password")["autocomplete"] == "new-password"
assert "Create an account" in page.text
diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py
index 0ab770c18..d957fa393 100644
--- a/tests/app/main/views/test_send.py
+++ b/tests/app/main/views/test_send.py
@@ -1519,9 +1519,11 @@ def test_link_to_upload_not_offered_when_entering_personalisation(
step_index=1,
)
+ # print(page.prettify()) # Print the full HTML response
+
# We’re entering personalization
assert page.select_one("input[type=text]")["name"] == "placeholder_value"
- assert page.select_one("label[for=phone-number]").text.strip() == "name"
+ assert page.select_one("label").text.strip() == "name"
# No ‘Upload’ link shown
assert len(page.select("main a")) == 0
assert "Upload" not in page.select_one("main").text
diff --git a/tests/app/main/views/test_tour.py b/tests/app/main/views/test_tour.py
index c6316d0a7..9a1774a13 100644
--- a/tests/app/main/views/test_tour.py
+++ b/tests/app/main/views/test_tour.py
@@ -175,7 +175,7 @@ def test_should_show_empty_text_box(
# shouldn’t also be set on the textbox itself
assert "data-module" not in textbox
- assert normalize_spaces(page.select_one("label[for=phone-number]").text) == "one"
+ assert normalize_spaces(page.select_one("label").text) == "one"
def test_should_prefill_answers_for_get_tour_step(
diff --git a/tests/javascripts/validation.test.js b/tests/javascripts/validation.test.js
new file mode 100644
index 000000000..fb6b1a67e
--- /dev/null
+++ b/tests/javascripts/validation.test.js
@@ -0,0 +1,62 @@
+const { showError, hideError, getFieldLabel, attachValidation } = require("../../app/assets/javascripts/validation.js");
+
+describe("Form Validation", () => {
+ let form, input, submitButton;
+
+ beforeEach(() => {
+ document.body.innerHTML = `
+
+ `;
+
+ form = document.querySelector(".send-one-off-form");
+ input = document.getElementById("test-input");
+ submitButton = form.querySelector("button");
+
+ // Manually attach validation logic for Jest
+ attachValidation();
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = ""; // Clean up DOM after each test
+ });
+
+ test("Displays an error message when input is empty", async () => {
+ form.dispatchEvent(new Event("submit", { bubbles: true }));
+
+ // Wait for the timeout to complete
+ await new Promise(resolve => setTimeout(resolve, 20));
+
+ const errorMessage = document.getElementById("test-input-error");
+ expect(errorMessage).not.toBeNull();
+ expect(errorMessage.textContent).toBe("Error: Test Input is required.");
+ expect(input.classList.contains("usa-input--error")).toBe(true);
+});
+
+ test("Removes error message when input is filled", () => {
+ // Trigger validation first
+ form.dispatchEvent(new Event("submit", { bubbles: true }));
+
+ // Simulate user typing to remove the error
+ input.value = "Some text";
+ input.dispatchEvent(new Event("input", { bubbles: true }));
+
+ const errorMessage = document.getElementById("test-input-error");
+ expect(errorMessage).not.toBeNull();
+ expect(errorMessage.style.display).toBe("none");
+ expect(input.classList.contains("usa-input--error")).toBe(false);
+ });
+
+ test("Focus moves to first invalid input", async () => {
+ const spy = jest.spyOn(input, "focus");
+
+ form.dispatchEvent(new Event("submit", { bubbles: true }));
+
+ await new Promise((resolve) => setTimeout(resolve, 10)); // Allow DOM updates
+
+ expect(spy).toHaveBeenCalled();
+ });
+});