The new-chat screen keeps its preselections in the query string, and the three ways off it each rebuilt the URL with only their own key: the Temporary button went to /chat?temporary=1, the model picker to /chat?model=<id>, and /temp to /chat?temporary=1. So each undid the other, and a temporary chat could only be started on the default model. The folder from "New chat here" and the agent kind were dropped the same way. chat_index now builds both URLs from one set of carried values and changes only its own key; /temp keeps location.search when run on /chat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
"""The keyboard, checked without a runtime.
|
|
|
|
There is no JavaScript test runner here and hard rule 1 keeps Node out of the
|
|
project, so the behaviour is driven by hand under a DOM stub before committing.
|
|
What can be pinned in the suite is the invariant the file states about itself:
|
|
`/help` reads `SHORTCUTS`, so a shortcut that is not in that list is a shortcut
|
|
nobody can discover. That is the direction this actually rots -- a key gets
|
|
added to the handler and the sheet is forgotten.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import lembas
|
|
|
|
SOURCE = (
|
|
Path(lembas.__file__).parent / "web/static/js/commands.js"
|
|
).read_text(encoding="utf-8")
|
|
|
|
# The declared list, up to where the command table starts.
|
|
SHORTCUTS = SOURCE[SOURCE.index("var SHORTCUTS") : SOURCE.index("/* --- The table")]
|
|
|
|
|
|
def test_every_letter_key_the_handlers_match_is_described():
|
|
letters = {match[-1] for match in re.findall(r'event\.code === "Key([A-Z])"', SOURCE)}
|
|
assert letters, "no letter shortcuts found at all, which means the regex is wrong"
|
|
|
|
missing = [letter for letter in sorted(letters) if f"+ {letter}" not in SHORTCUTS]
|
|
assert not missing, f"not in /help: {missing}"
|
|
|
|
|
|
def test_the_three_new_ones_are_there():
|
|
"""Named rather than only counted, because the point of them is being
|
|
findable: Enter already sends *inside the box*, and dictation and read-aloud
|
|
were click-only."""
|
|
assert "Ctrl/⌘ + Enter" in SHORTCUTS
|
|
assert "Alt + M" in SHORTCUTS
|
|
assert "Alt + R" in SHORTCUTS
|
|
|
|
|
|
def test_dictation_is_not_bound_to_alt_d():
|
|
"""Alt+D is the address bar in Chrome and Firefox on Windows and Linux. A
|
|
shortcut the browser wins is a shortcut that looks broken."""
|
|
assert 'event.code === "KeyD"' not in SOURCE
|
|
|
|
|
|
def test_the_shortcuts_are_matched_on_the_physical_key():
|
|
"""The file's own stated rule: `event.code`, so a Dvorak or Slovak layout
|
|
gets the same shortcuts rather than whichever letters sit there."""
|
|
assert "event.key ===" not in SOURCE
|
|
|
|
|
|
def test_send_from_anywhere_never_means_stop():
|
|
"""Send and Stop are the same element. Ctrl+Enter reaching it while it is
|
|
Stop would abandon a reply on a key people press to send -- and Esc already
|
|
stops."""
|
|
window = SOURCE[SOURCE.index('event.code === "Enter"') :][:600]
|
|
assert 'composerAction === "send"' in window
|
|
|
|
|
|
def test_temp_keeps_what_the_new_chat_screen_already_chose():
|
|
"""`/temp` went to a bare `/chat?temporary=1`, so on a new-chat screen with
|
|
a model picked it quietly swapped back to the default model."""
|
|
window = SOURCE[SOURCE.index('name: "temp"') :][:600]
|
|
assert 'window.location = "/chat?temporary=1"' not in window
|
|
assert "window.location.search" in window
|
|
assert 'query.set("temporary", "1")' in window
|