Files
Investidor-V2/tests/frontend/funding-public-presentation.test.mjs
T

309 lines
12 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';
import * as presentation from '../../resources/js/lib/funding-public-presentation.ts';
const require = createRequire(import.meta.url),
records = Object.values(
JSON.parse(
fs.readFileSync(
new URL(
'../../database/data/funding-institutions-nov2026.json',
import.meta.url,
),
'utf8',
),
).references,
).map((x) => x.draft);
function load(file) {
const module = { exports: {} };
const output = ts.transpileModule(
fs.readFileSync(
new URL('../../resources/js/' + file, import.meta.url),
'utf8',
),
{
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX,
},
},
).outputText;
compileFunction(output, ['require', 'module', 'exports'])(
(name) =>
name === '@/lib/funding-public-presentation'
? presentation
: name === '@/pages/investment/shared'
? {
Frame: ({ title, children }) =>
React.createElement(
'main',
null,
React.createElement('h1', null, title),
children,
),
card: 'card',
button: 'button',
input: 'input',
}
: name === '@inertiajs/react'
? {
Link: ({ children, href }) =>
React.createElement('a', { href }, children),
router: {},
useForm: (data) => ({ data, errors: {}, setData() {}, get() {} }),
}
: name === './shared'
? { windowLabels: {} }
: name === '@/lib/portal-api'
? {
portalRequest: () => {
throw Error(
'Read-only rendering must not send requests',
);
},
}
: name === '@/components/funding-classification'
? load('components/funding-classification.tsx')
: name === '@/components/funding-institution'
? load('components/funding-institution.tsx')
: name === '@/components/funding-dossier-choice'
? { FundingDossierChoice: () => null }
: name ===
'@/components/funding-criteria-assessment'
? load(
'components/funding-criteria-assessment.tsx',
)
: resolveLocal(name, file),
module,
module.exports,
);
return module.exports;
}
test('all270 imported institutions get readable descriptions and evidenced services without changing the published payload', () => {
assert.equal(records.length, 270);
for (const p of records) {
const original = JSON.stringify(p),
m = presentation.fundingPublicPresentation(p);
assert.ok(m.description);
assert.doesNotMatch(
m.description,
/documento fornecido|ficha|SHA-256|Público\/finalidade/,
);
assert.equal(JSON.stringify(p), original);
assert.equal(
m.products.length,
(p.products ?? []).filter((x) => x.availability !== 'closed')
.length,
);
}
});
test('deliberate editorial descriptions survive and missing products are never fabricated', () => {
const m = presentation.fundingPublicPresentation({
title: 'Example',
kind: 'institution',
summary: 'Apoio especializado ao investimento rural.',
products: [],
institution_types: ['insurer'],
});
assert.equal(m.description, 'Apoio especializado ao investimento rural.');
assert.deepEqual(m.products, []);
assert.deepEqual(m.services, []);
});
test('actual public cards and detail lead with description/products and keep import metadata out of the visitor text', () => {
const Index = load('pages/funding/index.tsx').default,
Show = load('pages/funding/show.tsx').default;
for (const raw of [
records[0],
records.find((p) => p.institution_types.includes('investment_manager')),
]) {
const p = { ...raw, windows: [], documents: [] },
item = {
id: 'synthetic',
version_id: 'synthetic-version',
version: 1,
payload: p,
};
const index = renderToStaticMarkup(
React.createElement(Index, {
items: { data: [item], links: [] },
filters: {},
kinds: {},
options: { territories: [], sectors: [] },
management: false,
}),
);
const detail = renderToStaticMarkup(
React.createElement(Show, {
record: item,
authenticated: false,
saved: null,
}),
);
for (const html of [index, detail]) {
assert.doesNotMatch(
html,
/ficha|documento fornecido|SHA-256|linha de origem|Instituições financeiras em Moçambique\.docx/i,
);
assert.match(html, /Produtos e serviços/);
assert.match(
html,
new RegExp(raw.title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
);
}
const out = new URL(
'../../outputs/funding-public-editorial-20261111/',
import.meta.url,
);
fs.mkdirSync(out, { recursive: true });
const name = p.products.length
? 'bank-with-products'
: 'investment-manager';
fs.writeFileSync(new URL(name + '-index.html', out), index);
fs.writeFileSync(new URL(name + '-detail.html', out), detail);
fs.writeFileSync(
new URL('verification.json', out),
JSON.stringify(
{
publishedSourceRecords: 270,
descriptionsPreserved: true,
productsNotFabricated: true,
actualPublicComponents: true,
metadataRemovedFromPresentation: true,
privateEditorialFieldsUnchanged: true,
activeDataWrites: 0,
visualBrowserReview: false,
},
null,
2,
),
);
assert.match(detail, /Descrição/);
if (p.products.length) assert.match(detail, /Soluções PME/);
}
});
test('Quorus and Moza use one shared clean profile in both list and detail', () => {
const Index = load('pages/funding/index.tsx').default,
Show = load('pages/funding/show.tsx').default;
const out = new URL(
'../../outputs/funding-uniform-menu-20261111/',
import.meta.url,
);
fs.mkdirSync(out, { recursive: true });
for (const title of ['Quorus', 'Moza Banco']) {
const raw = records.find((p) => p.title === title);
assert.ok(raw, title);
const p = { ...raw, windows: [], documents: [] },
item = {
id: 'synthetic',
version_id: 'synthetic-version',
version: 1,
payload: p,
};
const index = renderToStaticMarkup(
React.createElement(Index, {
items: { data: [item], links: [] },
filters: {},
kinds: {},
options: { territories: [], sectors: [] },
management: false,
}),
);
const detail = renderToStaticMarkup(
React.createElement(Show, {
record: item,
authenticated: false,
saved: null,
}),
);
for (const html of [
index.match(/<article class="card">([\s\S]*?)<\/article>/)?.[0] ??
index,
detail,
]) {
assert.doesNotMatch(
html,
/Por confirmar|por confirmar|correspondência confirmada|Instituição identificada|Entidade responsável|Sectores:|Cobertura|garantia de financiamento|Não encontrado|ficha|Sede ou contacto indicado|Informação institucional/,
);
assert.equal((html.match(/Produtos e serviços/g) || []).length, 1);
assert.equal((html.match(/Descrição/g) || []).length, 1);
}
if (title === 'Quorus') {
assert.equal(
(detail.match(/Entidade de corretagem de seguros/g) || [])
.length,
1,
);
assert.doesNotMatch(
detail,
/<h3[^>]*>Contactos|<h3[^>]*>Localização/,
);
} else {
assert.equal((detail.match(/21 342 000/g) || []).length, 1);
assert.equal(
(detail.match(/Rua dos Desportistas/g) || []).length,
1,
);
assert.equal(
(
detail.match(
/Tesouraria, investimento, construção e equipamento/g,
) || []
).length,
1,
);
assert.doesNotMatch(detail, /500\.000|30\.000\.000|Prime|spread/);
}
fs.writeFileSync(
new URL(title.replaceAll(' ', '-') + '-index.html', out),
index,
);
fs.writeFileSync(
new URL(title.replaceAll(' ', '-') + '-detail.html', out),
detail,
);
}
});
test('contact and location deduplication preserves different branches and original records', () => {
const moza = records.find((p) => p.title === 'Moza Banco'),
original = JSON.stringify(moza);
assert.equal(
presentation
.publicInstitutionContacts(moza.contacts)
.filter((c) => c.kind === 'phone').length,
1,
);
assert.equal(
presentation.publicInstitutionLocations(moza.locations).length,
1,
);
assert.equal(
presentation.publicInstitutionLocations([
...moza.locations,
{
...moza.locations[0],
role: 'branch',
address: 'Rua dos Desportistas, número 714',
},
]).length,
2,
);
assert.equal(JSON.stringify(moza), original);
});
function resolveLocal(name, parent) {
if (name === './territory-geometry-preview') return { default: () => null };
const relative = name.startsWith('@/') ? name.slice(2) : name.startsWith('.') ? path.posix.join(path.posix.dirname(parent), name) : null;
if (relative) for (const candidate of [relative, relative + '.tsx', relative + '.ts']) {
if (fs.existsSync(new URL('../../resources/js/' + candidate, import.meta.url))) return load(candidate);
}
return require(name);
}