mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-03 03:32:25 +01:00
* fix(ui): let confirm-dialog buttons grow to fit wrapped labels
The global `button { height:32px }` rule also applies to styledConfirm's
buttons. A label that wraps to two lines, such as "Delete chat and images",
"Launch anyway (CPU)" or a three-button dialog's alternate action, keeps
one line's height, so its second line renders below the button. On narrow
windows the dialog makes the buttons equal width, so this hits most
multi-word labels; it also happens on desktop, where the dialog box is
narrow too.
Give .styled-confirm-box .confirm-btn height:auto with min-height:32px.
Single-line buttons keep their current 32px.
Bumps the shared stylesheet/app cache token and the service worker cache
name, since a stylesheet changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(static): leave admin.js's cache tag unchanged
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>
92 lines
4.1 KiB
Python
92 lines
4.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_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
|