# -*- coding: utf-8 -*- """离屏验证节点菜单:IPv6-only 节点的标注与置灰(真实 PyQt5,不出窗口)。 需要 Windows venv 的真实 PyQt5: ..\\.venv\\Scripts\\python.exe test\\check_node_menu.py 读取 test/nodes.json 作为节点数据,只构建菜单不进事件循环。 """ import os, sys os.environ["QT_QPA_PLATFORM"] = "offscreen" from pathlib import Path LITE = Path(__file__).resolve().parent.parent # 脚本所在目录(test/)会被解释器自动放到 sys.path 首位,那里有 PyQt5 桩, # 会顶掉真实 PyQt5 —— 必须移除,改用本程序自己的 venv sys.path = [str(LITE)] + [p for p in sys.path if Path(p).resolve() != Path(__file__).resolve().parent] sys.stdout.reconfigure(encoding="utf-8") import proxy_lite as pp from PyQt5.QtWidgets import QApplication # 构造窗口时不写用户数据目录(避免污染),构造后再指向真实 nodes.json pp._user_init_local = lambda: None app = QApplication(sys.argv) w = pp.MainWindow() w.timer.stop() # 停掉状态轮询,纯静态检查 pp._USER_DIR = LITE / "test" # 真实 nodes.json 所在目录 nodes = pp._reload_nodes() print(f"本机有全局 IPv6: {pp._local_has_ipv6()}") print(f"节点数: {len(nodes)}") menu = w._build_node_menu(nodes) print("\n菜单项(enabled / 文案 / tooltip):") for act in menu.actions(): print(f" {str(act.isEnabled()):5} | {act.text()}") if act.toolTip(): print(f" └ {act.toolTip()}") # 点击被禁用的项不应切换节点 before = pp._selected_index() for act in menu.actions(): if not act.isEnabled(): act.trigger() print(f"\n点击禁用项后节点下标: {before} -> {pp._selected_index()} (应不变)") # ── 上次选中的节点已不可用:启动时自动回退到第一个可达节点 ────────── print("\n启动校验(_verify_selected_node):") bad = [i for i, n in enumerate(nodes) if not pp._node_reachable(n)] if not bad: print(" 本机所有节点均可达,无法验证回退(本机有 IPv6?)") else: import json _bak = (pp._USER_DIR / "settings.json").read_bytes() if (pp._USER_DIR / "settings.json").is_file() else None bad_idx = bad[0] pp._SELECTED_NODE = bad_idx # 模拟上次选了不可用节点 switched = pp._verify_selected_node() expect = pp._first_reachable_index(nodes) ok = switched and pp._selected_index() == expect print(f" 上次选中 {nodes[bad_idx].get('domain')}(下标 {bad_idx}) 不可用 -> " f"回退到下标 {pp._selected_index()} ({nodes[pp._selected_index()].get('domain')}) " f"{'PASS' if ok else 'FAIL'}") saved = json.loads((pp._USER_DIR / "settings.json").read_text("utf-8")).get("current_node") if (pp._USER_DIR / "settings.json").is_file() else "" ok2 = saved == str(nodes[expect].get("domain") or "") print(f" 回退结果已写入设置 current_node={saved!r} {'PASS' if ok2 else 'FAIL'}") # 再校验一次:已经是可达节点,不应再切换 ok3 = pp._verify_selected_node() is False print(f" 再次校验不重复切换 {'PASS' if ok3 else 'FAIL'}") if _bak is None: (pp._USER_DIR / "settings.json").unlink(missing_ok=True) else: (pp._USER_DIR / "settings.json").write_bytes(_bak) print("OK")