aboutsummaryrefslogtreecommitdiff
path: root/web/frontend/official-climb-profile-logic.test.ts
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-10-18 21:19:36 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-10-18 21:19:36 +0200
commit8a0032d08862dcf28c653bee3fd21052e967e35d (patch)
tree6cb0d8efe6f5c2bf9ce9eb3ef8306faecbe7cfeb /web/frontend/official-climb-profile-logic.test.ts
parentd575274d00c8da251df29d5d6a636044673944a3 (diff)
Cut climb profile sections in the backend
The chart steps are computed in Go and shipped as finished sections, next to the profile statistics the climb package already owns: climb.ProfileSections cuts a Route at its widest bends under a ProfilePolicy, and rideanalysis.MergeClimbProfile takes over the merge that lived in web/climbs.go, so web only loads the attempt rides and maps the DTOs. The payload carries sections instead of raw elevations, and the browser keeps what needs the canvas: slope bands, labels, distance ticks and colours. official-climb-profile-logic.ts and its tests are gone, replaced by climb/profile_test.go and a page test reading the embedded profile JSON.
Diffstat (limited to 'web/frontend/official-climb-profile-logic.test.ts')
-rw-r--r--web/frontend/official-climb-profile-logic.test.ts102
1 files changed, 0 insertions, 102 deletions
diff --git a/web/frontend/official-climb-profile-logic.test.ts b/web/frontend/official-climb-profile-logic.test.ts
deleted file mode 100644
index ca8dea2..0000000
--- a/web/frontend/official-climb-profile-logic.test.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import test from "node:test";
-import assert from "node:assert/strict";
-import { officialProfileSections, profileBandForSlope } from "./official-climb-profile-logic.ts";
-import type { ProfilePoint } from "./types.js";
-
-test("assigns each slope to its color band", () => {
- assert.equal(profileBandForSlope(-0.1), "downhill");
- assert.equal(profileBandForSlope(0), "downhill");
- assert.equal(profileBandForSlope(0.1), "0-3");
- assert.equal(profileBandForSlope(2.9), "0-3");
- assert.equal(profileBandForSlope(3), "3-6");
- assert.equal(profileBandForSlope(6), "6-9");
- assert.equal(profileBandForSlope(9), "9-plus");
- assert.equal(profileBandForSlope(12), "9-plus");
-});
-
-// buildProfile lays out one recorded point every 50 m along the given [length in km, slope in %] stretches.
-const buildProfile = (stretches: [number, number][]): ProfilePoint[] => {
- const points: ProfilePoint[] = [{ distanceKm: 0, elevationM: 0 }];
- let distanceKm = 0;
- let elevationM = 0;
- for (const [lengthKm, slopePercent] of stretches) {
- const pointCount = Math.round(lengthKm * 20);
- for (let index = 1; index <= pointCount; index++) {
- const distanceStepKm = (lengthKm * index) / pointCount;
- points.push({ distanceKm: distanceKm + distanceStepKm, elevationM: elevationM + distanceStepKm * 1000 * (slopePercent / 100) });
- }
- distanceKm += lengthKm;
- elevationM += lengthKm * 1000 * (slopePercent / 100);
- }
- return points;
-};
-
-test("keeps a short steep ramp as its own section", () => {
- const points = buildProfile([
- [0.3, 4],
- [0.3, 12],
- [0.3, 4],
- ]);
- const sections = officialProfileSections(points, 0, points.length - 1);
-
- assert.equal(sections.length, 3);
- assert.ok(Math.abs(sections[1].slopePercent - 12) < 0.01);
- assert.equal(sections[1].band, "9-plus");
- assert.equal(Math.round((sections[1].endDistanceKm - sections[1].startDistanceKm) * 1000), 300);
- assert.ok(Math.abs(sections[0].slopePercent - 4) < 0.01);
- assert.ok(Math.abs(sections[2].slopePercent - 4) < 0.01);
-});
-
-test("collapses a constant slope into a single section", () => {
- const points = buildProfile([[5, 6]]);
- const sections = officialProfileSections(points, 0, points.length - 1);
-
- assert.equal(sections.length, 1);
- assert.ok(Math.abs(sections[0].slopePercent - 6) < 0.01);
- assert.equal(sections[0].startDistanceKm, 0);
- assert.equal(sections[0].endDistanceKm, 5);
-});
-
-test("reports a descending stretch as downhill", () => {
- const points = buildProfile([[2, -5]]);
- const sections = officialProfileSections(points, 0, points.length - 1);
-
- assert.equal(sections.length, 1);
- assert.equal(sections[0].band, "downhill");
-});
-
-test("keeps a noisy profile within the readable section budget", () => {
- const points: ProfilePoint[] = [];
- for (let index = 0; index <= 200; index++) {
- const distanceKm = index * 0.05;
- points.push({ distanceKm, elevationM: distanceKm * 1000 * 0.05 + Math.sin(index) * 4 });
- }
- const sections = officialProfileSections(points, 0, points.length - 1);
-
- assert.ok(sections.length > 10 && sections.length <= 24, `expected 10-24 sections, got ${sections.length}`);
- assert.equal(sections[0].startDistanceKm, 0);
- assert.equal(sections[sections.length - 1].endDistanceKm, 10);
-});
-
-test("reads a slice of a longer profile", () => {
- const points = buildProfile([
- [1, 20],
- [1, 8],
- [1, 20],
- ]);
- const startIndex = points.findIndex((point) => point.distanceKm >= 1);
- const endIndex = points.findIndex((point) => point.distanceKm >= 2);
- const sections = officialProfileSections(points, startIndex, endIndex);
-
- assert.equal(sections.length, 1);
- assert.equal(Math.round(sections[0].slopePercent), 8);
- assert.equal(sections[0].startDistanceKm, points[startIndex].distanceKm);
- assert.equal(sections[0].endDistanceKm, points[endIndex].distanceKm);
-});
-
-test("returns no section for an empty range", () => {
- const points = buildProfile([[1, 6]]);
-
- assert.deepEqual(officialProfileSections(points, 3, 3), []);
- assert.deepEqual(officialProfileSections(points, 0, points.length), []);
-});