Files
SGF/scripts/purchasing-focus-ui.mjs
2026-11-04 09:37:20 +02:00

29 lines
8.3 KiB
JavaScript

import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';
import {readFocus,focusHref,appendProcessLinks,beginWorkspace} from '../public/purchasing-focus.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/purchasing-focus-read-'+tag),out=path.join(root,'purchasing-focus-ui.json');assert(!fs.existsSync(out));const checks=[],ok=(v,n)=>{assert(v,n);checks.push(n);};
for(const [stage,query,value]of [['requests','?request=3',{request:3}],['orders','?award=2',{award:2}],['receipts','?order=41&receipt=91',{order:41,receipt:91}],['invoices','?order=41&invoice=77',{order:41,invoice:77}],['obligations','?invoice=77',{invoice:77}]]){assert.deepEqual(readFocus(stage,query),value);checks.push('strict focus parser admits '+stage);}
for(const query of ['?order=0','?order=-1','?order=01','?order=1.5','?order=9007199254740992','?order=1&order=2','?receipt=3','?order=1&url=https://example.com','?order=1e2','?order=%3Cscript%3E']){assert.throws(()=>readFocus('receipts',query));checks.push('strict focus parser refuses '+query);}
ok(readFocus('requests','')===null&&focusHref('orders',{award:2})==='/purchasing/orders?award=2','empty focus never selects a record and generated href is local and explicit');
class Element{constructor(tag,text){this.tag=tag;this.textContent=text??'';this.children=[];this.dataset={focusEnabled:'true',context:'qa-context',csrf:'qa-csrf'};this.isConnected=true;}append(...nodes){this.children.push(...nodes);}prepend(...nodes){this.children.unshift(...nodes);}replaceChildren(...nodes){this.children=[...nodes];}setAttribute(k,v){this[k]=v;}dispatchEvent(){} }
globalThis.Option=class extends Element{constructor(text,value){super('option',text);this.value=value;}};globalThis.FormData=class{*[Symbol.iterator](){} };const walk=e=>[e,...e.children.flatMap(walk)],paginator=data=>({data,current_page:1,last_page:1,per_page:25,total:data.length}),rev='a'.repeat(64),calls=[];let elements=new Map,mismatchReceipt=false;
globalThis.document={createElement:tag=>new Element(tag),querySelector:selector=>{if(!elements.has(selector))elements.set(selector,new Element(selector));return elements.get(selector);}};
const request={id:3,number:'REQ-EXACT',status:'approved',items:[],steps:[],total_minor:10000,process_version:3};const invoice={id:77,number:'INV-EXACT',status:'validated',issued_on:'2026-11-03',total_minor:10000,created_by:1,validated_by:2,items:[],purchase_order_id:41};
const responses={
'/api/purchasing-requests':{new_revision:rev,centres:[],can_prepare:false,requests:paginator([])},'/api/purchasing-requests/3':{request,capabilities:{},history:[],revision:rev},
'/api/purchasing-sourcing/requests/3':{request,capabilities:{},comparison:{policy:{minimum_proposals:3,minimum_evaluators:2},ready:true,rows:[],top_ids:[]},quotes:[],award:{id:2,status:'approved',total_minor:10000,approvals:[]},history:[]},
'/api/purchasing-orders/awards/2':{basis:{award:{id:2,status:'approved',approvals:[],total_minor:10000},quote:{supplier:{name:'QA'},items:[],currency:'MZN'}},existing:{order:{id:41,number:'OC-EXACT',items:[]}},can_issue:false},
'/api/purchasing-receipts/orders/41':{order:{id:41,number:'OC-EXACT'},progress:{state:'complete'},history:{...paginator([]),last_page:2},can_confirm:false},
'/api/purchasing-receipts/history/91':{receipt:{id:91,purchase_order_id:41,number:'REC-EXACT'},documents_enabled:false},
'/api/purchasing-invoices/orders/41':{order:{id:41,number:'OC-EXACT',currency:'MZN',items:[]},invoices:[invoice],can_prepare:false,revision:rev,receipt_progress:{},physical_corrections:[]},
'/api/purchasing-obligations/77':{documents:[{local_id:77,number:'INV-EXACT',supplier_name:'QA',total_minor:10000,status:'validated',order_id:41,missing:[],lines:[]}],obligations:[],permissions:{},proposals:[],events:[],revision:rev,pagination:{proposals:{page:1,last_page:1},events:{page:1,last_page:1}}},
'/api/purchasing-dossiers/3':{request,view_revision:rev,_links:[{href:'/purchasing/requests?request=3',label:'Consultar esta requisição'}],stages:Object.fromEntries(['quotes','awards','orders','receipts','invoices','obligations'].map(k=>[k,{accessible:false}])),events:{accessible:true,data:[],pagination:{page:1,last_page:1}}}
};
globalThis.fetch=async(url,options={})=>{calls.push({url,method:options.method??'GET'});const pathname=url.split('?')[0];let data=responses[pathname];if(!data&&(/\/history$/.test(pathname)||/^\/api\/purchasing-(sourcing|orders|receipts|invoices|obligations|dossiers)$/.test(pathname)))data=paginator([]);if(!data)throw Error('Unexpected mock read '+url);if(mismatchReceipt&&pathname==='/api/purchasing-receipts/history/91')data={receipt:{id:91,purchase_order_id:42,number:'REC-MISMATCH'},documents_enabled:false};return {ok:true,json:async()=>structuredClone(data)};};
const settle=()=>new Promise(resolve=>setTimeout(resolve,10));let sequence=0;async function mount(file,query){elements=new Map;calls.length=0;globalThis.location={search:query};await import('../public/'+file+'.mjs?case='+tag+'-'+sequence++);await settle();await settle();return {calls:[...calls],detail:document.querySelector(file==='order-workspace'?'#award-detail':file==='sourcing-workspace'?'#process-detail':'#detail'),status:document.querySelector(file==='receipt-workspace'?'#message':'#status')};}
for(const [file,query,target,text]of [['request-workspace','?request=3','/api/purchasing-requests/3','REQ-EXACT'],['sourcing-workspace','?request=3','/api/purchasing-sourcing/requests/3','REQ-EXACT'],['order-workspace','?award=2','/api/purchasing-orders/awards/2','OC-EXACT'],['receipt-workspace','?order=41&receipt=91','/api/purchasing-receipts/history/91','REC-EXACT'],['supplier-invoice-workspace','?order=41&invoice=77','/api/purchasing-invoices/orders/41','INV-EXACT'],['supplier-obligation-workspace','?invoice=77','/api/purchasing-obligations/77','INV-EXACT'],['purchasing-dossier','?request=3','/api/purchasing-dossiers/3','REQ-EXACT']]){const d=await mount(file,query);ok(d.calls.some(c=>c.url.split('?')[0]===target)&&walk(d.detail).some(e=>e.textContent.includes(text)),'actual '+file+' opens exact focused source even when absent from first list page');ok(d.calls.every(c=>c.method==='GET'),'actual '+file+' startup never prepares or confirms a business operation');}
let d=await mount('supplier-invoice-workspace','?order=41&invoice=999');ok(d.status.textContent.includes('não pertence')&&!walk(d.detail).some(e=>e.textContent.includes('INV-EXACT')),'invoice of another or unknown order is not substituted with first available document');mismatchReceipt=true;d=await mount('receipt-workspace','?order=41&receipt=91');ok(d.status.textContent.includes('não pertence')&&!walk(d.detail).some(e=>e.textContent.includes('REC-MISMATCH')),'receipt parent mismatch is reported before rendering receipt evidence');mismatchReceipt=false;
d=await mount('order-workspace','?award=2&award=3');ok(d.status.textContent.includes('Atalho inválido')&&!d.calls.some(c=>c.url.includes('/awards/')),'ambiguous initial link performs list reads only and chooses no award');
const links=new Element('div');appendProcessLinks(links,[{href:'https://example.com',label:'external'},{href:'/purchasing/orders?award=2&award=3',label:'duplicate'},{href:'/purchasing/orders?award=2',label:'safe'}]);ok(links.children.length===1&&links.children[0].href==='/purchasing/orders?award=2','link renderer rejects external and duplicated-parameter hints');
let generation=0,release,opened=0;const pending=beginWorkspace({stage:'orders',root:new Element('root'),load:()=>{generation++;return new Promise(resolve=>{release=resolve;});},open:()=>{opened++;},current:()=>generation,error:()=>{},search:'?award=2'});generation++;release();await pending;ok(opened===0,'interaction during initial list request cancels late automatic focus');let listed=0;await beginWorkspace({stage:'orders',root:Object.assign(new Element('root'),{dataset:{focusEnabled:'false'}}),load:async()=>{listed++;},open:()=>{opened++;},current:()=>0,error:()=>{},search:'?award=2'});ok(listed===1&&opened===0,'disabled feature preserves list startup without focused read');
fs.writeFileSync(out,JSON.stringify({passed:true,checks,dom_simulation:true,business_commands:0,visual_review:false},null,2));console.log(JSON.stringify({passed:true,checks:checks.length}));