diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-15 00:49:54 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-15 00:49:54 +0200 |
| commit | 76724252d08162d4df39090af19796054bf4add8 (patch) | |
| tree | 2ed96118f3a0ee7139281b308f2489d7a176ea35 /packages/meshbay-hub/tests/test_spa_ordering.py | |
| parent | 4156b5b8c3986278a887d1dce4db265746e9155f (diff) | |
| download | meshbay-76724252d08162d4df39090af19796054bf4add8.tar.gz | |
fix(members): restore the member list and invite form, and retire the pairing form
Moving the invite form above the member list cut both out of MembersPanel and
pasted them into AdminPage, where `doInvite`, `members`, `adminId` and
`inviteCode` do not exist. A standard member saw an empty Members tab, the
group owner saw only a pairing form, and the hub's own Users tab referenced
four undefined names.
The pairing form outstaying its welcome is a second bug and an older one.
`is_node_admin` compares the connecting account with the account that owns the
node — it says nothing about whether *this browser's key* was ever paired, which
is the thing pairing changes and the thing that lets you sign an invite. So the
form showed for an operator who paired months ago, accepted a fresh code,
reported success, and stayed exactly where it was. The node already reports the
roster role in `join_result`; the transport keeps it, and the form appears only
when this identity is not an operator key yet.
Also dropped a clause from the pairing hint: the code never passing through the
hub is worth saying, the theory behind it is not.
test_spa_ordering.py gets three checks for this class of bug — a cut-and-paste
between components is invisible to every other test we have.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_spa_ordering.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_spa_ordering.py | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py index 0ef34fc..9301fcc 100644 --- a/packages/meshbay-hub/tests/test_spa_ordering.py +++ b/packages/meshbay-hub/tests/test_spa_ordering.py @@ -101,3 +101,45 @@ def test_the_ack_still_verifies_the_announced_node_key(): "the challenge's node_pk must be checked against the ack's") assert "verifyNodeSignature" in source, ( "the ack's signature over the handshake transcript must still be verified") + + +# ── Component boundaries ──────────────────────────────────────────────────── +# +# A second class of bug this file exists for. Moving a block between components +# is a plain cut and paste, and nothing checks that the paste landed somewhere +# the names it uses exist: the invite form and the member list were cut out of +# MembersPanel and pasted into AdminPage, which left a standard member seeing an +# empty Members tab, the group owner seeing only a pairing form, and the hub's +# Users tab referencing `doInvite`, `members` and `adminId` — none of which are +# defined there. + +APP = STATIC / "app.js" + + +def _component(name: str) -> str: + """The source of one top-level `function Name(...)`, up to the next one.""" + source = APP.read_text() + start = source.find(f"\nfunction {name}(") + assert start != -1, f"{name} is gone from app.js — update this test" + end = source.find("\nfunction ", start + 1) + return source[start:end if end != -1 else len(source)] + + +def test_members_panel_renders_what_it_owns(): + panel = _component("MembersPanel") + assert "members.map(" in panel, "the member list is not rendered" + assert "onSubmit=${doInvite}" in panel, "the invite form is not rendered" + assert "onSubmit=${doPair}" in panel, "the pairing form is not rendered" + + +def test_the_invite_form_comes_before_the_list(): + panel = _component("MembersPanel") + assert panel.index("onSubmit=${doInvite}") < panel.index("members.map("), \ + "the invite form belongs above the member list" + + +def test_admin_page_does_not_borrow_the_members_panel_state(): + admin = _component("AdminPage") + for name in ("doInvite", "adminId", "inviteCode", "setInviteUser"): + assert name not in admin, \ + f"AdminPage references {name}, which only exists in MembersPanel" |