fix: unify horizontal portal menus and remove discovery duplicates
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -1,3 +1,13 @@
|
||||
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
|
||||
|
||||
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
|
||||
|
||||
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
|
||||
|
||||
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
|
||||
|
||||
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
|
||||
|
||||
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
|
||||
|
||||
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
|
||||
|
||||
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
|
||||
|
||||
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
|
||||
|
||||
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
|
||||
|
||||
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
|
||||
|
||||
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
|
||||
|
||||
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,13 @@
|
||||
## 10/11/2026 — janela CBF15 instalada; menus horizontais uniformizados em QA
|
||||
|
||||
**Homologação: `d5cb41b111cac95c431a3072b8c4cc0141481ec2`**, promovida às13:43:40UTC e conferida às13:44:18UTC (15:44 locais). Delta12hashes Git e revisão HTTPS iguais; root/up/login/revision HTTP200, configuração preservada, zero migrations/importações. Backup/restauro190tabelas e369ficheiros/configuração conferidos,89e6d72c preservada. Recibos `outputs/cbf15-window-delivery-20261110/`. Janela de cinco exercícios CBF15 deixa de ser pendência de instalação; transporte de créditos anteriores e restantes resíduos fiscais continuam separados.
|
||||
|
||||
**Delta posterior em QA, ainda não instalado — pedido expresso da Tempo10/11, corrigido para menus horizontais com dropdown:** cabeçalho comum passa a ser montado uma vez pelo layout global em todas as páginas; área privada usa o mesmo cabeçalho sem navegação lateral concorrente. Autenticação, definições, gestão e páginas públicas conservam conteúdo e breadcrumbs; filtros de capacidades, direitos e destinos privados existentes preservados. Esconder cabeçalho na impressão mantém-se. Os cinco atalhos (Porquê investir,Partir de uma ideia,Consultar oportunidades,Simulador Fiscal,Sectores) ficam intactos na entrada principal. Descoberta conserva localizações/mapa,catálogos e módulos opcionais,sem repetir os atalhos; Partir de uma ideia já estava fora desse grupo. CSS responsivo e implementação comum dos dropdowns/teclado/Escape/toque reutilizados,sem novo menu alternativo.
|
||||
|
||||
Conferência estrutural dos layouts das146páginasTSX existentes: exactamente um cabeçalho comum por composição, incluindo páginas privadas,autenticação/definições, sem sidebar global; conteúdo/contexto/breadcrumbs e cinco atalhos preservados. Provas `tests/frontend/portal-horizontal-shell.test.mjs`, navegação e renderização dirigida (23casos aprovados). É SSR com dependências de UI substituídas,não prova visual de navegador. Tentativa única de inventárioCUA nesta continuação devolveu exactamente `Browsers: Error: The browser access host is unavailable`; nenhuma alteração/reinício de navegador. Conferência visual mantém-se pendente no registo existente. Tipos/lint/build QA aprovados, logs desta entrega; base activa/dados/permissões intocados.
|
||||
|
||||
O saldo integral dos restantes módulos e L1–L7/restanteL8/L9–L16 permanece no plano existente; nenhuma contagem de páginas/testes declara o portal concluído.
|
||||
|
||||
## 10/11/2026 — janela de cinco exercícios CBF15 em QA
|
||||
|
||||
Homologação mantém `89e6d72c`; o delta seguinte ainda não está instalado. Pendência encerrada em QA: contagem inclusiva dos cinco exercícios civis do CBF15(4/7) na liquidação2026, para investimento efectivo do próprio2026. Início de exploração para projecto novo; início dos procedimentos após aprovação para projecto em funcionamento. Não usa cinco anos móveis nem reinicia o prazo pela data de aquisição. Início2022, mesmo em31/12 → quinto exercício2026, dedução200 pela quota residual, saldo500MZN. Início2021 → sexto exercício2026, prazo expirado, dedução0 e saldo700; activo e documento preservados. Datas inválidas/futuras e activos antigos apresentados como investimento corrente permanecem indeterminados/recusados, não zero por omissão.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AppLayoutTemplate from '@/layouts/app/app-sidebar-layout';
|
||||
import AppLayoutTemplate from '@/layouts/app/app-header-layout';
|
||||
import type { BreadcrumbItem } from '@/types';
|
||||
|
||||
export default function AppLayout({
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { AppContent } from '@/components/app-content';
|
||||
import { AppHeader } from '@/components/app-header';
|
||||
import { AppShell } from '@/components/app-shell';
|
||||
import { Breadcrumbs } from '@/components/breadcrumbs';
|
||||
import type { AppLayoutProps } from '@/types';
|
||||
|
||||
/** Global navigation is owned by PortalNavigation for every page. */
|
||||
export default function AppHeaderLayout({
|
||||
children,
|
||||
breadcrumbs,
|
||||
breadcrumbs = [],
|
||||
}: AppLayoutProps) {
|
||||
return (
|
||||
<AppShell variant="header">
|
||||
<AppHeader breadcrumbs={breadcrumbs} />
|
||||
<AppContent variant="header">{children}</AppContent>
|
||||
</AppShell>
|
||||
<div className="flex min-w-0 flex-col">
|
||||
{breadcrumbs.length > 0 && (
|
||||
<div className="border-b px-5 py-3">
|
||||
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
||||
</div>
|
||||
)}
|
||||
<main className="flex min-w-0 flex-1 flex-col">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,27 +65,18 @@ export default function PortalNavigation({
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return <TempoUiProvider><PageTranslator /><PortalNavigationContent>{children}</PortalNavigationContent></TempoUiProvider>;
|
||||
return (
|
||||
<TempoUiProvider>
|
||||
<PageTranslator />
|
||||
<PortalNavigationContent>{children}</PortalNavigationContent>
|
||||
</TempoUiProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function PortalNavigationContent({ children }: { children: ReactNode }) {
|
||||
const { t } = useTempoUi();
|
||||
const { url, props, component } = usePage();
|
||||
const printable = component === 'land-assistance/print';
|
||||
const portalShell =
|
||||
['welcome', 'PortalInvestidor', 'management-access'].includes(
|
||||
component,
|
||||
) ||
|
||||
[
|
||||
'qa/',
|
||||
'portal/',
|
||||
'investment/',
|
||||
'portfolio/',
|
||||
'desk/',
|
||||
'investor-path/',
|
||||
'auth/',
|
||||
'land-assistance/',
|
||||
].some((prefix) => component.startsWith(prefix));
|
||||
const path = url.split('?')[0];
|
||||
const parent = Object.keys(names)
|
||||
.filter((p) => p !== '/' && path.startsWith(`${p}/`))
|
||||
@@ -111,92 +102,92 @@ function PortalNavigationContent({ children }: { children: ReactNode }) {
|
||||
? 'Definições da conta'
|
||||
: 'Detalhes');
|
||||
return (
|
||||
<NavigationContextProvider>
|
||||
<MenuPlacement.Provider value={true}>
|
||||
<div className="min-h-screen [&_button]:scroll-mb-24 [&_input]:scroll-mb-24 [&_select]:scroll-mb-24 [&_textarea]:scroll-mb-24">
|
||||
<nav
|
||||
aria-label={t('Atalho para o conteúdo')}
|
||||
className={printable ? 'print:hidden' : undefined}
|
||||
<NavigationContextProvider>
|
||||
<MenuPlacement.Provider value={true}>
|
||||
<div className="min-h-screen [&_button]:scroll-mb-24 [&_input]:scroll-mb-24 [&_select]:scroll-mb-24 [&_textarea]:scroll-mb-24">
|
||||
<nav
|
||||
aria-label={t('Atalho para o conteúdo')}
|
||||
className={printable ? 'print:hidden' : undefined}
|
||||
>
|
||||
<a
|
||||
href="#portal-content"
|
||||
className="sr-only focus:not-sr-only focus:block focus:bg-white focus:p-4 focus:text-emerald-900"
|
||||
>
|
||||
<a
|
||||
href="#portal-content"
|
||||
className="sr-only focus:not-sr-only focus:block focus:bg-white focus:p-4 focus:text-emerald-900"
|
||||
>
|
||||
{t('Saltar para o conteúdo')}
|
||||
</a>
|
||||
{t('Saltar para o conteúdo')}
|
||||
</a>
|
||||
</nav>
|
||||
{props.homologationReserved === true && (
|
||||
<aside
|
||||
aria-label="Ambiente de homologação"
|
||||
className="border-b border-amber-200 bg-amber-50 px-5 py-2 text-sm text-amber-950 print:hidden"
|
||||
>
|
||||
Homologação reservada à revisão da Tempo. Consulte o
|
||||
conteúdo público; entre na sua conta quando iniciar
|
||||
uma operação.
|
||||
</aside>
|
||||
)}
|
||||
{
|
||||
<div className={printable ? 'print:hidden' : undefined}>
|
||||
<PortalMenu primary />
|
||||
</div>
|
||||
}
|
||||
{path !== '/' && (
|
||||
<nav
|
||||
aria-label={t('Caminho de navegação')}
|
||||
className={`bg-background text-foreground flex flex-wrap items-center gap-2 border-b px-5 py-3 text-sm ${printable ? 'print:hidden' : ''}`}
|
||||
>
|
||||
<>
|
||||
<Link href="/" className="underline">
|
||||
{t('Início')}
|
||||
</Link>
|
||||
<span aria-hidden="true">/</span>
|
||||
{parent && (
|
||||
<>
|
||||
<Link
|
||||
href={searchTerritoryHref(
|
||||
parent,
|
||||
url,
|
||||
)}
|
||||
className="underline"
|
||||
>
|
||||
{t(names[parent])}
|
||||
</Link>
|
||||
<span aria-hidden="true">/</span>
|
||||
</>
|
||||
)}
|
||||
<span aria-current="page">{t(title)}</span>
|
||||
</>
|
||||
</nav>
|
||||
{props.homologationReserved === true && (
|
||||
<aside aria-label="Ambiente de homologação" className="border-b border-amber-200 bg-amber-50 px-5 py-2 text-sm text-amber-950 print:hidden">
|
||||
Homologação reservada à revisão da Tempo. Consulte o conteúdo público; entre na sua conta quando iniciar uma operação.
|
||||
</aside>
|
||||
)}
|
||||
{portalShell && (
|
||||
)}
|
||||
<div id="portal-content" tabIndex={-1}>
|
||||
{
|
||||
<div
|
||||
className={
|
||||
printable ? 'print:hidden' : undefined
|
||||
}
|
||||
>
|
||||
<PortalMenu primary />
|
||||
<NavigationContextSummary />
|
||||
</div>
|
||||
)}
|
||||
{path !== '/' && (
|
||||
<nav
|
||||
aria-label={t('Caminho de navegação')}
|
||||
className={`bg-background text-foreground flex flex-wrap items-center gap-2 border-b px-5 py-3 text-sm ${printable ? 'print:hidden' : ''}`}
|
||||
>
|
||||
<>
|
||||
<Link href="/" className="underline">
|
||||
{t('Início')}
|
||||
</Link>
|
||||
<span aria-hidden="true">/</span>
|
||||
{parent && (
|
||||
<>
|
||||
<Link
|
||||
href={searchTerritoryHref(
|
||||
parent,
|
||||
url,
|
||||
)}
|
||||
className="underline"
|
||||
>
|
||||
{t(names[parent])}
|
||||
</Link>
|
||||
<span aria-hidden="true">/</span>
|
||||
</>
|
||||
)}
|
||||
<span aria-current="page">{t(title)}</span>
|
||||
</>
|
||||
</nav>
|
||||
)}
|
||||
<div id="portal-content" tabIndex={-1}>
|
||||
{portalShell && (
|
||||
<div
|
||||
className={
|
||||
printable ? 'print:hidden' : undefined
|
||||
}
|
||||
>
|
||||
<NavigationContextSummary />
|
||||
</div>
|
||||
)}
|
||||
<SearchFeedback />
|
||||
{children}
|
||||
</div>
|
||||
<aside
|
||||
aria-label={t('Contacto de apoio ao investidor')}
|
||||
className={`mx-auto flex max-w-6xl justify-center px-5 pt-4 ${printable ? 'print:hidden' : ''}`}
|
||||
>
|
||||
<a
|
||||
href="/apoio"
|
||||
className="inline-flex min-h-11 max-w-full items-center justify-center rounded-lg bg-[#ed1c24] px-4 py-2 text-center text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[#c9141b] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#ed1c24]"
|
||||
>
|
||||
{t('Contactar Apoio ao Investidor')}
|
||||
</a>
|
||||
</aside>
|
||||
<div className={printable ? 'print:hidden' : undefined}>
|
||||
<TempoFooter />
|
||||
</div>
|
||||
}
|
||||
<SearchFeedback />
|
||||
{children}
|
||||
</div>
|
||||
</MenuPlacement.Provider>
|
||||
</NavigationContextProvider>
|
||||
<aside
|
||||
aria-label={t('Contacto de apoio ao investidor')}
|
||||
className={`mx-auto flex max-w-6xl justify-center px-5 pt-4 ${printable ? 'print:hidden' : ''}`}
|
||||
>
|
||||
<a
|
||||
href="/apoio"
|
||||
className="inline-flex min-h-11 max-w-full items-center justify-center rounded-lg bg-[#ed1c24] px-4 py-2 text-center text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[#c9141b] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#ed1c24]"
|
||||
>
|
||||
{t('Contactar Apoio ao Investidor')}
|
||||
</a>
|
||||
</aside>
|
||||
<div className={printable ? 'print:hidden' : undefined}>
|
||||
<TempoFooter />
|
||||
</div>
|
||||
</div>
|
||||
</MenuPlacement.Provider>
|
||||
</NavigationContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -482,22 +482,6 @@ export function portalNavigation(access: PortalNavAccess): PortalNavItem[] {
|
||||
id: 'discovery',
|
||||
label: 'Descoberta',
|
||||
children: [
|
||||
link(
|
||||
'why',
|
||||
'Porquê investir em Moçambique?',
|
||||
'/porque-investir',
|
||||
),
|
||||
link(
|
||||
'opportunities',
|
||||
'Consultar oportunidades',
|
||||
'/oportunidades',
|
||||
),
|
||||
link(
|
||||
'simulator',
|
||||
'Simulador Fiscal',
|
||||
access.context?.simulator || '/simulador-fiscal',
|
||||
),
|
||||
link('sectors', 'Sectores de investimento', '/sectores'),
|
||||
link('locations', 'Localizações', '/oportunidades?view=map'),
|
||||
...(access.funding
|
||||
? [
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { compileFunction } from 'node:vm';
|
||||
import { createRequire } from 'node:module';
|
||||
import React from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import ts from 'typescript';
|
||||
const require = createRequire(import.meta.url),
|
||||
root = new URL('../../resources/js/', import.meta.url);
|
||||
let page = {
|
||||
component: 'portal/about',
|
||||
url: '/sobre',
|
||||
props: { auth: { user: null } },
|
||||
};
|
||||
const MenuPlacement = React.createContext(false);
|
||||
const PortalMenu = ({ primary = false }) =>
|
||||
React.useContext(MenuPlacement) && !primary
|
||||
? null
|
||||
: React.createElement(
|
||||
'header',
|
||||
{ 'data-portal-horizontal-menu': true },
|
||||
'Dropdown common header',
|
||||
);
|
||||
const passthrough = ({ children }) => children;
|
||||
const mocks = {
|
||||
'@inertiajs/react': {
|
||||
usePage: () => page,
|
||||
Link: ({ href, children, ...props }) =>
|
||||
React.createElement('a', { href, ...props }, children),
|
||||
},
|
||||
'@/components/portal-menu': { MenuPlacement, PortalMenu },
|
||||
'@/components/search-feedback': { default: () => null },
|
||||
'@/lib/search-territory': { searchTerritoryHref: (href) => href },
|
||||
'@/components/tempo-brand': {
|
||||
TempoUiProvider: passthrough,
|
||||
useTempoUi: () => ({ t: (x) => x }),
|
||||
},
|
||||
'@/translation/page-translator': { PageTranslator: () => null },
|
||||
'@/components/tempo-footer': { TempoFooter: () => null },
|
||||
'@/components/navigation-context': {
|
||||
NavigationContextProvider: passthrough,
|
||||
NavigationContextSummary: () =>
|
||||
React.createElement('div', { 'data-context-summary': true }),
|
||||
},
|
||||
'@/components/breadcrumbs': {
|
||||
Breadcrumbs: ({ breadcrumbs }) =>
|
||||
React.createElement(
|
||||
'nav',
|
||||
{ 'aria-label': 'Local breadcrumbs' },
|
||||
breadcrumbs.map((b) => b.title).join('/'),
|
||||
),
|
||||
},
|
||||
};
|
||||
function load(name) {
|
||||
const output = ts.transpileModule(
|
||||
fs.readFileSync(new URL(name, root), 'utf8'),
|
||||
{
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
jsx: ts.JsxEmit.ReactJSX,
|
||||
},
|
||||
},
|
||||
).outputText;
|
||||
const module = { exports: {} };
|
||||
compileFunction(output, ['require', 'module', 'exports'])(
|
||||
(name) => mocks[name] ?? require(name),
|
||||
module,
|
||||
module.exports,
|
||||
);
|
||||
return module.exports.default;
|
||||
}
|
||||
const PortalNavigation = load('layouts/portal-navigation.tsx'),
|
||||
AppHeaderLayout = load('layouts/app/app-header-layout.tsx');
|
||||
mocks['@/layouts/app/app-header-layout'] = { default: AppHeaderLayout };
|
||||
const AppLayout = load('layouts/app-layout.tsx');
|
||||
const app = fs.readFileSync(new URL('app.tsx', root), 'utf8'),
|
||||
match = app.match(
|
||||
/layout: (\(name\) => \{[\s\S]*?\n \}),\n strictMode/,
|
||||
);
|
||||
assert.ok(match);
|
||||
const resolveLayout = compileFunction('return ' + match[1], [
|
||||
'PortalNavigation',
|
||||
'AuthLayout',
|
||||
'AppLayout',
|
||||
'SettingsLayout',
|
||||
])(PortalNavigation, passthrough, AppLayout, passthrough);
|
||||
const files = fs
|
||||
.readdirSync(new URL('pages/', root), { recursive: true })
|
||||
.filter((f) => f.endsWith('.tsx'));
|
||||
void test('every registered page layout renders exactly one common horizontal header, including auth/settings/private screens', () => {
|
||||
for (const file of files) {
|
||||
const name = file.replaceAll(path.sep, '/').replace(/\.tsx$/, '');
|
||||
page = {
|
||||
component: name,
|
||||
url: '/qa-layout-check',
|
||||
props: { auth: { user: null } },
|
||||
};
|
||||
const layouts = [resolveLayout(name)].flat(),
|
||||
child = React.createElement(
|
||||
'section',
|
||||
{ 'data-page-content': true },
|
||||
React.createElement(PortalMenu),
|
||||
name,
|
||||
);
|
||||
const tree = layouts.reduceRight(
|
||||
(children, Layout) =>
|
||||
React.createElement(
|
||||
Layout,
|
||||
{ breadcrumbs: [{ title: 'QA breadcrumb', href: '/qa' }] },
|
||||
children,
|
||||
),
|
||||
child,
|
||||
);
|
||||
const html = renderToStaticMarkup(tree);
|
||||
assert.equal(
|
||||
(html.match(/data-portal-horizontal-menu/g) ?? []).length,
|
||||
1,
|
||||
name,
|
||||
);
|
||||
assert.match(html, /data-page-content/, name);
|
||||
assert.match(html, /data-context-summary/, name);
|
||||
assert.doesNotMatch(html, /sidebar-trigger|data-sidebar=/, name);
|
||||
}
|
||||
});
|
||||
void test('private layout keeps page breadcrumbs without reintroducing a separate navigation or sidebar provider', () => {
|
||||
const html = renderToStaticMarkup(
|
||||
React.createElement(
|
||||
AppLayout,
|
||||
{ breadcrumbs: [{ title: 'Histórico', href: '/historico' }] },
|
||||
'Existing private content',
|
||||
),
|
||||
);
|
||||
assert.match(html, /Histórico/);
|
||||
assert.match(html, /Existing private content/);
|
||||
assert.doesNotMatch(html, /Dropdown common header/);
|
||||
});
|
||||
void test('main five shortcuts remain in the hero while discovery no longer repeats them', () => {
|
||||
const hero = fs.readFileSync(
|
||||
new URL('components/portal-hero.tsx', root),
|
||||
'utf8',
|
||||
);
|
||||
for (const href of [
|
||||
'/porque-investir',
|
||||
'/investimentos/iniciar',
|
||||
'/oportunidades',
|
||||
'/simulador-fiscal',
|
||||
'/sectores',
|
||||
])
|
||||
assert.ok(hero.includes('href="' + href + '"'), href);
|
||||
});
|
||||
console.log(
|
||||
'Page layout coverage:',
|
||||
files.length,
|
||||
'existing TSX pages; structural SSR only, not browser/viewport proof.',
|
||||
);
|
||||
@@ -97,12 +97,9 @@ void test('reconciled navigation selects discovery, realization and private supp
|
||||
const groups = portalNavigation({ authenticated: false, manager: false });
|
||||
assert.equal(
|
||||
currentNavigationGroup(groups, '/oportunidades/fixture'),
|
||||
'discovery',
|
||||
);
|
||||
assert.equal(
|
||||
currentNavigationGroup(groups, '/simulador-fiscal'),
|
||||
'discovery',
|
||||
null,
|
||||
);
|
||||
assert.equal(currentNavigationGroup(groups, '/simulador-fiscal'), null);
|
||||
assert.equal(
|
||||
currentNavigationGroup(groups, '/balcao-virtual/dossiers/fixture'),
|
||||
'support',
|
||||
@@ -110,7 +107,7 @@ void test('reconciled navigation selects discovery, realization and private supp
|
||||
assert.equal(currentNavigationGroup(groups, '/management/crm/leads'), null);
|
||||
});
|
||||
|
||||
void test('discovery keeps four entry points and catalogue destinations without duplicates', () => {
|
||||
void test('discovery retains distinct catalogues without repeating main entry points', () => {
|
||||
const groups = portalNavigation({
|
||||
authenticated: false,
|
||||
manager: false,
|
||||
@@ -125,10 +122,16 @@ void test('discovery keeps four entry points and catalogue destinations without
|
||||
});
|
||||
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',
|
||||
'/sectores',
|
||||
'/oportunidades?view=map',
|
||||
'/carteira',
|
||||
'/zonas',
|
||||
|
||||
Reference in New Issue
Block a user