import assert from 'node:assert/strict'; import test from 'node:test'; import { catalogueDetailHref, catalogueReturnHref, } from '../../resources/js/lib/catalogue-context.ts'; import { portalNavigation, legacyPortalNavigation, currentNavigationGroup, currentNavigationItem, canShowLegacyManagement, } from '../../resources/js/lib/portal-navigation.ts'; void test('public and authenticated navigation share groups without revealing management', () => { const publicGroups = portalNavigation({ authenticated: false, manager: false, }); const investor = portalNavigation({ authenticated: true, manager: false }); assert.deepEqual(publicGroups.slice(0, 3), investor.slice(0, 3)); assert.deepEqual( publicGroups.map((g) => g.id), ['discovery', 'realization', 'support', 'work', 'account'], ); const hrefs = JSON.stringify(investor); assert.equal(hrefs.includes('/management/'), false); assert.equal(JSON.stringify(publicGroups).includes('/login'), true); assert.equal(hrefs.includes('/investidor/profile'), true); assert.equal(hrefs.includes('/balcao-virtual'), true); }); void test('management and optional modules follow permissions and feature flags', () => { const groups = portalNavigation({ authenticated: true, manager: true, businesses: true, education: true, ai: true, }); assert.ok(groups.some((group) => group.id === 'work')); assert.equal(JSON.stringify(groups).includes('/investidor/negocios'), true); assert.equal(JSON.stringify(groups).includes('/management/ai'), true); assert.equal( JSON.stringify( portalNavigation({ authenticated: false, manager: false, education: false, }), ).includes('/educacao'), false, ); }); void test('detail URLs and filtered catalogue pages retain the current group', () => { // Preserve the retained catalogue's detail/filter matching independently // of the reconciled shell's new grouping names. const groups = legacyPortalNavigation({ authenticated: true, manager: true, }); for (const url of [ '/carteira/project/fixture', '/zonas?category=zee', '/zonas/fixture', '/oportunidades/fixture', ]) assert.equal(currentNavigationGroup(groups, url), 'explore'); assert.equal( currentNavigationGroup( groups, '/balcao-virtual/dossiers/fixture?phase=market', ), 'support', ); assert.equal( currentNavigationGroup(groups, '/investidor/profile'), 'account', ); assert.equal( currentNavigationGroup(groups, '/management/crm/leads/fixture'), 'management', ); assert.equal(currentNavigationGroup(groups, '/unknown'), null); assert.equal( currentNavigationItem(groups, '/carteira?q=QA&kind=project'), 'projects', ); assert.equal( currentNavigationItem(groups, '/zonas?category=zee&q=QA'), 'zee', ); }); void test('reconciled navigation selects discovery, realization and private support correctly', () => { const groups = portalNavigation({ authenticated: false, manager: false }); assert.equal( currentNavigationGroup(groups, '/oportunidades/fixture'), null, ); assert.equal(currentNavigationGroup(groups, '/simulador-fiscal'), null); assert.equal( currentNavigationGroup(groups, '/balcao-virtual/dossiers/fixture'), 'support', ); assert.equal(currentNavigationGroup(groups, '/management/crm/leads'), null); }); void test('discovery retains distinct catalogues without repeating main entry points', () => { const groups = portalNavigation({ authenticated: false, manager: false, businesses: true, education: true, }); const links = []; const collect = (items) => items.forEach((item) => { if (item.href) links.push(item.href); if (item.children) collect(item.children); }); collect(groups); assert.equal(new Set(links).size, links.length); const discovery = groups.find((group) => group.id === 'discovery'); const retained = JSON.stringify(discovery); for (const id of ['why', 'opportunities', 'simulator', 'sectors', 'start']) assert.equal( discovery.children.some((item) => item.id === id), false, ); assert.equal(retained.includes('/investimentos/iniciar'), false); for (const href of [ '/investimentos/iniciar', '/oportunidades?view=map', '/carteira', '/zonas', '/negocios', '/educacao', '/documentos', '/legislacao', ]) assert.ok(links.includes(href), href); }); void test('catalogue detail and return preserve the search inside the local catalogue', () => { const query = 'q=turismo+em+Inhambane&kind=cluster&province_id=fixture'; const href = catalogueDetailHref('/carteira/fixture', query); assert.equal(catalogueReturnHref('/carteira', href), '/carteira?' + query); assert.equal( catalogueReturnHref( '/zonas', '/zonas/fixture?catalogue_query=https%3A%2F%2Foutside.invalid', ), '/zonas?https%3A%2F%2Foutside.invalid=', ); }); const destinations = (items) => items.flatMap((item) => [ ...(item.href ? [item.href] : []), ...destinations(item.children ?? []), ]); void test('both menu consumers respect the existing scoped server decision rather than generic manager status', () => { assert.equal( canShowLegacyManagement({ user: { id: 1 }, canManagePortal: true, legacyManagementAccess: false, }), false, ); assert.equal( canShowLegacyManagement({ user: { id: 1 }, canManagePortal: true, legacyManagementAccess: true, }), true, ); assert.equal( canShowLegacyManagement({ user: null, canManagePortal: true, legacyManagementAccess: true, }), false, ); assert.equal( canShowLegacyManagement({ user: { id: 1 }, canManagePortal: true }), true, ); }); void test('work entry is discoverable without exposing private capabilities to an anonymous visitor', () => { const groups = portalNavigation({ authenticated: false, manager: true, contentManagementHref: '/gestao-conteudos', contentManagementAccess: { opportunities: { index: '/management/opportunities', create: '/management/opportunities/create', }, }, scopedAdministration: [ { label: 'Private QA administration', href: '/management/secret' }, ], }); assert.deepEqual( destinations(groups.find((g) => g.id === 'work').children), ['/acesso-gestao'], ); assert.equal(JSON.stringify(groups).includes('/management/'), false); assert.equal(JSON.stringify(groups).includes('/gestao-conteudos'), false); }); void test('work area exposes existing content capability even when the old dashboard flag has no menu object', () => { const groups = portalNavigation({ authenticated: true, manager: true, opportunityManagement: null, contentManagementHref: '/gestao-conteudos', contentManagementAccess: { opportunities: { index: '/management/opportunities', create: '/management/opportunities/create', }, }, }); const work = groups.find((g) => g.id === 'work'); assert.equal(work.label, 'Área de trabalho'); assert.ok( work.children.some( (i) => i.label === 'Preparar análise e ideia' && i.href === '/management/opportunities/create?analysis=proactive', ), ); assert.ok(work.children.some((i) => i.href === '/gestao-conteudos')); assert.ok( work.children.some((i) => i.href === '/management/opportunities'), ); }); void test('every previously authorised management/profile destination survives the regrouping exactly once', () => { const access = { authenticated: true, manager: true, businesses: true, education: true, ai: true, contentManagementHref: '/gestao-conteudos', profileAccess: { contexts: [], mine: '/minhas-oportunidades', create: '/minhas-oportunidades/create', request: '/perfil/promotor', review: null, audit: '/perfis/auditoria', administration: '/perfis/gestao', }, scopedAdministration: [ { label: 'Referências económicas', href: '/management/economic-references/lot-01', }, ], }; const old = destinations( legacyPortalNavigation(access).filter((g) => ['management', 'profile-opportunities'].includes(g.id), ), ); const groups = portalNavigation(access); const after = destinations(groups); for (const href of old) assert.equal(after.filter((x) => x === href).length, 1, href); assert.equal(after.length, new Set(after).size); assert.ok( destinations(groups.find((g) => g.id === 'work').children).includes( '/management/economic-references/lot-01', ), ); assert.equal( after.filter((href) => href === '/management/profile-install').length, 1, 'profile installation already present in the old authorised sidebar must survive', ); }); void test('ordinary authenticated account has an explanation entry and does not acquire creation or management links', () => { const groups = portalNavigation({ authenticated: true, manager: false }); assert.deepEqual( destinations(groups.find((g) => g.id === 'work').children), ['/acesso-gestao'], ); assert.equal(JSON.stringify(groups).includes('analysis=proactive'), false); assert.equal(JSON.stringify(groups).includes('/management/'), false); }); void test('direct analysis and ordinary creation preserve distinct selection state in the same work group', () => { const access = { authenticated: true, manager: false, profileAccess: { contexts: [], mine: '/minhas-oportunidades', create: '/minhas-oportunidades/create', request: '/perfil/promotor', review: null, administration: null, }, }; const groups = portalNavigation(access); assert.equal( currentNavigationGroup( groups, '/minhas-oportunidades/create?analysis=proactive', ), 'work', ); assert.equal( currentNavigationItem( groups, '/minhas-oportunidades/create?analysis=proactive', ), 'prepare-analysis', ); assert.equal( currentNavigationItem(groups, '/minhas-oportunidades/create'), 'new-my-opportunity', ); }); void test('investment entry belongs to realization exactly once', () => { const groups = portalNavigation({ authenticated: false, manager: false }); const realization = groups.find((g) => g.id === 'realization'); assert.ok( realization.children.some( (i) => i.id === 'start' && i.href === '/investimentos/iniciar', ), ); assert.equal( destinations(groups).filter((href) => href === '/investimentos/iniciar') .length, 1, ); assert.equal( currentNavigationGroup(groups, '/investimentos/iniciar'), 'realization', ); }); void test('portfolio and zone fiche return keeps the applied catalogue page and filters', () => { for (const base of [ '/carteira', '/zonas', '/management/portfolio', '/management/investment/zones', ]) { const href = catalogueDetailHref( base + '/synthetic', 'q=energia&province_id=p', base + '?q=energia&province_id=p&page=3&unrelated=private', ); const returned = new URL( catalogueReturnHref(base, href), 'https://portal.invalid', ); assert.equal(returned.searchParams.get('page'), '3'); assert.equal(returned.searchParams.get('q'), 'energia'); assert.equal(returned.searchParams.get('province_id'), 'p'); assert.equal(returned.searchParams.has('unrelated'), false); } }); void test('catalogue pagination inheritance rejects invalid page values', () => { for (const page of ['0', '-1', '1.5', 'invalid', '9007199254740992']) { const href = catalogueDetailHref( '/carteira/synthetic', 'kind=project', '/carteira?page=' + page, ); const query = new URLSearchParams( new URL(href, 'https://portal.invalid').searchParams.get( 'catalogue_query', ), ); assert.equal(query.has('page'), false); } }); void test('funding links require installation and saved list requires authentication', () => { const base = { authenticated: false, manager: false }; assert.equal( JSON.stringify(portalNavigation(base)).includes('/fundos'), false, ); const publicMenu = JSON.stringify( portalNavigation({ ...base, funding: true }), ); assert.equal(publicMenu.includes('/fundos'), true); assert.equal(publicMenu.includes('/fundos/guardados'), false); assert.equal( JSON.stringify( portalNavigation({ ...base, funding: true, authenticated: true }), ).includes('/fundos/guardados'), true, ); }); void test('installed concours has its own primary group and preserves public and private destinations', () => { const base = { authenticated: false, manager: false }; assert.equal( portalNavigation(base).some((g) => g.id === 'procurement-area'), false, ); for (const authenticated of [false, true]) { const groups = portalNavigation({ ...base, authenticated, procurement: true, }); const group = groups.find((g) => g.id === 'procurement-area'); assert.equal(group.label, 'Concursos e anúncios'); assert.equal( destinations(groups).filter((h) => h === '/concursos').length, 1, ); assert.equal( JSON.stringify(groups.find((g) => g.id === 'discovery')).includes( '/concursos', ), false, ); assert.equal( currentNavigationGroup(groups, '/concursos?q=obras'), 'procurement-area', ); assert.equal( currentNavigationGroup(groups, '/concursos/fixture'), 'procurement-area', ); assert.equal( destinations(group.children).includes('/concursos/acompanhar'), authenticated, ); if (authenticated) assert.equal( currentNavigationItem(groups, '/concursos/acompanhar'), 'procurement-watched', ); } });