+ {# Rendered open so it works without JavaScript; the script below folds it behind the button. #}
+
@@ -219,12 +220,14 @@
Profiles
var addBtn = document.getElementById('add-profile-btn');
var addBox = document.getElementById('add-profile');
- function setAdding(on) {
+ function setAdding(on, quiet) {
addBox.hidden = !on;
addBtn.hidden = on;
addBtn.setAttribute('aria-expanded', on ? 'true' : 'false');
- (on ? document.getElementById('name-new') : addBtn).focus();
+ if (!quiet) (on ? document.getElementById('name-new') : addBtn).focus();
}
+ document.getElementById('add-profile-cancel').hidden = false;
+ setAdding(addBox.dataset.open === 'true', true);
addBtn.addEventListener('click', function() { setAdding(true); });
document.getElementById('add-profile-cancel').addEventListener('click', function() { setAdding(false); });
diff --git a/templates/start_session.html b/templates/start_session.html
index ff8a85c..cd1cac1 100644
--- a/templates/start_session.html
+++ b/templates/start_session.html
@@ -224,7 +224,11 @@
{{ history_heading }}
var row = btn.closest('tr');
var banner = document.getElementById('health-export-banner');
if (banner && banner.dataset.sessionId === row.dataset.sessionId) banner.remove();
+ // Focus would fall to with the removed button (WCAG 2.4.3): move it to a neighbour's Delete, else Edit.
+ var near = row.nextElementSibling || row.previousElementSibling;
+ var next = near && near.querySelector('[data-delete-url]');
row.remove();
+ (next || editBtn).focus();
});
});
});
diff --git a/tests/test_routes.py b/tests/test_routes.py
index 6f14916..a1070e7 100644
--- a/tests/test_routes.py
+++ b/tests/test_routes.py
@@ -189,6 +189,20 @@ def test_active_belt_ladder_is_a_slider(client, running, monkeypatch):
assert 'aria-valuenow="2.8"' in ladder and 'aria-valuetext="2.8 mph"' in ladder
+def test_selected_preset_is_current_not_pressed(client, running, monkeypatch):
+ monkeypatch.setattr(app, "SLOW_WALK_SPEED_KMH", 4.5)
+ monkeypatch.setattr(app, "current_speed_kmh", 4.5)
+ html = client.get("/").get_data(as_text=True)
+ stops = re.findall(r']*class="stop"[^>]*>', html)
+ assert len(stops) == 3 and not any("aria-pressed" in b for b in stops)
+ assert [("Moderate" in b, 'aria-current="true"' in b) for b in stops] == [(False, False), (True, True), (False, False)]
+
+
+def test_theme_menu_button_announces_a_menu(client):
+ html = client.get("/").get_data(as_text=True)
+ assert re.search(r']*id="color-theme-toggle"[^>]*aria-haspopup="menu"', html)
+
+
def test_active_controls_start_disabled_while_transitioning(client, running, monkeypatch):
monkeypatch.setattr(app, "_belt_transitioning", True)
html = client.get("/").get_data(as_text=True)
@@ -1234,10 +1248,12 @@ def test_export_csv_has_no_walker_column_without_profiles(client):
assert header == "date,start_time,end_time,duration_seconds,distance_km,distance_mi,steps,calories,avg_speed_kmh,avg_speed_mph,id,has_samples"
-def test_add_profile_reveal_without_bootstrap(client):
+def test_add_profile_reachable_without_js(client):
+ # The form renders open, so it works with JavaScript off; ledger.js folds it behind the button on load.
html = client.get("/settings").get_data(as_text=True)
- assert 'aria-controls="add-profile" aria-expanded="false"' in html
- assert '
' in html
+ assert '
' in html
+ assert re.search(r']*id="add-profile-btn"[^>]*aria-controls="add-profile"[^>]*hidden>', html)
+ assert re.search(r']*id="add-profile-cancel"[^>]*hidden>', html)
assert "data-bs-" not in html
assert "confirm(" not in html
@@ -1262,8 +1278,7 @@ def test_settings_labels_name_their_inputs(client):
def test_add_profile_form_open_after_create_error(client):
html = client.post("/profiles", data=profile_form(lb="49")).get_data(as_text=True)
- assert 'aria-controls="add-profile" aria-expanded="true"' in html
- assert '