修:draw-ui / oil-motion 原被当子模块指针收录 ⇒ 改为正常文件入库(两份内容原先对别人是空的)

一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。

二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。

三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
This commit is contained in:
admin committed 2026-10-08 22:29:52 +08:00
1 parent e03465c398
commit 237a09a5b0
161 files changed
+19429 -2

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
"""从真实CLI入口验证尺寸与截图前提,不启动浏览器。"""
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Thread
from PIL import Image
SCRIPTS = Path(__file__).resolve().parents[1] / 'scripts'
class CalibrationTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
for name in ('reference', 'candidate'):
Image.new('RGB', (32, 64), 'white').save(self.root / (name+'.png'))
def tearDown(self):
self.temp.cleanup()
def compare(self, *extra):
return subprocess.run([sys.executable, str(SCRIPTS/'compare_mockup.py'), '--reference', str(self.root/'reference.png'), '--candidate', str(self.root/'candidate.png'), '--out-dir', str(self.root/'comparison'), *extra], capture_output=True, text=True)
def test_equal_and_invalid_clip(self):
self.assertEqual(self.compare().returncode, 0)
metrics=json.loads((self.root/'comparison/comparison-metrics.json').read_text())
self.assertFalse(metrics['resized'])
self.assertEqual(metrics['regions'][0]['mean_abs_diff'], 0)
for clip in ['bad:0,0,33,2', '../escape:0,0,1,1', 'full:0,0,1,1', 'bad:0,0,-1,1']:
with self.subTest(clip=clip):
self.assertNotEqual(self.compare('--clip', clip).returncode, 0)
def test_width_mismatch(self):
Image.new('RGB', (33,64), 'white').save(self.root/'candidate.png')
self.assertNotEqual(self.compare('--allow-height-difference').returncode, 0)
self.assertFalse((self.root/'comparison').exists())
def test_short_page_preserves_missing_tail(self):
Image.new('RGB', (32,50), 'white').save(self.root/'candidate.png')
self.assertNotEqual(self.compare().returncode, 0)
self.assertEqual(self.compare('--allow-height-difference','--clip','tail:0,50,32,14').returncode, 0)
metrics=json.loads((self.root/'comparison/comparison-metrics.json').read_text())
self.assertEqual(metrics['height_delta'], -14)
self.assertFalse(metrics['geometry_match'])
with Image.open(metrics['unmatched_tails']['reference']) as tail:
self.assertEqual(tail.size, (32,14))
self.assertIn('status',metrics['regions'][-1])
def test_capture_contract(self):
expected={'url':'http://127.0.0.1:4187/','title':'测试页','viewport':{'width':32,'height':64},'dpr':1,'full_page':False,'screenshot_scale':'css'}
capture={**expected,'fonts_ready':True,'images_ready':True,'document_height':64,'sections':[{'name':'hero','y':2}]}
asset={'src':'http://127.0.0.1:4187/logo-v2.png','natural_width':2048}
capture['assets']={'logo':asset}
manifest={'expected':expected,'capture_metadata':'capture.json','reference':'reference.png','candidate':'candidate.png','section_positions':{'hero':1},'expected_assets':{'logo':asset}}
(self.root/'run.json').write_text(json.dumps(manifest))
variants=[({},True),({'title':'其他项目'},False),({'url':'http://127.0.0.1:9999/'},False),({'fonts_ready':False},False),({'images_ready':False},False),({'dpr':2},False),({'viewport':{'width':31,'height':64}},False),({'screenshot_scale':'device'},False),({'assets':{}},False),({'assets':{'logo':{**asset,'src':'http://127.0.0.1:4187/old.png'}}},False),({'assets':{'logo':{**asset,'natural_width':100}}},False)]
for index,(changes,valid) in enumerate(variants):
with self.subTest(changes=changes):
(self.root/'capture.json').write_text(json.dumps({**capture,**changes}))
out=self.root/f'verify-{index}'
result=subprocess.run([sys.executable,str(SCRIPTS/'verify_capture.py'),'--manifest',str(self.root/'run.json'),'--out-dir',str(out)],capture_output=True,text=True)
self.assertEqual(result.returncode==0,valid,result.stderr)
if valid:
self.assertEqual(json.loads((out/'verification.json').read_text())['section_positions'][0]['delta_y'],1)
else:
self.assertFalse(out.exists())
def test_preflight_identity(self):
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write('<title>测试页</title>'.encode())
def log_message(self,*args):
pass
server=HTTPServer(('127.0.0.1',0),Handler)
thread=Thread(target=server.serve_forever,daemon=True)
thread.start()
try:
for title,valid in [('测试页',True),('其他项目',False)]:
(self.root/'run.json').write_text(json.dumps({'expected':{'url':f'http://127.0.0.1:{server.server_port}/','title':title}}))
result=subprocess.run([sys.executable,str(SCRIPTS/'verify_capture.py'),'--manifest',str(self.root/'run.json'),'--preflight'],capture_output=True,text=True)
self.assertEqual(result.returncode==0,valid,result.stderr)
finally:
server.shutdown()
server.server_close()
thread.join()
if __name__=='__main__':
unittest.main()