Files
exoplasim/tests/web-gui-workbench.js

41 lines
4.7 KiB
JavaScript

(async()=>{
'use strict';
const $=selector=>document.querySelector(selector),check=(value,message)=>{if(!value)throw Error(message);},wait=async condition=>{const deadline=performance.now()+4000;while(!condition()){if(performance.now()>deadline)throw Error('Workbench regression timed out.');await new Promise(resolve=>setTimeout(resolve,10));}},click=action=>$(`[data-action="${action}"]`).click(),name=value=>{const input=$('#experiment-name');input.value=value;input.dispatchEvent(new Event('input',{bubbles:true}));};
check(!$('#save-state').classList.contains('dirty'),'Do not run this fixture with unsaved user edits.');
const original=window.fetch,confirm=window.confirm,fixtures=new Set(),prefix='GUI response '+Date.now(),initialJob=$('[data-job][aria-current=true]')?.dataset.job,initialPreset=$('[data-experiment][aria-current=true]')?.dataset.experiment||$('[data-experiment]')?.dataset.experiment;
let saved,holdDelete=false,releaseDelete=null,failLists=true,listReads=0,mockQueue=false,queueReads=0;
const json=value=>new Response(JSON.stringify({ok:true,data:value}),{headers:{'Content-Type':'application/json'}});
try{
window.confirm=()=>true;
window.fetch=async(url,options={})=>{
const api=new URL(String(url),location.href).searchParams.get('api'),method=options.method||'GET';
if(api==='experiments'&&method==='GET'&&failLists){listReads++;throw Error('Injected experiment-list failure.');}
if(api==='jobs'&&method==='GET'&&mockQueue){queueReads++;throw Error('Injected job-list failure.');}
if(api==='jobs'&&method==='POST'&&mockQueue)return json({id:'f'.repeat(32),experiment_id:saved.id,name:saved.name,spec:saved.spec,kind:'model',status:'queued',phase:'queued',progress:0,total_cycles:saved.spec.run.cycles,completed_cycles:0,created_at:new Date().toISOString()});
const response=await original(url,options);
if(api==='experiments'&&method==='POST'&&response.ok){const data=(await response.clone().json()).data;saved=data.experiment||data;fixtures.add(saved.id);}
if(api==='experiment_delete'&&method==='POST'&&holdDelete){await new Promise(resolve=>{releaseDelete=resolve;});}
return response;
};
$(`[data-experiment="${initialPreset}"]`).click();click('clone');name(prefix+' A');click('save');await wait(()=>saved&&$('#save-state').textContent==='Saved');const a=saved.id;
check(listReads===0,'A successful save must not depend on a list request.');check($(`[data-experiment="${a}"]`),'The saved record must appear in the local list.');
click('clone');name(prefix+' B');click('save');await wait(()=>saved.id!==a&&$('#save-state').textContent==='Saved');const b=saved.id;
$(`[data-experiment="${a}"]`).click();holdDelete=true;click('delete');await wait(()=>releaseDelete!==null);
$(`[data-experiment="${b}"]`).click();name(prefix+' B pending edit');releaseDelete();releaseDelete=null;await wait(()=>!$(`[data-experiment="${a}"]`));
check($('#experiment-name').value===prefix+' B pending edit','An old delete response must not replace the selected draft.');check($(`[data-experiment="${b}"]`),'Deleting A must not remove B from the local list.');
click('delete');await wait(()=>releaseDelete!==null);name(prefix+' recovered edits');releaseDelete();releaseDelete=null;await wait(()=>!$(`[data-experiment="${b}"]`));
check($('#experiment-name').value===prefix+' recovered edits','Edits made during deletion must survive.');check($('#save-state').classList.contains('dirty'),'Recovered edits must remain unsaved.');
holdDelete=false;click('save');await wait(()=>saved.id!==b&&$('#save-state').textContent==='Saved');check(saved.id!==a,'A recovered draft must create a new saved record.');
mockQueue=true;click('save-run');await wait(()=>$('#workbench-notice').textContent==='Run added to queue.');
check($('[data-job="'+ 'f'.repeat(32)+'"]'),'The mutation response must populate the job list.');check(queueReads===0,'Queue success must not depend on a job-list request.');
return{passed:true,realSavedFixtures:fixtures.size,saveListReads:listReads,queueListReads:queueReads,queueResponse:'mocked; no scientific job was created',checks:['save response cache','delayed delete across preset selection','edits during deletion','recovered draft save','queue response cache']};
}finally{
holdDelete=false;releaseDelete?.();window.fetch=original;mockQueue=false;failLists=false;
for(const id of fixtures){const response=await original(window.EXOPLASIM.api.experiment_delete,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id})});if(!response.ok&&response.status!==404)console.error('Fixture deletion failed:',id,response.status);}
click('refresh');await new Promise(resolve=>setTimeout(resolve,200));
const preset=$(`[data-experiment="${initialPreset}"]`);if(preset)preset.click();
const job=$(`[data-job="${initialJob}"]`);if(job)job.click();
window.confirm=confirm;
}
})()