* stage-3955: blur composer on Escape for j/k nav + IME guard (Opus nit) * stage-3955: update test to match IME-guarded blur branch * Release OX: blur composer on Escape for j/k message nav (#3952) Self-rebased TomBanksAU's #3955 onto v0.51.436; full Codex+Opus+suite gate. Escape now blurs the focused composer so j/k session-nav works, after higher- priority Escape actions run; dropdown-Escape stops propagation; IME-composition guarded (Opus nit). Also restored the v0.51.434/435 CHANGELOG headings that an earlier batch left folded under v0.51.436 (hygiene fix). Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com> --------- Co-authored-by: nesquena-hermes <agent@nesquena-hermes> Co-authored-by: TomBanksAU <TomBanksAU@users.noreply.github.com>
75 lines
3.3 KiB
Python
75 lines
3.3 KiB
Python
"""Regression tests for #3952 composer Escape keyboard navigation."""
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
BOOT_JS = (REPO_ROOT / "static" / "boot.js").read_text(encoding="utf-8")
|
|
SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def _block_from_opening_brace(src: str, brace: int, label: str) -> str:
|
|
assert brace >= 0, f"{label} opening brace must exist"
|
|
depth = 1
|
|
idx = brace + 1
|
|
while idx < len(src) and depth:
|
|
if src[idx] == "{":
|
|
depth += 1
|
|
elif src[idx] == "}":
|
|
depth -= 1
|
|
idx += 1
|
|
assert depth == 0, f"{label} block must close"
|
|
return src[brace + 1 : idx - 1]
|
|
|
|
|
|
def _escape_block() -> str:
|
|
document_keydown = BOOT_JS.index("// B14: Cmd/Ctrl+K creates a new chat from anywhere")
|
|
start = BOOT_JS.index("if(e.key==='Escape'){", document_keydown)
|
|
brace = BOOT_JS.index("{", start)
|
|
return _block_from_opening_brace(BOOT_JS, brace, "document Escape handler")
|
|
|
|
|
|
def _composer_keydown_block() -> str:
|
|
start = BOOT_JS.index("$('msg').addEventListener('keydown',e=>{")
|
|
brace = BOOT_JS.index("{", start)
|
|
return _block_from_opening_brace(BOOT_JS, brace, "composer keydown handler")
|
|
|
|
|
|
def test_escape_blurs_focused_composer_after_higher_priority_escape_actions():
|
|
"""Escape should let keyboard-only users leave the composer and use j/k nav."""
|
|
block = _escape_block()
|
|
|
|
blur_idx = block.find("document.activeElement===$('msg')")
|
|
assert blur_idx != -1, "Escape handler must detect the focused composer"
|
|
assert "$('msg').blur();" in block[blur_idx:], "Escape handler must blur the composer"
|
|
# The blur must be skipped while an IME candidate window is composing, so
|
|
# Escape dismisses the candidate (CJK input) rather than blurring the composer.
|
|
blur_line = block[blur_idx : block.find("\n", blur_idx)]
|
|
assert "!e.isComposing" in blur_line and "!_imeComposing" in blur_line, (
|
|
"Escape blur must be guarded against IME composition"
|
|
)
|
|
|
|
assert block.index("skipOnboarding") < blur_idx, "onboarding dismissal stays higher priority"
|
|
assert block.index("_closeSettingsPanel") < blur_idx, "settings dismissal stays higher priority"
|
|
assert block.index("clearSessionSearch") < blur_idx, "session-search clearing stays higher priority"
|
|
assert block.index("msg-edit-cancel") < blur_idx, "message-edit cancel stays higher priority"
|
|
|
|
|
|
def test_jk_session_navigation_still_ignores_interactive_targets():
|
|
"""Composer text entry still owns j/k until Escape blurs it."""
|
|
nav_start = SESSIONS_JS.index("// Keyboard session navigation — J/K bindings")
|
|
nav_block = SESSIONS_JS[nav_start:]
|
|
assert "if(typeof _isInteractiveSwipeTarget==='function'&&_isInteractiveSwipeTarget(e.target)) return;" in nav_block
|
|
|
|
|
|
def test_escape_dismisses_command_dropdown_without_blurring_composer():
|
|
"""Escape should close slash-command autocomplete without bubbling to blur."""
|
|
block = _composer_keydown_block()
|
|
escape_idx = block.find("if(e.key==='Escape'){")
|
|
assert escape_idx != -1, "composer keydown handler must handle dropdown Escape"
|
|
escape_line = block[escape_idx : block.find("}", escape_idx) + 1]
|
|
|
|
assert "e.preventDefault();" in escape_line
|
|
assert "e.stopPropagation();" in escape_line
|
|
assert "hideCmdDropdown();" in escape_line
|