test(#3635): add profile-switcher source-of-truth invariant (rebased from #3639) (#3644)

Test-only. Adds TestProfileSwitcherSourceOfTruthInvariant generalizing the #3635
fix so the chip + dropdown can't re-split their source of truth (both must read
S.activeProfile). Rebased onto current master — the original #3639 branch was
stacked on the pre-squash #3637 and would have reverted ~5 shipped releases
(IF/IG/IH) if merged as-is; this carries ONLY the +74-line test delta.

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: nesquena <nesquena@users.noreply.github.com>
This commit is contained in:
nesquena-hermes
2026-06-05 00:51:23 -07:00
committed by GitHub
parent 9b933e2c83
commit 4c545a33f3

View File

@@ -89,3 +89,77 @@ class TestIssue3635ProfileChipActive:
"expected both syncTopbar chip-label setters to read "
"S.activeProfile||'default'; found: " + str(setters)
)
def _panels_js() -> str:
return (Path(__file__).parent.parent / "static" / "panels.js").read_text(encoding="utf-8")
def _render_profile_dropdown_body(src: str) -> str:
"""Return the source of renderProfileDropdown()."""
start = src.find("function renderProfileDropdown(")
assert start != -1, "renderProfileDropdown not found in panels.js"
i = src.find("{", start)
depth = 0
for j in range(i, len(src)):
if src[j] == "{":
depth += 1
elif src[j] == "}":
depth -= 1
if depth == 0:
return src[start : j + 1]
raise AssertionError("could not find end of renderProfileDropdown()")
class TestProfileSwitcherSourceOfTruthInvariant:
"""Standing invariant guard (generalizes #3635).
The profile chip (the switcher trigger, in syncTopbar()/ui.js) and the
profile dropdown's active/checkmark row (renderProfileDropdown()/panels.js)
are two renderings of the SAME thing — "which profile is active." They must
resolve from the same source of truth (S.activeProfile). #3331 broke this by
pointing the chip at S.session.profile while the dropdown still used
S.activeProfile, so the switcher trigger and the menu it opens disagreed
(#3635). This invariant fails fast if any future change re-splits them.
"""
def test_chip_keys_on_active_profile(self):
body = _sync_topbar_body(_ui_js())
# Every profileChipLabel.textContent assignment must read S.activeProfile.
assignments = re.findall(r"profileChipLabel'\);[\s\S]{0,120}?\.textContent=([^;]+);", body)
assert assignments, "no profileChipLabel assignment found in syncTopbar()"
for expr in assignments:
assert "S.activeProfile" in expr and "S.session.profile" not in expr, (
"profile chip (switcher trigger) must resolve from S.activeProfile, "
"not the loaded session's profile: " + expr.strip()
)
def test_dropdown_active_row_keys_on_active_profile(self):
body = _render_profile_dropdown_body(_panels_js())
# The dropdown's active row is computed into `const active = ...`.
m = re.search(r"const active\s*=\s*([\s\S]*?);", body)
assert m, "could not find the `const active =` computation in renderProfileDropdown()"
expr = m.group(1)
assert "S.activeProfile" in expr, (
"dropdown active-row must resolve from S.activeProfile so it agrees "
"with the chip (switcher source-of-truth invariant): " + expr.strip()
)
def test_chip_and_dropdown_share_source_of_truth(self):
"""The chip and the dropdown active-row must BOTH key on S.activeProfile.
This is the cross-file invariant that #3635 violated: a passing version
of this test means the switcher trigger and the menu it opens can never
again silently disagree about which profile is active.
"""
chip_body = _sync_topbar_body(_ui_js())
dd_body = _render_profile_dropdown_body(_panels_js())
chip_ok = "S.activeProfile" in chip_body and \
"(S.session&&S.session.profile)||S.activeProfile" not in chip_body
dd_ok = "S.activeProfile" in dd_body
assert chip_ok and dd_ok, (
"profile chip (ui.js syncTopbar) and dropdown active-row "
"(panels.js renderProfileDropdown) must share S.activeProfile as the "
"single source of truth for 'active profile' (#3635). "
f"chip_ok={chip_ok} dd_ok={dd_ok}"
)