auu
This commit is contained in:
parent
d995726462
commit
0a343ecbc5
326 changed files with 15584 additions and 0 deletions
46
test/helpers/app-source.js
Normal file
46
test/helpers/app-source.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
const fs=require('fs');
|
||||
const path=require('path');
|
||||
const vm=require('vm');
|
||||
const root=path.resolve(__dirname,'../..');
|
||||
const read=name=>fs.readFileSync(path.join(root,name),'utf8');
|
||||
const app=read('app.js'),html=read('index.html'),css=read('style.css'),worker=read('puzzle-worker.js'),appLogicSource=read('app-logic.js');
|
||||
function assert(value,message){if(!value)throw new Error(message)}
|
||||
function functionSource(name,source=app){
|
||||
let start=source.indexOf(`async function ${name}(`);if(start<0)start=source.indexOf(`function* ${name}(`);if(start<0)start=source.indexOf(`function ${name}(`);
|
||||
assert(start>=0,`Missing ${name}`);
|
||||
let paren=source.indexOf('(',start),parenDepth=0,body=-1;
|
||||
for(let i=paren;i<source.length;i++){
|
||||
if(source[i]==='(')parenDepth++;
|
||||
else if(source[i]===')'&&--parenDepth===0){body=source.indexOf('{',i);break}
|
||||
}
|
||||
assert(body>=0,`Missing body for ${name}`);let depth=0;
|
||||
for(let i=body;i<source.length;i++){
|
||||
if(source[i]==='{')depth++;
|
||||
else if(source[i]==='}'&&--depth===0)return source.slice(start,i+1);
|
||||
}
|
||||
throw new Error(`Unclosed ${name}`);
|
||||
}
|
||||
function createContext(context={}){vm.createContext(context);return context}
|
||||
function runFunctions(names,context,expose=names){
|
||||
createContext(context);
|
||||
const sources=names.map(name=>functionSource(name)).join('\n');
|
||||
vm.runInContext(`${sources}\nthis.logic={${expose.join(',')}};`,context);
|
||||
return context.logic;
|
||||
}
|
||||
function loadBendPuzzle(){
|
||||
delete global.BendPuzzle;
|
||||
delete require.cache[require.resolve(path.join(root,'puzzle-core.js'))];
|
||||
require(path.join(root,'puzzle-core.js'));
|
||||
return global.BendPuzzle;
|
||||
}
|
||||
function loadAppLogic(){
|
||||
delete require.cache[require.resolve(path.join(root,'app-logic.js'))];
|
||||
return require(path.join(root,'app-logic.js'));
|
||||
}
|
||||
function starterPuzzle(){
|
||||
const match=app.match(/const STARTER_PUZZLE=Object\.freeze\((\{.*\})\);\nconst SCORE_VERSION/s);
|
||||
assert(match,'Bundled origin puzzle missing');
|
||||
return JSON.parse(match[1]);
|
||||
}
|
||||
module.exports={root,read,app,html,css,worker,appLogicSource,assert,functionSource,createContext,runFunctions,loadBendPuzzle,loadAppLogic,starterPuzzle,vm,path,fs};
|
||||
52
test/helpers/realtime-client.js
Normal file
52
test/helpers/realtime-client.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
'use strict';
|
||||
|
||||
const assert=require('assert/strict');
|
||||
|
||||
async function connectRealtime(base,session,{timeout=4000}={}){
|
||||
assert(session?.playerId&&session?.token,'Realtime session is required');
|
||||
const url=base.replace(/^http/,'ws')+'/api/realtime';
|
||||
const socket=new WebSocket(url);
|
||||
const queue=[];
|
||||
const waiters=[];
|
||||
let closed=false;
|
||||
function dispatch(message){
|
||||
for(let index=0;index<waiters.length;index++){
|
||||
const waiter=waiters[index];
|
||||
if(waiter.predicate(message)){
|
||||
waiters.splice(index,1);clearTimeout(waiter.timer);waiter.resolve(message);return;
|
||||
}
|
||||
}
|
||||
queue.push(message);
|
||||
}
|
||||
socket.addEventListener('message',event=>{
|
||||
try{dispatch(JSON.parse(String(event.data)))}catch(error){dispatch({type:'test-parse-error',error})}
|
||||
});
|
||||
socket.addEventListener('close',()=>{closed=true;for(const waiter of waiters.splice(0)){clearTimeout(waiter.timer);waiter.reject(new Error('Realtime socket closed'))}});
|
||||
await new Promise((resolve,reject)=>{
|
||||
const timer=setTimeout(()=>reject(new Error('Realtime socket open timed out')),timeout);
|
||||
socket.addEventListener('open',()=>{clearTimeout(timer);resolve()},{once:true});
|
||||
socket.addEventListener('error',()=>{clearTimeout(timer);reject(new Error('Realtime socket failed'))},{once:true});
|
||||
});
|
||||
socket.send(JSON.stringify({type:'hello',playerId:session.playerId,token:session.token}));
|
||||
async function waitFor(predicateOrType,waitTimeout=timeout){
|
||||
const predicate=typeof predicateOrType==='function'?predicateOrType:message=>message?.type===predicateOrType;
|
||||
const found=queue.findIndex(predicate);
|
||||
if(found>=0)return queue.splice(found,1)[0];
|
||||
if(closed)throw new Error('Realtime socket is closed');
|
||||
return new Promise((resolve,reject)=>{
|
||||
const waiter={predicate,resolve,reject,timer:null};
|
||||
waiter.timer=setTimeout(()=>{const index=waiters.indexOf(waiter);if(index>=0)waiters.splice(index,1);reject(new Error('Realtime message timed out'))},waitTimeout);
|
||||
waiters.push(waiter);
|
||||
});
|
||||
}
|
||||
const ready=await waitFor('ready');
|
||||
return{
|
||||
socket,ready,
|
||||
send(message){socket.send(JSON.stringify(message))},
|
||||
waitFor,
|
||||
close(){try{socket.close()}catch(_){}},
|
||||
queued:queue
|
||||
};
|
||||
}
|
||||
|
||||
module.exports={connectRealtime};
|
||||
Loading…
Add table
Add a link
Reference in a new issue