Files
gods-eye-view/src/data/routeSteps.test.mjs
T
Sameh Khamis d14205160a Format every metre/kilometre distance through one shared helper
Directions, the Contacts marker and panel, placed annotation labels, the
drawn-line measure, the Cockpit and formatRouteDistance each chose the unit
and precision on their own, and most chose before rounding: #1450, #1433 and
formatDistanceText or kilometreDigits from src/data/distanceFormat.js, which
cut after rounding. Each keeps its own metre step and rendering.

That also fixes the 10 km cut the Contacts marker and panel, the drawn-line
measure and the Cockpit still had (9,960 m read 10.0 km). The helper returns
kilometres already rounded, so the plain marker and the localized panel no
longer disagree on half-tenths such as 1,450 m.

A negative annotation distance now leaves the label without a distance, and
a drawn line with a coordinate that is not a number shows no measure, rather
than printing 0 m or NaN km. The HUD altitude tag and the spoken view height
keep their own unit choice.
2026-11-01 22:40:28 -08:00

249 lines
7.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
ROUTE_STEPS_MAX,
formatRouteDistance,
formatRouteDuration,
instructionFor,
normalizeOsrmSteps,
roadLabel,
} from './routeSteps.js';
test('instructions read like a navigator, one sentence per OSRM maneuver', () => {
assert.equal(
instructionFor({ type: 'depart', name: 'Kaivokatu' }),
'Head out on Kaivokatu',
);
assert.equal(instructionFor({ type: 'depart' }), 'Head out');
assert.equal(
instructionFor({
type: 'new name',
modifier: 'straight',
name: 'Simonkatu',
}),
'Continue onto Simonkatu',
);
assert.equal(
instructionFor({
type: 'end of road',
modifier: 'left',
name: 'Annankatu',
}),
'At the end of the road, turn left onto Annankatu',
);
assert.equal(
instructionFor({ type: 'turn', modifier: 'right', name: 'Kansakoulukatu' }),
'Turn right onto Kansakoulukatu',
);
assert.equal(
instructionFor({
type: 'turn',
modifier: 'slight left',
name: 'Ring I',
ref: 'Kt 50',
}),
'Turn slightly left onto Ring I (Kt 50)',
);
assert.equal(
instructionFor({ type: 'turn', modifier: 'uturn' }),
'Make a U-turn',
);
assert.equal(
instructionFor({
type: 'roundabout',
modifier: 'right',
exit: 2,
name: 'Malminrinne',
}),
'At the roundabout, take the 2nd exit onto Malminrinne',
);
assert.equal(
instructionFor({ type: 'rotary', exit: 1 }),
'At the roundabout, take the 1st exit',
);
assert.equal(
instructionFor({ type: 'roundabout', exit: 11 }),
'At the roundabout, take the 11th exit',
);
assert.equal(
instructionFor({ type: 'fork', modifier: 'slight right', ref: 'E12' }),
'Keep slightly right at the fork onto E12',
);
assert.equal(
instructionFor({ type: 'off ramp', modifier: 'right', name: 'Turunväylä' }),
'Take the exit on the right onto Turunväylä',
);
assert.equal(
instructionFor({ type: 'on ramp', modifier: 'left' }),
'Take the ramp on the left',
);
assert.equal(
instructionFor({ type: 'on ramp', modifier: 'slight left', name: 'I-95' }),
'Take the ramp slightly left onto I-95',
);
assert.equal(
instructionFor({ type: 'off ramp', modifier: 'sharp right', ref: 'I-280' }),
'Take the exit sharply right onto I-280',
);
assert.equal(
instructionFor({ type: 'on ramp', modifier: 'straight', name: 'US 101' }),
'Take the ramp onto US 101',
);
assert.equal(
instructionFor({ type: 'use lane', modifier: 'slight left' }),
'Use the left lane',
);
assert.equal(
instructionFor({ type: 'use lane', modifier: 'right', name: 'Main St' }),
'Use the right lane onto Main St',
);
assert.equal(
instructionFor({
type: 'merge',
modifier: 'slight left',
name: 'Länsiväylä',
}),
'Merge slightly left onto Länsiväylä',
);
assert.equal(
instructionFor({ type: 'arrive', modifier: 'right' }),
'Arrive at your destination, on the right',
);
assert.equal(
instructionFor({ type: 'arrive' }),
'Arrive at your destination',
);
assert.equal(
instructionFor({ type: 'made-up', name: 'X' }),
'Continue onto X',
);
assert.equal(instructionFor(null), 'Continue');
});
test('road label prefers "Name (Ref)" and never repeats a ref already in the name', () => {
assert.equal(roadLabel({ name: 'Ring I', ref: 'Kt 50' }), 'Ring I (Kt 50)');
assert.equal(
roadLabel({ name: 'E12 Turunväylä', ref: 'E12' }),
'E12 Turunväylä',
);
assert.equal(roadLabel({ ref: 'E12' }), 'E12');
assert.equal(roadLabel({}), '');
});
test('normalizeOsrmSteps flattens legs, folds roundabout exits, and drops steps without a location', () => {
const route = {
legs: [
{
steps: [
{
maneuver: { type: 'depart', location: [24.9384, 60.1699] },
name: 'Kaivokatu',
distance: 8.3,
duration: 1.3,
},
{
maneuver: {
type: 'roundabout',
modifier: 'right',
exit: 2,
location: [24.9313, 60.1675],
},
name: '',
distance: 34.7,
duration: 7.3,
},
{
maneuver: {
type: 'exit roundabout',
modifier: 'slight right',
exit: 2,
location: [24.931, 60.1674],
},
name: 'Malminrinne',
distance: 100.8,
duration: 17.1,
},
{
maneuver: { type: 'turn', modifier: 'left' },
name: 'Nowhere',
distance: 5,
duration: 1,
},
],
},
{
steps: [
{
maneuver: {
type: 'arrive',
modifier: 'left',
location: [24.6559, 60.2055],
},
name: 'Kirkkojärventie',
distance: 0,
duration: 0,
},
],
},
],
};
const { steps, truncated } = normalizeOsrmSteps(route);
assert.equal(truncated, false, 'a short route is complete');
assert.deepEqual(
steps.map((s) => s.type),
['depart', 'roundabout', 'arrive'],
);
assert.equal(steps[1].distanceM, 136);
assert.equal(steps[1].durationS, 24);
assert.equal(
steps[1].instruction,
'At the roundabout, take the 2nd exit onto Malminrinne',
);
assert.deepEqual(
steps.map((s) => s.index),
[0, 1, 2],
);
assert.equal(steps[2].instruction, 'Arrive at your destination, on the left');
assert.equal(steps[0].lon, 24.9384);
assert.equal(steps[0].lat, 60.1699);
assert.deepEqual(normalizeOsrmSteps(null), { steps: [], truncated: false });
assert.deepEqual(normalizeOsrmSteps({ legs: [{ steps: null }] }), {
steps: [],
truncated: false,
});
});
test('the step list is capped, and the cap is reported rather than hidden', () => {
const many = Array.from({ length: ROUTE_STEPS_MAX + 50 }, (_, i) => ({
maneuver: { type: 'turn', modifier: 'left', location: [i * 0.001, 0.5] },
name: `Street ${i}`,
distance: 10,
duration: 2,
}));
const cut = normalizeOsrmSteps({ legs: [{ steps: many }] });
assert.equal(cut.steps.length, ROUTE_STEPS_MAX);
// The arrival step is among the ones dropped; a caller that is not told so
// would present a route that simply stops in the middle of a road.
assert.equal(cut.truncated, true);
const exact = normalizeOsrmSteps({
legs: [{ steps: many.slice(0, ROUTE_STEPS_MAX) }],
});
assert.equal(exact.steps.length, ROUTE_STEPS_MAX);
assert.equal(exact.truncated, false, 'a route that fits is not cut');
});
test('distance and duration formatting', () => {
assert.equal(formatRouteDistance(8), '8 m');
assert.equal(formatRouteDistance(846), '850 m');
assert.equal(formatRouteDistance(1234), '1.2 km');
assert.equal(formatRouteDistance(996), '1.0 km');
assert.equal(formatRouteDistance(9960), '10 km');
assert.equal(formatRouteDistance(21479), '21 km');
assert.equal(formatRouteDistance(-1), '');
assert.equal(formatRouteDuration(40), '40 s');
assert.equal(formatRouteDuration(1478), '25 min');
assert.equal(formatRouteDuration(3900), '1 h 5 min');
assert.equal(formatRouteDuration(7200), '2 h');
assert.equal(formatRouteDuration(NaN), '');
});