Files
Investidor-V2/tests/frontend/company-comparison.test.mjs
T

336 lines
11 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { compileFunction } from 'node:vm';
import { createRequire } from 'node:module';
import { renderToStaticMarkup } from 'react-dom/server';
import ts from 'typescript';
const require = createRequire(import.meta.url);
function load(file, mocks = {}) {
const code = ts.transpileModule(
fs.readFileSync(new URL(file, import.meta.url), 'utf8'),
{
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX,
},
},
).outputText;
const module = { exports: {} };
compileFunction(code, ['require', 'module', 'exports'])(
(key) => mocks[key] ?? require(key),
module,
module.exports,
);
return module.exports;
}
const urls = load('../../resources/js/lib/company-comparison.ts');
function nodes(tree) {
if (!tree || typeof tree !== 'object') return [];
if (Array.isArray(tree)) return tree.flatMap(nodes);
return [tree, ...nodes(tree.props?.children)];
}
const questions = {
purpose: {
label: 'Finalidade',
options: {
new: 'Nova',
representation: 'Representação',
unknown: 'Por definir',
},
},
participants: {
label: 'Participantes',
options: { one: 'Um', several: 'Vários', unknown: 'Por definir' },
},
regulated: {
label: 'Regras específicas',
options: { unknown: 'Por conferir' },
},
preferred: {
label: 'Preferência',
options: { quotas: 'Quotas', unknown: 'Por definir' },
},
};
const props = {
definition: { version: 'v1', questions },
answers: {
purpose: 'new',
participants: 'one',
regulated: 'unknown',
preferred: 'quotas',
},
result: {
engine_version: 'v1',
questions,
active: Object.keys(questions),
pending: ['regulated'],
route: 'Compare alternativas.',
preparation: ['Preparar contrato.'],
scope: 'Edição indicada.',
source: {
title: 'Código Comercial',
edition: 'Edição consultada',
issued_on: '2022-05-25',
consulted_on: '2026-11-02',
url: 'https://tsrm.gov.mz/source.pdf',
},
forms: [
{
code: 'quotas',
title: 'Sociedade <SYNTHETIC>',
description: 'Capital em quotas',
articles: 'Artigo 281',
source_href: 'https://tsrm.gov.mz/source.pdf#page=42',
selected: true,
constraints: ['SYNTHETIC incompatibility'],
notes: ['Conferir modalidade.'],
},
],
},
case: null,
saved: null,
history: [],
historyNext: null,
historical: false,
workingCopy: false,
canWrite: false,
};
function harness() {
let cursor = 0;
const values = [],
refs = [],
visits = [],
requests = [];
let rejectSave = false;
const hooks = {
useState(initial) {
const i = cursor++;
if (!(i in values))
values[i] = typeof initial === 'function' ? initial() : initial;
return [
values[i],
(v) => {
values[i] = v;
},
];
},
useRef(initial) {
const i = cursor++;
if (!(i in values)) {
values[i] = { current: initial };
refs.push(values[i]);
}
return values[i];
},
};
const module = load(
'../../resources/js/pages/investment/company-comparison.tsx',
{
react: hooks,
'@inertiajs/react': {
Link: 'a',
router: { visit: (...args) => visits.push(args) },
},
'./shared': { Frame: 'main', card: '', input: '', button: '' },
'@/lib/company-comparison': urls,
'@/lib/operation-key': {
createOperationKey: () => 'SYNTHETIC-operation',
},
'@/lib/profile-json': {
profileJson: async (...args) => {
requests.push(args);
if (rejectSave)
throw new Error('SYNTHETIC conflict; keep edits');
return { saved: true };
},
},
},
);
return {
render(extra = {}) {
cursor = 0;
const outer = module.default({ ...props, ...extra });
return outer.type(outer.props);
},
refs,
visits,
requests,
fail() {
rejectSave = true;
},
};
}
const find = (tree, name) =>
nodes(tree).find((node) => node.props?.name === `answers[${name}]`);
const button = (tree, text) =>
nodes(tree).find(
(node) => node.type === 'button' && node.props.children === text,
);
await test('public comparison is a real GET form, safely renders sources and constraints, and asks no identity data', () => {
const h = harness(),
tree = h.render(),
html = renderToStaticMarkup(tree);
const form = nodes(tree).find((node) => node.type === 'form');
assert.equal(form.props.method, 'get');
assert.equal(form.props.action, '/formas-juridicas');
assert.match(html, /Sociedade &lt;SYNTHETIC&gt;/);
assert.match(html, /Incompatibilidade a resolver/);
assert.match(html, /Preferência para aprofundar/);
assert.match(html, /2022-05-25/);
assert.doesNotMatch(html, /name="(email|nuit|name|registration)"/);
});
await test('changing to representation hides inapplicable questions while keeping their answers for later recovery', () => {
const h = harness();
find(h.render(), 'purpose').props.onChange({
target: { value: 'representation' },
});
const tree = h.render();
assert.equal(find(tree, 'participants').type, 'input');
assert.equal(find(tree, 'participants').props.type, 'hidden');
assert.equal(find(tree, 'participants').props.value, 'one');
assert.equal(find(tree, 'preferred').props.value, 'quotas');
find(tree, 'purpose').props.onChange({ target: { value: 'new' } });
assert.equal(find(h.render(), 'participants').props.value, 'one');
});
await test('dossier choice uses current browser values and transports only answer codes through normal authentication', () => {
const h = harness(),
tree = h.render();
h.refs[0].current = {};
const old = globalThis.FormData;
globalThis.FormData = class {
get(key) {
return key === 'answers[participants]' ? 'several' : null;
}
};
try {
button(tree, 'Escolher dossier para guardar').props.onClick();
} finally {
globalThis.FormData = old;
}
const target = new URL(h.visits[0][0], 'https://portal.test');
assert.equal(target.pathname, '/investimentos');
assert.equal(
target.searchParams.get('company_comparison[participants]'),
'several',
);
assert.equal(
target.searchParams.get('company_comparison[preferred]'),
'quotas',
);
});
await test('a failed save keeps actual field values and context, and never navigates as though the comparison was saved', async () => {
const h = harness();
h.fail();
const extra = {
case: { id: 'dossier-a', title: 'SYNTHETIC dossier', version: 7 },
canWrite: true,
};
let tree = h.render(extra);
h.refs[0].current = {};
find(tree, 'participants').props.onChange({ target: { value: 'several' } });
tree = h.render(extra);
const old = globalThis.FormData;
globalThis.FormData = class {
get(key) {
return key === 'answers[participants]' ? 'several' : null;
}
};
try {
await button(tree, 'Guardar no dossier').props.onClick();
} finally {
globalThis.FormData = old;
}
assert.equal(h.requests[0][0], '/investimentos/dossier-a/forma-juridica');
assert.equal(h.requests[0][1].version, 7);
assert.equal(h.requests[0][1].answers.participants, 'several');
assert.equal(h.visits.length, 0);
const after = h.render(extra);
assert.equal(find(after, 'participants').props.value, 'several');
assert.match(renderToStaticMarkup(after), /SYNTHETIC conflict; keep edits/);
assert.equal(
button(after, 'Reabrir com estas respostas').props.type,
'submit',
);
assert.equal(
nodes(after).find((node) => node.type === 'form').props.method,
'get',
);
});
await test('historical and superseded results retain their own question labels and source and require explicit current preparation', () => {
const h = harness();
const historicalResult = {
...props.result,
engine_version: 'older',
questions: {
...questions,
participants: {
...questions.participants,
label: 'SYNTHETIC historical wording',
},
},
};
const tree = h.render({
case: { id: 'dossier-a', title: 'Dossier', version: 3 },
canWrite: true,
historical: true,
saved: { revision: 1, at: '2026-11-01', result: historicalResult },
result: historicalResult,
});
const html = renderToStaticMarkup(tree);
assert.match(html, /SYNTHETIC historical wording/);
assert.equal(nodes(tree).filter((node) => node.type === 'form').length, 0);
const link = nodes(tree).find(
(node) =>
node.props?.children ===
'Preparar nova comparação com estas respostas',
);
assert.equal(
new URL(link.props.href, 'https://portal.test').searchParams.get(
'answers[participants]',
),
'one',
);
assert.equal(
new URL(link.props.href, 'https://portal.test').searchParams.has(
'revision',
),
false,
);
});
await test('successful save recovers the saved dossier edition and history links retain the exact dossier scope', async () => {
const h = harness(),
extra = {
case: { id: 'dossier-a', title: 'Dossier', version: 5 },
canWrite: true,
history: [
{
id: 'event-a',
revision: 2,
at: '2026-11-02',
engine_version: 'v1',
},
],
historyNext: 100,
};
const tree = h.render(extra);
h.refs[0].current = {};
const old = globalThis.FormData;
globalThis.FormData = class {
get() {
return null;
}
};
try {
await button(tree, 'Guardar no dossier').props.onClick();
} finally {
globalThis.FormData = old;
}
assert.equal(h.visits[0][0], '/investimentos/dossier-a/forma-juridica');
assert.equal(h.visits[0][1].preserveState, false);
const html = renderToStaticMarkup(tree);
assert.match(html, /dossier-a\/forma-juridica\?revision=event-a/);
assert.match(html, /dossier-a\/forma-juridica\?history_before=100/);
});