""" Two wire contracts in transport.js that fail quietly when broken. Neither can be reached from Python, and `QE/deploy/e2e.py` is a second implementation of the client rather than a test of this one, so these read the source. That is weak evidence in general, and the right kind here: both defects below produce a plausible screen rather than an error. - History paged forward from the oldest message, so a group with more than 200 of them opened on its first screen and the recent conversation could not be reached. Nothing threw; the wrong messages were simply shown. - A reply that is not routed falls through to "resolve the oldest pending request". Adding a ping made that dangerous: a pong handed to a waiting history request satisfies it with a message that has no `messages` field, and the conversation renders empty. """ import re from pathlib import Path import pytest STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" TRANSPORT = STATIC / "transport.js" APP = STATIC / "app.js" pytestmark = pytest.mark.skipif( not TRANSPORT.exists(), reason="the SPA sources are not available") @pytest.fixture(scope="module") def transport(): return TRANSPORT.read_text() @pytest.fixture(scope="module") def app(): return APP.read_text() def test_chat_history_pages_backwards(transport): body = transport[transport.index("async fetchChatHistory"):] body = body[:body.index("\n }")] assert "before" in body, "the request must carry a backward cursor" assert "since" not in body, ( "`since` pages forward from the oldest message — that was the bug") def test_chat_history_reports_whether_more_exists(transport): body = transport[transport.index("async fetchChatHistory"):] body = body[:body.index("\n }")] assert "has_more" in body, ( "without it the 'load older' control cannot know when to stop offering") def test_the_browser_asks_for_the_newest_page_first(app): """A group opens on the newest messages, not the oldest.""" assert "fetchChatHistory({ limit: CHAT_PAGE })" in app assert re.search(r"const CHAT_PAGE\s*=\s*100", app) assert re.search(r"const CHAT_OLDER_PAGE\s*=\s*50", app) def test_older_pages_are_requested_with_a_cursor_not_an_offset(app): assert "before: messages[0].id" in app, ( "paging by offset repeats or skips messages when one arrives mid-scroll") def test_pong_is_routed_by_its_echoed_token(transport): """Not left to the oldest-pending fallback, which would empty a chat.""" assert "if (msg.type === 'pong')" in transport routing = transport[transport.index("if (msg.type === 'pong')"):] routing = routing[:routing.index("const oldest")] assert "handler._key === key" in routing assert "return;" in routing, ( "a pong for a timed-out probe must stop here, not fall through") def test_ping_requests_are_keyed(transport): assert "`ping:${obj.token}`" in transport, ( "an unkeyed ping cannot be matched to its pong") def test_ping_can_time_out_sooner_than_a_transfer(transport): """30 s is right for a chunk and useless for a liveness probe.""" assert "_sendAndWait(obj, timeoutMs = 30000)" in transport body = transport[transport.index(" async ping("):] body = body[:body.index("\n }")] assert "timeoutMs" in body def test_scroll_position_is_anchored_when_older_messages_are_prepended(app): """Everything above the viewport grows, so scrollTop alone is not enough.""" assert "scrollHeight - list.scrollTop" in app, "the anchor is measured from the bottom" assert "list.scrollTop = list.scrollHeight - anchorRef.current" in app assert "useLayoutEffect" in app, ( "correcting after paint shows the jump it is meant to prevent") def test_the_view_only_follows_new_messages_when_already_at_the_bottom(app): assert "if (atBottomRef.current) list.scrollTop = list.scrollHeight" in app, ( "scrolling unconditionally fights someone reading back through history") def test_the_bottom_is_reached_by_scrollTop_not_a_sentinel(app): """scrollIntoView on a zero-height marker stops short of the true bottom. The list has padding and a flex gap below the last bubble, so aligning an empty div to the viewport bottom left the bar a few pixels from the end — visible on opening a group and again after sending a message. """ # The call, not the word: a comment explaining why it is gone should not # be able to fail this. assert ".scrollIntoView(" not in app assert "list.scrollTo({ top: list.scrollHeight" in app, ( "jumping to the latest should also land on the real bottom") def test_messages_are_keyed_by_id_not_index(app): """Index keys plus prepending makes Preact reuse the wrong bubbles.""" assert "key=${m.id}" in app assert "key=${i}" not in app.split("function ChatPanel")[1].split("\n}")[0] def test_presence_has_three_states_and_a_label_for_each(app): for state in ("online", "offline", "unknown"): assert f"presence-{state}" in (STATIC / "style.css").read_text() assert "t('presence.' + state)" in app, ( "red and green are the pair colour-blind readers cannot separate, so " "the dot needs a title and an aria-label, not just a colour") def test_a_refusal_from_the_node_counts_as_present(app): """The node answering "no" proves it is up; only silence proves nothing.""" assert "err.reason ? 'online' : 'offline'" in app # ── The create-group form ───────────────────────────────────────────────────── def test_choosing_public_settles_the_admission_question(app): """Public implies open, so the policy selector has nothing left to ask. Enforced twice on purpose: the API refuses public+invite with a 422, and the form never offers the combination. A form that can build a request the server rejects is a form that produces an error message instead of a group. """ assert "setVisibility('public'); setJoinPolicy('open');" in app, ( "picking Public must settle the policy, not leave the previous one") assert "setVisibility('private'); setJoinPolicy('invite');" in app, ( "going back to Private must not leave the group open by accident") form = app[app.index("function CreateGroupPage"):] form = form[:form.index("\n}\n")] selector = form.index("t('create_group.join_policy')") guard = form.rindex("visibility === 'public'", 0, selector) assert guard != -1, "the policy selector must sit behind a visibility guard" assert "create_group.public_is_open" in form[guard:selector], ( "a public group should say why there is nothing to choose") def test_the_form_starts_on_a_combination_the_api_accepts(app): form = app[app.index("function CreateGroupPage"):] assert "useState('private')" in form[:form.index("return html")] assert "useState('invite')" in form[:form.index("return html")] # ── Dead references in the SPA ──────────────────────────────────────────────── def test_no_setter_survives_the_state_it_belonged_to(app): """A removed useState leaves its setter behind, and nothing complains. `setActionsOpen` outlived `actionsOpen` when the Actions dropdown became a row of buttons, and shipped: every action in the Files panel threw ReferenceError on click. No Python test could see it and e2e.py does not drive the SPA, so the shape is checked here instead. A grep for the state name does not find it — `setActionsOpen` does not contain `actionsOpen`, the capital breaks the match. That is exactly how it got through. """ import re declared = set(re.findall(r"const \[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState", app)) # Names brought in from another module are defined, just not here. imported = set() for names in re.findall(r"import\s*\{([^}]*)\}\s*from", app): imported.update(n.strip().split(" as ")[-1].strip() for n in names.split(",")) # A bare call only: `downloads.setMode(...)` and `view.setUint32(...)` belong # to their object, not to this component. called = set(re.findall(r"(?