feat: expose map layer controls and explicit data coverage
This commit is contained in:
@@ -2185,3 +2185,16 @@ O editor captura corpo e sequência em conjunto. Antes do envio em fila e da apr
|
||||
Dois casos novos falharam contra o código anterior e passaram na candidata. Vinte e dois testes frontend dirigidos passaram: temporizador antigo após edição, envio ultrapassado em fila, resposta tardia depois de A→B→A, sequência crescente e confirmação somente do rascunho actual. Tipos, lint, formatação e compilação QA passaram; provas históricas aceites reutilizadas, sem repetição integral. Recibo/hashes em `context_autosave_ordering_increment` de `local-validation-20261029.json`.
|
||||
|
||||
**Visual pendente pela equipa:** em QA, editar sucessivamente enquanto um envio automático aguarda, incluindo regressar ao valor inicial; aguardar a confirmação da revisão actual, sair/regressar e conferir os últimos valores recuperados. Usar atraso controlado de rede em QA, sem alterações sintéticas no activo. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. A prova com temporizadores/fila não substitui a sessão no navegador. Sem instalação ou avanço de main; requisitos transversais permanecem parciais.
|
||||
|
||||
|
||||
### 29/10/2026 — ACT-20261029-02: camadas existentes e cobertura explícita do mapa
|
||||
|
||||
Incremento funcional sobre `87b74f0c`, reutilizando o mapa, as APIs e os filtros territoriais existentes (V2-07/V2-10). Antes da execução foi conferida a matriz e o contrato de `InvestmentCatalogue::map`: pontos, áreas/limites, registos sem coordenadas, fonte/data, limite de resposta e contagens já estavam disponíveis. Não houve perda confirmada nem se reabriu a correcção provincial instalada.
|
||||
|
||||
O painel permite ocultar/repor independentemente pontos e áreas/limites, ambos visíveis por defeito. As opções não mudam filtros, não fazem nova consulta nem eliminam registos. O novo resumo mostra marcadores/áreas recebidos, falta de fonte/data e investimentos sem ponto utilizável, incluindo camadas ocultas; mantém as ligações às fichas. Distingue investimento de marcador: um investimento pode ter vários locais. O total passa a usar `matched_count` da API, em vez de somar marcadores como investimentos; fallback deduplica os identificadores. A indicação de truncagem é agora aviso de cobertura, não falha de ligação. Fontes/datas vazias não passam a comprovadas; ausência de registos não é ausência de investimento.
|
||||
|
||||
Dois testes frontend dirigidos passaram: contagens/proveniência desconhecida/resposta limitada/ausência de dados e ocultação/reposição sem perder registos, filtros ou fichas. O exemplo contém dois marcadores do mesmo investimento e distingue o total encontrado do subconjunto recebido. Tipos, lint, formatação e compilação QA passaram. Provas territoriais e das funcionalidades anteriores reutilizadas, sem repetição integral. Recibo/hashes no campo `map_layer_coverage_increment` de `local-validation-20261029.json`.
|
||||
|
||||
**ACT-02 permanece parcial:** este recorte implementa controlo das camadas já existentes e explicitação de cobertura. Não cria camadas factuais de recursos, infra-estruturas, mão-de-obra ou potencial económico, nem declara completa a cobertura provincial/distrital. Backend, geometrias, divulgação, permissões, fontes, datas e cálculo preservados; sem importações ou escrita no activo.
|
||||
|
||||
**Visual pendente pela equipa:** na candidata, conferir os mapas da ficha de oportunidade, carteira e zona de investimento; ocultar/repor cada camada e ambas, conferir contagens de investimentos/marcadores, fonte/data ausentes e fichas sem coordenadas, e mudar filtros territoriais. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. O painel de contexto administrativo separado mantém o seu âmbito. Sem instalação ou avanço de main.
|
||||
|
||||
@@ -1474,5 +1474,46 @@
|
||||
"preservation": "Frontend sequencing only. Existing backend version/sequence conflict checks preserved, not disabled. No schema, data, permissions or tax changes and no active writes. Historical suites reused.",
|
||||
"visual_acceptance": "Pending team execution and recording; controlled timer/queue tests do not establish browser-session acceptance.",
|
||||
"installation": "Not triggered; main and active release unchanged."
|
||||
},
|
||||
"map_layer_coverage_increment": {
|
||||
"recorded_at": "2026-10-29T21:15:10.944454+00:00",
|
||||
"base_commit": "87b74f0c2a0712bcc35ddea1f6788670d7390fe9",
|
||||
"requirements": [
|
||||
"ACT-20261029-02",
|
||||
"V2-07",
|
||||
"V2-10"
|
||||
],
|
||||
"behavior": [
|
||||
"Existing point and area/limit layers can be hidden and restored independently; both remain enabled by default. No extra query, filter change or record deletion on toggle.",
|
||||
"Coverage table reports returned markers/areas, missing source/date and unlocated opportunities, including hidden layers. Whitespace-only source/date is treated as not indicated, not as evidence.",
|
||||
"Count investments using existing API matched_count, with distinct point identifiers plus unlocated count as fallback. Distinguish marker count from distinct georeferenced investments.",
|
||||
"A truncated response is a coverage warning rather than a failed request. Explicitly distinguish incomplete coverage from absence of investment or availability of resources/infrastructure/labour."
|
||||
],
|
||||
"api_inventory": "InvestmentCatalogue::map already returns matched_count, georeferenced_count, marker_count, unlocated, areas, feature provenance and truncation; backend unchanged.",
|
||||
"checks": {
|
||||
"new_directed_frontend_cases": 2,
|
||||
"passed": 2,
|
||||
"types": "passed",
|
||||
"lint": "passed",
|
||||
"format": "passed",
|
||||
"qa_build": "passed"
|
||||
},
|
||||
"source_sha256": {
|
||||
"resources/js/components/investment-map-panel.tsx": "04834dc0f80282ec57dd1ba596f82a0871b0195bf16b99f411afbfd4c9d7ffa8",
|
||||
"resources/js/components/investment-map-coverage.tsx": "c0b6ea26dd5fdf6d93b7f5b061a48e65f74df6caa7e12490e9a264365671be1c",
|
||||
"tests/frontend/investment-map-panel.test.mjs": "67526c11a9cce25e295b7e0e49ad639077d882dc6e6d83b2c603e25c180ab549"
|
||||
},
|
||||
"evidence": {
|
||||
"outputs/validation-20261029/map-coverage-tests.log": "ffc2170e5e98e2d4aefdd97ff8046291fa37f60fbb708d0244cba6d00f5616c6",
|
||||
"outputs/validation-20261029/map-coverage-types.log": "82962651e47f564de653ad2883f8fd34e7dae4b4468e4ac459cb3331962a3879",
|
||||
"outputs/validation-20261029/map-coverage-lint.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"outputs/validation-20261029/map-coverage-format-check.log": "def31e032d94cd0946ed119a7baceba3757ed29dfaf53c194fe000e28db43747",
|
||||
"outputs/validation-20261029/map-coverage-build.log": "4fd244d1b2bd0016a156b99fd97f684ff207a8a778e689f93da93c8f78cd76f7",
|
||||
"outputs/validation-20261029/map-coverage-build-exit.json": "124fd50cb6d223cdd18fedc166d00af7254199b6e6dedf9be82f71db87af6d01"
|
||||
},
|
||||
"remaining": "ACT-02 remains partial: structured resource/infrastructure/labour/economic-potential layers and factual provincial/district coverage are not implemented or accepted by this increment.",
|
||||
"preservation": "Existing territorial queries, publication/permissions, geometry/source/date values and unlocated links retained. No backend/schema/active data changes, new factual imports or fiscal changes. Prior proofs reused.",
|
||||
"visual_acceptance": "Pending team candidate browser session and recording for portfolio, opportunity and investment-zone map consumers.",
|
||||
"installation": "Not triggered; main and active release unchanged."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7722,3 +7722,16 @@ O editor captura corpo e sequência em conjunto. Antes do envio em fila e da apr
|
||||
Dois casos novos falharam contra o código anterior e passaram na candidata. Vinte e dois testes frontend dirigidos passaram: temporizador antigo após edição, envio ultrapassado em fila, resposta tardia depois de A→B→A, sequência crescente e confirmação somente do rascunho actual. Tipos, lint, formatação e compilação QA passaram; provas históricas aceites reutilizadas, sem repetição integral. Recibo/hashes em `context_autosave_ordering_increment` de `local-validation-20261029.json`.
|
||||
|
||||
**Visual pendente pela equipa:** em QA, editar sucessivamente enquanto um envio automático aguarda, incluindo regressar ao valor inicial; aguardar a confirmação da revisão actual, sair/regressar e conferir os últimos valores recuperados. Usar atraso controlado de rede em QA, sem alterações sintéticas no activo. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. A prova com temporizadores/fila não substitui a sessão no navegador. Sem instalação ou avanço de main; requisitos transversais permanecem parciais.
|
||||
|
||||
|
||||
### 29/10/2026 — ACT-20261029-02: camadas existentes e cobertura explícita do mapa
|
||||
|
||||
Incremento funcional sobre `87b74f0c`, reutilizando o mapa, as APIs e os filtros territoriais existentes (V2-07/V2-10). Antes da execução foi conferida a matriz e o contrato de `InvestmentCatalogue::map`: pontos, áreas/limites, registos sem coordenadas, fonte/data, limite de resposta e contagens já estavam disponíveis. Não houve perda confirmada nem se reabriu a correcção provincial instalada.
|
||||
|
||||
O painel permite ocultar/repor independentemente pontos e áreas/limites, ambos visíveis por defeito. As opções não mudam filtros, não fazem nova consulta nem eliminam registos. O novo resumo mostra marcadores/áreas recebidos, falta de fonte/data e investimentos sem ponto utilizável, incluindo camadas ocultas; mantém as ligações às fichas. Distingue investimento de marcador: um investimento pode ter vários locais. O total passa a usar `matched_count` da API, em vez de somar marcadores como investimentos; fallback deduplica os identificadores. A indicação de truncagem é agora aviso de cobertura, não falha de ligação. Fontes/datas vazias não passam a comprovadas; ausência de registos não é ausência de investimento.
|
||||
|
||||
Dois testes frontend dirigidos passaram: contagens/proveniência desconhecida/resposta limitada/ausência de dados e ocultação/reposição sem perder registos, filtros ou fichas. O exemplo contém dois marcadores do mesmo investimento e distingue o total encontrado do subconjunto recebido. Tipos, lint, formatação e compilação QA passaram. Provas territoriais e das funcionalidades anteriores reutilizadas, sem repetição integral. Recibo/hashes no campo `map_layer_coverage_increment` de `local-validation-20261029.json`.
|
||||
|
||||
**ACT-02 permanece parcial:** este recorte implementa controlo das camadas já existentes e explicitação de cobertura. Não cria camadas factuais de recursos, infra-estruturas, mão-de-obra ou potencial económico, nem declara completa a cobertura provincial/distrital. Backend, geometrias, divulgação, permissões, fontes, datas e cálculo preservados; sem importações ou escrita no activo.
|
||||
|
||||
**Visual pendente pela equipa:** na candidata, conferir os mapas da ficha de oportunidade, carteira e zona de investimento; ocultar/repor cada camada e ambas, conferir contagens de investimentos/marcadores, fonte/data ausentes e fichas sem coordenadas, e mudar filtros territoriais. Registar revisão, perfil, percurso, esperado/observado e evidência nesta matriz. O painel de contexto administrativo separado mantém o seu âmbito. Sem instalação ou avanço de main.
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import type {
|
||||
InvestmentMapArea,
|
||||
InvestmentMapFeature,
|
||||
} from './interactive-investment-map';
|
||||
|
||||
export default function InvestmentMapCoverage({
|
||||
points,
|
||||
areas,
|
||||
unlocated,
|
||||
truncated,
|
||||
}: {
|
||||
points: InvestmentMapFeature[];
|
||||
areas: InvestmentMapArea[];
|
||||
unlocated: number;
|
||||
truncated: boolean;
|
||||
}) {
|
||||
const rows = [
|
||||
{
|
||||
label: 'Marcadores de investimento',
|
||||
records: points.map((point) => point.properties),
|
||||
},
|
||||
{ label: 'Áreas e limites', records: areas },
|
||||
];
|
||||
return (
|
||||
<section
|
||||
className="space-y-2 rounded border p-3"
|
||||
aria-label="Cobertura dos dados do mapa"
|
||||
>
|
||||
<h3 className="font-semibold">Cobertura desta consulta</h3>
|
||||
{truncated && (
|
||||
<p role="note">
|
||||
Resultados limitados pela consulta. Refine os filtros; as
|
||||
contagens abaixo não representam o total do cadastro.
|
||||
</p>
|
||||
)}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<caption className="text-left">
|
||||
Registos recebidos para os filtros actuais, incluindo
|
||||
camadas ocultas.
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Camada</th>
|
||||
<th scope="col">Registos</th>
|
||||
<th scope="col">Sem fonte indicada</th>
|
||||
<th scope="col">Sem data indicada</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.label}>
|
||||
<th scope="row">{row.label}</th>
|
||||
<td>{row.records.length}</td>
|
||||
<td>
|
||||
{
|
||||
row.records.filter(
|
||||
(record) =>
|
||||
!record.source_reference?.trim(),
|
||||
).length
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
row.records.filter(
|
||||
(record) =>
|
||||
!record.source_date?.trim(),
|
||||
).length
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p>
|
||||
Investimentos com ponto nesta resposta:{' '}
|
||||
{new Set(points.map((point) => point.properties.id)).size}. Um
|
||||
investimento pode ter vários marcadores.
|
||||
</p>
|
||||
<p>
|
||||
Investimentos sem ponto público utilizável nesta consulta:{' '}
|
||||
{unlocated}.
|
||||
{unlocated > 0 && ' As fichas continuam disponíveis abaixo.'}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
A indicação de fonte ou data não comprova cobertura completa nem
|
||||
actualidade. Ausência de registos não significa ausência de
|
||||
investimento. Esta consulta não mede disponibilidade de
|
||||
recursos, infra-estruturas ou mão-de-obra.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import SearchState from '@/components/search-state';
|
||||
import { useEffect, useState } from 'react';
|
||||
import TerritoryGeometryPreview from './territory-geometry-preview';
|
||||
import InvestmentMapCoverage from './investment-map-coverage';
|
||||
import InteractiveInvestmentMap, {
|
||||
type InvestmentMapFeature,
|
||||
type InvestmentMapArea,
|
||||
@@ -21,6 +22,10 @@ export default function InvestmentMapPanel({
|
||||
const [error, setError] = useState('');
|
||||
const [retry, setRetry] = useState(0);
|
||||
const [areas, setAreas] = useState<InvestmentMapArea[]>([]);
|
||||
const [truncated, setTruncated] = useState(false);
|
||||
const [showPoints, setShowPoints] = useState(true);
|
||||
const [showAreas, setShowAreas] = useState(true);
|
||||
const [matched, setMatched] = useState(0);
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
const params = new URLSearchParams(JSON.parse(key));
|
||||
@@ -28,6 +33,8 @@ export default function InvestmentMapPanel({
|
||||
setError('');
|
||||
setMissing([]);
|
||||
setAreas([]);
|
||||
setTruncated(false);
|
||||
setMatched(0);
|
||||
fetch(`/api/v1/investment/map?${params}`, {
|
||||
signal: controller.signal,
|
||||
headers: { Accept: 'application/json' },
|
||||
@@ -41,10 +48,14 @@ export default function InvestmentMapPanel({
|
||||
setPoints(data.features);
|
||||
setAreas(data.areas ?? []);
|
||||
setMissing(data.unlocated ?? []);
|
||||
setError(
|
||||
data.truncated
|
||||
? 'Cobertura limitada: refine os filtros.'
|
||||
: '',
|
||||
setTruncated(Boolean(data.truncated));
|
||||
setMatched(
|
||||
data.matched_count ??
|
||||
new Set(
|
||||
(data.features as InvestmentMapFeature[]).map(
|
||||
(point) => point.properties.id,
|
||||
),
|
||||
).size + (data.unlocated?.length ?? 0),
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -57,13 +68,53 @@ export default function InvestmentMapPanel({
|
||||
<section className="space-y-3">
|
||||
<SearchState
|
||||
scope="Registos consultados pelo mapa"
|
||||
countLabel="registos, incluindo os que não têm coordenadas"
|
||||
countLabel="investimentos, incluindo os que não têm coordenadas"
|
||||
loading={points === null && !error}
|
||||
error={error}
|
||||
total={(points?.length ?? 0) + missing.length}
|
||||
total={matched}
|
||||
retry={() => setRetry((value) => value + 1)}
|
||||
/>
|
||||
<InteractiveInvestmentMap points={points} areas={areas} />
|
||||
<fieldset className="flex flex-wrap gap-4 rounded border p-3">
|
||||
<legend className="font-semibold">
|
||||
Camadas desta consulta
|
||||
</legend>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showPoints}
|
||||
onChange={(event) =>
|
||||
setShowPoints(event.target.checked)
|
||||
}
|
||||
/>{' '}
|
||||
Investimentos com ponto
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showAreas}
|
||||
onChange={(event) => setShowAreas(event.target.checked)}
|
||||
/>{' '}
|
||||
Áreas e limites
|
||||
</label>
|
||||
</fieldset>
|
||||
{!showPoints && !showAreas && (
|
||||
<p role="status">
|
||||
Camadas ocultas. Active uma camada para voltar a consultar
|
||||
os registos no mapa.
|
||||
</p>
|
||||
)}
|
||||
<InteractiveInvestmentMap
|
||||
points={points === null ? null : showPoints ? points : []}
|
||||
areas={showAreas ? areas : []}
|
||||
/>
|
||||
{points !== null && (
|
||||
<InvestmentMapCoverage
|
||||
points={points}
|
||||
areas={areas}
|
||||
unlocated={missing.length}
|
||||
truncated={truncated}
|
||||
/>
|
||||
)}
|
||||
<TerritoryGeometryPreview
|
||||
territoryId={
|
||||
territoryId ||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
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('map layer choices preserve received records, coverage, unlocated links and filters without another request', 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 },
|
||||
'./territory-geometry-preview': { default: () => null },
|
||||
'./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);
|
||||
flat(tree)
|
||||
.filter((node) => node.type === 'input')[0]
|
||||
.props.onChange({ target: { checked: false } });
|
||||
tree = render();
|
||||
assert.deepEqual(map().props.points, []);
|
||||
assert.equal(map().props.areas.length, 1);
|
||||
flat(tree)
|
||||
.filter((node) => node.type === 'input')[1]
|
||||
.props.onChange({ target: { checked: false } });
|
||||
tree = render();
|
||||
assert.deepEqual(map().props.areas, []);
|
||||
const html = renderToStaticMarkup(tree);
|
||||
assert.match(html, /Camadas ocultas/);
|
||||
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>/,
|
||||
);
|
||||
for (const checkbox of flat(tree).filter(
|
||||
(node) => node.type === 'input',
|
||||
))
|
||||
checkbox.props.onChange({ target: { checked: true } });
|
||||
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/);
|
||||
} finally {
|
||||
cleanup?.();
|
||||
globalThis.fetch = previousFetch;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user