MapWorkspace.tsx was 3.157 lines: a props interface, 1.200 lines of derived state and handlers, and two complete render paths — the map-first explorer and the advanced workbench behind it. It is now five modules, and the container is nineteen lines that choose between the two. The obstacle was the props signature. The explorer reads 97 derived values and the workbench 40, so passing them individually would have produced a 97-field interface — worse than the file it replaced. Extracting the derived state into a hook that returns one object solves it: MapWorkspaceViewModel is ReturnType<typeof useMapWorkspaceViewModel>, so the shape is derived from what the hook actually produces and cannot drift from it. Each view then names two typed objects, and the JSX moved unchanged. The contract tests found the one place where widening a negative assertion is wrong. "The map workspace performs no transport" was true of the old file and false of the whole feature, because the hooks call the API by design. It is now scoped to the presentational modules, which is what it always meant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
68 lines
2.7 KiB
Python
68 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
from tests.frontend_contract import read_feature
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def test_app_uses_quality_and_map_presentational_components() -> None:
|
|
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
|
|
|
assert "from './components/quality/QualityResultsPanel'" in app
|
|
assert "from './components/map/MapWorkspace'" in app
|
|
assert "<QualityResultsPanel" in app
|
|
assert "<MapWorkspace" in app
|
|
assert "<h2>QA/QC Results</h2>" not in app
|
|
assert "<h2>Map workspace</h2>" not in app
|
|
assert "<GeoMap" not in app
|
|
|
|
|
|
def test_map_workspace_lives_in_task_based_workbench_shell() -> None:
|
|
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
|
styles = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
|
|
|
assert "activeWorkspace === 'map'" in app
|
|
assert app.index("activeWorkspace === 'map'") < app.index("<MapWorkspace")
|
|
assert "'workbench-main workbench-main-map' : 'workbench-main'" in app
|
|
assert 'className="workbench-inspector"' in app
|
|
assert ".workbench-layout" in styles
|
|
assert ".workbench-sidebar" in styles
|
|
assert ".workbench-main" in styles
|
|
assert ".workbench-inspector" in styles
|
|
|
|
|
|
def test_quality_results_panel_owns_persisted_quality_check_markup() -> None:
|
|
quality_panel = (
|
|
ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx"
|
|
).read_text(encoding="utf-8")
|
|
|
|
assert "QualityCheckRead" in quality_panel
|
|
assert "Kwaliteitsresultaten vernieuwen" in quality_panel
|
|
assert "Nog geen bewaarde kwaliteitsresultaten" in quality_panel
|
|
assert "check.metrics.map" in quality_panel
|
|
assert "fetch(" not in quality_panel
|
|
assert "api" not in quality_panel.lower()
|
|
|
|
|
|
def test_map_workspace_owns_map_controls_and_feature_inspector_markup() -> None:
|
|
map_workspace = read_feature("map_workspace")
|
|
|
|
assert "GeoMap" in map_workspace
|
|
assert "map-toolbar" in map_workspace
|
|
assert "Area" in map_workspace
|
|
assert "Werkgebied" in map_workspace
|
|
assert "Actieve kaartlaag" in map_workspace
|
|
assert "Objectinspectie" in map_workspace
|
|
assert "onFeatureSelect={onSelectMapFeature}" in map_workspace
|
|
# The markup layer owns interaction, never transport. Scoped to the
|
|
# presentational modules because the workspace's hooks do perform
|
|
# transport, by design. A bare "api" substring also matches
|
|
# useMapImageOverlays, so name what is actually forbidden.
|
|
presentation = read_feature("map_workspace_presentation")
|
|
assert "fetch(" not in presentation
|
|
assert "services/api" not in presentation
|
|
assert not re.search(r"\w*[Aa]pi\.(get|post|put|delete)\(", presentation)
|