222 lines
7.1 KiB
JavaScript
222 lines
7.1 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 React from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import ts from 'typescript';
|
|
const require = createRequire(import.meta.url);
|
|
const source = fs.readFileSync(
|
|
new URL(
|
|
'../../resources/js/components/investment-map-indicators.tsx',
|
|
import.meta.url,
|
|
),
|
|
'utf8',
|
|
);
|
|
const compiled = ts.transpileModule(source, {
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.CommonJS,
|
|
target: ts.ScriptTarget.ES2022,
|
|
jsx: ts.JsxEmit.ReactJSX,
|
|
},
|
|
}).outputText;
|
|
const module = { exports: {} };
|
|
compileFunction(compiled, ['require', 'module', 'exports'])(
|
|
require,
|
|
module,
|
|
module.exports,
|
|
);
|
|
const Component = module.exports.default;
|
|
const filterIndicators = module.exports.filterMapIndicators;
|
|
const row = {
|
|
id: '1',
|
|
zone_id: 'park-a',
|
|
zone_name: 'Synthetic park',
|
|
zone_version: 3,
|
|
url: '/zonas/park-a',
|
|
theme: 'workforce',
|
|
indicator: 'employment',
|
|
label: 'Synthetic declared jobs',
|
|
value: '0',
|
|
unit: 'pessoas',
|
|
period_start: '2025-01-01',
|
|
period_end: '2025-12-31',
|
|
scope: 'Park A only',
|
|
population: 'Declared establishments',
|
|
methodology: 'Documentary register',
|
|
source_reference: '<script>synthetic source</script>',
|
|
source_date: '2026-01-15',
|
|
knowledge: 'documented',
|
|
missing_metadata: [],
|
|
};
|
|
const context = {
|
|
state: 'available',
|
|
records: [
|
|
row,
|
|
{
|
|
...row,
|
|
id: '2',
|
|
theme: 'economic_potential',
|
|
indicator: 'production',
|
|
label: 'Synthetic production',
|
|
value: '12.3400',
|
|
unit: 'toneladas',
|
|
period_start: '2024-01-01',
|
|
period_end: '2024-12-31',
|
|
},
|
|
],
|
|
zones_consulted: 1,
|
|
zone_limit: 30,
|
|
record_limit: 200,
|
|
truncated: false,
|
|
};
|
|
const render = (data, theme = 'all') =>
|
|
renderToStaticMarkup(
|
|
React.createElement(Component, { context: data, theme }),
|
|
);
|
|
|
|
test('quantitative observations keep zero, decimal precision, units, periods, scope, methodology and public source', () => {
|
|
const html = render(context);
|
|
for (const value of [
|
|
'0',
|
|
'12.3400',
|
|
'pessoas',
|
|
'toneladas',
|
|
'2025-01-01',
|
|
'2024-01-01',
|
|
'Park A only',
|
|
'Declared establishments',
|
|
'Documentary register',
|
|
'2026-01-15',
|
|
'Versão pública 3',
|
|
])
|
|
assert.ok(html.includes(value), value);
|
|
assert.match(html, /Sem somas, conversões de unidade ou moeda/);
|
|
assert.match(html, /Emprego não mede trabalhadores disponíveis/);
|
|
assert.match(html, /não constituem um índice de potencial/);
|
|
assert.doesNotMatch(html, /<script>/);
|
|
assert.match(html, /href="\/zonas\/park-a"/);
|
|
});
|
|
|
|
test('themes separate labour and economic observations without inventing totals or losing metadata', () => {
|
|
assert.match(render(context, 'workforce'), /pessoas/);
|
|
assert.doesNotMatch(render(context, 'workforce'), /toneladas/);
|
|
assert.match(render(context, 'economic_potential'), /12.3400/);
|
|
assert.doesNotMatch(
|
|
render(context, 'economic_potential'),
|
|
/Synthetic declared jobs/,
|
|
);
|
|
assert.equal(render(context, 'resource'), '');
|
|
});
|
|
|
|
test('unknown values, disputes, missing metadata, incomplete coverage and excluded scope remain explicit', () => {
|
|
const html = render({
|
|
...context,
|
|
truncated: true,
|
|
records: [
|
|
{
|
|
...row,
|
|
value: null,
|
|
unit: null,
|
|
knowledge: 'unknown',
|
|
missing_metadata: ['unit', 'population'],
|
|
},
|
|
{ ...row, id: '3', knowledge: 'disputed' },
|
|
],
|
|
});
|
|
assert.match(html, /Por determinar/);
|
|
assert.match(html, /Em divergência/);
|
|
assert.match(html, /Informação em falta: unidade, população\/base/);
|
|
assert.match(html, /limitada a 30 fichas e 200 observações/);
|
|
assert.match(
|
|
render({ ...context, records: [] }),
|
|
/Valor desconhecido não equivale a zero/,
|
|
);
|
|
assert.match(
|
|
render({ ...context, state: 'not_included' }),
|
|
/Não foi feita uma medição quantitativa/,
|
|
);
|
|
assert.match(
|
|
render({ ...context, state: 'unavailable' }),
|
|
/indisponíveis nesta versão/,
|
|
);
|
|
assert.match(render(null), /A carregar indicadores/);
|
|
assert.match(
|
|
render({ ...context, state: 'error' }),
|
|
/Não foi possível consultar os indicadores/,
|
|
);
|
|
});
|
|
|
|
test('indicator and inclusive temporal overlap filters preserve exact source values without mutation', () => {
|
|
const records = [
|
|
row,
|
|
{
|
|
...row,
|
|
id: 'prior',
|
|
period_start: '2024-01-01',
|
|
period_end: '2024-12-31',
|
|
},
|
|
{
|
|
...row,
|
|
id: 'production',
|
|
indicator: 'production',
|
|
value: '1234567890123.456789',
|
|
},
|
|
];
|
|
const before = JSON.stringify(records);
|
|
assert.deepEqual(
|
|
filterIndicators(records, 'employment', '2025-12-31', '2026-01-01').map(
|
|
(item) => item.id,
|
|
),
|
|
['1'],
|
|
);
|
|
assert.deepEqual(
|
|
filterIndicators(records, 'all', '', '2024-01-01').map(
|
|
(item) => item.id,
|
|
),
|
|
['prior'],
|
|
);
|
|
assert.equal(
|
|
filterIndicators(records, 'production', '2025-01-01', '').at(0).value,
|
|
'1234567890123.456789',
|
|
);
|
|
assert.equal(
|
|
filterIndicators(records, 'all', '2026-01-01', '2025-01-01').length,
|
|
0,
|
|
);
|
|
assert.equal(JSON.stringify(records), before);
|
|
});
|
|
|
|
test('missing, partial, invalid and inverted source periods are visible unfiltered but never inferred from source date', () => {
|
|
const records = [
|
|
{ ...row, id: 'missing', period_start: null },
|
|
{ ...row, id: 'partial', period_start: '2025' },
|
|
{ ...row, id: 'invalid', period_start: '2025-02-30' },
|
|
{ ...row, id: 'inverted', period_start: '2026-01-01' },
|
|
{ ...row, id: 'unknown', value: null, knowledge: 'unknown' },
|
|
{ ...row, id: 'disputed', knowledge: 'disputed' },
|
|
];
|
|
assert.equal(filterIndicators(records, 'all', '', '').length, 6);
|
|
assert.deepEqual(
|
|
filterIndicators(records, 'all', '2025-01-01', '2025-12-31').map(
|
|
(item) => item.id,
|
|
),
|
|
['unknown', 'disputed'],
|
|
);
|
|
assert.equal(
|
|
filterIndicators([row], 'all', '2026-01-15', '2026-01-15').length,
|
|
0,
|
|
);
|
|
});
|
|
|
|
test('quantitative filters are labelled, scoped and explain source date and coverage limits', () => {
|
|
const html = render(context);
|
|
assert.match(html, /Filtrar observações do contexto territorial/);
|
|
assert.match(html, /Período de referência — desde/);
|
|
assert.match(html, /Período de referência — até/);
|
|
assert.match(html, /A data da fonte não substitui o período de referência/);
|
|
assert.match(html, /não alteram oportunidades ou limites de cobertura/);
|
|
assert.match(html, /Limpar filtros dos indicadores/);
|
|
});
|