const fs=require('fs'),path=require('path'),vm=require('vm') const ROOT=path.resolve(__dirname,'../..') function createHarness(){ const storage=new Map([['token','synthetic-token'],['user_id',42],['userInfo',{id:42}]]),files=new Map(),calls=[],draws=[] const flags={},state={profile:{id:42,nickname:'合成小林',photo:'avatar-not-for-card',photos:['synthetic-photo'],city:'合成城市',degree:'本科',industry:'设计',introduction:'喜欢阅读和认真生活。'.repeat(20),ideal_mate:'真诚沟通'},overview:{total:12,verified:7,can_apply:true},abort:0,exportID:0} const copy=value=>value&&typeof value==='object'?JSON.parse(JSON.stringify(value)):value const later=fn=>queueMicrotask(fn) const ctx={fillRect(){},beginPath(){},moveTo(){},lineTo(){},arcTo(){},closePath(){},fill(){},save(){},restore(){},clip(){},arc(){},stroke(){},drawImage(image){draws.push(['image',image.src])},fillText(text){draws.push(['text',text])},measureText(text){return {width:Array.from(text).length*25}}} const canvases={} function canvas(id){if(!canvases[id])canvases[id]={id,width:750,height:1120,getContext(){return ctx},createImage(){const image={width:500,height:500};Object.defineProperty(image,'src',{set(value){this.value=value;if(!flags.imageHang)later(()=>(flags.imageFail||flags.failImageSrc===value||(flags.packageRelative&&value.startsWith('pages/')))?this.onerror():this.onload())},get(){return this.value}});return image}};return canvases[id]} const fileSystem={ access({path,success,fail}){later(()=>files.has(path)?success():fail({}))}, writeFile({filePath,data,success,fail}){later(()=>{if(flags.writeFail)return fail({});files.set(filePath,data);success()})}, copyFile({srcPath,destPath,success,fail}){later(()=>{if(flags.copyFail||!files.has(srcPath))return fail({});files.set(destPath,files.get(srcPath));success()})}, unlink({filePath}){files.delete(filePath)} } const wx={env:{USER_DATA_PATH:'/synthetic-user'},getStorageSync(k){return copy(storage.get(k))},setStorageSync(k,v){if(flags.storageFail)throw Error('synthetic');storage.set(k,copy(v))},removeStorageSync(k){storage.delete(k)},getFileSystemManager(){return fileSystem}, request(options){calls.push(options);const task={abort(){state.abort++}};if(flags.requestHang)return task;later(()=>{ if(flags.requestFail)return options.fail({}) if(flags.response)return options.success(flags.response) if(flags.route){const result=flags.route(options);if(result==='hang')return;if(result)return options.success(result)} let data if(options.url.endsWith('/profile'))data=state.profile else if(options.url.endsWith('/overview'))data=state.overview else if(options.url.endsWith('/invitees'))data={page:options.data.page,has_more:options.data.page===1,data:options.data.page===1?[{id:71,nickname:'合成用户',verified:true}]:[]} else if(options.url.endsWith('/code'))data={mime:'image/png',image:'c3ludGhldGljLXFy'} else throw Error('Unexpected request in test') options.success({statusCode:200,data:{code:0,data:copy(data)}}) });return task}, createSelectorQuery(){let id;const q={in(){return q},select(value){id=value;return q},fields(){return q},exec(cb){if(!flags.canvasHang)later(()=>cb(flags.canvasMissing?[]:[{node:canvas(id)}]))}};return q}, getImageInfo({src,success,fail}){if(!flags.imageInfoHang)later(()=>flags.imageInfoFail?fail({}):success({path:flags.packageRelative&&src.startsWith('/pages/')?src.slice(1):src,width:500,height:500}))}, canvasToTempFilePath({canvas,success,fail}){if(!flags.exportHang)later(()=>{if(flags.exportFail)return fail({});const file='/tmp/export-'+(++state.exportID)+'.png';files.set(file,'rendered-'+canvas.id);success({tempFilePath:file})})}, saveImageToPhotosAlbum({success,fail}){later(()=>flags.saveFail?fail({}):success({}))},showToast(value){calls.push({toast:value})},showModal(value){calls.push({modal:value})},previewImage(value){calls.push({preview:value})} } const modules=new Map() const context=vm.createContext({wx,console,setTimeout:(fn,ms)=>setTimeout(fn,Math.min(ms,50)),clearTimeout,queueMicrotask}) function load(filename){ filename=path.resolve(filename) if(filename===path.join(ROOT,'src/config.js'))return {service:{user:'https://synthetic.invalid/user'}} if(modules.has(filename))return modules.get(filename) let source=fs.readFileSync(filename,'utf8'),names=[] source=source.replace(/^import\s+(.+?)\s+from\s+['"](.+?)['"];?$/gm,(_,binding,ref)=>{const target=path.resolve(path.dirname(filename),ref)+'.js';return binding.startsWith('{')?`const ${binding}=__load(${JSON.stringify(target)})`:`const ${binding}=__load(${JSON.stringify(target)}).default`}) source=source.replace(/export default function (\w+)/g,'exports.default = function $1').replace(/export (async )?function (\w+)/g,(_,async,name)=>{names.push(name);return (async||'')+'function '+name}) source+='\n'+names.map(name=>`exports.${name}=${name}`).join(';') const exports={};modules.set(filename,exports) vm.runInContext('(function(exports,__load){'+source+'\n})',context,{filename})(exports,load) return exports } function page(name){ const filename=path.join(ROOT,'src/pages/users',name+'.wpy') let source=fs.readFileSync(filename,'utf8').match(/