Files
Investidor-V2/tests/frontend/investment-map-panel.test.mjs
T
2026-11-02 20:31:15 +02:00

218 lines
7.3 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);
function load(path, mocks = {}) {
const source = fs.readFileSync(new URL(path, import.meta.url), 'utf8');
const output = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX,
},
}).outputText;
const module = { exports: {} };
compileFunction(output, ['require', 'module', 'exports'])(
(name) => mocks[name] ?? require(name),
module,
module.exports,
);
return module.exports.default;
}
const Coverage = load(
'../../resources/js/components/investment-map-coverage.tsx',
);
const flat = (node) =>
!node || typeof node !== 'object'
? []
: [node, ...React.Children.toArray(node.props.children).flatMap(flat)];
const data = {
features: [
{
geometry: { coordinates: [35, -20] },
properties: {
id: 1,
title: 'Synthetic point',
url: '/synthetic/1',
source_reference: 'Synthetic source',
source_date: '2026-10-29',
},
},
{
geometry: { coordinates: [36, -21] },
properties: {
id: 1,
title: 'Synthetic unknown',
url: '/synthetic/2',
source_reference: ' ',
source_date: null,
},
},
],
areas: [
{
title: 'Synthetic area',
geometry: { type: 'Polygon', coordinates: [] },
url: '/synthetic/area',
source_reference: 'Synthetic area source',
source_date: '',
},
],
unlocated: [{ id: 3, title: 'Synthetic unlocated', url: '/synthetic/3' }],
truncated: true,
matched_count: 8,
};
void test('map coverage distinguishes returned counts, missing provenance and truncated coverage without changing data', () => {
const before = JSON.stringify(data);
const html = renderToStaticMarkup(
React.createElement(Coverage, {
points: data.features,
areas: data.areas,
unlocated: data.unlocated.length,
truncated: true,
}),
);
assert.match(
html,
/as contagens abaixo não representam o total do cadastro/,
);
assert.match(
html,
/<th scope="row">Marcadores de investimento<\/th><td>2<\/td><td>1<\/td><td>1<\/td>/,
);
assert.match(
html,
/<th scope="row">Áreas e limites<\/th><td>1<\/td><td>0<\/td><td>1<\/td>/,
);
assert.match(
html,
/Investimentos sem ponto público utilizável nesta consulta: 1/,
);
assert.match(html, /Investimentos com ponto nesta resposta: 1/);
assert.match(
html,
/Ausência de registos não significa ausência de investimento/,
);
assert.equal(JSON.stringify(data), before);
const empty = renderToStaticMarkup(
React.createElement(Coverage, {
points: [],
areas: [],
unlocated: 0,
truncated: false,
}),
);
assert.doesNotMatch(empty, /As fichas continuam disponíveis abaixo/);
assert.doesNotMatch(empty, /Resultados limitados/);
});
void test('one layered map preserves records, coverage, unlocated links and applied filters without a duplicate map or nonadministrative inference', async () => {
const previousFetch = globalThis.fetch;
let resolve;
const requests = [];
globalThis.fetch = (url, options) => {
requests.push({ url, options });
return new Promise((done) => {
resolve = done;
});
};
const memory = [];
let cursor = 0;
const effects = [];
const hooks = {
...React,
useState(initial) {
const index = cursor++;
if (!(index in memory)) memory[index] = initial;
return [
memory[index],
(value) => {
memory[index] =
typeof value === 'function'
? value(memory[index])
: value;
},
];
},
useEffect(callback) {
effects.push(callback);
},
};
const MapView = () => null;
const SearchState = () => null;
const Panel = load(
'../../resources/js/components/investment-map-panel.tsx',
{
react: hooks,
'@/components/search-state': { default: SearchState },
'./catalogue-investment-map': { default: MapView },
'./interactive-investment-map': { default: MapView },
'./investment-map-coverage': { default: Coverage },
},
);
const render = () => {
cursor = 0;
effects.length = 0;
return Panel({ filters: { province_id: 'synthetic-province' } });
};
let cleanup;
try {
assert.equal(
flat(render()).some((node) => node.type === Coverage),
false,
);
cleanup = effects[0]();
resolve({ ok: true, json: async () => data });
await new Promise((done) => setImmediate(done));
let tree = render();
assert.equal(
flat(tree).find((node) => node.type === SearchState).props.error,
'',
);
assert.equal(
flat(tree).find((node) => node.type === SearchState).props.total,
8,
);
const map = () => flat(tree).find((node) => node.type === MapView);
assert.equal(map().props.points.length, 2);
assert.equal(map().props.areas.length, 1);
assert.equal(
flat(tree).filter((node) => node.type === MapView).length,
1,
);
assert.equal(
flat(tree).filter((node) => node.type === 'input').length,
0,
);
assert.equal(map().props.territoryId, 'synthetic-province');
const html = renderToStaticMarkup(tree);
assert.match(html, /incluindo camadas ocultas/);
assert.match(html, /href="\/synthetic\/3"/);
assert.match(
html,
/<th scope="row">Marcadores de investimento<\/th><td>2<\/td>/,
);
tree = render();
assert.equal(map().props.points, data.features);
assert.equal(map().props.areas, data.areas);
assert.equal(requests.length, 1);
assert.match(requests[0].url, /province_id=synthetic-province/);
cursor = 0;
const changed = Panel({
filters: { geo_location_id: 'synthetic-park' },
});
const changedMap = flat(changed).find((node) => node.type === MapView);
assert.equal(changedMap.props.territoryId, undefined);
assert.equal(changedMap.props.points, null);
assert.deepEqual(changedMap.props.areas, []);
assert(!flat(changed).some((node) => node.type === Coverage));
} finally {
cleanup?.();
globalThis.fetch = previousFetch;
}
});