64 lines
6.1 KiB
JavaScript
64 lines
6.1 KiB
JavaScript
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(){},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)?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: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(/<script>([\s\S]*?)<\/script>/)[1],definition
|
|
source=source.replace(/^import\s+(.+?)\s+from\s+['"](.+?)['"];?$/gm,(_,binding,ref)=>{if(ref==='@wepy/core')return 'const wepy=__wepy';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`})
|
|
vm.runInContext('(function(__load,__wepy){'+source+'\n})',context,{filename})(load,{page(value){definition=value}})
|
|
const entries=[...(definition.mixins||[]),definition],instance={$wx:{}}
|
|
for(const entry of entries){Object.assign(instance,copy(entry.data||{}),entry.methods||{})}
|
|
instance.hook=name=>{for(const entry of entries)if(entry[name])entry[name].call(instance)}
|
|
instance.definition=definition
|
|
return instance
|
|
}
|
|
return {wx,state,flags,storage,files,calls,draws,load:name=>load(path.join(ROOT,'src',name+'.js')),page}
|
|
}
|
|
async function settle(test,message='condition'){for(let i=0;i<80;i++){if(test())return;await new Promise(r=>setTimeout(r,5))}throw Error('Timed out waiting for '+message)}
|
|
module.exports={createHarness,settle,ROOT}
|