mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-03 11:42:23 +01:00
* fix(ui): keep the confirm dialog's OK button in line with the others #styled-confirm-ok was moved down 2px (position: relative; top: 2px), so the OK button sat 2px below Cancel and the alternate button in every styledConfirm dialog. Its label, and the alternate button's, now sit in a span nudged the same way as the Cancel label, so the text keeps its place and no button moves. Stylesheets and app.js get a new cache-bust token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): build confirm labels inline and share the Cancel nudge rule Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(cache): keep upstream's service worker cache name format dev's editor tests require CACHE_NAME to match odysseus-v<N>-pr40-editor-precache, so keep the version bump and use that suffix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
5.1 KiB
Python
110 lines
5.1 KiB
Python
"""Pin the dialog accessibility semantics added for the roadmap a11y pass.
|
|
|
|
Screen readers only announce "dialog" (and its name) when the container
|
|
carries role="dialog" plus an accessible name. These checks lock that in for
|
|
the static modals in index.html and the JS-built confirm/prompt dialogs, and
|
|
guard against a close button shipping without an accessible label again.
|
|
|
|
Plain text/regex assertions (no bs4 dependency), matching the lightweight style
|
|
of the other tests in this suite.
|
|
"""
|
|
import re
|
|
from pathlib import Path
|
|
from tests.helpers.stylesheets import app_css
|
|
|
|
_REPO = Path(__file__).resolve().parent.parent
|
|
_INDEX = (_REPO / "static" / "index.html").read_text(encoding="utf-8")
|
|
_UI = (_REPO / "static" / "js" / "ui.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_static_modals_expose_dialog_role_and_name():
|
|
# Each static tool window must announce itself as a named dialog. These are
|
|
# dockable/tiling windows, so they are role="dialog" WITHOUT aria-modal.
|
|
for name in ("Memory", "Theme", "Prompt", "Rename session", "Cookbook", "Settings"):
|
|
assert f'role="dialog" aria-label="{name}"' in _INDEX, f"missing dialog role/name for {name!r}"
|
|
|
|
|
|
def test_no_modal_close_button_is_unlabeled():
|
|
# Every .close-btn must carry an accessible name (text glyph alone reads as
|
|
# "heavy multiplication x"). Catch any new close button that forgets one.
|
|
buttons = re.findall(r'<button[^>]*class="close-btn"[^>]*>', _INDEX)
|
|
assert buttons, "expected to find close-btn buttons in index.html"
|
|
unlabeled = [b for b in buttons if "aria-label=" not in b]
|
|
assert not unlabeled, f"close buttons missing aria-label: {unlabeled}"
|
|
|
|
|
|
def test_styled_confirm_and_prompt_are_modal_dialogs():
|
|
# The JS-built confirm/prompt overlays ARE blocking modals, so they get
|
|
# role="dialog" + aria-modal="true" and are labelled by their title.
|
|
assert 'class="modal-content styled-confirm-box" role="dialog" aria-modal="true"' in _UI
|
|
assert 'aria-labelledby="styled-confirm-title"' in _UI
|
|
assert '<h4 id="styled-confirm-title">Confirm</h4>' in _UI
|
|
|
|
assert 'styled-prompt-box" role="dialog" aria-modal="true"' in _UI
|
|
assert 'aria-labelledby="styled-prompt-title"' in _UI
|
|
# The label/description targets the styled-prompt dialog points at must exist.
|
|
assert 'id="styled-prompt-title"' in _UI
|
|
assert 'id="styled-prompt-msg"' in _UI
|
|
|
|
|
|
def test_styled_confirm_cancel_or_close_label_is_shifted_without_moving_button():
|
|
css = app_css()
|
|
|
|
assert "cancelLabel.textContent = cancelText;" in _UI
|
|
assert "cancelBtn.appendChild(cancelLabel);" in _UI
|
|
rule_start = css.index("#styled-confirm-cancel .styled-confirm-cancel-label")
|
|
rule = css[rule_start:rule_start + 150]
|
|
assert "position:relative;" in rule
|
|
assert "top:2px;" in rule
|
|
|
|
|
|
def test_styled_confirm_ok_and_alternate_labels_shift_without_moving_buttons():
|
|
# Moving #styled-confirm-ok itself (position:relative; top:2px) left it 2px
|
|
# below Cancel and the alternate button. The OK and alternate labels get the
|
|
# same label nudge as Cancel instead, and no rule moves a footer button.
|
|
css = app_css()
|
|
|
|
assert "okBtn.replaceChildren(btnLabel(" in _UI
|
|
assert "altBtn.replaceChildren(btnLabel(" in _UI
|
|
rule_start = css.index("#styled-confirm-ok .styled-confirm-btn-label")
|
|
rule = css[rule_start:css.index("}", rule_start)]
|
|
assert "#styled-confirm-alt .styled-confirm-btn-label" in rule
|
|
assert "position:relative;" in rule and "top:2px;" in rule
|
|
for selector in ("#styled-confirm-ok {", "#styled-confirm-alt {", "#styled-confirm-cancel {"):
|
|
if selector in css:
|
|
block = css[css.index(selector):].split("}", 1)[0]
|
|
assert "top:" not in block.replace(" ", ""), selector
|
|
|
|
|
|
def test_styled_confirm_buttons_grow_to_fit_wrapped_labels():
|
|
# The global `button { height:32px }` held a two-line label (e.g.
|
|
# "Delete chat and images") to one line's height, so its second line
|
|
# rendered below the button. Confirm buttons must be allowed to grow,
|
|
# with 32px kept as the minimum.
|
|
css = app_css()
|
|
|
|
assert re.search(r"(^|\n)\s*button\s*\{\s*height:32px;", css)
|
|
rule_start = css.index(".styled-confirm-box .confirm-btn {")
|
|
rule = css[rule_start:rule_start + 120]
|
|
assert "height:auto;" in rule
|
|
assert "min-height:32px;" in rule
|
|
|
|
|
|
def test_styled_dialogs_manage_focus():
|
|
# A dialog is only really accessible if it restores focus to the trigger on
|
|
# close and traps Tab while open. Both styledConfirm and styledPrompt should
|
|
# capture the previously-focused element, restore it, and trap Tab.
|
|
assert _UI.count("const _prevFocus = document.activeElement;") == 2
|
|
assert _UI.count("_prevFocus && _prevFocus.focus && _prevFocus.focus()") == 2
|
|
assert _UI.count("e.key === 'Tab'") == 2
|
|
|
|
|
|
def test_toast_has_dismiss_button():
|
|
"""Both showToast and showError must include a close button with aria-label."""
|
|
# Read fresh every time so edits to ui.js are picked up
|
|
ui = (_REPO / "static" / "js" / "ui.js").read_text(encoding="utf-8")
|
|
assert "toast-close-btn" in ui
|
|
assert "aria-label" in ui
|
|
assert "Dismiss" in ui
|
|
assert ui.count("toast-close-btn") >= 2
|