UNPKG

minotor

Version:

A lightweight client-side transit routing library.

335 lines (300 loc) 10.2 kB
/* eslint-disable @typescript-eslint/no-non-null-assertion */ import assert from 'node:assert'; import { describe, it } from 'node:test'; import { timeFromHM } from '../../timetable/time.js'; import { RangeRaptorState } from '../rangeState.js'; import { RoutingState, UNREACHED_TIME } from '../state.js'; const NB_STOPS = 4; const MAX_ROUNDS = 3; describe('RangeRaptorState', () => { describe('constructor', () => { it('creates roundLabels of length maxRounds + 2', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); assert.strictEqual(state.roundLabels.length, MAX_ROUNDS + 2); }); it('initialized all roundLabels to UNREACHED_TIME', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); for (const round of state.roundLabels) { for (const val of round) { assert.strictEqual(val, UNREACHED_TIME); } } }); it('stores latestDeparture', () => { const latest = timeFromHM(11, 30); const state = new RangeRaptorState(MAX_ROUNDS, NB_STOPS, latest); assert.strictEqual(state.latestDeparture, latest); }); }); describe('setCurrentRun', () => { it('seeds round-0 shared labels from origin nodes', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, origins: [1], graph: [[[1, { stopId: 1, arrival: timeFromHM(9, 0) }]]], }), ); assert.strictEqual(state.roundLabels[0]![1], timeFromHM(9, 0)); }); it('skips an origin that has no edge in round 0', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, origins: [1], graph: [[]], }), ); assert.strictEqual(state.roundLabels[0]![1], UNREACHED_TIME); }); it('delegates origins and graph getters to the active run', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); const run = RoutingState.fromTestData({ nbStops: NB_STOPS, origins: [0, 2], graph: [[]], }); state.setCurrentRun(run); assert.deepStrictEqual(state.origins, [0, 2]); assert.strictEqual(state.graph, run.graph); }); }); describe('improvementBound', () => { it('returns the cross-run shared label, tighter than the per-run arrival', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2] }), ); state.updateArrival(2, timeFromHM(9, 30), 1); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2] }), ); assert.strictEqual(state.arrivalTime(2), UNREACHED_TIME); assert.strictEqual(state.improvementBound(1, 2), timeFromHM(9, 30)); }); }); describe('updateArrival', () => { it('improves the shared roundLabel for the given round and stop', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2] }), ); state.updateArrival(2, timeFromHM(9, 0), 1); assert.strictEqual(state.roundLabels[1]![2], timeFromHM(9, 0)); }); it('does not worsen a shared roundLabel that is already tight', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2] }), ); state.updateArrival(2, timeFromHM(9, 0), 1); state.updateArrival(2, timeFromHM(9, 30), 1); assert.strictEqual(state.roundLabels[1]![2], timeFromHM(9, 0)); }); it('updates destinationBest when a destination stop is first reached', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [3] }), ); assert.strictEqual(state.destinationBest, UNREACHED_TIME); state.updateArrival(3, timeFromHM(10, 0), 1); assert.strictEqual(state.destinationBest, timeFromHM(10, 0)); }); it('destinationBest persists when the run is swapped', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [3] }), ); state.updateArrival(3, timeFromHM(10, 0), 1); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [3] }), ); assert.strictEqual(state.destinationBest, timeFromHM(10, 0)); }); }); describe('initRound', () => { it('propagates the round k-1 label into round k for changed stops', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2] }), ); state.updateArrival(2, timeFromHM(9, 0), 1); state.initRound(2); assert.strictEqual(state.roundLabels[2]![2], timeFromHM(9, 0)); }); it('does not overwrite a tighter label already present in round k', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2] }), ); state.updateArrival(2, timeFromHM(8, 30), 2); state.updateArrival(2, timeFromHM(9, 0), 1); state.initRound(2); assert.strictEqual(state.roundLabels[2]![2], timeFromHM(8, 30)); }); it('is a no-op when no stop changed in the previous round', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun(RoutingState.fromTestData({ nbStops: NB_STOPS })); state.initRound(1); for (const val of state.roundLabels[1]!) { assert.strictEqual(val, UNREACHED_TIME); } }); it('clears the changed-stop list so a subsequent call propagates nothing new', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun(RoutingState.fromTestData({ nbStops: NB_STOPS })); state.updateArrival(2, timeFromHM(9, 0), 0); state.initRound(1); state.initRound(2); assert.strictEqual(state.roundLabels[2]![2], UNREACHED_TIME); }); }); describe('updateArrival aggregate overwrite behavior', () => { it('does not overwrite the current run aggregate with a later arrival from a higher round', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); const run = RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2], arrivals: [[2, timeFromHM(8, 30), 1]], }); state.setCurrentRun(run); state.updateArrival(2, timeFromHM(8, 45), 2); assert.deepStrictEqual(run.getArrival(2), { arrival: timeFromHM(8, 30), legNumber: 1, }); assert.strictEqual(state.roundLabels[2]![2], timeFromHM(8, 45)); }); it('does not overwrite the current run aggregate with an equal-time arrival using more legs', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); const run = RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2], arrivals: [[2, timeFromHM(8, 30), 2]], }); state.setCurrentRun(run); state.updateArrival(2, timeFromHM(8, 30), 3); assert.deepStrictEqual(run.getArrival(2), { arrival: timeFromHM(8, 30), legNumber: 2, }); assert.strictEqual(state.roundLabels[3]![2], timeFromHM(8, 30)); }); it('prefers fewer legs for the current run aggregate when arrival time is equal', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); const run = RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2], arrivals: [[2, timeFromHM(8, 30), 3]], }); state.setCurrentRun(run); state.updateArrival(2, timeFromHM(8, 30), 2); assert.deepStrictEqual(run.getArrival(2), { arrival: timeFromHM(8, 30), legNumber: 2, }); }); it('still updates the current run aggregate when the new arrival is earlier', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); const run = RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2], arrivals: [[2, timeFromHM(8, 45), 1]], }); state.setCurrentRun(run); state.updateArrival(2, timeFromHM(8, 30), 3); assert.deepStrictEqual(run.getArrival(2), { arrival: timeFromHM(8, 30), legNumber: 3, }); }); }); describe('isDestination', () => { it('delegates to the current run', () => { const state = new RangeRaptorState( MAX_ROUNDS, NB_STOPS, timeFromHM(12, 0), ); state.setCurrentRun( RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [3] }), ); assert.strictEqual(state.isDestination(3), true); assert.strictEqual(state.isDestination(0), false); }); }); });