文件

60 行
5.2 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import assert from 'node:assert/strict'
import {fileURLToPath} from 'node:url'
import {createDictionary,digitsOf,code14Of,KEY14_GROUPS,display14,candidatePages,edit,erase,preview,learn,sanitizePreferences,chars} from '../src/common/ime-engine.js'
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../src')
let count=0
function check(ok,label){assert.ok(ok,label);count++}
const dictionary=createDictionary(async uri=>fs.readFileSync(root+uri,'utf8'))
const meta=JSON.parse(fs.readFileSync(root+'/assets/ime-dict/meta.json','utf8'))
check(meta.keys>10000&&meta.candidates>50000,'full upstream lexicon present')
for(const [input,expected] of [['ni','你'],['nihao','你好'],['shijie','世界'],['zhongguo','中国'],['shurufa','输入法'],['beijing','北京']]){
const alpha=await dictionary.lookup(input,false,{})
check(alpha.some(x=>x.word===expected&&x.consume===input.length),'full pinyin '+input)
const t9=await dictionary.lookup(digitsOf(input),true,{})
check(t9.some(x=>x.word===expected&&x.consume===input.length),'T9 '+input)
}
const full=await dictionary.lookup('nihaoshijie',false,{})
check(full.some(x=>x.word==='你好'&&x.consume===5),'continuous input consumes matched prefix')
check(KEY14_GROUPS.length===14&&KEY14_GROUPS.join('').split('').sort().join('')==='abcdefghijklmnopqrstuvwxyz','14 groups cover each QWERTY letter once')
check(code14Of('nihao')==='c3754'&&code14Of('a!')==='','14-key stable token mapping rejects nonletters')
for(const [input,expected]of[['nihao','你好'],['shijie','世界'],['zhongguo','中国'],['shurufa','输入法'],['beijing','北京'],['lv','绿']]){
const result=await dictionary.lookup(code14Of(input),'14',{})
check(result.some(x=>x.word===expected&&x.consume===input.length),'14-key disambiguation '+input)
}
const pairSentence=await dictionary.lookup(code14Of('nihaoshijie'),'14',{})
check(pairSentence.some(x=>x.word==='你好'&&x.consume===5),'14-key continuous input consumes only selected prefix')
const pairMany=await dictionary.lookup(code14Of('shi'),'14',{})
const fullShi=await dictionary.lookup('shi',false,{})
check(fullShi.filter(x=>x.pinyin==='shi').every(x=>pairMany.some(y=>x.word===y.word&&x.consume===y.consume)),'14-key retains every shi candidate')
const pairLast=pairMany.at(-1),pairInput=code14Of('shi'),pairPreferences=learn({},'k14:'+pairInput,pairLast)
check((await dictionary.lookup(pairInput,'14',pairPreferences))[0].word===pairLast.word,'14-key learned candidate order uses separate scheme namespace')
check((await dictionary.lookup(pairInput,'14',{[pairInput]:[{word:pairLast.word,consume:pairLast.consume}]}))[0].word===(await dictionary.lookup(pairInput,'14',{}))[0].word,'plain pinyin preferences cannot leak into 14-key ranking')
check((await dictionary.lookup('hello!','14',{})).length===0,'14-key rejects invalid token stream')
check(display14('c3754')==='BN UI GH AS OP'&&display14('c3754'.repeat(6)).length<24,'14-key preview displays bounded paired-letter groups')
const many=await dictionary.lookup('shi',false,{})
const grid=candidatePages(many),flatten=pages=>pages.flatMap(rows=>rows.flatMap(row=>row.items.map(entry=>entry.item)))
check(grid[0].length===4&&flatten([grid[0]]).length===16,'single characters form a four by four page')
check(flatten(grid).every((item,index)=>item===many[index]),'grid retains all candidates in their exact original order')
const phrases=[{word:'你好'},{word:'完整词组'},{word:'很长的词组必须能够完整显示而不能被截断'},{word:'😀'}]
const phraseGrid=candidatePages(phrases)
check(flatten(phraseGrid).length===phrases.length&&phraseGrid[0][0].items.length===2,'phrases use wider cells without dropping results')
check(phraseGrid.every(rows=>rows.reduce((sum,row)=>sum+row.units,0)<=4),'long phrases use row units within the page budget')
const original=JSON.parse(fs.readFileSync(root+'/assets/ime-dict/s'+digitsOf('shi')+'.json','utf8')).entries.shi
check(original.every(word=>many.some(x=>x.word===word&&x.consume===3)),'every original shi candidate remains accessible')
check(dictionary.cacheSize<=4,'bounded shard cache')
check((await dictionary.lookup('not valid!',false,{})).length===0,'reject invalid composition')
const candidate=many.at(-1),preferences=learn({},'shi',candidate)
check((await dictionary.lookup('shi',false,preferences))[0].word===candidate.word,'real selected candidate learns ordering')
const injected=await dictionary.lookup('shi',false,{shi:[{word:'injected',consume:3}]})
check(injected.every(x=>x.word!=='injected'),'preference cannot inject nonexistent candidate')
check(Object.keys(sanitizePreferences(JSON.parse('{"bad!":[],"shi":[{"word":7}]}'))).length===1,'sanitize persisted preference shape')
check(edit('你好',1,'😀',3).text==='你😀好','Unicode insertion at cursor')
check(erase('你😀好',2).text==='你好','Unicode backspace preserves surrogate pairs')
check(edit('你好',2,'世界',3).limited,'maximum length enforced')
check(!preview('my-secret',5,true).includes('secret'),'secret preview masked')
check(chars('😀').length===1,'Unicode length counts codepoints')
dictionary.clear();check(dictionary.cacheSize===0,'cache release')
console.log(JSON.stringify({suite:'ime-engine',assertions:count,keys:meta.keys,candidates:meta.candidates,status:'passed'}))