1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
"""
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")
|