import fs from 'node:fs' import path from 'node:path' import {fileURLToPath} from 'node:url' import assert from 'node:assert/strict' import {syncInput,checkInput,watchInput} from '../scripts/install.mjs' const parent=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../.glyphix-work') fs.mkdirSync(parent,{recursive:true}) const scratch=fs.mkdtempSync(path.join(parent,'sync-test-')),source=path.join(scratch,'source'),host=path.join(scratch,'host') function write(root,name,body){const file=path.join(root,name);fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,body)} function fixture(root){ write(root,'src/manifest.json',JSON.stringify({versionName:'fixture',router:{pages:{}}})) for(const name of ['keyboard/index.ux','components/CrabInput/index.ux','assets/ime-icons/icon.png',...['engine','theme','lifecycle'].map(n=>'common/ime-'+n+'.js')])write(root,'src/'+name,'first') write(root,'src/assets/ime-dict/meta.json',JSON.stringify({shards:['2222']}));write(root,'src/assets/ime-dict/2222.json','{}') for(const name of ['crabKeyboard','jieba','remix-icon'])write(root,'third_party/'+name+'/LICENSE','MIT') write(root,'LICENSE','MIT');write(root,'NOTICE.md','third party notices') } let stop try{ fixture(source);write(host,'src/manifest.json',JSON.stringify({router:{pages:{}}}));write(host,'src/assets/host-data.txt','preserve') write(host,'src/assets/ime-dict/meta.json',JSON.stringify({shards:['1111']}));write(host,'src/assets/ime-dict/1111.json','old format') const first=syncInput(host,source);assert.equal(first.copied,13);assert.equal(first.removed,1);checkInput(host,source) const timestamp=fs.statSync(path.join(host,'src/components/CrabInput/index.ux')).mtimeMs assert.equal(syncInput(host,source).copied,0);assert.equal(fs.statSync(path.join(host,'src/components/CrabInput/index.ux')).mtimeMs,timestamp) write(host,'src/common/ime-theme.js','stale');assert.throws(()=>checkInput(host,source),/differs/);assert.equal(syncInput(host,source).copied,1) write(source,'src/common/ime-engine.js','second');assert.throws(()=>checkInput(host,source),/changed/) write(host,'input-component.json',JSON.stringify({schema:1,integration:'component'}));syncInput(host,source) assert.ok(!JSON.parse(fs.readFileSync(path.join(host,'src/manifest.json'),'utf8')).router.pages.keyboard) assert.ok(!fs.existsSync(path.join(host,'src/keyboard/index.ux'))) write(source,'src/keyboard/index.ux','unused compatibility route update');checkInput(host,source) const events=[];stop=watchInput(host,source,event=>events.push(event)) const initial=events.length write(source,'src/components/CrabInput/index.ux','component update') write(source,'src/common/ime-theme.js','theme update') write(source,'src/assets/ime-icons/new-icon.png','new icon') write(source,'src/common/ime-layout.js','new related module') write(source,'src/assets/ime-dict/meta.json',JSON.stringify({shards:['3333'],association_shards:['a0f'],completion_shards:['c01']}));write(source,'src/assets/ime-dict/3333.json','{"changed":true}');write(source,'src/assets/ime-dict/a0f.json','{}');write(source,'src/assets/ime-dict/c01.json','{}') const deadline=Date.now()+8000 while(Date.now()setTimeout(resolve,50));try{checkInput(host,source);break}catch{}} const current=checkInput(host,source) assert.ok(fs.existsSync(path.join(host,'src/assets/ime-dict/a0f.json'))&&fs.existsSync(path.join(host,'src/assets/ime-dict/c01.json')),'prediction shards synchronize') assert.notEqual(current.sourceHash,first.sourceHash);assert.ok(events.length>initial) assert.equal(fs.readFileSync(path.join(host,'src/common/ime-layout.js'),'utf8'),'new related module') assert.ok(!fs.existsSync(path.join(host,'src/assets/ime-dict/2222.json'))) assert.equal(fs.readFileSync(path.join(host,'src/assets/host-data.txt'),'utf8'),'preserve') const receipt=JSON.parse(fs.readFileSync(path.join(host,'src/assets/crab-input-source.json'),'utf8')) receipt.files['../outside']={};write(host,'src/assets/crab-input-source.json',JSON.stringify(receipt)) assert.throws(()=>syncInput(host,source),/Invalid managed/) assert.ok(!fs.existsSync(path.join(host,'src/assets/crab-input-source.json')),'failed sync invalidates receipt') console.log(JSON.stringify({suite:'input-sync',status:'passed',watchEvents:events.length,checks:['incremental','tamper rejection','source drift rejection','watch component engine theme icon dictionary update','obsolete shard removal','host isolation','invalid receipt rejection']})) }finally{ if(stop)stop() if(!scratch.startsWith(parent+path.sep+'sync-test-'))throw Error('Unexpected test directory') fs.rmSync(scratch,{recursive:true,force:true}) }