13 lines
8.9 KiB
JavaScript
13 lines
8.9 KiB
JavaScript
import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';import {mountObligationEvidence} from '../public/obligation-evidence.mjs';
|
|
const tag=process.argv[2];assert(/^[a-f0-9]{12}$/.test(tag??''));const run=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')),root=path.join(run.root,'private/receipt-composition-'+tag),out=path.join(root,'obligation-evidence-ui.json');assert(!fs.existsSync(out));const checks=[],ok=(v,name)=>{assert(v,name);checks.push(name);};
|
|
class Element{constructor(tag,text){this.tag=tag;this.textContent=text??'';this.children=[];this.dataset={};this.value='';this.isConnected=true;this.disabled=false;}append(...nodes){this.children.push(...nodes);}replaceChildren(...nodes){this.children=[...nodes];}setAttribute(k,v){this[k]=v;}querySelectorAll(selector){const tags=selector.split(',');return walk(this).slice(1).filter(e=>tags.includes(e.tag));}}
|
|
const walk=node=>[node,...node.children.flatMap(walk)],findButton=(node,text)=>walk(node).find(e=>e.tag==='button'&&e.textContent===text),tick=()=>new Promise(resolve=>setTimeout(resolve,0));const elements=new Map;globalThis.document={createElement:tag=>new Element(tag),querySelector:selector=>{if(!elements.has(selector))elements.set(selector,new Element(selector));return elements.get(selector);}};
|
|
const proposal=id=>({id,capture_version:id,prepared_by:1,created_at:'2026-11-03',reason:'QA captured source',missing:[]}),data=(pp=1,ep=1)=>({revision:'a'.repeat(64),current_actor:2,permissions:{prepare:false,validate:true},documents:[{local_id:7,number:'QA-INVOICE',supplier_name:'QA',total_minor:10000,status:'validated',order_id:9,missing:[],lines:[]}],proposals:[proposal(pp)],obligations:[],events:[{id:ep,kind:'qa-evidence',created_at:'2026-11-03',actor_id:1,payload:{page:ep}}],pagination:{proposals:{page:pp,last_page:3},events:{page:ep,last_page:3}}});let active=true,invalidated=0,refreshed=0,message='',requests=[],created=0;const container=new Element('section');const api=async path=>{requests.push(path);const q=new URL('http://qa'+path).searchParams;return data(Number(q.get('proposal_page')),Number(q.get('event_page')));};const mount=(target,overrides={})=>mountObligationEvidence({id:7,data:data(),container:target,api,active:()=>active,makeDecision:(p,box)=>{created++;const form=new Element('form'),reason=new Element('textarea');reason.value='fundamento individual ainda não gravado';form.append(reason);box.append(form);},onStale:()=>{invalidated++;},refresh:()=>{refreshed++;},message:text=>message=text,...overrides});
|
|
mount(container);ok(created===0&&requests.length===0,'initial evidence lists proposals without selecting or preparing a decision');findButton(container,'Conferir proposta #1').onclick();const form=walk(container).find(e=>e.tag==='form'),reason=walk(form).find(e=>e.tag==='textarea');form.dataset.preview='retained-preview';ok(created===1&&reason.value.length>10,'explicit proposal choice creates one separate decision area');findButton(container,'Conferir proposta #1').onclick();ok(created===1&&walk(container).includes(form),'choosing same proposal preserves original form and preview');let nextButtons=walk(container).filter(e=>e.tag==='button'&&e.textContent==='Seguinte');await nextButtons[1].onclick();ok(new URL('http://qa'+requests.at(-1)).searchParams.get('event_page')==='2'&&reason.value==='fundamento individual ainda não gravado'&&form.dataset.preview==='retained-preview','event pagination preserves unsaved reason and prepared preview');nextButtons=walk(container).filter(e=>e.tag==='button'&&e.textContent==='Seguinte');await nextButtons[0].onclick();ok(requests.at(-1).includes('proposal_page=2')&&requests.at(-1).includes('event_page=2')&&walk(container).includes(form),'proposal pagination preserves selected decision while keeping other pane page');findButton(container,'Conferir proposta #2').onclick();ok(created===2&&!walk(container).includes(form),'explicit selection of another proposal starts a distinct decision');findButton(container,'Actualizar origem e conferências').onclick();ok(refreshed===1,'source refresh remains explicit');
|
|
const pending=[],race=new Element('section');mount(race,{api:path=>new Promise(resolve=>pending.push({path,resolve}))});nextButtons=walk(race).filter(e=>e.tag==='button'&&e.textContent==='Seguinte');const a=nextButtons[0].onclick(),b=nextButtons[1].onclick();ok(pending[1].path.includes('proposal_page=2')&&pending[1].path.includes('event_page=2'),'concurrent pane requests retain latest desired page pair');pending[1].resolve(data(2,2));await b;pending[0].resolve(data(2,1));await a;ok(walk(race).some(e=>e.textContent==='Histórico · página 2 de 3')&&!walk(race).some(e=>e.textContent==='Histórico · página 1 de 3'),'late page response cannot move another pane backwards');
|
|
const changed=new Element('section');mount(changed,{api:async()=>({...data(),revision:'b'.repeat(64)})});await walk(changed).find(e=>e.tag==='button'&&e.textContent==='Seguinte').onclick();ok(invalidated===1&&message.includes('mudaram')&&findButton(changed,'Conferir proposta #1').disabled,'changed source revision disables former evidence actions');const revoked=new Element('section');mount(revoked,{api:async()=>({...data(),permissions:{prepare:false,validate:false}})});await walk(revoked).find(e=>e.tag==='button'&&e.textContent==='Seguinte').onclick();ok(invalidated===2,'changed permission snapshot requires refreshed conference even if source revision is unchanged');const denied=new Element('section');mount(denied,{api:async()=>{throw Object.assign(Error('Forbidden'),{status:403});}});await walk(denied).find(e=>e.tag==='button'&&e.textContent==='Seguinte').onclick();ok(invalidated===3,'permission refusal invalidates current decision presentation');const network=new Element('section');mount(network,{api:async()=>{throw Error('Network interruption');}});await walk(network).find(e=>e.tag==='button'&&e.textContent==='Seguinte').onclick();ok(invalidated===3&&message.includes('sem perder'),'temporary read failure preserves decision for retry');
|
|
const sameAuthor=new Element('section');mount(sameAuthor,{data:{...data(),current_actor:1}});ok(!findButton(sameAuthor,'Conferir proposta #1'),'preparer cannot select own proposal for independent recognition');const recognised=new Element('section');mount(recognised,{data:{...data(),obligations:[{id:1}]}});ok(!findButton(recognised,'Conferir proposta #1'),'recognised obligation offers no duplicate-recognition proposal action');const selecting=new Element('section');mount(selecting,{canSelect:()=>false});const beforeBusy=created;findButton(selecting,'Conferir proposta #1').onclick();ok(created===beforeBusy&&message.includes('Aguarde'),'in-flight operation prevents switching decision selection');
|
|
// Exercise the actual workspace and its in-flight preview guards.
|
|
elements.clear();const uiRoot=document.querySelector('#supplier-obligations');uiRoot.dataset={context:'qa',csrf:'qa',focusEnabled:'true',evidenceEnabled:'true',continuityEnabled:'false'};globalThis.location={search:'?invoice=7'};let previewResolve,sourceChanged=false;const calls=[];globalThis.fetch=async(url,options={})=>{calls.push({url,method:options.method??'GET',body:options.body?JSON.parse(options.body):null});if(url.includes('/preview'))return await new Promise(resolve=>{previewResolve=resolve;});const value=url==='/api/purchasing-obligations?page=1'?{data:[],last_page:1}:sourceChanged?{...data(),revision:'c'.repeat(64)}:data();return {ok:true,json:async()=>value};};await import('../public/supplier-obligation-workspace.mjs?test='+tag);await tick();await tick();const detail=document.querySelector('#detail');ok(!walk(detail).some(e=>e.tag==='form')&&calls.every(v=>v.method==='GET'),'actual workspace creates no recognition form or command before explicit proposal selection');findButton(detail,'Conferir proposta #1').onclick();const realForm=walk(detail).find(e=>e.tag==='form'),realReason=walk(realForm).find(e=>e.tag==='textarea');realReason.value='Fundamento QA distinto para proposta seleccionada.';realForm.oninput();const preparing=realForm.onsubmit({preventDefault(){}});await tick();ok(calls.at(-1).body.proposal_id===1&&calls.at(-1).body.action==='validate','actual preview carries individually selected proposal id and action');sourceChanged=true;await walk(detail).find(e=>e.tag==='button'&&e.textContent==='Seguinte').onclick();ok(realForm.dataset.stale==='true'&&realReason.disabled&&realReason.value.includes('Fundamento'),'actual source change disables form while preserving written reason');previewResolve({ok:true,json:async()=>({preview:'obsolete',impact:{result:{state:'prepared'},principal_minor:10000,reason:'QA'}})});await preparing;const confirm=findButton(realForm,'Confirmar esta decisão');await confirm.onclick();ok(confirm.disabled&&!calls.some(v=>v.url.endsWith('/confirm')),'late preview cannot reactivate or confirm obsolete decision');
|
|
fs.writeFileSync(out,JSON.stringify({passed:true,checks,dom_simulation:true,business_confirmations:0,visual_review:false},null,2));console.log(JSON.stringify({passed:true,checks:checks.length}));
|