"""邀请卡静态回归网:不变量"未展示 ⇒ 零占位、零拦截"。 背景(2026-09-01 bug):点 X 写入 permanent 后刷新,startInviteTimer 在 storage 守卫早退 ⇒ showInvite 永不执行 ⇒ invitePanel 引用恒为 null ⇒ hideInvite 提前返回 ⇒ .invite-panel 停留"display:block + opacity:0"的 隐形占位态,.invite-container(z-index:99998、无 pointer-events:none) 拦截页面右下角所有点击。修复 = 出厂即折叠(A)+ 容器点击穿透(C)。 """ import re from pathlib import Path STATIC = Path(__file__).resolve().parent.parent / "static" def _read(rel): return (STATIC / rel).read_text(encoding="utf-8") def _extract_function(src, name): """花括号计数提取函数源码(与 test_theme_static.py 同款)。""" start = src.index("function " + name + "(") i = src.index("{", 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("unbalanced braces in " + name) def test_invite_panel_ships_hidden(): """A:面板出厂即带 hide —— 任何绕过 showInvite 的路径都零占位。""" src = _read("widget.js") assert '
' in src, \ "invite 模板必须出厂带 hide(未展示 ⇒ 无布局占位)" def test_invite_container_click_through(): """C:容器 pointer-events:none —— 卡片未展示时整棵子树点击穿透。""" m = re.search(r"\.invite-container \{[^}]*\}", _read("widget.js")) assert m, "widget.js 缺 .invite-container 规则" assert "pointer-events:none" in m.group(0), \ ".invite-container 必须 pointer-events:none(防隐形拦截点击)" def test_show_invite_reflow_between_unhide_and_show(): """入场动画:remove(hide) 与 add(show) 之间必须强制回流。 出厂带 hide 后,若 remove 与 add 同帧执行,display:none→block 与 opacity 变化合并进同一次样式重算,0.3s 淡入退化为瞬现。 """ fn = _extract_function(_read("widget.js"), "showInvite") for token in ('remove("hide")', "offsetWidth", 'add("show")'): assert token in fn, f"showInvite 缺 {token}" assert fn.index('remove("hide")') < fn.index("offsetWidth") < fn.index('add("show")'), \ "showInvite 须 remove(hide) → offsetWidth 回流 → add(show) 顺序执行" def test_autoinvite_specialcase_removed_and_guard_kept(): """A 落地后 !autoInvite 预折叠特判删除;startInviteTimer 的配置守卫保留。""" src = _read("widget.js") assert "disabledInvitePanel" not in src, \ "!autoInvite 预折叠特判应删除(出厂 hide 统一覆盖该不变量)" fn = _extract_function(src, "startInviteTimer") assert "if (!autoInvite) return;" in fn, \ "startInviteTimer 必须保留 data-auto-invite=false 永不自动弹的守卫"