""" Reading back through the conversation must work. The scroll-to-bottom on arrival has been patched five times, and the sixth patch took the other side away: the panel re-pinned the list about 120 times a second, so a wheel gesture was undone in the frame it happened in and the older messages became unreachable. Every pin in the source is guarded by "only if the reader is at the bottom" — the reader simply never got to stop being at the bottom, because the `scroll` event that records it is delivered a frame after the pin that erased it. None of that is visible in the source, which is why this measures the real `ChatPanel` in a browser instead of reading `chat-app.js`. `test_chat_scroll_ bottom.py` keeps the structural guards; this one keeps the behaviour. """ import json import shutil import subprocess from pathlib import Path import pytest HARNESS = Path(__file__).parent / "harness" / "chat_scroll_probe.py" STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" pytestmark = pytest.mark.skipif( shutil.which("google-chrome") is None or not (STATIC / "chat-app.js").exists(), reason="Chrome or the SPA sources are not available") # A bare document fires a couple of these as it settles. Anything above this is # the panel driving itself, which is what the loop looked like. IDLE_RESIZE_CEILING = 20 # The harness scrolls up by six frames of 120px. Chrome's scroll anchoring # moves the reader with the content when preview cards land above them, so the # distance from the bottom is not expected to be exactly 720 afterwards — only # to stay well clear of it. SCROLLED_UP_FLOOR = 400 @pytest.fixture(scope="module") def probe(): run = subprocess.run(["python3", str(HARNESS)], capture_output=True, timeout=180) assert run.returncode == 0, run.stderr.decode()[-2000:] data = json.loads(run.stdout.decode()) return data, {s["label"]: s for s in data["steps"]} def test_arrives_on_the_newest_message(probe): """Opening the tab lands at the end of the conversation, after the late layout and the panel sizing itself.""" _, steps = probe assert steps["arrived"]["fromBottom"] < 40, ( "the chat must open on the newest message; it opened " f"{steps['arrived']['fromBottom']}px above it") def test_the_panel_does_not_resize_itself(probe): """fit() runs on `resize` and must not produce one. When it did, it re-ran every frame and re-pinned the scroll with it.""" data, _ = probe assert data["idle"]["viewportResizes"] <= IDLE_RESIZE_CEILING, ( f"{data['idle']['viewportResizes']} viewport resizes on a page nobody " "touched -- fit() is feeding the event it listens for, and every pass " "re-pins the chat to the bottom") assert data["idle"]["documentOverflow"] <= 0, ( "the chat tab must not leave the document taller than the window") def test_scrolling_up_holds(probe): """The gesture must survive the frame it happened in.""" _, steps = probe assert steps["scrolled up"]["fromBottom"] >= SCROLLED_UP_FLOOR, ( "scrolling up was undone: the list came back to " f"{steps['scrolled up']['fromBottom']}px from the bottom") assert steps["scrolled up"]["jumpButton"], ( "the panel never noticed the reader leave the bottom -- the pin beat " "the scroll event, so atBottom stayed true and no jump button appeared") def test_late_content_does_not_drag_the_reader_down(probe): """Link previews and thumbnails arrive over the following seconds and grow the list. That is what the arrival pin is for, and it must be over by now.""" _, steps = probe assert steps["previews landed"]["fromBottom"] >= SCROLLED_UP_FLOOR, ( "preview cards landing pulled the reader back to the bottom") def test_a_new_message_does_not_yank_the_reader_down(probe): """Somebody writing while you read back marks the spot; it does not move you.""" _, steps = probe assert steps["message arrived"]["fromBottom"] >= SCROLLED_UP_FLOOR, ( "an incoming message pulled the reader away from what they were reading") def test_a_resize_does_not_yank_the_reader_down(probe): """A window resize, a rotation, or a phone's URL bar collapsing.""" _, steps = probe assert steps["window resized"]["fromBottom"] >= SCROLLED_UP_FLOOR, ( "a resize pulled the reader back to the bottom")