minotor 11.1.2 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cspell.json +7 -1
- package/CHANGELOG.md +3 -3
- package/README.md +111 -86
- package/dist/cli/perf.d.ts +57 -18
- package/dist/cli.mjs +1371 -342
- package/dist/cli.mjs.map +1 -1
- package/dist/parser.cjs.js +57 -4
- package/dist/parser.cjs.js.map +1 -1
- package/dist/parser.esm.js +57 -4
- package/dist/parser.esm.js.map +1 -1
- package/dist/router.cjs.js +1 -1
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +5 -5
- package/dist/router.esm.js +1 -1
- package/dist/router.esm.js.map +1 -1
- package/dist/router.umd.js +1 -1
- package/dist/router.umd.js.map +1 -1
- package/dist/routing/__tests__/access.test.d.ts +1 -0
- package/dist/routing/__tests__/plainRouter.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeResult.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeRouter.test.d.ts +1 -0
- package/dist/routing/__tests__/rangeState.test.d.ts +1 -0
- package/dist/routing/__tests__/raptor.test.d.ts +1 -0
- package/dist/routing/__tests__/state.test.d.ts +1 -0
- package/dist/routing/access.d.ts +55 -0
- package/dist/routing/plainRouter.d.ts +21 -0
- package/dist/routing/plotter.d.ts +9 -0
- package/dist/routing/query.d.ts +132 -13
- package/dist/routing/rangeResult.d.ts +155 -0
- package/dist/routing/rangeRouter.d.ts +24 -0
- package/dist/routing/rangeState.d.ts +83 -0
- package/dist/routing/raptor.d.ts +96 -0
- package/dist/routing/result.d.ts +27 -7
- package/dist/routing/route.d.ts +5 -21
- package/dist/routing/router.d.ts +20 -91
- package/dist/routing/state.d.ts +92 -17
- package/dist/timetable/route.d.ts +8 -0
- package/dist/timetable/timetable.d.ts +17 -1
- package/package.json +1 -1
- package/src/__e2e__/benchmark.json +18 -0
- package/src/__e2e__/router.test.ts +461 -127
- package/src/cli/minotor.ts +39 -3
- package/src/cli/perf.ts +324 -60
- package/src/cli/repl.ts +96 -41
- package/src/router.ts +11 -3
- package/src/routing/__tests__/access.test.ts +294 -0
- package/src/routing/__tests__/plainRouter.test.ts +1633 -0
- package/src/routing/__tests__/plotter.test.ts +8 -8
- package/src/routing/__tests__/rangeResult.test.ts +273 -0
- package/src/routing/__tests__/rangeRouter.test.ts +472 -0
- package/src/routing/__tests__/rangeState.test.ts +246 -0
- package/src/routing/__tests__/raptor.test.ts +366 -0
- package/src/routing/__tests__/result.test.ts +27 -27
- package/src/routing/__tests__/route.test.ts +28 -0
- package/src/routing/__tests__/router.test.ts +75 -1587
- package/src/routing/__tests__/state.test.ts +78 -0
- package/src/routing/access.ts +144 -0
- package/src/routing/plainRouter.ts +60 -0
- package/src/routing/plotter.ts +53 -6
- package/src/routing/query.ts +116 -13
- package/src/routing/rangeResult.ts +292 -0
- package/src/routing/rangeRouter.ts +167 -0
- package/src/routing/rangeState.ts +150 -0
- package/src/routing/raptor.ts +416 -0
- package/src/routing/result.ts +68 -26
- package/src/routing/route.ts +15 -53
- package/src/routing/router.ts +40 -480
- package/src/routing/state.ts +191 -32
- package/src/timetable/__tests__/timetable.test.ts +373 -0
- package/src/timetable/route.ts +16 -4
- package/src/timetable/timetable.ts +54 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { describe, it } from 'node:test';
|
|
4
|
+
|
|
5
|
+
import { Route } from '../../timetable/route.js';
|
|
6
|
+
import { timeFromHM } from '../../timetable/time.js';
|
|
7
|
+
import {
|
|
8
|
+
ALL_TRANSPORT_MODES,
|
|
9
|
+
ServiceRoute,
|
|
10
|
+
StopAdjacency,
|
|
11
|
+
Timetable,
|
|
12
|
+
TripTransfers,
|
|
13
|
+
} from '../../timetable/timetable.js';
|
|
14
|
+
import { encode } from '../../timetable/tripStopId.js';
|
|
15
|
+
import { QueryOptions } from '../query.js';
|
|
16
|
+
import { Raptor } from '../raptor.js';
|
|
17
|
+
import { RoutingState } from '../state.js';
|
|
18
|
+
|
|
19
|
+
// ─── Base fixture ─────────────────────────────────────────────────────────────
|
|
20
|
+
// 3 stops: 0 (origin), 1 (transfer stop), 2 (destination)
|
|
21
|
+
// Route 0 (BUS): 0→1 depart 08:10 / arrive 08:30
|
|
22
|
+
// Route 1 (BUS): 1→2 depart 08:35 / arrive 08:50
|
|
23
|
+
// Stop adjacency: stop 0 → [0], stop 1 → [0, 1], stop 2 → [1]
|
|
24
|
+
|
|
25
|
+
const NB_STOPS = 3;
|
|
26
|
+
|
|
27
|
+
const route0 = Route.of({
|
|
28
|
+
id: 0,
|
|
29
|
+
serviceRouteId: 0,
|
|
30
|
+
trips: [
|
|
31
|
+
{
|
|
32
|
+
stops: [
|
|
33
|
+
{
|
|
34
|
+
id: 0,
|
|
35
|
+
arrivalTime: timeFromHM(8, 10),
|
|
36
|
+
departureTime: timeFromHM(8, 10),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 1,
|
|
40
|
+
arrivalTime: timeFromHM(8, 30),
|
|
41
|
+
departureTime: timeFromHM(8, 30),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const route1 = Route.of({
|
|
49
|
+
id: 1,
|
|
50
|
+
serviceRouteId: 1,
|
|
51
|
+
trips: [
|
|
52
|
+
{
|
|
53
|
+
stops: [
|
|
54
|
+
{
|
|
55
|
+
id: 1,
|
|
56
|
+
arrivalTime: timeFromHM(8, 35),
|
|
57
|
+
departureTime: timeFromHM(8, 35),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 2,
|
|
61
|
+
arrivalTime: timeFromHM(8, 50),
|
|
62
|
+
departureTime: timeFromHM(8, 50),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const stopsAdjacency: StopAdjacency[] = [
|
|
70
|
+
{ routes: [0] }, // stop 0: origin
|
|
71
|
+
{ routes: [0, 1] }, // stop 1: transfer stop
|
|
72
|
+
{ routes: [1] }, // stop 2: destination
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const serviceRoutes: ServiceRoute[] = [
|
|
76
|
+
{ type: 'BUS', name: 'Route 0', routes: [0] },
|
|
77
|
+
{ type: 'BUS', name: 'Route 1', routes: [1] },
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// ─── Extended fixture: slower direct route 0→2 ────────────────────────────────
|
|
81
|
+
// Route 2 (BUS): 0→2 depart 08:10 / arrive 09:00
|
|
82
|
+
|
|
83
|
+
const route2 = Route.of({
|
|
84
|
+
id: 2,
|
|
85
|
+
serviceRouteId: 2,
|
|
86
|
+
trips: [
|
|
87
|
+
{
|
|
88
|
+
stops: [
|
|
89
|
+
{
|
|
90
|
+
id: 0,
|
|
91
|
+
arrivalTime: timeFromHM(8, 10),
|
|
92
|
+
departureTime: timeFromHM(8, 10),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 2,
|
|
96
|
+
arrivalTime: timeFromHM(9, 0),
|
|
97
|
+
departureTime: timeFromHM(9, 0),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const stopsAdjacencyWithDirectRoute: StopAdjacency[] = [
|
|
105
|
+
{ routes: [0, 2] }, // stop 0
|
|
106
|
+
{ routes: [0, 1] }, // stop 1
|
|
107
|
+
{ routes: [1, 2] }, // stop 2
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const serviceRoutesWithDirectRoute: ServiceRoute[] = [
|
|
111
|
+
{ type: 'BUS', name: 'Route 0', routes: [0] },
|
|
112
|
+
{ type: 'BUS', name: 'Route 1', routes: [1] },
|
|
113
|
+
{ type: 'BUS', name: 'Route 2', routes: [2] },
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// ─── Extended fixture: walking transfer from stop 1 to stop 2 ─────────────────
|
|
117
|
+
// Stop 2 has no routes; can only be reached via the 5-minute walk from stop 1.
|
|
118
|
+
|
|
119
|
+
const stopsAdjacencyWithTransfer: StopAdjacency[] = [
|
|
120
|
+
{ routes: [0] },
|
|
121
|
+
{
|
|
122
|
+
routes: [0],
|
|
123
|
+
transfers: [
|
|
124
|
+
{ destination: 2, type: 'REQUIRES_MINIMAL_TIME', minTransferTime: 5 },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{ routes: [] },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
// ─── Extended fixture: mixed transport modes ──────────────────────────────────
|
|
131
|
+
// Route 0 is BUS, route 1 is RAIL — used to verify mode filtering.
|
|
132
|
+
|
|
133
|
+
const mixedModeServiceRoutes: ServiceRoute[] = [
|
|
134
|
+
{ type: 'BUS', name: 'Route 0', routes: [0] },
|
|
135
|
+
{ type: 'RAIL', name: 'Route 1', routes: [1] },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// ─── Extended fixture: in-seat trip continuation ──────────────────────────────
|
|
139
|
+
// Encodes: at stop-index 1 of route 0, trip 0 → continue as route 1, trip 0,
|
|
140
|
+
// boarding at stop-index 0.
|
|
141
|
+
|
|
142
|
+
const tripContinuations: TripTransfers = new Map([
|
|
143
|
+
[encode(1, 0, 0), [{ stopIndex: 0, routeId: 1, tripIndex: 0 }]],
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
describe('Raptor', () => {
|
|
149
|
+
describe('route scanning', () => {
|
|
150
|
+
it('marks the destination with the correct arrival time', () => {
|
|
151
|
+
const timetable = new Timetable(
|
|
152
|
+
stopsAdjacency,
|
|
153
|
+
[route0, route1],
|
|
154
|
+
serviceRoutes,
|
|
155
|
+
);
|
|
156
|
+
const state = new RoutingState(
|
|
157
|
+
timeFromHM(8, 0),
|
|
158
|
+
[1],
|
|
159
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
160
|
+
NB_STOPS,
|
|
161
|
+
6,
|
|
162
|
+
);
|
|
163
|
+
const raptor = new Raptor(timetable);
|
|
164
|
+
const options: QueryOptions = {
|
|
165
|
+
maxTransfers: 5,
|
|
166
|
+
minTransferTime: 2,
|
|
167
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
168
|
+
};
|
|
169
|
+
raptor.run(options, state);
|
|
170
|
+
assert.strictEqual(state.getArrival(1)?.arrival, timeFromHM(8, 30));
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('finds a two-leg journey via a route change', () => {
|
|
174
|
+
const timetable = new Timetable(
|
|
175
|
+
stopsAdjacency,
|
|
176
|
+
[route0, route1],
|
|
177
|
+
serviceRoutes,
|
|
178
|
+
);
|
|
179
|
+
const state = new RoutingState(
|
|
180
|
+
timeFromHM(8, 0),
|
|
181
|
+
[2],
|
|
182
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
183
|
+
NB_STOPS,
|
|
184
|
+
6,
|
|
185
|
+
);
|
|
186
|
+
const raptor = new Raptor(timetable);
|
|
187
|
+
const options: QueryOptions = {
|
|
188
|
+
maxTransfers: 5,
|
|
189
|
+
minTransferTime: 2,
|
|
190
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
191
|
+
};
|
|
192
|
+
raptor.run(options, state);
|
|
193
|
+
assert.strictEqual(state.getArrival(1)?.arrival, timeFromHM(8, 30));
|
|
194
|
+
assert.strictEqual(state.getArrival(1)?.legNumber, 1);
|
|
195
|
+
assert.strictEqual(state.getArrival(2)?.arrival, timeFromHM(8, 50));
|
|
196
|
+
assert.strictEqual(state.getArrival(2)?.legNumber, 2);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('prefers the faster two-leg route over a slower direct route', () => {
|
|
200
|
+
const timetable = new Timetable(
|
|
201
|
+
stopsAdjacencyWithDirectRoute,
|
|
202
|
+
[route0, route1, route2],
|
|
203
|
+
serviceRoutesWithDirectRoute,
|
|
204
|
+
);
|
|
205
|
+
const state = new RoutingState(
|
|
206
|
+
timeFromHM(8, 0),
|
|
207
|
+
[2],
|
|
208
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
209
|
+
NB_STOPS,
|
|
210
|
+
6,
|
|
211
|
+
);
|
|
212
|
+
const raptor = new Raptor(timetable);
|
|
213
|
+
const options: QueryOptions = {
|
|
214
|
+
maxTransfers: 5,
|
|
215
|
+
minTransferTime: 2,
|
|
216
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
217
|
+
};
|
|
218
|
+
raptor.run(options, state);
|
|
219
|
+
// The 0→1→2 journey arrives at 08:50; the direct 0→2 route arrives at 09:00.
|
|
220
|
+
assert.strictEqual(state.getArrival(2)?.arrival, timeFromHM(8, 50));
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('maxTransfers', () => {
|
|
225
|
+
it('stops after maxTransfers+1 rounds', () => {
|
|
226
|
+
// maxTransfers=0 means only round 1 runs: stop 1 is reachable (1 leg),
|
|
227
|
+
// but stop 2 requires a second round and must remain unreached.
|
|
228
|
+
const timetable = new Timetable(
|
|
229
|
+
stopsAdjacency,
|
|
230
|
+
[route0, route1],
|
|
231
|
+
serviceRoutes,
|
|
232
|
+
);
|
|
233
|
+
const state = new RoutingState(
|
|
234
|
+
timeFromHM(8, 0),
|
|
235
|
+
[2],
|
|
236
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
237
|
+
NB_STOPS,
|
|
238
|
+
1, // maxRounds = maxTransfers + 1
|
|
239
|
+
);
|
|
240
|
+
const raptor = new Raptor(timetable);
|
|
241
|
+
const options: QueryOptions = {
|
|
242
|
+
maxTransfers: 0,
|
|
243
|
+
minTransferTime: 2,
|
|
244
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
245
|
+
};
|
|
246
|
+
raptor.run(options, state);
|
|
247
|
+
assert.strictEqual(state.getArrival(1)?.arrival, timeFromHM(8, 30));
|
|
248
|
+
assert.strictEqual(state.getArrival(2), undefined);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('early termination', () => {
|
|
253
|
+
it('exits when no trips are catchable', () => {
|
|
254
|
+
// Departing at 09:00 — all trips have already left (route 0 at 08:10,
|
|
255
|
+
// route 1 at 08:35). No stop should be marked.
|
|
256
|
+
const timetable = new Timetable(
|
|
257
|
+
stopsAdjacency,
|
|
258
|
+
[route0, route1],
|
|
259
|
+
serviceRoutes,
|
|
260
|
+
);
|
|
261
|
+
const state = new RoutingState(
|
|
262
|
+
timeFromHM(9, 0),
|
|
263
|
+
[2],
|
|
264
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
265
|
+
NB_STOPS,
|
|
266
|
+
6,
|
|
267
|
+
);
|
|
268
|
+
const raptor = new Raptor(timetable);
|
|
269
|
+
const options: QueryOptions = {
|
|
270
|
+
maxTransfers: 5,
|
|
271
|
+
minTransferTime: 2,
|
|
272
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
273
|
+
};
|
|
274
|
+
raptor.run(options, state);
|
|
275
|
+
assert.strictEqual(state.getArrival(1), undefined);
|
|
276
|
+
assert.strictEqual(state.getArrival(2), undefined);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('walking transfers', () => {
|
|
281
|
+
it('marks stops reached only via a timed walk', () => {
|
|
282
|
+
// Only route 0 exists (0→1). Stop 2 has no routes but is reachable from
|
|
283
|
+
// stop 1 via a 5-minute REQUIRES_MINIMAL_TIME transfer.
|
|
284
|
+
// Expected: stop 2 arrival = 08:30 + 5 = 08:35.
|
|
285
|
+
const timetable = new Timetable(
|
|
286
|
+
stopsAdjacencyWithTransfer,
|
|
287
|
+
[route0],
|
|
288
|
+
[serviceRoutes[0]!],
|
|
289
|
+
);
|
|
290
|
+
const state = new RoutingState(
|
|
291
|
+
timeFromHM(8, 0),
|
|
292
|
+
[2],
|
|
293
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
294
|
+
NB_STOPS,
|
|
295
|
+
6,
|
|
296
|
+
);
|
|
297
|
+
const raptor = new Raptor(timetable);
|
|
298
|
+
const options: QueryOptions = {
|
|
299
|
+
maxTransfers: 5,
|
|
300
|
+
minTransferTime: 2,
|
|
301
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
302
|
+
};
|
|
303
|
+
raptor.run(options, state);
|
|
304
|
+
assert.strictEqual(state.getArrival(2)?.arrival, timeFromHM(8, 35));
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('transport mode filtering', () => {
|
|
309
|
+
it('skips routes of excluded mode', () => {
|
|
310
|
+
// Route 0 is BUS; route 1 is RAIL. When only RAIL is allowed, route 0
|
|
311
|
+
// is filtered out, stop 0 has no eligible route, and stop 1 is never
|
|
312
|
+
// reached — so neither stop 1 nor stop 2 should be marked.
|
|
313
|
+
const timetable = new Timetable(
|
|
314
|
+
stopsAdjacency,
|
|
315
|
+
[route0, route1],
|
|
316
|
+
mixedModeServiceRoutes,
|
|
317
|
+
);
|
|
318
|
+
const state = new RoutingState(
|
|
319
|
+
timeFromHM(8, 0),
|
|
320
|
+
[2],
|
|
321
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
322
|
+
NB_STOPS,
|
|
323
|
+
6,
|
|
324
|
+
);
|
|
325
|
+
const raptor = new Raptor(timetable);
|
|
326
|
+
const options: QueryOptions = {
|
|
327
|
+
maxTransfers: 5,
|
|
328
|
+
minTransferTime: 2,
|
|
329
|
+
transportModes: new Set(['RAIL']),
|
|
330
|
+
};
|
|
331
|
+
raptor.run(options, state);
|
|
332
|
+
assert.strictEqual(state.getArrival(1), undefined);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe('in-seat transfer continuations', () => {
|
|
337
|
+
it('reaches a stop in the same round via a continuation', () => {
|
|
338
|
+
// Continuation: route 0, trip 0, hop-off stop-index 1 → route 1, trip 0,
|
|
339
|
+
// boarding at stop-index 0. Because the continuation is processed within
|
|
340
|
+
// round 1 (no extra round needed), stop 2 should have legNumber === 1
|
|
341
|
+
// instead of the legNumber === 2 that a normal two-round journey produces.
|
|
342
|
+
const timetable = new Timetable(
|
|
343
|
+
stopsAdjacency,
|
|
344
|
+
[route0, route1],
|
|
345
|
+
serviceRoutes,
|
|
346
|
+
tripContinuations,
|
|
347
|
+
);
|
|
348
|
+
const state = new RoutingState(
|
|
349
|
+
timeFromHM(8, 0),
|
|
350
|
+
[2],
|
|
351
|
+
[{ fromStopId: 0, toStopId: 0, duration: 0 }],
|
|
352
|
+
NB_STOPS,
|
|
353
|
+
6,
|
|
354
|
+
);
|
|
355
|
+
const raptor = new Raptor(timetable);
|
|
356
|
+
const options: QueryOptions = {
|
|
357
|
+
maxTransfers: 5,
|
|
358
|
+
minTransferTime: 2,
|
|
359
|
+
transportModes: ALL_TRANSPORT_MODES,
|
|
360
|
+
};
|
|
361
|
+
raptor.run(options, state);
|
|
362
|
+
assert.strictEqual(state.getArrival(2)?.arrival, timeFromHM(8, 50));
|
|
363
|
+
assert.strictEqual(state.getArrival(2)?.legNumber, 1);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -179,7 +179,7 @@ describe('Result', () => {
|
|
|
179
179
|
describe('bestRoute', () => {
|
|
180
180
|
it('should return undefined when no route exists', () => {
|
|
181
181
|
const result = new Result(
|
|
182
|
-
mockQuery,
|
|
182
|
+
mockQuery.to,
|
|
183
183
|
RoutingState.fromTestData({ nbStops: NB_STOPS, destinations: [2, 3] }),
|
|
184
184
|
mockStopsIndex,
|
|
185
185
|
mockTimetable,
|
|
@@ -191,13 +191,13 @@ describe('Result', () => {
|
|
|
191
191
|
|
|
192
192
|
it('should return undefined for unreachable destination', () => {
|
|
193
193
|
const result = new Result(
|
|
194
|
-
mockQuery,
|
|
194
|
+
mockQuery.to,
|
|
195
195
|
RoutingState.fromTestData({
|
|
196
196
|
nbStops: NB_STOPS,
|
|
197
197
|
origins: [0],
|
|
198
198
|
destinations: [2, 3],
|
|
199
199
|
arrivals: [[1, timeFromHMS(8, 30, 0), 0]],
|
|
200
|
-
graph: [[[0, { arrival: timeFromHMS(8, 0, 0) }]]],
|
|
200
|
+
graph: [[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]]],
|
|
201
201
|
}),
|
|
202
202
|
mockStopsIndex,
|
|
203
203
|
mockTimetable,
|
|
@@ -217,7 +217,7 @@ describe('Result', () => {
|
|
|
217
217
|
};
|
|
218
218
|
|
|
219
219
|
const result = new Result(
|
|
220
|
-
mockQuery,
|
|
220
|
+
mockQuery.to,
|
|
221
221
|
RoutingState.fromTestData({
|
|
222
222
|
nbStops: NB_STOPS,
|
|
223
223
|
origins: [0],
|
|
@@ -228,7 +228,7 @@ describe('Result', () => {
|
|
|
228
228
|
[3, timeFromHMS(9, 30, 0), 1],
|
|
229
229
|
],
|
|
230
230
|
graph: [
|
|
231
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
231
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
232
232
|
[[2, vehicleEdge]], // round 1
|
|
233
233
|
],
|
|
234
234
|
}),
|
|
@@ -255,7 +255,7 @@ describe('Result', () => {
|
|
|
255
255
|
};
|
|
256
256
|
|
|
257
257
|
const result = new Result(
|
|
258
|
-
mockQuery,
|
|
258
|
+
mockQuery.to,
|
|
259
259
|
RoutingState.fromTestData({
|
|
260
260
|
nbStops: NB_STOPS,
|
|
261
261
|
origins: [2],
|
|
@@ -266,7 +266,7 @@ describe('Result', () => {
|
|
|
266
266
|
[6, timeFromHMS(10, 30, 0), 2],
|
|
267
267
|
],
|
|
268
268
|
graph: [
|
|
269
|
-
[[2, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
269
|
+
[[2, { stopId: 2, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
270
270
|
[
|
|
271
271
|
[
|
|
272
272
|
2,
|
|
@@ -304,7 +304,7 @@ describe('Result', () => {
|
|
|
304
304
|
};
|
|
305
305
|
|
|
306
306
|
const result = new Result(
|
|
307
|
-
mockQuery,
|
|
307
|
+
mockQuery.to,
|
|
308
308
|
RoutingState.fromTestData({
|
|
309
309
|
nbStops: NB_STOPS,
|
|
310
310
|
origins: [0],
|
|
@@ -314,7 +314,7 @@ describe('Result', () => {
|
|
|
314
314
|
[2, timeFromHMS(9, 0, 0), 1],
|
|
315
315
|
],
|
|
316
316
|
graph: [
|
|
317
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
317
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
318
318
|
[[2, vehicleEdge]], // round 1
|
|
319
319
|
],
|
|
320
320
|
}),
|
|
@@ -349,7 +349,7 @@ describe('Result', () => {
|
|
|
349
349
|
};
|
|
350
350
|
|
|
351
351
|
const result = new Result(
|
|
352
|
-
mockQuery,
|
|
352
|
+
mockQuery.to,
|
|
353
353
|
RoutingState.fromTestData({
|
|
354
354
|
nbStops: NB_STOPS,
|
|
355
355
|
origins: [0],
|
|
@@ -360,7 +360,7 @@ describe('Result', () => {
|
|
|
360
360
|
[3, timeFromHMS(9, 45, 0), 2],
|
|
361
361
|
],
|
|
362
362
|
graph: [
|
|
363
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
363
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
364
364
|
[[2, firstVehicleEdge]], // round 1
|
|
365
365
|
[[3, secondVehicleEdge]], // round 2
|
|
366
366
|
],
|
|
@@ -394,7 +394,7 @@ describe('Result', () => {
|
|
|
394
394
|
};
|
|
395
395
|
|
|
396
396
|
const result = new Result(
|
|
397
|
-
mockQuery,
|
|
397
|
+
mockQuery.to,
|
|
398
398
|
RoutingState.fromTestData({
|
|
399
399
|
nbStops: NB_STOPS,
|
|
400
400
|
origins: [0],
|
|
@@ -404,7 +404,7 @@ describe('Result', () => {
|
|
|
404
404
|
[2, timeFromHMS(9, 0, 0), 1],
|
|
405
405
|
],
|
|
406
406
|
graph: [
|
|
407
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
407
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
408
408
|
[[2, vehicleEdge]], // round 1
|
|
409
409
|
],
|
|
410
410
|
}),
|
|
@@ -431,7 +431,7 @@ describe('Result', () => {
|
|
|
431
431
|
};
|
|
432
432
|
|
|
433
433
|
const result = new Result(
|
|
434
|
-
mockQuery,
|
|
434
|
+
mockQuery.to,
|
|
435
435
|
RoutingState.fromTestData({
|
|
436
436
|
nbStops: NB_STOPS,
|
|
437
437
|
origins: [0],
|
|
@@ -442,7 +442,7 @@ describe('Result', () => {
|
|
|
442
442
|
[3, timeFromHMS(9, 45, 0), 1],
|
|
443
443
|
],
|
|
444
444
|
graph: [
|
|
445
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
445
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
446
446
|
[[2, vehicleEdge]], // round 1
|
|
447
447
|
],
|
|
448
448
|
}),
|
|
@@ -480,7 +480,7 @@ describe('Result', () => {
|
|
|
480
480
|
};
|
|
481
481
|
|
|
482
482
|
const result = new Result(
|
|
483
|
-
mockQuery,
|
|
483
|
+
mockQuery.to,
|
|
484
484
|
RoutingState.fromTestData({
|
|
485
485
|
nbStops: NB_STOPS,
|
|
486
486
|
origins: [0],
|
|
@@ -491,7 +491,7 @@ describe('Result', () => {
|
|
|
491
491
|
[2, timeFromHMS(9, 0, 0), 1],
|
|
492
492
|
],
|
|
493
493
|
graph: [
|
|
494
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
494
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
495
495
|
[
|
|
496
496
|
[1, firstVehicleEdge],
|
|
497
497
|
[2, continuousVehicleEdge],
|
|
@@ -530,7 +530,7 @@ describe('Result', () => {
|
|
|
530
530
|
};
|
|
531
531
|
|
|
532
532
|
const result = new Result(
|
|
533
|
-
mockQuery,
|
|
533
|
+
mockQuery.to,
|
|
534
534
|
RoutingState.fromTestData({
|
|
535
535
|
nbStops: NB_STOPS,
|
|
536
536
|
origins: [0],
|
|
@@ -540,7 +540,7 @@ describe('Result', () => {
|
|
|
540
540
|
[3, timeFromHMS(9, 45, 0), 1],
|
|
541
541
|
],
|
|
542
542
|
graph: [
|
|
543
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
543
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
544
544
|
[[3, continuousVehicleEdge]], // round 1
|
|
545
545
|
],
|
|
546
546
|
}),
|
|
@@ -591,7 +591,7 @@ describe('Result', () => {
|
|
|
591
591
|
[3, timeFromHMS(9, 15, 0), 2],
|
|
592
592
|
],
|
|
593
593
|
graph: [
|
|
594
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
594
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
595
595
|
[
|
|
596
596
|
[1, firstVehicleEdge],
|
|
597
597
|
[2, transferEdge],
|
|
@@ -601,7 +601,7 @@ describe('Result', () => {
|
|
|
601
601
|
});
|
|
602
602
|
|
|
603
603
|
const result = new Result(
|
|
604
|
-
mockQuery,
|
|
604
|
+
mockQuery.to,
|
|
605
605
|
state,
|
|
606
606
|
mockStopsIndex,
|
|
607
607
|
mockTimetable,
|
|
@@ -649,7 +649,7 @@ describe('Result', () => {
|
|
|
649
649
|
const arrivalTime = { arrival: timeFromHMS(9, 0, 0), legNumber: 1 };
|
|
650
650
|
|
|
651
651
|
const result = new Result(
|
|
652
|
-
mockQuery,
|
|
652
|
+
mockQuery.to,
|
|
653
653
|
RoutingState.fromTestData({
|
|
654
654
|
nbStops: NB_STOPS,
|
|
655
655
|
destinations: [2],
|
|
@@ -665,7 +665,7 @@ describe('Result', () => {
|
|
|
665
665
|
|
|
666
666
|
it('should return undefined for unreachable stop', () => {
|
|
667
667
|
const result = new Result(
|
|
668
|
-
mockQuery,
|
|
668
|
+
mockQuery.to,
|
|
669
669
|
RoutingState.fromTestData({
|
|
670
670
|
nbStops: NB_STOPS,
|
|
671
671
|
destinations: [2],
|
|
@@ -683,7 +683,7 @@ describe('Result', () => {
|
|
|
683
683
|
const earlierArrival = { arrival: timeFromHMS(9, 0, 0), legNumber: 1 };
|
|
684
684
|
|
|
685
685
|
const result = new Result(
|
|
686
|
-
mockQuery,
|
|
686
|
+
mockQuery.to,
|
|
687
687
|
RoutingState.fromTestData({
|
|
688
688
|
nbStops: NB_STOPS,
|
|
689
689
|
destinations: [4],
|
|
@@ -721,7 +721,7 @@ describe('Result', () => {
|
|
|
721
721
|
};
|
|
722
722
|
|
|
723
723
|
const result = new Result(
|
|
724
|
-
mockQuery,
|
|
724
|
+
mockQuery.to,
|
|
725
725
|
RoutingState.fromTestData({
|
|
726
726
|
nbStops: NB_STOPS,
|
|
727
727
|
origins: [0],
|
|
@@ -732,7 +732,7 @@ describe('Result', () => {
|
|
|
732
732
|
// falling back to the graph rather than the global best.
|
|
733
733
|
arrivals: [[2, timeFromHMS(9, 0, 0), 2]],
|
|
734
734
|
graph: [
|
|
735
|
-
[[0, { arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
735
|
+
[[0, { stopId: 0, arrival: timeFromHMS(8, 0, 0) }]], // round 0 – origins
|
|
736
736
|
[[2, vehicleEdge1]], // round 1 – direct (9:30)
|
|
737
737
|
[[2, vehicleEdge2]], // round 2 – with transfer
|
|
738
738
|
],
|
|
@@ -752,7 +752,7 @@ describe('Result', () => {
|
|
|
752
752
|
|
|
753
753
|
it('should handle non-existent stops', () => {
|
|
754
754
|
const result = new Result(
|
|
755
|
-
mockQuery,
|
|
755
|
+
mockQuery.to,
|
|
756
756
|
RoutingState.fromTestData({
|
|
757
757
|
nbStops: NB_STOPS,
|
|
758
758
|
destinations: [2],
|
|
@@ -103,4 +103,32 @@ describe('Route', () => {
|
|
|
103
103
|
const route = new Route([transferLeg]);
|
|
104
104
|
assert.throws(() => route.arrivalTime(), /No vehicle leg found in route/);
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
describe('toString', () => {
|
|
108
|
+
it('includes leg numbers, stop names, and travel details for each leg', () => {
|
|
109
|
+
const route = new Route([vehicleLeg, transferLeg, secondVehicleLeg]);
|
|
110
|
+
const str = route.toString();
|
|
111
|
+
assert(str.includes('Leg 1:'));
|
|
112
|
+
assert(str.includes('Leg 2:'));
|
|
113
|
+
assert(str.includes('Leg 3:'));
|
|
114
|
+
assert(str.includes('Stop A'));
|
|
115
|
+
assert(str.includes('Stop D'));
|
|
116
|
+
assert(str.includes('BUS Route 1'));
|
|
117
|
+
assert(str.includes('RAIL Route 2'));
|
|
118
|
+
assert(str.includes('Transfer: RECOMMENDED'));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('includes platform info when present', () => {
|
|
122
|
+
const stopWithPlatform: Stop = {
|
|
123
|
+
...stopA,
|
|
124
|
+
platform: '3A',
|
|
125
|
+
};
|
|
126
|
+
const legWithPlatform: VehicleLeg = {
|
|
127
|
+
...vehicleLeg,
|
|
128
|
+
from: stopWithPlatform,
|
|
129
|
+
};
|
|
130
|
+
const route = new Route([legWithPlatform]);
|
|
131
|
+
assert(route.toString().includes('Pl. 3A'));
|
|
132
|
+
});
|
|
133
|
+
});
|
|
106
134
|
});
|