minotor 3.0.1 → 3.0.2

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.
Files changed (81) hide show
  1. package/.cspell.json +12 -1
  2. package/.gitattributes +3 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +3 -0
  4. package/.github/workflows/minotor.yml +17 -1
  5. package/CHANGELOG.md +2 -2
  6. package/README.md +34 -14
  7. package/dist/__e2e__/router.test.d.ts +1 -0
  8. package/dist/cli/perf.d.ts +28 -0
  9. package/dist/cli/utils.d.ts +6 -2
  10. package/dist/cli.mjs +1967 -823
  11. package/dist/cli.mjs.map +1 -1
  12. package/dist/gtfs/trips.d.ts +1 -0
  13. package/dist/gtfs/utils.d.ts +1 -1
  14. package/dist/parser.cjs.js +1030 -627
  15. package/dist/parser.cjs.js.map +1 -1
  16. package/dist/parser.d.ts +4 -2
  17. package/dist/parser.esm.js +1030 -627
  18. package/dist/parser.esm.js.map +1 -1
  19. package/dist/router.cjs.js +1 -1
  20. package/dist/router.cjs.js.map +1 -1
  21. package/dist/router.d.ts +10 -5
  22. package/dist/router.esm.js +1 -1
  23. package/dist/router.esm.js.map +1 -1
  24. package/dist/router.umd.js +1 -1
  25. package/dist/router.umd.js.map +1 -1
  26. package/dist/routing/__tests__/result.test.d.ts +1 -0
  27. package/dist/routing/query.d.ts +27 -6
  28. package/dist/routing/result.d.ts +1 -1
  29. package/dist/routing/route.d.ts +47 -2
  30. package/dist/routing/router.d.ts +15 -1
  31. package/dist/stops/stopsIndex.d.ts +3 -3
  32. package/dist/timetable/__tests__/route.test.d.ts +1 -0
  33. package/dist/timetable/__tests__/time.test.d.ts +1 -0
  34. package/dist/timetable/io.d.ts +7 -1
  35. package/dist/timetable/proto/timetable.d.ts +1 -1
  36. package/dist/timetable/route.d.ts +155 -0
  37. package/dist/timetable/time.d.ts +21 -0
  38. package/dist/timetable/timetable.d.ts +41 -61
  39. package/package.json +36 -34
  40. package/src/__e2e__/benchmark.json +22 -0
  41. package/src/__e2e__/router.test.ts +209 -0
  42. package/src/__e2e__/timetable/stops.bin +3 -0
  43. package/src/__e2e__/timetable/timetable.bin +3 -0
  44. package/src/cli/minotor.ts +51 -1
  45. package/src/cli/perf.ts +136 -0
  46. package/src/cli/repl.ts +26 -13
  47. package/src/cli/utils.ts +6 -28
  48. package/src/gtfs/__tests__/parser.test.ts +12 -15
  49. package/src/gtfs/__tests__/services.test.ts +1 -0
  50. package/src/gtfs/__tests__/transfers.test.ts +0 -1
  51. package/src/gtfs/__tests__/trips.test.ts +67 -74
  52. package/src/gtfs/profiles/ch.ts +1 -1
  53. package/src/gtfs/routes.ts +4 -4
  54. package/src/gtfs/services.ts +15 -2
  55. package/src/gtfs/stops.ts +7 -3
  56. package/src/gtfs/transfers.ts +6 -3
  57. package/src/gtfs/trips.ts +33 -16
  58. package/src/gtfs/utils.ts +13 -2
  59. package/src/parser.ts +4 -2
  60. package/src/router.ts +17 -11
  61. package/src/routing/__tests__/result.test.ts +392 -0
  62. package/src/routing/__tests__/router.test.ts +94 -137
  63. package/src/routing/query.ts +28 -7
  64. package/src/routing/result.ts +10 -5
  65. package/src/routing/route.ts +95 -9
  66. package/src/routing/router.ts +82 -66
  67. package/src/stops/__tests__/io.test.ts +1 -1
  68. package/src/stops/__tests__/stopFinder.test.ts +1 -1
  69. package/src/stops/proto/stops.ts +4 -4
  70. package/src/stops/stopsIndex.ts +3 -3
  71. package/src/timetable/__tests__/io.test.ts +16 -23
  72. package/src/timetable/__tests__/route.test.ts +317 -0
  73. package/src/timetable/__tests__/time.test.ts +494 -0
  74. package/src/timetable/__tests__/timetable.test.ts +64 -75
  75. package/src/timetable/io.ts +32 -26
  76. package/src/timetable/proto/timetable.proto +1 -1
  77. package/src/timetable/proto/timetable.ts +13 -13
  78. package/src/timetable/route.ts +347 -0
  79. package/src/timetable/time.ts +40 -8
  80. package/src/timetable/timetable.ts +74 -165
  81. package/tsconfig.build.json +1 -1
@@ -0,0 +1,494 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { Duration } from '../duration.js';
5
+ import { Time } from '../time.js';
6
+
7
+ describe('Time', () => {
8
+ describe('Static factory methods', () => {
9
+ describe('infinity()', () => {
10
+ it('should return a Time instance representing infinity', () => {
11
+ const infinityTime = Time.infinity();
12
+ assert.strictEqual(infinityTime.toMinutes(), Number.MAX_SAFE_INTEGER);
13
+ });
14
+
15
+ it('should return the same infinity value for multiple calls', () => {
16
+ const infinity1 = Time.infinity();
17
+ const infinity2 = Time.infinity();
18
+ assert(infinity1.equals(infinity2));
19
+ });
20
+ });
21
+
22
+ describe('origin()', () => {
23
+ it('should return a Time instance representing midnight (0 minutes)', () => {
24
+ const midnight = Time.origin();
25
+ assert.strictEqual(midnight.toMinutes(), 0);
26
+ assert.strictEqual(midnight.toString(), '00:00');
27
+ });
28
+ });
29
+
30
+ describe('fromMinutes()', () => {
31
+ it('should create a Time instance from minutes', () => {
32
+ const time = Time.fromMinutes(120);
33
+ assert.strictEqual(time.toMinutes(), 120);
34
+ assert.strictEqual(time.toString(), '02:00');
35
+ });
36
+
37
+ it('should handle zero minutes', () => {
38
+ const time = Time.fromMinutes(0);
39
+ assert.strictEqual(time.toMinutes(), 0);
40
+ assert.strictEqual(time.toString(), '00:00');
41
+ });
42
+
43
+ it('should handle minutes beyond 24 hours', () => {
44
+ const time = Time.fromMinutes(1500); // 25 hours
45
+ assert.strictEqual(time.toMinutes(), 1500);
46
+ assert.strictEqual(time.toString(), '01:00'); // wraps around to next day
47
+ });
48
+ });
49
+
50
+ describe('fromHMS()', () => {
51
+ it('should create a Time instance from hours, minutes, and seconds', () => {
52
+ const time = Time.fromHMS(14, 30, 45);
53
+ assert.strictEqual(time.toMinutes(), 14 * 60 + 31); // rounds 30:45 to 31 minutes
54
+ assert.strictEqual(time.toString(), '14:31');
55
+ });
56
+
57
+ it('should round seconds to the nearest minute', () => {
58
+ const time1 = Time.fromHMS(10, 30, 29); // rounds down
59
+ assert.strictEqual(time1.toMinutes(), 10 * 60 + 30);
60
+
61
+ const time2 = Time.fromHMS(10, 30, 30); // rounds up
62
+ assert.strictEqual(time2.toMinutes(), 10 * 60 + 31);
63
+ });
64
+
65
+ it('should handle midnight', () => {
66
+ const time = Time.fromHMS(0, 0, 0);
67
+ assert.strictEqual(time.toMinutes(), 0);
68
+ assert.strictEqual(time.toString(), '00:00');
69
+ });
70
+
71
+ it('should throw error for negative values', () => {
72
+ assert.throws(() => Time.fromHMS(-1, 0, 0), /Invalid time/);
73
+ assert.throws(() => Time.fromHMS(0, -1, 0), /Invalid time/);
74
+ assert.throws(() => Time.fromHMS(0, 0, -1), /Invalid time/);
75
+ });
76
+
77
+ it('should throw error for invalid minute values', () => {
78
+ assert.throws(() => Time.fromHMS(10, 60, 0), /Invalid time/);
79
+ assert.throws(() => Time.fromHMS(10, 65, 0), /Invalid time/);
80
+ });
81
+
82
+ it('should throw error for invalid second values', () => {
83
+ assert.throws(() => Time.fromHMS(10, 30, 60), /Invalid time/);
84
+ assert.throws(() => Time.fromHMS(10, 30, 75), /Invalid time/);
85
+ });
86
+ });
87
+
88
+ describe('fromHM()', () => {
89
+ it('should create a Time instance from hours and minutes', () => {
90
+ const time = Time.fromHM(15, 45);
91
+ assert.strictEqual(time.toMinutes(), 15 * 60 + 45);
92
+ assert.strictEqual(time.toString(), '15:45');
93
+ });
94
+
95
+ it('should handle midnight', () => {
96
+ const time = Time.fromHM(0, 0);
97
+ assert.strictEqual(time.toMinutes(), 0);
98
+ assert.strictEqual(time.toString(), '00:00');
99
+ });
100
+
101
+ it('should throw error for negative hours', () => {
102
+ assert.throws(() => Time.fromHM(-1, 30), /Invalid time/);
103
+ });
104
+
105
+ it('should throw error for negative minutes', () => {
106
+ assert.throws(() => Time.fromHM(10, -5), /Invalid time/);
107
+ });
108
+
109
+ it('should throw error for invalid minute values', () => {
110
+ assert.throws(() => Time.fromHM(10, 60), /Invalid time/);
111
+ assert.throws(() => Time.fromHM(10, 75), /Invalid time/);
112
+ });
113
+ });
114
+
115
+ describe('fromDate()', () => {
116
+ it('should create a Time instance from a Date object', () => {
117
+ const date = new Date(2023, 5, 15, 14, 30, 45);
118
+ const time = Time.fromDate(date);
119
+ assert.strictEqual(time.toMinutes(), 14 * 60 + 31); // rounds seconds
120
+ assert.strictEqual(time.toString(), '14:31');
121
+ });
122
+
123
+ it('should handle midnight date', () => {
124
+ const date = new Date(2023, 5, 15, 0, 0, 0);
125
+ const time = Time.fromDate(date);
126
+ assert.strictEqual(time.toMinutes(), 0);
127
+ assert.strictEqual(time.toString(), '00:00');
128
+ });
129
+
130
+ it('should handle date near end of day', () => {
131
+ const date = new Date(2023, 5, 15, 23, 59, 30);
132
+ const time = Time.fromDate(date);
133
+ assert.strictEqual(time.toMinutes(), 24 * 60); // rounds up to next day
134
+ assert.strictEqual(time.toString(), '00:00');
135
+ });
136
+ });
137
+
138
+ describe('fromString()', () => {
139
+ it('should parse HH:MM format', () => {
140
+ const time = Time.fromString('14:30');
141
+ assert.strictEqual(time.toMinutes(), 14 * 60 + 30);
142
+ assert.strictEqual(time.toString(), '14:30');
143
+ });
144
+
145
+ it('should parse HH:MM:SS format', () => {
146
+ const time = Time.fromString('14:30:45');
147
+ assert.strictEqual(time.toMinutes(), 14 * 60 + 31); // rounds seconds
148
+ assert.strictEqual(time.toString(), '14:31');
149
+ });
150
+
151
+ it('should handle midnight', () => {
152
+ const time = Time.fromString('00:00');
153
+ assert.strictEqual(time.toMinutes(), 0);
154
+ assert.strictEqual(time.toString(), '00:00');
155
+ });
156
+
157
+ it('should handle single digit hours and minutes', () => {
158
+ const time = Time.fromString('9:05');
159
+ assert.strictEqual(time.toMinutes(), 9 * 60 + 5);
160
+ assert.strictEqual(time.toString(), '09:05');
161
+ });
162
+
163
+ it('should throw error for invalid format', () => {
164
+ assert.throws(
165
+ () => Time.fromString('invalid'),
166
+ /Input string must be in the format/,
167
+ );
168
+ assert.throws(() => Time.fromString('12:65'), /Invalid time/);
169
+ assert.throws(() => Time.fromString('12:30:65'), /Invalid time/);
170
+ });
171
+
172
+ it('should throw error for missing components', () => {
173
+ assert.throws(
174
+ () => Time.fromString('14'),
175
+ /Input string must be in the format/,
176
+ );
177
+ assert.throws(
178
+ () => Time.fromString('14:'),
179
+ /Input string must be in the format/,
180
+ );
181
+ assert.throws(
182
+ () => Time.fromString(':30'),
183
+ /Input string must be in the format/,
184
+ );
185
+ });
186
+
187
+ it('should throw error for non-numeric values', () => {
188
+ assert.throws(
189
+ () => Time.fromString('ab:cd'),
190
+ /Input string must be in the format/,
191
+ );
192
+ assert.throws(
193
+ () => Time.fromString('12:ab'),
194
+ /Input string must be in the format/,
195
+ );
196
+ assert.throws(
197
+ () => Time.fromString('12:30:ab'),
198
+ /Input string must be in the format/,
199
+ );
200
+ });
201
+ });
202
+ });
203
+
204
+ describe('Instance methods', () => {
205
+ describe('toString()', () => {
206
+ it('should format time as HH:MM', () => {
207
+ const time = Time.fromMinutes(14 * 60 + 30);
208
+ assert.strictEqual(time.toString(), '14:30');
209
+ });
210
+
211
+ it('should pad single digits with zeros', () => {
212
+ const time = Time.fromMinutes(9 * 60 + 5);
213
+ assert.strictEqual(time.toString(), '09:05');
214
+ });
215
+
216
+ it('should handle midnight', () => {
217
+ const time = Time.fromMinutes(0);
218
+ assert.strictEqual(time.toString(), '00:00');
219
+ });
220
+
221
+ it('should wrap hours beyond 24', () => {
222
+ const time = Time.fromMinutes(25 * 60 + 30); // 25:30
223
+ assert.strictEqual(time.toString(), '01:30');
224
+ });
225
+
226
+ it('should handle exactly 24 hours', () => {
227
+ const time = Time.fromMinutes(24 * 60);
228
+ assert.strictEqual(time.toString(), '00:00');
229
+ });
230
+ });
231
+
232
+ describe('toMinutes()', () => {
233
+ it('should return the minutes since midnight', () => {
234
+ const time = Time.fromMinutes(150);
235
+ assert.strictEqual(time.toMinutes(), 150);
236
+ });
237
+
238
+ it('should return 0 for midnight', () => {
239
+ const time = Time.origin();
240
+ assert.strictEqual(time.toMinutes(), 0);
241
+ });
242
+ });
243
+
244
+ describe('plus()', () => {
245
+ it('should add duration to time', () => {
246
+ const time = Time.fromMinutes(120); // 02:00
247
+ const duration = Duration.fromMinutes(30);
248
+ const result = time.plus(duration);
249
+ assert.strictEqual(result.toMinutes(), 150); // 02:30
250
+ });
251
+
252
+ it('should handle adding duration with seconds', () => {
253
+ const time = Time.fromMinutes(120);
254
+ const duration = Duration.fromSeconds(90); // 1.5 minutes
255
+ const result = time.plus(duration);
256
+ assert.strictEqual(result.toMinutes(), 122); // rounds to nearest minute
257
+ });
258
+
259
+ it('should not modify original time', () => {
260
+ const time = Time.fromMinutes(120);
261
+ const duration = Duration.fromMinutes(30);
262
+ time.plus(duration);
263
+ assert.strictEqual(time.toMinutes(), 120); // original unchanged
264
+ });
265
+ });
266
+
267
+ describe('minus()', () => {
268
+ it('should subtract duration from time', () => {
269
+ const time = Time.fromMinutes(150); // 02:30
270
+ const duration = Duration.fromMinutes(30);
271
+ const result = time.minus(duration);
272
+ assert.strictEqual(result.toMinutes(), 120); // 02:00
273
+ });
274
+
275
+ it('should handle subtracting duration with seconds', () => {
276
+ const time = Time.fromMinutes(150);
277
+ const duration = Duration.fromSeconds(90); // 1.5 minutes
278
+ const result = time.minus(duration);
279
+ assert.strictEqual(result.toMinutes(), 149); // rounds to nearest minute
280
+ });
281
+
282
+ it('should wrap to previous day for negative results', () => {
283
+ const time = Time.fromMinutes(30); // 00:30
284
+ const duration = Duration.fromMinutes(60); // 1 hour
285
+ const result = time.minus(duration);
286
+ assert.strictEqual(result.toMinutes(), 23 * 60 + 30); // 23:30 previous day
287
+ });
288
+
289
+ it('should not modify original time', () => {
290
+ const time = Time.fromMinutes(150);
291
+ const duration = Duration.fromMinutes(30);
292
+ time.minus(duration);
293
+ assert.strictEqual(time.toMinutes(), 150); // original unchanged
294
+ });
295
+ });
296
+
297
+ describe('diff()', () => {
298
+ it('should return absolute difference between times', () => {
299
+ const time1 = Time.fromMinutes(150); // 02:30
300
+ const time2 = Time.fromMinutes(120); // 02:00
301
+ const diff = time1.diff(time2);
302
+ assert.strictEqual(diff.toSeconds(), 30 * 60); // 30 minutes
303
+ });
304
+
305
+ it('should return absolute difference regardless of order', () => {
306
+ const time1 = Time.fromMinutes(120); // 02:00
307
+ const time2 = Time.fromMinutes(150); // 02:30
308
+ const diff = time1.diff(time2);
309
+ assert.strictEqual(diff.toSeconds(), 30 * 60); // 30 minutes
310
+ });
311
+
312
+ it('should return zero for same times', () => {
313
+ const time = Time.fromMinutes(120);
314
+ const diff = time.diff(time);
315
+ assert.strictEqual(diff.toSeconds(), 0);
316
+ });
317
+ });
318
+
319
+ describe('Comparison methods', () => {
320
+ describe('isAfter()', () => {
321
+ it('should return true when time is after other time', () => {
322
+ const time1 = Time.fromMinutes(150);
323
+ const time2 = Time.fromMinutes(120);
324
+ assert.strictEqual(time1.isAfter(time2), true);
325
+ });
326
+
327
+ it('should return false when time is before other time', () => {
328
+ const time1 = Time.fromMinutes(120);
329
+ const time2 = Time.fromMinutes(150);
330
+ assert.strictEqual(time1.isAfter(time2), false);
331
+ });
332
+
333
+ it('should return false when times are equal', () => {
334
+ const time1 = Time.fromMinutes(120);
335
+ const time2 = Time.fromMinutes(120);
336
+ assert.strictEqual(time1.isAfter(time2), false);
337
+ });
338
+ });
339
+
340
+ describe('isBefore()', () => {
341
+ it('should return true when time is before other time', () => {
342
+ const time1 = Time.fromMinutes(120);
343
+ const time2 = Time.fromMinutes(150);
344
+ assert.strictEqual(time1.isBefore(time2), true);
345
+ });
346
+
347
+ it('should return false when time is after other time', () => {
348
+ const time1 = Time.fromMinutes(150);
349
+ const time2 = Time.fromMinutes(120);
350
+ assert.strictEqual(time1.isBefore(time2), false);
351
+ });
352
+
353
+ it('should return false when times are equal', () => {
354
+ const time1 = Time.fromMinutes(120);
355
+ const time2 = Time.fromMinutes(120);
356
+ assert.strictEqual(time1.isBefore(time2), false);
357
+ });
358
+ });
359
+
360
+ describe('equals()', () => {
361
+ it('should return true when times are equal', () => {
362
+ const time1 = Time.fromMinutes(120);
363
+ const time2 = Time.fromMinutes(120);
364
+ assert.strictEqual(time1.equals(time2), true);
365
+ });
366
+
367
+ it('should return false when times are different', () => {
368
+ const time1 = Time.fromMinutes(120);
369
+ const time2 = Time.fromMinutes(150);
370
+ assert.strictEqual(time1.equals(time2), false);
371
+ });
372
+
373
+ it('should work with times created differently but representing same time', () => {
374
+ const time1 = Time.fromHM(2, 30);
375
+ const time2 = Time.fromMinutes(150);
376
+ assert.strictEqual(time1.equals(time2), true);
377
+ });
378
+ });
379
+ });
380
+ });
381
+
382
+ describe('Static utility methods', () => {
383
+ describe('max()', () => {
384
+ it('should return the maximum time from multiple times', () => {
385
+ const time1 = Time.fromMinutes(120);
386
+ const time2 = Time.fromMinutes(180);
387
+ const time3 = Time.fromMinutes(90);
388
+ const maxTime = Time.max(time1, time2, time3);
389
+ assert.strictEqual(maxTime.toMinutes(), 180);
390
+ });
391
+
392
+ it('should work with single time', () => {
393
+ const time = Time.fromMinutes(120);
394
+ const maxTime = Time.max(time);
395
+ assert.strictEqual(maxTime.toMinutes(), 120);
396
+ });
397
+
398
+ it('should work with duplicate times', () => {
399
+ const time1 = Time.fromMinutes(120);
400
+ const time2 = Time.fromMinutes(120);
401
+ const maxTime = Time.max(time1, time2);
402
+ assert.strictEqual(maxTime.toMinutes(), 120);
403
+ });
404
+
405
+ it('should throw error for empty array', () => {
406
+ assert.throws(
407
+ () => Time.max(),
408
+ /At least one Time instance is required/,
409
+ );
410
+ });
411
+
412
+ it('should handle infinity time', () => {
413
+ const time1 = Time.fromMinutes(120);
414
+ const infinity = Time.infinity();
415
+ const maxTime = Time.max(time1, infinity);
416
+ assert.strictEqual(maxTime, infinity);
417
+ });
418
+ });
419
+
420
+ describe('min()', () => {
421
+ it('should return the minimum time from multiple times', () => {
422
+ const time1 = Time.fromMinutes(120);
423
+ const time2 = Time.fromMinutes(180);
424
+ const time3 = Time.fromMinutes(90);
425
+ const minTime = Time.min(time1, time2, time3);
426
+ assert.strictEqual(minTime.toMinutes(), 90);
427
+ });
428
+
429
+ it('should work with single time', () => {
430
+ const time = Time.fromMinutes(120);
431
+ const minTime = Time.min(time);
432
+ assert.strictEqual(minTime.toMinutes(), 120);
433
+ });
434
+
435
+ it('should work with duplicate times', () => {
436
+ const time1 = Time.fromMinutes(120);
437
+ const time2 = Time.fromMinutes(120);
438
+ const minTime = Time.min(time1, time2);
439
+ assert.strictEqual(minTime.toMinutes(), 120);
440
+ });
441
+
442
+ it('should throw error for empty array', () => {
443
+ assert.throws(
444
+ () => Time.min(),
445
+ /At least one Time instance is required/,
446
+ );
447
+ });
448
+
449
+ it('should handle origin time', () => {
450
+ const time1 = Time.fromMinutes(120);
451
+ const origin = Time.origin();
452
+ const minTime = Time.min(time1, origin);
453
+ assert.strictEqual(minTime, origin);
454
+ });
455
+ });
456
+ });
457
+
458
+ describe('Edge cases and special scenarios', () => {
459
+ it('should handle times beyond 24 hours correctly', () => {
460
+ const time = Time.fromMinutes(25 * 60); // 25:00
461
+ assert.strictEqual(time.toMinutes(), 25 * 60);
462
+ assert.strictEqual(time.toString(), '01:00'); // wraps to next day for display
463
+ });
464
+
465
+ it('should handle very large time values', () => {
466
+ const largeTime = Time.fromMinutes(1000000);
467
+ assert.strictEqual(largeTime.toMinutes(), 1000000);
468
+ });
469
+
470
+ it('should maintain precision with rounding', () => {
471
+ const time = Time.fromHMS(10, 30, 29); // should round down
472
+ assert.strictEqual(time.toMinutes(), 10 * 60 + 30);
473
+
474
+ const time2 = Time.fromHMS(10, 30, 31); // should round up
475
+ assert.strictEqual(time2.toMinutes(), 10 * 60 + 31);
476
+ });
477
+
478
+ it('should work with chained operations', () => {
479
+ const time = Time.fromHM(10, 0)
480
+ .plus(Duration.fromMinutes(30))
481
+ .minus(Duration.fromMinutes(15));
482
+ assert.strictEqual(time.toMinutes(), 10 * 60 + 15);
483
+ });
484
+
485
+ it('should handle comparison with infinity', () => {
486
+ const normalTime = Time.fromMinutes(1000);
487
+ const infinity = Time.infinity();
488
+
489
+ assert.strictEqual(normalTime.isBefore(infinity), true);
490
+ assert.strictEqual(infinity.isAfter(normalTime), true);
491
+ assert.strictEqual(infinity.equals(normalTime), false);
492
+ });
493
+ });
494
+ });
@@ -2,21 +2,23 @@ import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
 
4
4
  import { Duration } from '../duration.js';
5
+ import { NOT_AVAILABLE, REGULAR, Route } from '../route.js';
5
6
  import { Time } from '../time.js';
6
7
  import {
7
8
  RoutesAdjacency,
9
+ RouteType,
8
10
  ServiceRoutesMap,
9
11
  StopsAdjacency,
10
12
  Timetable,
11
13
  } from '../timetable.js';
12
14
 
13
- describe('timetable io', () => {
15
+ describe('Timetable', () => {
14
16
  const stopsAdjacency: StopsAdjacency = new Map([
15
17
  [
16
18
  1,
17
19
  {
18
20
  transfers: [{ destination: 2, type: 'RECOMMENDED' }],
19
- routes: ['route1'],
21
+ routes: ['route1', 'route2'],
20
22
  },
21
23
  ],
22
24
  [
@@ -29,66 +31,56 @@ describe('timetable io', () => {
29
31
  minTransferTime: Duration.fromMinutes(3),
30
32
  },
31
33
  ],
32
- routes: ['route2'],
33
- },
34
- ],
35
- ]);
36
- const routesAdjacency: RoutesAdjacency = new Map([
37
- [
38
- 'route1',
39
- {
40
- stopTimes: new Uint16Array([
41
- Time.fromHMS(16, 40, 0).toMinutes(),
42
- Time.fromHMS(16, 50, 0).toMinutes(),
43
- Time.fromHMS(17, 20, 0).toMinutes(),
44
- Time.fromHMS(17, 30, 0).toMinutes(),
45
- Time.fromHMS(18, 0, 0).toMinutes(),
46
- Time.fromHMS(18, 10, 0).toMinutes(),
47
- Time.fromHMS(19, 0, 0).toMinutes(),
48
- Time.fromHMS(19, 10, 0).toMinutes(),
49
- ]),
50
- pickUpDropOffTypes: new Uint8Array([
51
- 0,
52
- 0, // REGULAR
53
- 1,
54
- 0, // NOT_AVAILABLE, REGULAR
55
- 0,
56
- 0, // REGULAR
57
- 0,
58
- 0, // REGULAR
59
- ]),
60
- stops: new Uint32Array([1, 2]),
61
- stopIndices: new Map([
62
- [1, 0],
63
- [2, 1],
64
- ]),
65
- serviceRouteId: 'gtfs1',
34
+ routes: ['route2', 'route1'],
66
35
  },
67
36
  ],
68
37
  [
69
- 'route2',
38
+ 3,
70
39
  {
71
- stopTimes: new Uint16Array([
72
- Time.fromHMS(18, 20, 0).toMinutes(),
73
- Time.fromHMS(18, 30, 0).toMinutes(),
74
- Time.fromHMS(23, 20, 0).toMinutes(),
75
- Time.fromHMS(23, 30, 0).toMinutes(),
76
- ]),
77
- pickUpDropOffTypes: new Uint8Array([
78
- 0,
79
- 0, // REGULAR
80
- 0,
81
- 0, // REGULAR
82
- ]),
83
- stops: new Uint32Array([2, 1]),
84
- stopIndices: new Map([
85
- [2, 0],
86
- [1, 1],
87
- ]),
88
- serviceRouteId: 'gtfs2',
40
+ transfers: [],
41
+ routes: [],
89
42
  },
90
43
  ],
91
44
  ]);
45
+ const route1 = new Route(
46
+ new Uint16Array([
47
+ Time.fromHMS(16, 40, 0).toMinutes(),
48
+ Time.fromHMS(16, 50, 0).toMinutes(),
49
+ Time.fromHMS(17, 20, 0).toMinutes(),
50
+ Time.fromHMS(17, 30, 0).toMinutes(),
51
+ Time.fromHMS(18, 0, 0).toMinutes(),
52
+ Time.fromHMS(18, 10, 0).toMinutes(),
53
+ Time.fromHMS(19, 0, 0).toMinutes(),
54
+ Time.fromHMS(19, 10, 0).toMinutes(),
55
+ ]),
56
+ new Uint8Array([
57
+ REGULAR,
58
+ REGULAR,
59
+ NOT_AVAILABLE,
60
+ REGULAR,
61
+ REGULAR,
62
+ REGULAR,
63
+ REGULAR,
64
+ REGULAR,
65
+ ]),
66
+ new Uint32Array([1, 2]),
67
+ 'gtfs1',
68
+ );
69
+ const route2 = new Route(
70
+ new Uint16Array([
71
+ Time.fromHMS(18, 20, 0).toMinutes(),
72
+ Time.fromHMS(18, 30, 0).toMinutes(),
73
+ Time.fromHMS(23, 20, 0).toMinutes(),
74
+ Time.fromHMS(23, 30, 0).toMinutes(),
75
+ ]),
76
+ new Uint8Array([REGULAR, REGULAR, REGULAR, REGULAR]),
77
+ new Uint32Array([2, 1]),
78
+ 'gtfs2',
79
+ );
80
+ const routesAdjacency: RoutesAdjacency = new Map([
81
+ ['route1', route1],
82
+ ['route2', route2],
83
+ ]);
92
84
  const routes: ServiceRoutesMap = new Map([
93
85
  ['gtfs1', { type: 'RAIL', name: 'Route 1' }],
94
86
  ['gtfs2', { type: 'RAIL', name: 'Route 2' }],
@@ -114,7 +106,7 @@ describe('timetable io', () => {
114
106
  it('should find the earliest trip for stop1 on route1', () => {
115
107
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
116
108
  const route = sampleTimetable.getRoute('route1')!;
117
- const tripIndex = sampleTimetable.findEarliestTrip(route, 1);
109
+ const tripIndex = route.findEarliestTrip(1);
118
110
  assert.strictEqual(tripIndex, 0);
119
111
  });
120
112
 
@@ -122,12 +114,7 @@ describe('timetable io', () => {
122
114
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123
115
  const route = sampleTimetable.getRoute('route1')!;
124
116
  const afterTime = Time.fromHMS(17, 0, 0);
125
- const tripIndex = sampleTimetable.findEarliestTrip(
126
- route,
127
- 1,
128
- undefined,
129
- afterTime,
130
- );
117
+ const tripIndex = route.findEarliestTrip(1, afterTime);
131
118
  assert.strictEqual(tripIndex, 1);
132
119
  });
133
120
 
@@ -135,38 +122,40 @@ describe('timetable io', () => {
135
122
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
136
123
  const route = sampleTimetable.getRoute('route1')!;
137
124
  const afterTime = Time.fromHMS(23, 40, 0);
138
- const tripIndex = sampleTimetable.findEarliestTrip(
139
- route,
140
- 1,
141
- undefined,
142
- afterTime,
143
- );
125
+ const tripIndex = route.findEarliestTrip(1, afterTime);
144
126
  assert.strictEqual(tripIndex, undefined);
145
127
  });
146
128
  it('should return undefined if the stop on a trip has pick up not available', () => {
147
129
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
148
130
  const route = sampleTimetable.getRoute('route1')!;
149
- const tripIndex = sampleTimetable.findEarliestTrip(route, 2);
131
+ const tripIndex = route.findEarliestTrip(2);
150
132
  assert.strictEqual(tripIndex, 1);
151
133
  });
152
134
  it('should find reachable routes from a set of stop IDs', () => {
153
135
  const fromStops = new Set([1]);
154
136
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
155
- assert.strictEqual(reachableRoutes.size, 1);
156
- assert.strictEqual(reachableRoutes.get('route1'), 1);
137
+ assert.strictEqual(reachableRoutes.size, 2);
138
+ assert.deepStrictEqual(
139
+ reachableRoutes,
140
+ new Map([
141
+ [route1, 1],
142
+ [route2, 1],
143
+ ]),
144
+ );
157
145
  });
158
146
 
159
147
  it('should find no reachable routes if starting from a non-existent stop', () => {
160
- const fromStops = new Set([5]);
148
+ const fromStops = new Set([3]);
161
149
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
162
150
  assert.strictEqual(reachableRoutes.size, 0);
163
151
  });
164
152
 
165
153
  it('should find reachable routes filtered by transport modes', () => {
166
154
  const fromStops = new Set([1]);
167
- const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops, [
168
- 'BUS',
169
- ]);
155
+ const reachableRoutes = sampleTimetable.findReachableRoutes(
156
+ fromStops,
157
+ new Set<RouteType>(['BUS']),
158
+ );
170
159
  assert.strictEqual(reachableRoutes.size, 0);
171
160
  });
172
161
  });