文件
crab-input-rtos/com.sydxky.rtos.inputmethod/tests/client.test.mjs
T

47 行
5.8 KiB
JavaScript

import fs from 'node:fs'
import vm from 'node:vm'
import assert from 'node:assert/strict'
import * as engine from '../src/common/ime-engine.js'
import {normalizeTheme,themeStyles as stylesForTheme} from '../src/common/ime-theme.js'
const ux=fs.readFileSync(new URL('../src/keyboard/index.ux',import.meta.url),'utf8')
const source=ux.match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^\s*import .+$/gm,'').replace('export default','module.exports=')
let count=0
const closed=[],store=new Map(),timers=new Map();let nextTimer=1,storageFails=false
function check(ok,label){assert.ok(ok,label);count++}
function page(params={}){
const module={exports:{}}
vm.runInNewContext(source,{module,console,...engine,normalizeTheme,stylesForTheme,router:{close(p,r){closed.push(r)}},storage:{get:k=>store.get(k),set:(k,v)=>{if(storageFails)throw Error('storage full');store.set(k,v)},delete:k=>store.delete(k)},file:{readText:async({uri})=>fs.readFileSync(new URL('../src'+uri,import.meta.url),'utf8')},setTimeout:fn=>{const id=nextTimer++;timers.set(id,fn);return id},clearTimeout:id=>timers.delete(id),setInterval:fn=>{const id=nextTimer++;timers.set(id,fn);return id},clearInterval:id=>timers.delete(id)})
const p={...module.exports,...JSON.parse(JSON.stringify(module.exports.data)),...params};delete p.data;p.onInit();return p
}
let p=page();check(p.mode==='t9'&&!p.learning,'T9 large keys, learning opt-in')
p.chinese();check(p.keyRows.map(row=>row.map(key=>key.value).join('')).join('/')==='qwertyuiop/asdfghjkl/zxcvbnm','26-key pinyin uses standard QWERTY rows')
p.english();check(p.keyRows.map(row=>row.map(key=>key.value).join('')).join('/')==='qwertyuiop/asdfghjkl/zxcvbnm','English uses the same standard QWERTY order')
p.fourteen();check(p.mode==='14'&&p.keyRows.map(row=>row.map(key=>key.label).join(' ')).join('/')==='QW ER TY UI OP/AS DF GH JK L/ZX CV BN M','14-key layout matches public grouping')
for(const code of engine.code14Of('nihao'))p.press(code)
p.invalidate();await p.query(p.requestId)
check(p.compositionDisplay==='BN UI GH AS OP'&&p.bar.some(x=>x.word==='你好'),'14-key UX queries and displays paired composition')
p.english();check(p.mode==='14'&&p.composition===engine.code14Of('nihao'),'14-key mode switch preserves uncommitted composition')
p.rawInput();check(p.text===''&&p.composition===engine.code14Of('nihao'),'14-key tokens never commit as raw letters')
p.choose(p.bar.find(x=>x.word==='你好'));check(p.text==='你好'&&!p.composition,'14-key selection commits real dictionary word')
p=page()
p.press('6');p.press('4');p.press('4');p.press('2');p.press('6');p.invalidate();await p.query(p.requestId)
const hello=p.allCandidates.find(x=>x.word==='你好');check(hello,'full T9 phrase candidates')
p.choose({...hello});check(p.text==='你好'&&!p.composition,'candidate event payload may be reactive copy')
p.finish();check(closed.at(-1).accepted&&closed.at(-1).text==='你好','finish returns text')
p=page({initial:'unchanged'});p.insert('!');p.cancel();check(closed.at(-1).accepted===false,'cancel does not return edited text')
p=page({inputType:'password',initial:'😀secret'});p.toggleLearning();check(!p.learning&&!p.display.includes('secret'),'password masked and cannot learn');p.onHide();check(!p.text&&!p.initial&&!p.original,'password clears on hide')
p=page({initial:'你😀好'});p.cursor=2;p.backspace();check(p.text==='你好','UI Unicode deletion')
p.startDelete();check(p.repeatDelay!==null,'delete hold delay starts');p.onHide();check(p.repeatDelay===null&&p.repeatTimer===null,'hide stops repeat timers')
p=page();let resolve;p.dictionary={lookup:()=>new Promise(yes=>resolve=yes)};p.composition='64';const pending=p.query(p.requestId);p.onHide();resolve([{word:'你',consume:2,pinyin:'ni'}]);await pending;check(p.total===0,'late dictionary response cannot update hidden page')
p=page();p.mode='py';p.composition='shi';await p.query(p.requestId);p.expand();check(p.visibleCandidates.length===16&&p.candidateRows.length===4,'expanded single characters use 16 real candidate cells');p.nextPage();check(p.candidatePage===1&&p.visibleCandidates[0]===p.allCandidates[16],'next grid page continues without omissions');check(!ux.includes('item.pinyin}}')&&!ux.includes('class="reading"'),'expanded cells do not display pronunciation');
p=page({maxLength:2,initial:'你'});p.composition='nihao';p.allCandidates=[{word:'你好',consume:5,pinyin:'nihao'}];p.choose(p.allCandidates[0]);check(p.text==='你'&&p.composition==='nihao','candidate is not silently truncated')
p.finish();check(p.composition==='nihao','finish does not silently drop uncommitted composition')
p=page({inputType:'number'});p.insert('12x😀.3');check(p.text==='12.3','number field filters nonnumeric input')
p=page({inputType:'url'});p.insert('https://a b.com');check(p.text==='https://ab.com','URL field filters whitespace')
p.symbols();const symbols=p.keyRows.flat().map(x=>x.value);p.symbols();symbols.push(...p.keyRows.flat().map(x=>x.value));check(Array.from({length:94},(_,i)=>String.fromCharCode(i+33)).filter(x=>!/[a-z0-9]/i.test(x)).every(x=>symbols.includes(x)),'all printable ASCII punctuation supported')
p.english();p.english();check(p.keyRows.flat().some(x=>x.value==='Z'),'uppercase supported')
p=page({initial:'test'});p.clearText();check(p.text==='test'&&p.clearArmed,'destructive clear needs second tap');p.clearText();check(p.text==='','confirmed clear')
p.composition='shi';p.schedule();const owned=p.lookupTimer;p.onDestroy();check(!timers.has(owned)&&p.lookupTimer===null,'destroy releases owned lookup timer')
p=page();p.learning=true;p.composition='64';p.allCandidates=[{word:'你',pinyin:'ni',consume:2}];storageFails=true;p.choose(p.allCandidates[0]);check(p.text==='你'&&!p.composition&&!p.learning,'storage failure cannot duplicate committed candidate');p.toggleLearning();check(!p.learning,'storage failure leaves learning off');storageFails=false
console.log(JSON.stringify({suite:'ime-client',assertions:count,status:'passed'}))