minotor 3.0.1 → 4.0.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.
Files changed (82) 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 +8 -3
  6. package/README.md +35 -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 +2130 -909
  11. package/dist/cli.mjs.map +1 -1
  12. package/dist/gtfs/trips.d.ts +7 -1
  13. package/dist/gtfs/utils.d.ts +1 -1
  14. package/dist/parser.cjs.js +1236 -755
  15. package/dist/parser.cjs.js.map +1 -1
  16. package/dist/parser.d.ts +4 -2
  17. package/dist/parser.esm.js +1236 -755
  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 +3 -2
  36. package/dist/timetable/route.d.ts +157 -0
  37. package/dist/timetable/time.d.ts +21 -0
  38. package/dist/timetable/timetable.d.ts +42 -62
  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 +25 -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 +56 -74
  52. package/src/gtfs/parser.ts +49 -9
  53. package/src/gtfs/profiles/ch.ts +1 -1
  54. package/src/gtfs/routes.ts +4 -4
  55. package/src/gtfs/services.ts +15 -2
  56. package/src/gtfs/stops.ts +7 -3
  57. package/src/gtfs/transfers.ts +6 -3
  58. package/src/gtfs/trips.ts +206 -108
  59. package/src/gtfs/utils.ts +13 -2
  60. package/src/parser.ts +4 -2
  61. package/src/router.ts +17 -11
  62. package/src/routing/__tests__/result.test.ts +392 -0
  63. package/src/routing/__tests__/router.test.ts +94 -137
  64. package/src/routing/query.ts +28 -7
  65. package/src/routing/result.ts +10 -5
  66. package/src/routing/route.ts +95 -9
  67. package/src/routing/router.ts +82 -66
  68. package/src/stops/__tests__/io.test.ts +1 -1
  69. package/src/stops/__tests__/stopFinder.test.ts +1 -1
  70. package/src/stops/proto/stops.ts +4 -4
  71. package/src/stops/stopsIndex.ts +3 -3
  72. package/src/timetable/__tests__/io.test.ts +16 -23
  73. package/src/timetable/__tests__/route.test.ts +325 -0
  74. package/src/timetable/__tests__/time.test.ts +494 -0
  75. package/src/timetable/__tests__/timetable.test.ts +60 -75
  76. package/src/timetable/io.ts +32 -26
  77. package/src/timetable/proto/timetable.proto +3 -2
  78. package/src/timetable/proto/timetable.ts +15 -14
  79. package/src/timetable/route.ts +361 -0
  80. package/src/timetable/time.ts +40 -8
  81. package/src/timetable/timetable.ts +75 -166
  82. 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
+ });
@@ -1,22 +1,25 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
 
4
+ import { encodePickUpDropOffTypes } from '../../gtfs/trips.js';
4
5
  import { Duration } from '../duration.js';
6
+ import { NOT_AVAILABLE, REGULAR, Route } from '../route.js';
5
7
  import { Time } from '../time.js';
6
8
  import {
7
9
  RoutesAdjacency,
10
+ RouteType,
8
11
  ServiceRoutesMap,
9
12
  StopsAdjacency,
10
13
  Timetable,
11
14
  } from '../timetable.js';
12
15
 
13
- describe('timetable io', () => {
16
+ describe('Timetable', () => {
14
17
  const stopsAdjacency: StopsAdjacency = new Map([
15
18
  [
16
19
  1,
17
20
  {
18
21
  transfers: [{ destination: 2, type: 'RECOMMENDED' }],
19
- routes: ['route1'],
22
+ routes: ['route1', 'route2'],
20
23
  },
21
24
  ],
22
25
  [
@@ -29,66 +32,51 @@ describe('timetable io', () => {
29
32
  minTransferTime: Duration.fromMinutes(3),
30
33
  },
31
34
  ],
32
- routes: ['route2'],
35
+ routes: ['route2', 'route1'],
33
36
  },
34
37
  ],
35
- ]);
36
- const routesAdjacency: RoutesAdjacency = new Map([
37
38
  [
38
- 'route1',
39
+ 3,
39
40
  {
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',
66
- },
67
- ],
68
- [
69
- 'route2',
70
- {
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',
41
+ transfers: [],
42
+ routes: [],
89
43
  },
90
44
  ],
91
45
  ]);
46
+
47
+ const route1 = new Route(
48
+ new Uint16Array([
49
+ Time.fromHMS(16, 40, 0).toMinutes(),
50
+ Time.fromHMS(16, 50, 0).toMinutes(),
51
+ Time.fromHMS(17, 20, 0).toMinutes(),
52
+ Time.fromHMS(17, 30, 0).toMinutes(),
53
+ Time.fromHMS(18, 0, 0).toMinutes(),
54
+ Time.fromHMS(18, 10, 0).toMinutes(),
55
+ Time.fromHMS(19, 0, 0).toMinutes(),
56
+ Time.fromHMS(19, 10, 0).toMinutes(),
57
+ ]),
58
+ encodePickUpDropOffTypes(
59
+ [REGULAR, NOT_AVAILABLE, REGULAR, REGULAR],
60
+ [REGULAR, REGULAR, REGULAR, REGULAR],
61
+ ),
62
+ new Uint32Array([1, 2]),
63
+ 'gtfs1',
64
+ );
65
+ const route2 = new Route(
66
+ new Uint16Array([
67
+ Time.fromHMS(18, 20, 0).toMinutes(),
68
+ Time.fromHMS(18, 30, 0).toMinutes(),
69
+ Time.fromHMS(23, 20, 0).toMinutes(),
70
+ Time.fromHMS(23, 30, 0).toMinutes(),
71
+ ]),
72
+ encodePickUpDropOffTypes([REGULAR, REGULAR], [REGULAR, REGULAR]),
73
+ new Uint32Array([2, 1]),
74
+ 'gtfs2',
75
+ );
76
+ const routesAdjacency: RoutesAdjacency = new Map([
77
+ ['route1', route1],
78
+ ['route2', route2],
79
+ ]);
92
80
  const routes: ServiceRoutesMap = new Map([
93
81
  ['gtfs1', { type: 'RAIL', name: 'Route 1' }],
94
82
  ['gtfs2', { type: 'RAIL', name: 'Route 2' }],
@@ -114,7 +102,7 @@ describe('timetable io', () => {
114
102
  it('should find the earliest trip for stop1 on route1', () => {
115
103
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
116
104
  const route = sampleTimetable.getRoute('route1')!;
117
- const tripIndex = sampleTimetable.findEarliestTrip(route, 1);
105
+ const tripIndex = route.findEarliestTrip(1);
118
106
  assert.strictEqual(tripIndex, 0);
119
107
  });
120
108
 
@@ -122,12 +110,7 @@ describe('timetable io', () => {
122
110
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
123
111
  const route = sampleTimetable.getRoute('route1')!;
124
112
  const afterTime = Time.fromHMS(17, 0, 0);
125
- const tripIndex = sampleTimetable.findEarliestTrip(
126
- route,
127
- 1,
128
- undefined,
129
- afterTime,
130
- );
113
+ const tripIndex = route.findEarliestTrip(1, afterTime);
131
114
  assert.strictEqual(tripIndex, 1);
132
115
  });
133
116
 
@@ -135,38 +118,40 @@ describe('timetable io', () => {
135
118
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
136
119
  const route = sampleTimetable.getRoute('route1')!;
137
120
  const afterTime = Time.fromHMS(23, 40, 0);
138
- const tripIndex = sampleTimetable.findEarliestTrip(
139
- route,
140
- 1,
141
- undefined,
142
- afterTime,
143
- );
121
+ const tripIndex = route.findEarliestTrip(1, afterTime);
144
122
  assert.strictEqual(tripIndex, undefined);
145
123
  });
146
124
  it('should return undefined if the stop on a trip has pick up not available', () => {
147
125
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
148
126
  const route = sampleTimetable.getRoute('route1')!;
149
- const tripIndex = sampleTimetable.findEarliestTrip(route, 2);
127
+ const tripIndex = route.findEarliestTrip(2);
150
128
  assert.strictEqual(tripIndex, 1);
151
129
  });
152
130
  it('should find reachable routes from a set of stop IDs', () => {
153
131
  const fromStops = new Set([1]);
154
132
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
155
- assert.strictEqual(reachableRoutes.size, 1);
156
- assert.strictEqual(reachableRoutes.get('route1'), 1);
133
+ assert.strictEqual(reachableRoutes.size, 2);
134
+ assert.deepStrictEqual(
135
+ reachableRoutes,
136
+ new Map([
137
+ [route1, 1],
138
+ [route2, 1],
139
+ ]),
140
+ );
157
141
  });
158
142
 
159
143
  it('should find no reachable routes if starting from a non-existent stop', () => {
160
- const fromStops = new Set([5]);
144
+ const fromStops = new Set([3]);
161
145
  const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops);
162
146
  assert.strictEqual(reachableRoutes.size, 0);
163
147
  });
164
148
 
165
149
  it('should find reachable routes filtered by transport modes', () => {
166
150
  const fromStops = new Set([1]);
167
- const reachableRoutes = sampleTimetable.findReachableRoutes(fromStops, [
168
- 'BUS',
169
- ]);
151
+ const reachableRoutes = sampleTimetable.findReachableRoutes(
152
+ fromStops,
153
+ new Set<RouteType>(['BUS']),
154
+ );
170
155
  assert.strictEqual(reachableRoutes.size, 0);
171
156
  });
172
157
  });