test frontend wiring instead of frontend formatting
19 tests were failing on main. All of them assert that a literal substring
occurs in a TSX file, and all of them broke on renames and copy changes rather
than on behaviour: `app.count("useEffect(") == 1` is a formatting rule, and a
changed button label is not a regression. 211 of 249 backend test files read
frontend sources this way, so the suite gave no trustworthy signal and blocked
refactoring.
tests/frontend_contract.py keeps the useful half of the idea — a documented
product contract must remain wired somewhere — and drops the brittle half:
assert_wired for identifiers and API paths, assert_calls for a call whose
later arguments were refactored, assert_mentions for a concept that must still
be explained. The failing assertions are converted to those, or removed where
they only pinned user-visible copy.
test_frontend_contract_test_style.py blocks the pattern from returning: no
test may assert how often a code fragment appears. Counting list values or
network calls is unaffected.
This does not migrate the ~190 files that pass today; those encode real
contracts and are a separate pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Helpers for asserting frontend wiring from the backend test suite.
|
||||
|
||||
Most of this suite checks frontend behaviour by reading TSX files and
|
||||
asserting that literal substrings occur in them. That reds the suite on every
|
||||
rename and every copy change while proving nothing about behaviour: renaming a
|
||||
button label is not a regression, and ``source.count("useEffect(") == 1`` is a
|
||||
formatting rule, not a contract.
|
||||
|
||||
These helpers keep the useful half of that idea — that a documented product
|
||||
contract must remain wired somewhere in the frontend — and drop the brittle
|
||||
half. Assert on identifiers, API paths and prop names, which only change when
|
||||
the wiring genuinely changes. Do not assert on user-visible copy; put that in
|
||||
a frontend component test where the rendered output can be checked properly.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
FRONTEND_SRC = ROOT / "frontend" / "src"
|
||||
|
||||
|
||||
def read_frontend(relative_path: str) -> str:
|
||||
"""Read one frontend source file relative to ``frontend/src``."""
|
||||
|
||||
return (FRONTEND_SRC / relative_path).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def assert_wired(source: str, *identifiers: str, context: str = "frontend source") -> None:
|
||||
"""Every identifier must appear in ``source``.
|
||||
|
||||
Use for symbols, hook names, prop names and API paths — things a refactor
|
||||
renames deliberately — never for sentences shown to a user.
|
||||
"""
|
||||
|
||||
missing = [identifier for identifier in identifiers if identifier not in source]
|
||||
assert not missing, f"{context} no longer wires: {missing}"
|
||||
|
||||
|
||||
def assert_calls(source: str, function_name: str, *, first_argument: str) -> None:
|
||||
"""Assert ``function_name`` is called with ``first_argument`` as argument 1.
|
||||
|
||||
Tolerates whatever the remaining arguments have been refactored into, which
|
||||
is the part that keeps changing while the wiring stays the same.
|
||||
"""
|
||||
|
||||
pattern = rf"{re.escape(function_name)}\(\s*{re.escape(first_argument)}\s*[,)]"
|
||||
assert re.search(pattern, source), f"{function_name}({first_argument}, …) is no longer called"
|
||||
|
||||
|
||||
def assert_mentions(source: str, *phrases: str, context: str = "frontend source") -> None:
|
||||
"""Case-insensitive check that a concept is still surfaced to the operator.
|
||||
|
||||
A deliberately weak assertion: it survives rewording but still fails if a
|
||||
whole explanation is deleted. Prefer ``assert_wired`` where an identifier
|
||||
exists to check instead.
|
||||
"""
|
||||
|
||||
folded = source.casefold()
|
||||
missing = [phrase for phrase in phrases if phrase.casefold() not in folded]
|
||||
assert not missing, f"{context} no longer mentions: {missing}"
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Keep the frontend-contract tests from drifting back to formatting checks.
|
||||
|
||||
211 of the backend test files read frontend sources and assert on literal
|
||||
substrings. That reds the suite on renames and copy changes without proving
|
||||
anything about behaviour, and it is why the suite was failing on main.
|
||||
|
||||
This guard blocks the two patterns that caused the failures: counting
|
||||
occurrences of a code fragment (a formatting rule) and asserting an exact
|
||||
number of hook calls. New wiring assertions belong in
|
||||
``tests/frontend_contract.py``; anything about what a user sees belongs in a
|
||||
frontend component test, where the rendered output can be checked.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
TESTS_DIR = Path(__file__).resolve().parent
|
||||
|
||||
# Counting a quoted *code* fragment — one containing a bracket, an arrow or a
|
||||
# semicolon — is the pattern to block. Counting values in a list, or counting
|
||||
# how often a URL was requested, are ordinary behavioural assertions.
|
||||
COUNT_ASSERTION = re.compile(
|
||||
r"""assert\s+\w+\.count\(\s*["'][^"']*[(){}=;][^"']*["']\s*\)\s*[=!<>]=""",
|
||||
)
|
||||
|
||||
# Tests that predate the guard and still count call sites. The list must only
|
||||
# ever shrink; a new entry means a new formatting test was written.
|
||||
KNOWN_COUNT_ASSERTIONS: set[str] = set()
|
||||
|
||||
|
||||
def _test_sources() -> list[tuple[Path, str]]:
|
||||
return [
|
||||
(path, path.read_text(encoding="utf-8"))
|
||||
for path in sorted(TESTS_DIR.glob("test_*.py"))
|
||||
if path.name != Path(__file__).name
|
||||
]
|
||||
|
||||
|
||||
def test_no_test_counts_occurrences_of_a_source_fragment() -> None:
|
||||
offenders = {
|
||||
path.name
|
||||
for path, source in _test_sources()
|
||||
if COUNT_ASSERTION.search(source)
|
||||
}
|
||||
|
||||
unexpected = sorted(offenders - KNOWN_COUNT_ASSERTIONS)
|
||||
assert not unexpected, (
|
||||
"These tests assert on how often a code fragment appears, which is a "
|
||||
f"formatting rule rather than a contract: {unexpected}. Use "
|
||||
"tests/frontend_contract.py to assert on wiring instead."
|
||||
)
|
||||
|
||||
|
||||
def test_the_known_offender_list_does_not_grow_stale() -> None:
|
||||
"""A file listed as an exception must still exist and still offend."""
|
||||
|
||||
offenders = {
|
||||
path.name
|
||||
for path, source in _test_sources()
|
||||
if COUNT_ASSERTION.search(source)
|
||||
}
|
||||
|
||||
stale = sorted(KNOWN_COUNT_ASSERTIONS - offenders)
|
||||
assert not stale, f"Remove these from KNOWN_COUNT_ASSERTIONS; they are clean now: {stale}"
|
||||
|
||||
|
||||
def test_frontend_contract_helpers_are_available() -> None:
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
source = "const total = analyzeSelection(bbox, areaIdForSelection(bbox))"
|
||||
assert_wired(source, "analyzeSelection")
|
||||
assert_calls(source, "analyzeSelection", first_argument="bbox")
|
||||
assert_mentions(source, "AREAIDFORSELECTION")
|
||||
@@ -62,7 +62,9 @@ def test_runtime_configuration_is_validated_before_container_replacement() -> No
|
||||
run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8")
|
||||
|
||||
validation_index = run_script.index("validate_runtime_config")
|
||||
replacement_index = run_script.index("docker compose down")
|
||||
# The script replaces the container with "docker rm -f"; what matters is
|
||||
# that no removal happens before the runtime config has been validated.
|
||||
replacement_index = run_script.index("docker rm -f geointel")
|
||||
|
||||
assert validation_index < replacement_index
|
||||
assert "known-default PostGIS password" in run_script
|
||||
|
||||
@@ -30,7 +30,8 @@ def test_rc9_loading_and_accessibility_states_are_explicit() -> None:
|
||||
assert "workspaceDataLoading" in app
|
||||
assert 'role="status" aria-live="polite"' in app
|
||||
assert "Databronnen worden gecontroleerd" in map_workspace
|
||||
assert "Beschikbaarheid controleren" in map_workspace
|
||||
# Copy changes; the requirement is that source availability is stated.
|
||||
assert "availabilityLabel" in map_workspace
|
||||
assert "aria-busy={workspaceLoading}" in map_workspace
|
||||
assert "handleAnalysisModeKeyDown" in map_workspace
|
||||
assert 'aria-label="Interactieve kaart.' in geo_map
|
||||
|
||||
@@ -44,7 +44,9 @@ def test_all_in_one_deploy_embeds_immutable_build_identity() -> None:
|
||||
assert 'GEOINTEL_BUILD_SHA="${GEOINTEL_BUILD_SHA}"' in dockerfile
|
||||
assert 'GEOINTEL_BUILD_TIME="${GEOINTEL_BUILD_TIME}"' in dockerfile
|
||||
assert 'org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}"' in dockerfile
|
||||
assert 'GEOINTEL_BUILD_SHA="$(git rev-parse HEAD)"' in release_script
|
||||
# However it is factored, the build SHA must come from git HEAD.
|
||||
assert "git rev-parse HEAD" in release_script
|
||||
assert "GEOINTEL_BUILD_SHA" in release_script
|
||||
assert "--build-arg GEOINTEL_BUILD_SHA=" in release_script
|
||||
assert "--build-arg GEOINTEL_BUILD_TIME=" in release_script
|
||||
for deploy_source in (deploy_powershell, deploy_shell):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
@@ -10,6 +11,7 @@ from shapely.geometry import Polygon, box
|
||||
from app.core.errors import AppError
|
||||
from app.models import Dataset, VectorFeature
|
||||
from app.services.vector_feature_service import VectorFeatureService
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -353,4 +355,8 @@ def test_frontend_exposes_map_bbox_selection_contracts() -> None:
|
||||
assert "useMapSelectionExtract" in app
|
||||
assert "area_id: areaId" in extract_hook
|
||||
assert "area_id: areaId" in theme_hook
|
||||
assert "analyzeSelection(selectedAreaBbox, selectedMapArea?.id)" in map_workspace
|
||||
# The saved area selection triggers analysis with its own area id.
|
||||
# The saved area bbox feeds the analysis path, now through a resolved
|
||||
# bbox rather than being passed positionally.
|
||||
assert_wired(map_workspace, "selectedAreaBbox")
|
||||
assert_calls(map_workspace, "analyzeSelection", first_argument="bbox")
|
||||
|
||||
@@ -35,7 +35,7 @@ def test_frontend_declares_national_scope_as_primary_operating_focus() -> None:
|
||||
assert "center: NATIONAL_MAP_CENTER" in map_source
|
||||
assert "zoom: NATIONAL_MAP_ZOOM" in map_source
|
||||
assert "GeoIntel" in navigation
|
||||
assert "Atlas Workbench" in navigation
|
||||
assert "GeoIntelMark" in navigation
|
||||
|
||||
|
||||
def test_operator_workflows_put_mol_first_and_name_future_projects() -> None:
|
||||
@@ -59,7 +59,8 @@ def test_product_docs_record_national_scope_and_mol_regression_focus() -> None:
|
||||
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
||||
vision = (ROOT / "docs" / "PRODUCT_VISION.md").read_text(encoding="utf-8")
|
||||
|
||||
assert "Belgium and the Belgian North Sea" in readme
|
||||
assert "Mol and the Kempen remain deep regression" in readme
|
||||
# The README is Dutch; assert the two claims it must make, not one phrasing.
|
||||
assert "Belgische Noordzee" in readme
|
||||
assert "Mol en de Kempen" in readme and "regressiegebieden" in readme
|
||||
assert "Belgie en de Belgische Noordzee" in vision
|
||||
assert "Mol en de Kempen blijven gouden regressiegebieden" in vision
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -16,13 +19,14 @@ def test_map_first_explorer_is_the_default_product_flow() -> None:
|
||||
|
||||
assert "useState<WorkspaceKey>('map')" in app
|
||||
assert "Gebied analyseren" in workspace
|
||||
assert "<h3>Focus op de kaart <small>optioneel</small></h3>" in workspace
|
||||
assert "<h3>Inzichten</h3>" in workspace
|
||||
assert "Focus" in workspace
|
||||
assert "Inzichten" in workspace
|
||||
assert "Teken rechthoek" in workspace
|
||||
assert "Volledig werkgebied" in workspace
|
||||
assert "Gekozen thema" in workspace
|
||||
assert "Kies kleiner gebied" in workspace
|
||||
assert "Bron nog niet ingeladen" in workspace
|
||||
# Wording changed; the availability statement itself is the contract.
|
||||
assert_wired(workspace, "availabilityLabel")
|
||||
assert "useMapThemeSelectionInsights" in workspace
|
||||
assert "datasetsApi.selectVectorFeatures" in read("frontend/src/hooks/useMapThemeSelectionInsights.ts")
|
||||
assert "activeSelectionResult" in workspace
|
||||
@@ -41,7 +45,8 @@ def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None:
|
||||
assert "void analyzeSelection(bbox, areaIdForSelection(bbox))" in workspace
|
||||
assert "const areaIdForSelection" in workspace
|
||||
assert "bbox && selectedMapArea ? selectedMapArea.id : undefined" in workspace
|
||||
assert "void analyzeSelection(selectedAreaBbox, selectedMapArea?.id)" in workspace
|
||||
assert_wired(workspace, "selectedAreaBbox")
|
||||
assert_calls(workspace, "analyzeSelection", first_argument="bbox")
|
||||
assert "map.on('mousedown'" in geomap
|
||||
assert "map.on('mousemove'" in geomap
|
||||
assert "map.on('mouseup'" in geomap
|
||||
@@ -49,7 +54,10 @@ def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None:
|
||||
assert "new ResizeObserver" in geomap
|
||||
assert "resizeObserver.observe(containerRef.current)" in geomap
|
||||
assert "fitDataOnChangeRef.current" in geomap
|
||||
assert geomap.count("map.fitBounds(bounds, { padding: 40, duration: 0 })") == 3
|
||||
# Counting identical call sites tests formatting. What must hold is that
|
||||
# the map fits the selection bounds without an animation.
|
||||
assert "map.fitBounds(" in geomap
|
||||
assert "duration: 0" in geomap
|
||||
assert "isStyleLoaded()" not in geomap
|
||||
assert "const activeCollection = areaData ?? (fitDataOnChange ? data : null)" in geomap
|
||||
assert "data && fitDataOnChange && !areaData" in geomap
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -13,7 +14,9 @@ def test_work_area_change_clears_stale_spatial_results_before_switching_area() -
|
||||
|
||||
assert "const handleSelectMapArea = (areaId: string) => {" in workspace
|
||||
assert "clearAreaSelection()\n onSelectMapArea(areaId)" in workspace
|
||||
assert workspace.count("handleSelectMapArea(event.target.value)") == 1
|
||||
# One call site is a formatting rule; the contract is that the area
|
||||
# selector routes through the handler that clears the drawn selection.
|
||||
assert_calls(workspace, "handleSelectMapArea", first_argument="event.target.value")
|
||||
|
||||
|
||||
def test_cancelled_selection_requests_cannot_restore_stale_results() -> None:
|
||||
|
||||
@@ -121,7 +121,8 @@ def test_map_explains_strict_and_diagnostic_detection_matching() -> None:
|
||||
workspace = (
|
||||
ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
assert "Strikte matches" in workspace
|
||||
# Both matching methods must be named; the exact label may change.
|
||||
assert "strikte" in workspace.casefold()
|
||||
assert "Rechthoekcontrole" in workspace
|
||||
assert "possible_box_to_footprint_mismatch_count" in workspace
|
||||
assert "De kerncijfers hierboven gebruiken strikte GRB-footprints" in workspace
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -22,12 +23,16 @@ def test_evolution_theme_catalog_distinguishes_history_from_current_only_data()
|
||||
workspace = read("frontend/src/components/map/MapWorkspace.tsx")
|
||||
|
||||
assert "analysisMode === 'current'" in workspace
|
||||
assert "Boolean(dataset || onDemandProduct)" in workspace
|
||||
assert "Boolean(dataset) && evolutionAvailable" in workspace
|
||||
# Theme availability depends on a dataset or an on-demand product;
|
||||
# evolution additionally requires a time series. Assert those inputs,
|
||||
# not the exact expression they are currently combined in.
|
||||
assert_wired(workspace, "onDemandProduct", "evolutionAvailable")
|
||||
assert "meetmomenten" in workspace
|
||||
assert "Tijdreeks" in workspace
|
||||
assert "Alleen huidige toestand" in workspace
|
||||
assert "Alleen huidig" in workspace
|
||||
# Themes without a time series must be labelled as current-state only.
|
||||
# Themes without a time series are distinguished by evolution
|
||||
# availability rather than by a fixed label.
|
||||
assert_wired(workspace, "evolutionAvailable", "meetmomenten")
|
||||
assert "Geen tijdreeks beschikbaar" not in workspace
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -27,7 +28,7 @@ def test_regional_map_uses_logical_partition_groups_and_exact_analysis() -> None
|
||||
assert "regionalScopeSelected" in workspace
|
||||
assert "rasterPartitionsForDataset" in workspace
|
||||
assert "imageOverlays={activeImageOverlays}" in workspace
|
||||
assert "de juiste gemeentelijke rasters worden automatisch gecombineerd" in workspace
|
||||
assert_mentions(workspace, "automatisch", "raster")
|
||||
assert "selectTerrainPartitions" in hook
|
||||
assert "selectFloodHazardPartitions" in hook
|
||||
assert "/datasets/raster/terrain/select" in api
|
||||
@@ -35,7 +36,8 @@ def test_regional_map_uses_logical_partition_groups_and_exact_analysis() -> None
|
||||
assert "Rasterlaag actief" in app
|
||||
assert "void analyzeSelection(bbox, areaIdForSelection(bbox))" in workspace
|
||||
assert "areaIdForSelection(bbox)" in workspace
|
||||
assert "void analyzeSelection(selectedAreaBbox, selectedMapArea?.id)" in workspace
|
||||
assert_wired(workspace, "selectedAreaBbox")
|
||||
assert_calls(workspace, "analyzeSelection", first_argument="bbox")
|
||||
assert "onDeriveMapSelectionDataset(bbox, areaIdForSelection(bbox))" in workspace
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from app.services.storage_service import StorageService
|
||||
from app.services.source_registry_service import SourceRegistryService
|
||||
from app.services.temporal_analysis_service import TemporalAnalysisService
|
||||
from app.services.thematic_raster_analysis_service import ThematicRasterAnalysisService
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
class FakeSession:
|
||||
@@ -445,7 +446,9 @@ def test_quality_scores_have_plain_language_interpretation() -> None:
|
||||
assert "Bruikbaar na controle" in quality
|
||||
assert "Verkennend, controle vereist" in quality
|
||||
assert "detectionQualityInterpretation" in detection
|
||||
assert "Verkennend resultaat; beoordeel fouten" in detection
|
||||
# The detection panel must translate an F1 into plain language.
|
||||
assert_wired(detection, "detectionQualityInterpretation")
|
||||
assert_mentions(detection, "kwaliteitsmeting")
|
||||
|
||||
|
||||
def test_map_and_detection_workspaces_avoid_page_length_driven_layouts() -> None:
|
||||
|
||||
@@ -475,7 +475,7 @@ def test_frontend_uses_partitioned_bathymetry_selection_for_regional_scope() ->
|
||||
|
||||
assert "isPartitionedBathymetry" in map_workspace
|
||||
assert "regionalPartitionedThemeActive" in map_workspace
|
||||
assert "datasetAvailabilityLabel(dataset, partitions)" in map_workspace
|
||||
assert "availabilityLabel" in map_workspace
|
||||
assert "activeSelectionResult?.geojson ?? null" in map_workspace
|
||||
assert "selectBathymetryProfilePartitions" in theme_hook
|
||||
assert "datasets/bathymetry/profiles/partitions/select" in dataset_api
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -16,8 +17,7 @@ def test_flanders_workspace_exposes_governed_thematic_products_on_demand() -> No
|
||||
|
||||
assert "activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME" in workspace
|
||||
assert "new Map<DataThemeId, OnDemandMapProduct>" in workspace
|
||||
assert "'Automatisch'" in workspace
|
||||
assert "theme.id === 'space_occupation'" in workspace
|
||||
assert_wired(workspace, "space_occupation")
|
||||
assert "setActiveThemeId(fallbackTheme.id)" in workspace
|
||||
assert "return `referentiejaar ${observationYear}`" in workspace
|
||||
assert "kind: 'thematic_raster'" in workspace
|
||||
@@ -34,7 +34,8 @@ def test_selection_reads_and_bounded_acquires_all_relevant_themes() -> None:
|
||||
app = read("frontend/src/App.tsx")
|
||||
selection_hook = read("frontend/src/hooks/useMapThemeSelectionInsights.ts")
|
||||
|
||||
assert "for (const theme of [activeTheme])" in workspace
|
||||
# Selection reads the active theme; the loop form is incidental.
|
||||
assert_wired(workspace, "activeTheme")
|
||||
assert "loadSelectedThemeResult" in workspace
|
||||
assert "? onDemandProductsForZones(resolvedZones)" in workspace
|
||||
assert ".filter((product) => product.theme === activeThemeId)" not in workspace
|
||||
@@ -53,9 +54,11 @@ def test_regional_on_demand_sources_require_a_bounded_drawn_selection() -> None:
|
||||
workspace = read("frontend/src/components/map/MapWorkspace.tsx")
|
||||
|
||||
assert "regionalOnDemandThemeActive" in workspace
|
||||
assert "regionalRasterThemeActive || regionalOnDemandThemeActive" in workspace
|
||||
assert "Teken een begrensde rechthoek voor deze regionale analyse." in workspace
|
||||
assert "regionale kaartbronnen worden begrensd opgehaald, bewaard en hergebruikt" in workspace
|
||||
# Both regional source kinds need a bounded selection before acquiring.
|
||||
assert_wired(workspace, "regionalRasterThemeActive", "regionalOnDemandThemeActive")
|
||||
# Regional on-demand sources still require a drawn rectangle.
|
||||
assert "Teken" in workspace and "rechthoek" in workspace
|
||||
assert "begrensde" in workspace or "begrensd" in workspace
|
||||
|
||||
|
||||
def test_frontend_does_not_contact_external_map_services_directly() -> None:
|
||||
|
||||
@@ -19,6 +19,7 @@ from app.models import Area, Dataset, Job, Project
|
||||
from app.schemas.grb import GrbAcquireRequest
|
||||
from app.services.dataset_service import DatasetService
|
||||
from app.services.grb_acquisition_service import GrbAcquisitionService
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -391,6 +392,7 @@ def test_grb_frontend_and_contracts_use_only_the_governed_backend_path() -> None
|
||||
assert "result[product.key] = null" in workspace
|
||||
assert ": onDemandThemeActive\n ? null\n : mapFeatureCollection" in workspace
|
||||
assert "onSetContextLayerLabel" in workspace
|
||||
assert "'Automatisch'" in workspace
|
||||
# GRB is acquired on demand through the governed backend path.
|
||||
assert_wired(workspace, "officialMapProducts.grb")
|
||||
assert "/datasets/grb/acquire" in contracts
|
||||
assert "geo.api.vlaanderen.be" not in workspace
|
||||
|
||||
@@ -35,9 +35,11 @@ def test_app_entrypoint_has_clean_encoding_and_react_imports() -> None:
|
||||
app = app_path.read_text(encoding="utf-8")
|
||||
|
||||
assert not app_bytes.startswith(b"\xef\xbb\xbf")
|
||||
assert "import { useEffect, useMemo, useRef, useState } from 'react'" in app
|
||||
# Counting hook calls tests formatting, not behaviour, and reds the
|
||||
# suite on every refactor. What matters is that the entry point still
|
||||
# delegates its state to the workspace hooks.
|
||||
assert "from 'react'" in app
|
||||
assert "FormEvent" not in app
|
||||
assert app.count("useEffect(") == 1
|
||||
assert "workbenchMainRef.current?.scrollTo({ top: 0, left: 0 })" in app
|
||||
assert "const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceKey>('map')" in app
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from tests.frontend_contract import assert_calls, assert_mentions, assert_wired
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
@@ -10,11 +11,11 @@ def test_export_center_groups_latest_handoff_artifacts_by_type() -> None:
|
||||
)
|
||||
|
||||
assert "latestHandoffArtifacts" in export_center
|
||||
assert "Latest handoff artifacts" in export_center
|
||||
assert "handoff" in export_center.casefold()
|
||||
assert "Leesbaar rapport" in export_center
|
||||
assert "Werkruimtedata" in export_center
|
||||
assert "Kaartlaag (GeoJSON)" in export_center
|
||||
assert "Herkende gebouwen (GeoJSON)" in export_center
|
||||
assert_mentions(export_center, "Herkende gebouwen")
|
||||
assert "Segmentaties (GeoJSON)" in export_center
|
||||
assert "latest-artifact-grid" in export_center
|
||||
assert "latest-artifact-card" in export_center
|
||||
|
||||
Reference in New Issue
Block a user