chore: import original project baseline

Import the pre-repair source tree as the history baseline.
Runtime data (data/), virtualenvs, bytecode caches and logs are
gitignored so local secrets and user state stay out of the repo.
This commit is contained in:
2026-09-17 16:40:01 +08:00
commit a7412824e0
124 changed files with 26747 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
"""公式渲染端到端验证:真实会话消息 → 真实前端管线 → DOM 断言
运行: PYTHONIOENCODING=utf-8 QT_QPA_PLATFORM=offscreen python tests/verify_math_render.py
铁律:不污染真实 DB —— 先把 data/chat_history.db 复制到临时文件,再指向副本。
"""
import os
import sys
import json
import time
import shutil
import tempfile
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
os.environ.setdefault("HAOCODE_RENDER", "software")
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu"
_REAL_DB = os.path.join(os.path.dirname(__file__), "..", "data", "chat_history.db")
_TMP_DB = os.path.join(tempfile.gettempdir(), f"haocode_verify_math_{os.getpid()}.db")
shutil.copyfile(_REAL_DB, _TMP_DB)
import core.db_manager as _dbm # noqa: E402
_dbm._DEFAULT_DB = _TMP_DB
from PyQt6.QtWidgets import QApplication # noqa: E402
from PyQt6.QtCore import QTimer # noqa: E402
from ui.views.main_window import MainWindow # noqa: E402
app = QApplication(sys.argv)
results = []
win = {"w": None, "sid": None}
JS_QUERY = r"""(function(){
var scope = document.getElementById('chat-container') || document.body;
var q = function(s){ return scope.querySelectorAll(s).length; };
// 只取助手消息(user 消息按设计是纯文本,本来就不渲染公式)
var asst = scope.querySelectorAll('.message-wrapper.assistant');
var asstScope = asst.length ? asst[asst.length - 1] : scope;
// 可见文本:剔除 katex-mathmlCSS 视觉隐藏的 MathML 层,内部保留原始 tex 属正常)
var clone = asstScope.cloneNode(true);
clone.querySelectorAll('.katex-mathml').forEach(function(e){ e.parentNode.removeChild(e); });
var visTxt = clone.textContent || '';
var codeTxt = '';
asstScope.querySelectorAll('pre, code').forEach(function(e){ codeTxt += e.textContent + '\n'; });
return {
katex: q('.katex'),
display: q('.katex-display'),
mathml: q('.katex-mathml'),
vislayer: q('.katex-html'),
leakedPlaceholder: visTxt.indexOf('@@K') !== -1,
rawBackslash: visTxt.indexOf('\\operatorname') !== -1,
bareBracketFormula: visTxt.indexOf('P_4=\\operatorname') !== -1,
codeHasDollar: codeTxt.indexOf('$x + y$') !== -1,
codeHasArr: codeTxt.indexOf('arr[0]') !== -1
};
})()"""
def check(name, cond, extra=""):
print((" PASS " if cond else " FAIL ") + name + ((" | " + str(extra)) if extra else ""))
results.append(bool(cond))
def run_js(js, timeout_s=15):
r = {"v": None, "d": False}
def cb(val):
if isinstance(val, str):
try:
val = json.loads(val)
except Exception:
pass
r["v"] = val
r["d"] = True
b = win["w"].browser
if hasattr(b, "execute_js_async"):
b.execute_js_async(js, cb)
else:
b.page().runJavaScript(js, cb)
t0 = time.time()
while not r["d"] and time.time() - t0 < timeout_s:
app.processEvents()
time.sleep(0.05)
return r["v"]
def boot():
w = MainWindow()
win["w"] = w
sid = None
for s in w.db.get_all_sessions():
if s.get("title") == "公式渲染验收":
sid = s["id"]
break
if not sid:
print("FAIL 未找到「公式渲染验收」会话(先跑 tests/inject_math_demo.py")
app.quit()
return
win["sid"] = sid
print(f"会话: {sid}")
w.load_messages_to_web(sid)
QTimer.singleShot(9000, phase_check)
def phase_check():
for _ in range(40):
if run_js("window.jsReady === true ? 1 : 0", timeout_s=3) == 1:
break
time.sleep(0.5)
r = run_js(JS_QUERY)
if not isinstance(r, dict):
print(f"FAIL DOM 查询失败: {r!r}")
app.quit()
return
print("\n DOM 统计: " + json.dumps(r, ensure_ascii=False))
check("KaTeX 渲染出公式(.katex > 0", r["katex"] > 0, f"katex={r['katex']}")
check("块公式 7 个(.katex-display == 7", r["display"] == 7, f"display={r['display']}")
check("可视层存在(.katex-html > 0", r["vislayer"] > 0, f"vislayer={r['vislayer']}")
check("无占位符泄漏", not r["leakedPlaceholder"])
check("可见文本无原始 tex 残留", not r["rawBackslash"])
check("无裸括号公式残留", not r["bareBracketFormula"])
check("代码块内 $x + y$ 保持原样", r["codeHasDollar"])
check("代码块内 arr[0] 保持原样", r["codeHasArr"])
try:
shot_path = os.path.join(os.path.dirname(__file__), "_tmp_math_render.png")
win["w"].browser.grab().save(shot_path)
print(f" 截图: {shot_path}")
except Exception as e:
print(f" 截图失败: {e}")
print(f"\n===== {'ALL PASS' if all(results) else 'HAS FAILURES'}: {sum(results)}/{len(results)} =====")
try:
win["w"].close()
except Exception:
pass
app.quit()
QTimer.singleShot(400, boot)
QTimer.singleShot(120000, app.quit)
app.exec()
try:
os.remove(_TMP_DB)
except Exception:
pass
sys.exit(0 if all(results) else 1)