236 lines
8.2 KiB
JavaScript
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);
|
|
});
|