Files
Investidor-V2/tests/frontend/demographic-reading.test.mjs
T
2026-11-15 18:56:39 +02:00

236 lines
8.2 KiB
JavaScript

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 React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import ts from 'typescript';
import pathModule from 'node:path';
const require = createRequire(import.meta.url);
function load(path, overrides = {}) {
const source = fs.readFileSync(
new URL('../../resources/js/' + path, import.meta.url),
'utf8',
);
const module = { exports: {} };
const code = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX,
},
}).outputText;
compileFunction(code, ['require', 'module', 'exports'])(
(n) => {
if (n in overrides) return overrides[n];
const relative = n.startsWith('@/') ? n.slice(2) : n.startsWith('.') ? pathModule.posix.join(pathModule.posix.dirname(path), n) : 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, overrides);
}
}
return require(n);
},
module,
module.exports,
);
return module.exports;
}
const helpers = load('lib/demographic-reading.ts');
const Summary = load('components/demographic-reading-summary.tsx', {
'@/lib/demographic-reading': helpers,
}).default;
const Details = load('components/evidence-details.tsx').default;
const Component = load('components/demographic-evidence.tsx', {
'./demographic-reading-summary': { default: Summary },
'./evidence-details': { default: Details },
}).default;
const row = (age, value, residence = 'all', year = 2026, extra = {}) => ({
year,
indicator: 'population',
age_group: age,
residence,
value,
original_value: value,
unit: 'pessoas',
data_kind: 'projected',
sheet: String(year),
cell: `${age}-${residence}`,
validation_status: 'valid',
...extra,
});
const series = (values) => ({
import_id: 'source-one',
source: 'INE',
version: 'SYNTHETIC test edition',
sha256: 'a'.repeat(64),
file_name: 'source.xlsx',
missing_years: [],
issue_counts: {},
values,
});
const ages = [
'15-19',
'20-24',
'25-29',
'30-34',
'35-39',
'40-44',
'45-49',
'50-54',
'55-59',
'60-64',
];
const base = () =>
series([
row('all', '1000.1234567890123'),
row('all', '400', 'urban'),
row('all', '600.1234567890123', 'rural'),
row('0-4', '100'),
row('5-9', '100'),
row('10-14', '100'),
...ages.map((a) => row(a, '50')),
]);
const evidence = (s) => ({
state: 'available',
start_year: 2026,
end_year: 2026,
sector: 'general',
limitations: ['Population is not demand.'],
comparisons: [],
differences: [],
territories: [
{
id: 'district-one',
name: 'Báruè',
level: 'district',
factors_to_assess: [],
series: s,
},
],
});
test('summarises compatible age/residence groups without changing original decimals', () => {
const s = base(),
before = JSON.stringify(s),
r = helpers.demographicReading(s, 2026, 2026);
assert.equal(r.last.under15, 300);
assert.equal(r.last.age15to64, 500);
assert.ok(Math.abs(r.last.ruralShare - 60.00493766) < 0.00001);
assert.equal(JSON.stringify(s), before);
assert.equal(r.growth, null);
});
test('missing age bands remain unknown; zeros are preserved', () => {
const s = base();
s.values = s.values.filter((r) => r.age_group !== '5-9');
assert.equal(helpers.demographicReading(s, 2026, 2026).last.under15, null);
const r = helpers.demographicReading(
series([
row('all', '0'),
row('all', '0', 'urban'),
row('all', '0', 'rural'),
]),
2026,
2026,
);
assert.equal(r.last.total, 0);
assert.equal(r.last.ruralShare, null);
});
test('duplicate dimensions and incompatible kinds cannot supply invented shares', () => {
const s = base();
s.values.push(row('0-4', '200'));
s.values.find((r) => r.residence === 'rural').data_kind = 'observed';
const r = helpers.demographicReading(s, 2026, 2026).last;
assert.equal(r.under15, null);
assert.equal(r.rural, null);
assert.equal(r.ruralShare, null);
});
test('inconsistent residence components do not produce percentages', () => {
const s = series([
row('all', '100'),
row('all', '80', 'urban'),
row('all', '80', 'rural'),
]);
const r = helpers.demographicReading(s, 2026, 2026).last;
assert.equal(r.urban, 80);
assert.equal(r.urbanShare, null);
assert.equal(r.ruralShare, null);
});
test('growth uses explicit comparable endpoints and does not fill missing intermediate years', () => {
const s = series([
row('all', '100', 'all', 2026),
row('all', '120', 'all', 2028),
]);
const r = helpers.demographicReading(s, 2026, 2028);
assert.equal(r.growth, 20);
assert.equal(r.years[1].total, null);
assert.equal(helpers.demographicReading(s, 2026, 2030).growth, null);
s.values[1].data_kind = 'observed';
assert.equal(helpers.demographicReading(s, 2026, 2028).growth, null);
});
test('technical source and full decimals remain available in closed details after a readable summary', () => {
const html = renderToStaticMarkup(
React.createElement(Component, { evidence: evidence([base()]) }),
);
assert.ok(
html.indexOf('Retrato da população') <
html.indexOf('Valores originais por ano'),
);
assert.match(html, /1000.1234567890123/);
assert.match(html, /Valores por dimensão, fonte e advertências/);
assert.doesNotMatch(html, /<details[^>]*\bopen\b/);
assert.match(html, /um único ano/);
assert.match(html, /capacidade instalada/);
assert.match(html, /qualificações/);
});
test('multiple editions receive separate summaries rather than a summed population', () => {
const a = series([row('all', '100')]),
b = { ...series([row('all', '900')]), import_id: 'source-two' };
const html = renderToStaticMarkup(
React.createElement(Component, { evidence: evidence([a, b]) }),
);
assert.equal((html.match(/Retrato da população/g) || []).length, 2);
assert.match(html, />100<\/dd>/);
assert.match(html, />900<\/dd>/);
});
test('demographic page offers territory-only preparation independent of old commercial filters', () => {
const Page = load('pages/investment/demography.tsx', {
'@inertiajs/react': { router: {} },
'@/components/demographic-evidence': { default: Component },
'./shared': {
Frame: ({ children }) =>
React.createElement('main', null, children),
button: '',
},
}).default;
const html = renderToStaticMarkup(
React.createElement(Page, {
evidence: evidence([base()]),
filters: { territories: ['district-one'], sector: 'general' },
territories: [],
sectors: { general: 'Contexto geral' },
}),
);
assert.match(html, /source=territory%3Adistrict-one/);
assert.doesNotMatch(html, /compra de propriedade|sector=turismo|modality=/);
assert.match(html, /Não é necessário haver uma oportunidade publicada/);
});
test('Barue 2026 provided evidence produces the readable proportions without changing originals', () => {
const fixture = JSON.parse(
fs.readFileSync(
new URL('../Fixtures/demography/barue-2026.json', import.meta.url),
'utf8',
),
);
const s = series(fixture.values),
before = JSON.stringify(s);
const r = helpers.demographicReading(s, 2026, 2026).last;
assert.equal(Math.round(r.total), 248518);
assert.equal(Number(r.ruralShare.toFixed(1)), 65.1);
assert.equal(Number(r.under15Share.toFixed(1)), 45.6);
assert.equal(Number(r.age15to64Share.toFixed(1)), 51.3);
assert.equal(JSON.stringify(s), before);
});