Files
Investidor-V2/tests/frontend/partnership-history.test.mjs
T

238 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { createRequire } from 'node:module';
import { compileFunction } from 'node:vm';
import ts from 'typescript';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
const require = createRequire(import.meta.url);
const sharedText = fs.readFileSync(
'resources/js/pages/partnerships/shared.tsx',
'utf8',
);
const labelSource = sharedText.match(
/export const labels: Record<string, string> = (\{[\s\S]*?\n\});/,
)[1];
const labels = compileFunction(`return (${labelSource});`, [])();
function load(file) {
const source = ts.transpileModule(fs.readFileSync(file, 'utf8'), {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
jsx: ts.JsxEmit.ReactJSX,
},
}).outputText;
const module = { exports: {} };
compileFunction(source, ['require', 'module', 'exports'])(
(id) => {
if (id === './shared')
return {
Frame: ({ title, children }) =>
React.createElement(
'main',
{},
React.createElement('h1', {}, title),
children,
),
labels,
};
if (id === '@inertiajs/react')
return {
Link: ({ children, ...props }) =>
React.createElement('a', props, children),
router: {},
};
return require(id);
},
module,
module.exports,
);
return module.exports.default;
}
const Page = load('resources/js/pages/partnerships/interest-history.tsx');
const Dashboard = load('resources/js/pages/partnerships/dashboard.tsx');
const snapshot = () => ({
availability: 'available',
version: 1,
status: 'submitted',
scope_known: true,
organization_id: null,
fields: { proposal: 'SYNTHETIC proposal', capacity: null },
consent_known: true,
consent: false,
assessment_recorded: true,
assessment: [],
references: [],
complete: true,
});
const entry = () => ({
version: 2,
restricted: false,
action: 'assess',
actor_name: 'SYNTHETIC owner',
occurred_at: '2026-11-03',
reason: 'SYNTHETIC reason',
previous: snapshot(),
after: snapshot(),
partnership_version: 4,
matching: {
score_min: 0,
score_max: 0,
eligibility: 'unknown',
algorithm_version: 'synthetic-v1',
weights_hash: 'synthetic-hash',
unknown_weight: 0,
criteria: [
{
code: 'capacity',
label: 'SYNTHETIC frozen label',
weight: 20,
mandatory: true,
state: 'unknown',
evidence: 'SYNTHETIC frozen evidence',
explanation: 'SYNTHETIC frozen explanation',
},
],
},
});
const props = () => ({
candidacy_id: 10,
entries: [entry()],
next_url: null,
first_url: '/parcerias/candidaturas/10/historico',
older_page: false,
partnership_href: null,
dashboard_href: '/parcerias/painel',
});
const render = (p = props()) =>
renderToStaticMarkup(React.createElement(Page, p));
test('history renders frozen decision parameters, including valid zero scores', () => {
const html = render();
for (const value of [
'0–0%',
'Peso: 20',
'Obrigatório',
'SYNTHETIC frozen label',
'SYNTHETIC frozen evidence',
'SYNTHETIC frozen explanation',
'synthetic-v1',
'synthetic-hash',
])
assert.ok(html.includes(value), value);
assert.ok(!html.includes('Por determinar'));
});
test('missing frozen scores and obligations remain unknown instead of false or zero', () => {
const p = props();
p.entries[0].matching.score_min = null;
p.entries[0].matching.criteria[0].mandatory = null;
p.entries[0].matching.criteria[0].weight = null;
const html = render(p);
assert.ok(html.includes('Por determinar'));
assert.ok(html.includes('Obrigatoriedade não registada'));
assert.ok(!html.includes('0–0%'));
assert.ok(!html.includes('Não obrigatório'));
});
test('old incomplete events are explicit and never supplied from a current proposal', () => {
const p = props();
p.entries[0].after = {
availability: 'available',
complete: false,
fields: [],
assessment: [],
assessment_recorded: false,
scope_known: false,
};
p.entries[0].matching = null;
p.entries[0].action = null;
const html = render(p);
for (const value of [
'Registo parcial',
'Não consta do registo',
'Âmbito não registado',
'Não foi recalculado',
'Alteração registada',
])
assert.ok(html.includes(value), value);
});
test('missing and intentionally empty fields or consent are distinguished', () => {
const html = render();
assert.match(html, /Capacidade disponível<\/dt><dd[^>]*>Não indicado/);
assert.match(
html,
/Contribuição proposta<\/dt><dd[^>]*>Não consta do registo/,
);
assert.ok(html.includes('Não autorizada'));
assert.ok(html.includes('Sem critérios avaliados nesta versão'));
});
test('restricted scope and missing legacy scope have different explanations', () => {
const p = props();
p.entries = [
{
...entry(),
restricted: true,
previous: { availability: 'restricted' },
after: { availability: 'scope_unknown' },
matching: null,
actor_name: null,
reason: null,
},
];
const html = render(p);
assert.ok(html.includes('Conteúdo reservado'));
assert.ok(html.includes('acesso actual'));
assert.ok(html.includes('âmbito desta versão antiga'));
assert.ok(!html.includes('SYNTHETIC owner'));
assert.ok(!html.includes('SYNTHETIC proposal'));
assert.ok(!html.includes('Correspondência guardada'));
});
test('read-only page has no write or restore controls and uses only supplied cursor navigation', () => {
const p = props();
p.next_url = '/parcerias/candidaturas/10/historico?before=opaque%2Btoken';
p.older_page = true;
const html = render(p);
assert.ok(html.includes('before=opaque%2Btoken'));
assert.ok(html.includes('Versões mais recentes'));
assert.ok(!/<(?:form|button|input|select|textarea)\b/.test(html));
assert.ok(!html.includes('Consultar parceria'));
p.partnership_href = '/parcerias/1';
assert.ok(render(p).includes('href="/parcerias/1"'));
});
test('saved user text is escaped and current catalogue names are identified', () => {
const p = props();
p.entries[0].after.fields.proposal = '<script>synthetic()</script>';
p.entries[0].after.assessment = [
{
code: 'capacity',
label: 'SYNTHETIC current catalogue',
state: 'unknown',
evidence: 'SYNTHETIC recorded evidence',
},
];
const html = render(p);
assert.ok(!html.includes('<script>'));
assert.ok(html.includes('&lt;script&gt;'));
assert.ok(html.includes('catálogo actual'));
assert.ok(html.includes('cadastro actual'));
assert.ok(html.includes('SYNTHETIC recorded evidence'));
});
test('candidate dashboard links directly to each history without relying on proposal publication', () => {
const html = renderToStaticMarkup(
React.createElement(Dashboard, {
mine: [],
interests: [
{
id: 10,
partnership_id: 1,
title: 'SYNTHETIC',
status: 'withdrawn',
room_id: null,
},
],
moderation: [],
notifications: [],
}),
);
assert.ok(html.includes('href="/parcerias/candidaturas/10/historico"'));
assert.ok(html.includes('Consultar versões e decisões'));
});