fix(输入法): 修复光标间隙并扩大26键及明确大小写切换
这个提交包含在:
1 父节点
b95e7caae7
当前提交
100757f684
35 个文件已更改
+675
-243
没有与搜索条件相匹配的文件
@@ -0,0 +1,107 @@
|
||||
"""Native caret/QWERTY fixture using an owned emulator without Inspector."""
|
||||
import argparse,hashlib,importlib.util,json,math,pathlib,shutil,subprocess,time
|
||||
APP=pathlib.Path(__file__).resolve().parents[1]
|
||||
spec=importlib.util.spec_from_file_location('native',APP/'tests/native.py')
|
||||
native=importlib.util.module_from_spec(spec);spec.loader.exec_module(native)
|
||||
emu=native.emu;emu.APPS=['inputmethod']
|
||||
|
||||
PROBE='''<template><div><p>输入组件原生验收</p></div></template>
|
||||
<script>
|
||||
import router from '@system.router'
|
||||
import tc from '@system.test'
|
||||
import {activeInputs} from '../common/ime-lifecycle.js'
|
||||
import {themeStyles} from '../common/ime-theme.js'
|
||||
export default {started:false,onShow(){if(this.started)return;this.started=true;setTimeout(()=>{
|
||||
tc.testcase('preview-qwerty',async()=>{
|
||||
router.push({uri:'/keyboard',params:{initial:'不'}});await tc.wait(.5);
|
||||
var page=router.getPages().slice(-1)[0],p=activeInputs().filter(input=>input.$page===page)[0];
|
||||
function check(ok,label){if(!ok)throw new Error(label);console.log('PROBE_CHECK '+label)}
|
||||
async function rect(id){return await p.$element(id).getBoundingClientRect()}
|
||||
var cases=[['chinese','不',1,false],['phrase','你好',2,false],['ascii','watch',5,false],['mixed','ab你😀好',4,false],['start','你好',0,false],['middle','你好',1,false],['long','长😀'.repeat(30),30,false],['secret','私密😀',2,true]];
|
||||
for(var theme of ['bluewhite','blackgreen']){
|
||||
p.invalidate();p.inputTheme=theme;p.themeStyles=themeStyles(theme);
|
||||
for(var item of cases){p.invalidate();p.mode='en';p.upper=false;p.secret=item[3];p.revealSecret=false;p.text=item[1];p.cursor=item[2];p.composition='';p.renderKeys();p.updateDisplay();await tc.wait(.15);
|
||||
console.log('PROBE_CAPTURE '+JSON.stringify({name:theme+'-'+item[0],theme:theme,kind:'caret',before:await rect('ime-preview-before'),caret:await rect('ime-caret'),after:await rect('ime-preview-after'),preview:await rect('ime-preview')}));await tc.wait(1.1);}
|
||||
for(var mode of ['py','en'])for(var upper of [false,true]){
|
||||
p.invalidate();p.secret=false;p.text='';p.cursor=0;p.composition='';p.mode=mode;p.upper=upper;p.renderKeys();p.updateDisplay();await tc.wait(.15);
|
||||
var alphabet='qwertyuiopasdfghjklzxcvbnm',keys=[];
|
||||
for(var letter of alphabet)keys.push({key:letter,rect:await rect('ime-key-'+letter)});
|
||||
console.log('PROBE_CAPTURE '+JSON.stringify({name:theme+'-'+mode+'-'+(upper?'upper':'lower'),theme:theme,kind:'qwerty',keys:keys}));await tc.wait(1.1);}
|
||||
}
|
||||
p.invalidate();p.secret=false;p.text='';p.cursor=0;p.composition='';p.mode='en';p.upper=false;p.renderKeys();p.updateDisplay();await tc.wait(.2);
|
||||
await tc.getById('ime-key-q').click();await tc.getById('ime-english').click();await tc.wait(.15);check(p.upper,'uppercase enabled');await tc.getById('ime-key-q').click();await tc.getById('ime-english').click();await tc.wait(.15);check(!p.upper,'lowercase restored');await tc.getById('ime-key-q').click();check(p.text==='qQq','actual native keys commit qQq');
|
||||
console.log('PROBE_DONE');});tc.run('preview-qwerty');},300)}}
|
||||
</script>'''
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--work',type=pathlib.Path,required=True);parser.add_argument('--output',type=pathlib.Path,required=True);parser.add_argument('--profiles',nargs='+',default=emu.PROFILES)
|
||||
args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=True);fixture=args.work/'fixture';fixture.mkdir(parents=True,exist_ok=True)
|
||||
shutil.copytree(APP/'src',fixture/'src',dirs_exist_ok=True);shutil.copy2(APP/'glyphix.config.js',fixture/'glyphix.config.js')
|
||||
metadata=json.loads((fixture/'src/manifest.json').read_text(encoding='utf-8'));metadata['router']['entry']='native-probe';metadata['router']['pages']['native-probe']={'component':'index'}
|
||||
(fixture/'src/manifest.json').write_text(json.dumps(metadata,ensure_ascii=False),encoding='utf-8')
|
||||
(fixture/'src/native-probe').mkdir(exist_ok=True);(fixture/'src/native-probe/index.ux').write_text(PROBE,encoding='utf-8')
|
||||
source_hash=hashlib.sha256((APP/'src/components/CrabInput/index.ux').read_bytes()).hexdigest()
|
||||
assert (fixture/'src/components/CrabInput/index.ux').read_bytes()==(APP/'src/components/CrabInput/index.ux').read_bytes()
|
||||
shell=shutil.which('pwsh')or'powershell';stable=pathlib.Path(r'C:\Users\herry\AppData\Local\Programs\PowerShell\7\pwsh.exe')
|
||||
if stable.exists():shell=str(stable)
|
||||
with (args.output/'fixture-build.log').open('w',encoding='utf-8')as log:
|
||||
result=subprocess.run([shell,'-NoProfile','-File',str(APP.parent/'build-rtos-game.ps1'),'-ProjectRoot',str(fixture)],stdout=log,stderr=subprocess.STDOUT)
|
||||
assert result.returncode==0,'See fixture-build.log'
|
||||
report={'method':'owned native console; production keyboard copied byte-for-byte into an auto-running fixture','production_keyboard_sha256':source_hash,'production_keyboard_path':'src/components/CrabInput/index.ux','production_route_sha256':hashlib.sha256((APP/'src/keyboard/index.ux').read_bytes()).hexdigest(),'inspector_used':False,'cases':[],'profiles':[],'passed':False}
|
||||
def rectangle(value):
|
||||
if isinstance(value,dict)and'rect'in value:value=value['rect']
|
||||
return [value.get('left',value.get('x',0)),value.get('top',value.get('y',0)),value.get('width',value.get('w',0)),value.get('height',value.get('h',0))]
|
||||
for profile in args.profiles:
|
||||
image,artifacts=emu.prepare(profile,args.work)
|
||||
device='generic-watch-'+('466x466'if profile.startswith('round')else'390x450');package=metadata['package']
|
||||
binary=fixture/'.glyphix-work'/('dist/'+device+'-quickapp/'+package+'/bundle.pkg'if profile.endswith('-quickapp')else'image/'+device+'/apps/'+package+'.pkg')
|
||||
shutil.copy2(binary,image/'apps'/(package+'.pkg'))
|
||||
command=[str(emu.ROOT/'temp/glyphix-v0.7.2-windows-x64/bin/glyphix-emu.exe'),'--config','pkgs.db','--target',package]
|
||||
if profile.startswith('round'):command+=['--width','624','--height','624','--dpi','326','--font-size','32','--round','--surface','surface/surface-466x466.json']
|
||||
else:command+=['--width','390','--height','450','--dpi','321','--font-size','29']
|
||||
command+=['--memory-profile','8192','--font-map','font-faces','--font-family','sans-serif']
|
||||
log_path=args.output/(profile+'-console.log');seen=set();finished=False;captures=0
|
||||
with log_path.open('w',encoding='utf-8')as log:
|
||||
process=subprocess.Popen(command,cwd=image,stdin=subprocess.PIPE,stdout=log,stderr=subprocess.STDOUT,creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
try:
|
||||
deadline=time.monotonic()+100
|
||||
while time.monotonic()<deadline and process.poll()is None:
|
||||
lines=log_path.read_text(encoding='utf-8',errors='replace').splitlines()
|
||||
for line in lines:
|
||||
if 'PROBE_CAPTURE 'in line:
|
||||
try:state=json.JSONDecoder().raw_decode(line.split('PROBE_CAPTURE ',1)[1].lstrip())[0]
|
||||
except json.JSONDecodeError:continue
|
||||
if state['name']in seen:continue
|
||||
seen.add(state['name']);state['profile']=profile
|
||||
# The vendor console accepts relative screenshot names only.
|
||||
filename=profile+'-'+state['name']+'.png';process.stdin.write(('screenshot '+filename+'\n').encode());process.stdin.flush()
|
||||
if state['kind']=='caret':
|
||||
before,caret,after,preview=[rectangle(state[key])for key in ['before','caret','after','preview']]
|
||||
state.update(left_gap_px=caret[0]-before[0]-before[2],right_gap_px=after[0]-caret[0]-caret[2])
|
||||
state['passed']=abs(state['left_gap_px'])<=1 and abs(state['right_gap_px'])<=1 and 0<caret[2]<=2 and preview[0]<=caret[0]and caret[0]+caret[2]<=preview[0]+preview[2]
|
||||
else:
|
||||
width=466 if profile.startswith('round')else 390;height=466 if profile.startswith('round')else 450;failures=[]
|
||||
for key in state['keys']:
|
||||
x,y,w,h=rectangle(key['rect'])
|
||||
if w<=0 or h<=0 or x<0 or y<0 or x+w>width or y+h>height:failures.append(key['key'])
|
||||
if profile.startswith('round')and max(math.hypot(tx-233,ty-233)for tx in [x,x+w]for ty in [y,y+h])>231:failures.append(key['key'])
|
||||
state.update(failures=failures,passed=len(state['keys'])==26 and not failures)
|
||||
captures+=1;report['cases'].append(state);native.save(args.output/'caret-results.json',report);print(json.dumps(state,ensure_ascii=False),flush=True)
|
||||
if 'PROBE_DONE'in line:finished=True
|
||||
if finished:break
|
||||
time.sleep(.05)
|
||||
assert finished and captures==24,'Native fixture did not finish; see '+str(log_path)
|
||||
assert 'terminated:'not in log_path.read_text(encoding='utf-8',errors='replace'),'Native assertion failed'
|
||||
for state in report['cases']:
|
||||
if state['profile']!=profile:continue
|
||||
filename=profile+'-'+state['name']+'.png';snapshot=image/filename
|
||||
assert snapshot.is_file() and snapshot.read_bytes().startswith(b'\x89PNG\r\n\x1a\n'),'Screenshot missing: '+filename
|
||||
shutil.copy2(snapshot,args.output/filename);state['screenshot']=filename
|
||||
report['profiles'].append({'profile':profile,'native_case_switch_passed':True,'captures':captures,'fixture_binary_sha256':hashlib.sha256(binary.read_bytes()).hexdigest()})
|
||||
finally:
|
||||
if process.poll()is None:process.terminate()
|
||||
process.wait(timeout=10);process.stdin.close()
|
||||
report['passed']=all(row['passed']for row in report['cases']);native.save(args.output/'caret-results.json',report)
|
||||
assert report['passed'],'See caret-results.json'
|
||||
|
||||
if __name__=='__main__':main()
|
||||
在新工单中引用
屏蔽一个用户