64 lines
6.0 KiB
JavaScript
64 lines
6.0 KiB
JavaScript
// Render-only checks: no browser, database, credentials or additional dependencies.
|
|
import assert from 'node:assert/strict';
|
|
import React from 'react';
|
|
import {renderToStaticMarkup} from 'react-dom/server';
|
|
import {createServer} from 'vite';
|
|
const server=await createServer({server:{middlewareMode:true},appType:'custom'});
|
|
try {
|
|
const {navigationGroups,navigationTopics,default:Navigation}=await server.ssrLoadModule('/resources/js/WorkspaceNavigation.jsx');
|
|
const {default:Home}=await server.ssrLoadModule('/resources/js/WorkspaceHome.jsx');
|
|
const {default:PurchasingHome}=await server.ssrLoadModule('/resources/js/PurchasingHome.jsx');
|
|
const context={company:{name:'Empresa sintética'},unit:{id:7,name:'Unidade sintética'}};
|
|
const check=(grants)=>{
|
|
const can=(permission,unit=null)=>grants.some(g=>g[0]===permission&&g[1]===unit);
|
|
const groups=navigationGroups(can,7);
|
|
return {groups,html:renderToStaticMarkup(React.createElement(Home,{context,can,groups,navigate:()=>{}}))};
|
|
};
|
|
const preparer=check([['company.view',null],['industrial_budgets.view',7],['industrial_budgets.prepare',7]]);
|
|
assert(preparer.html.includes('<h1>Início</h1>'));
|
|
assert(!preparer.groups.some(g=>g.id==='billing'));assert(!preparer.groups.flatMap(g=>g.items).some(i=>i.id==='access'));
|
|
const reviewer=check([['company.view',null],['industrial_budgets.view',7],['industrial_budgets.validate',7]]);
|
|
assert(!reviewer.html.includes('<button'));
|
|
const otherUnit=check([['company.view',null],['industrial_budgets.view',99],['industrial_budgets.prepare',99]]);
|
|
assert(!otherUnit.groups.some(g=>g.id==='costs'));
|
|
const noAccess=check([]);assert.deepEqual(noAccess.groups.map(g=>g.id),['start']);
|
|
const reader=check([['company.view',null],['industrial_budgets.view',7]]);
|
|
assert(!reader.html.includes('<h2>A validar</h2>'));assert(!reader.html.includes('<h2>A preparar e acompanhar</h2>'));
|
|
assert(!reader.html.includes('home-context'));
|
|
const allGroups=navigationGroups(()=>true,7),topics=navigationTopics(allGroups);
|
|
assert.deepEqual(topics.map(t=>t.label),['Início','Comercial','Compras','Stock','Oficinas','Financeiro','Contabilidade','Configuração']);
|
|
// Independent inventory: comparing the generated menu with itself cannot detect a removed module.
|
|
assert.deepEqual(allGroups.map(g=>g.id).sort(),['start','purchasing','stock','services','billing','billing_support','receivables','workshop','costs','accounting','admin'].sort());
|
|
const accountingTopic=topics.find(t=>t.id==='accounting');
|
|
assert.deepEqual(accountingTopic.sections.map(g=>g.id),['accounting']);
|
|
assert.deepEqual(accountingTopic.sections[0].items.map(i=>i.id),['accounting_panel','accounting_daily','accounting_review','accounting_analysis','accounting_closing','accounting_configuration','catalogue_accounting','service_fiscal']);
|
|
for(const grants of [[['company.view',null]],[['catalogue_accounting.view',null]],[['service_fiscal.view',null]]]) {
|
|
const authorised=navigationTopics(check(grants).groups);
|
|
assert(authorised.some(t=>t.id==='accounting'&&t.label==='Contabilidade'));
|
|
}
|
|
assert(!navigationTopics(noAccess.groups).some(t=>t.id==='accounting'));
|
|
const before=allGroups.flatMap(g=>g.items.map(i=>i.id)).sort(),after=topics.flatMap(t=>t.sections.flatMap(g=>g.items.map(i=>i.id))).sort();
|
|
assert.deepEqual(after,before);assert.equal(new Set(after).size,after.length);
|
|
const groupedHome=renderToStaticMarkup(React.createElement(Home,{context,can:()=>true,groups:allGroups,topics,navigate:()=>{}}));
|
|
assert(groupedHome.includes('<h1>Início</h1>'));
|
|
for(const marker of ['<button','<details','<summary','class="panel','Consultas e atalhos permitidos','A preparar e acompanhar','A validar','Contexto actual'])assert(!groupedHome.includes(marker));
|
|
for(const id of ['accounting_panel','purchasing_home'])assert(allGroups.flatMap(g=>g.items).some(i=>i.id===id));
|
|
const navHtml=renderToStaticMarkup(React.createElement(Navigation,{groups:allGroups,page:'billing_drafts',navigate:()=>{},user:{name:'Avaliador sintético',email:'consulta@example.test'},busy:false,logout:()=>{}}));
|
|
assert(navHtml.includes('workspace-topbar'));assert(!navHtml.includes('grouped-sidebar'));assert(!navHtml.includes('navigation-drawer'));
|
|
for(const topic of topics.filter(t=>t.id!=='start'))assert(navHtml.includes(`aria-controls="topic-${topic.id}"`));
|
|
assert(navHtml.includes('aria-expanded="false"'));assert(navHtml.includes('topic-item-active'));assert(navHtml.includes('aria-current="page"'));
|
|
const foreignOnly=navigationTopics(otherUnit.groups);assert(!foreignOnly.flatMap(t=>t.sections).some(g=>g.id==='costs'));
|
|
const future=navigationTopics([{id:'future_module',label:'Novo assunto',items:[{id:'future_page',label:'Destino autorizado'}]}]);assert.equal(future[0].sections[0].items[0].id,'future_page');
|
|
assert.equal(navigationTopics(noAccess.groups).length,1);
|
|
const companyOnly=check([['company.view',null]]),purchaseTopic=navigationTopics(companyOnly.groups).find(t=>t.id==='purchasing');
|
|
assert(purchaseTopic);assert.deepEqual(purchaseTopic.sections[0].items.map(i=>i.id),['purchasing_home']);
|
|
assert(navigationTopics(companyOnly.groups).some(t=>t.id==='stock'));
|
|
const purchaseHtml=renderToStaticMarkup(React.createElement(PurchasingHome,{context,can:p=>p==='company.view',navigate:()=>{}}));
|
|
assert(purchaseHtml.includes('Área de Compras'));assert(purchaseHtml.includes('ainda não apresenta percursos operacionais'));
|
|
assert(!purchaseHtml.includes('<button'));
|
|
const budgetOnly=check([['company.view',null],['purchasing_budget.view',7]]);
|
|
assert(budgetOnly.groups.find(g=>g.id==='purchasing').items.some(i=>i.id==='purchasing_budget'));
|
|
assert(!budgetOnly.groups.find(g=>g.id==='purchasing').items.some(i=>i.id==='supplier_payables'));
|
|
console.log('Home/navigation render checks passed: preparer, reviewer, reader, other unit and absent permissions. No database or browser used.');
|
|
} finally {await server.close();}
|