test(probe): teach the layout probe about <details>, and guard the flag parser

THE PROBE. A control inside a CLOSED <details> is laid out but sits
outside its collapsed parent's box, so elementFromPoint at its centre
returns an ancestor and it reports OCCLUDED — 23 of them on sindra-set,
every one a false positive. Verified both ways before believing it:
closed, elementFromPoint returns div.gallery; opened, the button itself,
and a real trial click lands on it.

Opening every <details> rather than skipping them is the deliberate
choice. Skipping would make the probe quiet by declaring put-away
controls out of scope, and the add-note button inside
details.item-addnote is exactly the class of control this instrument
exists to check. Fourth false-positive class this probe has grown a
guard for; the other three are already in its header.

THE FLAG PARSER. Three cases that silently break and are cheap to
pin: the flags on either side of the glob (a session should not have to
remember which), a why carrying quotes, an em-dash, a newline and
non-ASCII, and --why with no value after it, which must produce usage
rather than eating the booth name and creating a booth called nothing.

313 tests.
This commit is contained in:
Vuong Hoang
2026-09-22 01:01:20 -07:00
parent c9a175ba4a
commit aa61fcf5fd
2 changed files with 62 additions and 0 deletions
+44
View File
@@ -207,3 +207,47 @@ def test_the_link_board_announces_itself_as_the_booths_own(tmp_path):
m = _manifest(tmp_path / "links")
assert m is not None and m.handle == "booth"
assert m.why
def test_the_flags_can_sit_on_either_side_of_the_files(tmp_path):
"""`booth add b *.png --why "..."` and `booth add b --why "..." *.png` both
work. A glob is usually last and a flag usually after it, but nothing
enforces that and a session should not have to remember which."""
src = tmp_path / "a.png"
src.write_bytes(b"x")
env = {**os.environ, "ALTHING_HANDLE": "booth-dev",
"BOOTH_DATA_DIR": str(tmp_path), "BOOTH_URL": "http://booth.invalid"}
for name, args in (("after", ["add", "after", str(src), "--why", "w"]),
("before", ["add", "before", "--why", "w", str(src)])):
r = subprocess.run([str(SCRIPT), *args], capture_output=True, text=True,
timeout=30, env=env)
assert r.returncode == 0, r.stderr
assert _manifest(tmp_path / name).why == "w"
assert (tmp_path / name / "a.png").exists(), "the files stopped being copied"
def test_a_why_survives_quotes_and_non_ascii_and_is_flattened(tmp_path):
"""The reason this goes through manifest.py instead of printf-ing JSON from
the shell: a why containing a quote, a backslash or a newline is not an edge
case, it is a sentence somebody wrote. Newlines flatten because the field
renders inside a card's sub-line."""
r = subprocess.run(
[str(SCRIPT), "new", "b", "--why", 'he said "pick v3" — line1\nline2 · ünï'],
capture_output=True, text=True, timeout=30,
env={**os.environ, "ALTHING_HANDLE": "booth-dev",
"BOOTH_DATA_DIR": str(tmp_path), "BOOTH_URL": "http://booth.invalid"})
assert r.returncode == 0, r.stderr
why = _manifest(tmp_path / "b").why
assert why == 'he said "pick v3" — line1 line2 · ünï'
def test_a_flag_with_no_value_does_not_eat_the_booth_name(tmp_path):
"""`booth new b --why` with nothing after it must not consume `b` as the
value and then create a booth called nothing. Usage, and no directory."""
r = subprocess.run([str(SCRIPT), "new", "b", "--why"], capture_output=True,
text=True, timeout=30,
env={**os.environ, "BOOTH_DATA_DIR": str(tmp_path),
"BOOTH_URL": "http://booth.invalid"})
assert r.returncode == 2
assert "usage:" in r.stderr
assert not (tmp_path / "b").exists()