diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx index aa5440d58..ef6ab3044 100644 --- a/frontend/src/components/LeftoverMapPlot.tsx +++ b/frontend/src/components/LeftoverMapPlot.tsx @@ -174,7 +174,8 @@ function leftoverMapPlotAxisText( * comparison-graphic and rank labels rather than adding SPA translation debt. * ADR 0319 captions leftover-map distance on that comparison graphic using * the same localized composition boundary rather than adding another static - * comparison-only translation key. + * comparison-only translation key. ADR 0320 names comparison coordinate ticks + * through the same composition boundary, preserving localized generic tick copy. * Never invent a leftover score. */ export function LeftoverMapPlot({ @@ -300,9 +301,16 @@ export function LeftoverMapPlot({ {layout.ticks.map((tick) => ( { expect(originAxis1).toMatchObject({ x: layout?.originX, y: layout?.originY, label: "0.00" }); }); + it("keeps distinct persisted coordinate ticks when their displayed labels match", () => { + const layout = layoutLeftoverMapPlot( + [ + pair({ + leftover_map_person_axis_1: 0.501, + leftover_map_item_axis_1: 0.504, + }), + ], + criterionLabel, + ); + const matchingTicks = layout?.ticks.filter( + (tick) => tick.axis === 1 && tick.label === "+0.50", + ); + expect(matchingTicks?.map((tick) => tick.value)).toEqual([0.501, 0.504]); + expect(matchingTicks?.[0]?.x).not.toBe(matchingTicks?.[1]?.x); + }); + it("does not invent drawing-scale leftover-map ticks on a rank-0 origin cell", () => { const layout = layoutLeftoverMapPlot( [ diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts index 5689e4a36..02f5ba9ba 100644 --- a/frontend/src/leftoverMapPlotLayout.ts +++ b/frontend/src/leftoverMapPlotLayout.ts @@ -227,17 +227,19 @@ function boundedCaptionY(y: number, height: number): number { } function uniqueCoordinateTicks(values: number[]): { value: number; label: string }[] { - const byLabel = new Map(); + const uniqueValues = new Set(); + const ticks: { value: number; label: string }[] = []; for (const value of values) { const label = formatSignedLeftoverValue(value); if (label === null) { continue; } - if (!byLabel.has(label)) { - byLabel.set(label, value); + if (!uniqueValues.has(value)) { + uniqueValues.add(value); + ticks.push({ value, label }); } } - return [...byLabel.entries()].map(([label, value]) => ({ value, label })); + return ticks; } function leftoverMapCoordinateTicks( diff --git a/tests/test_grouping_comparison_graphic_tick_contract.py b/tests/test_grouping_comparison_graphic_tick_contract.py new file mode 100644 index 000000000..d0c3af61e --- /dev/null +++ b/tests/test_grouping_comparison_graphic_tick_contract.py @@ -0,0 +1,42 @@ +"""Executable contract for coordinate ticks on the grouping-comparison graphic.""" + +from pathlib import Path +import re + + +ROOT = Path(__file__).resolve().parents[1] +PLOT_SOURCE = ROOT / "frontend" / "src" / "components" / "LeftoverMapPlot.tsx" +LAYOUT_SOURCE = ROOT / "frontend" / "src" / "leftoverMapPlotLayout.ts" + + +def test_comparison_graphic_names_ticks_with_distinct_accessible_copy() -> None: + """Comparison ticks compose existing localized comparison and tick copy.""" + plot_source = PLOT_SOURCE.read_text(encoding="utf-8") + + assert "LEFTOVER_MAP_COMPARE_PLOT_LABEL" in plot_source + assert "LEFTOVER_MAP_PLOT_TICK" in plot_source + assert re.search( + r'variant\s*===\s*"comparison"\s*\?\s*`\$\{t\(LEFTOVER_MAP_COMPARE_PLOT_LABEL\)\}:\s*\$\{tf\(LEFTOVER_MAP_PLOT_TICK,\s*\{\s*axis:\s*tick\.axis,\s*value:\s*tick\.label,?\s*\}\)\}`\s*:\s*tf\(LEFTOVER_MAP_PLOT_TICK,\s*\{\s*axis:\s*tick\.axis,\s*value:\s*tick\.label,?\s*\}\)', + plot_source, + re.DOTALL, + ) + assert "LEFTOVER_MAP_COMPARE_PLOT_TICK" not in plot_source + + +def test_tick_positions_come_from_persisted_coordinates_not_distance() -> None: + """Tick candidates remain the origin plus finite persisted ξ/ζ projections.""" + layout_source = LAYOUT_SOURCE.read_text(encoding="utf-8") + + assert "const axis1Values = [0];" in layout_source + assert "const axis2Values = [0];" in layout_source + assert "pair.leftover_map_person_axis_1 as number" in layout_source + assert "pair.leftover_map_item_axis_1 as number" in layout_source + assert "pair.leftover_map_person_axis_2 as number" in layout_source + assert "pair.leftover_map_item_axis_2 as number" in layout_source + tick_builder = re.search( + r"function uniqueCoordinateTicks\(.*?\n}\n", + layout_source, + re.DOTALL, + ) + assert tick_builder is not None + assert "leftover_distance" not in tick_builder.group(0)