Files
Investidor-V2/tests/frontend/portal-horizontal-shell.test.mjs
T
2026-11-10 15:48:23 +02:00

159 lines
5.6 KiB
JavaScript

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.',
);