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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user