- Introduced ServiceWindow component to display the last departure today and the first tomorrow. - Implemented cancellation notice parsing to handle route cancellations effectively. - Enhanced departure display logic to include cancellation alerts and status updates. - Created information blocks for displaying service information, weather, and QR codes. - Added tests for departure features, information handling, and service window functionality. - Updated fixtures for testing various scenarios including cancellations and long wait times.
67 lines
5.3 KiB
JavaScript
67 lines
5.3 KiB
JavaScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { loadTimetable } from '../server/timetable.mjs';
|
||
import { isTimetable, localDate, timetableAvailable, timetableDepartures } from '../src/lib/timetable.mjs';
|
||
import { passengerLink } from '../src/lib/passenger.mjs';
|
||
|
||
const now = Date.parse('2026-10-08T12:00:00+03:00');
|
||
const route = { id: 37, route_number: '5', name: 'А — В', type: 'bus' };
|
||
const stops = [{ stop_id: 1, sequence: 1, time_offset_minutes: 0, name: 'А' }, { stop_id: 2, sequence: 2, time_offset_minutes: 15, name: 'Б' }, { stop_id: 3, sequence: 3, time_offset_minutes: 30, name: 'В' }];
|
||
const schedule = (day_type, trips, extra = {}) => ({ route_id: 37, day_type, trips, ...extra });
|
||
async function bundle(schedules, options = {}) {
|
||
return loadTimetable({ cityId: 1, cityName: 'Полоцк', stop: { id: 2, name: 'Б', routes: [route] }, now,
|
||
upstream: async path => path.includes('holidays') ? options.holidays || [] : path.endsWith('/stops') ? stops : schedules });
|
||
}
|
||
test('full cache contains all trips and regenerates Friday and Saturday without network', async () => {
|
||
const data = await bundle([schedule('weekday', ['08:00', '12:00', '15:00', '18:00', '22:00'].map(departureTime => ({ departureTime }))), schedule('saturday', [{ departureTime: '09:00' }])]);
|
||
assert.equal(isTimetable(data, 1, 2), true);
|
||
assert.equal(isTimetable(data, 1, 1), false);
|
||
assert.equal(isTimetable({ ...data, routes: [{ ...data.routes[0], schedules: [{ dayType: 'weekday', trips: [{ arrivalSeconds: NaN }] }] }] }, 1, 2), false);
|
||
const departures = timetableDepartures(data, '2026-10-09');
|
||
assert.equal(departures.filter(item => localDate(Date.parse(item.scheduledAt)) === '2026-10-09').length, 5);
|
||
assert.equal(departures.find(item => localDate(Date.parse(item.scheduledAt)) === '2026-10-10').scheduledTime, '09:15');
|
||
assert.equal(timetableAvailable(data, now + 86400000), true);
|
||
assert.equal(timetableAvailable(data, now + 7 * 86400000), false);
|
||
});
|
||
test('holiday timetable wins; missing holiday variant falls back to Sunday', async () => {
|
||
const holidays = [{ date: '2026-10-09T00:00:00.000Z' }];
|
||
const schedules = [schedule('weekday', [{ departureTime: '08:00' }]), schedule('sunday', [{ departureTime: '09:00' }])];
|
||
const fallback = timetableDepartures(await bundle(schedules, { holidays }), '2026-10-09');
|
||
assert.equal(fallback[0].scheduledTime, '09:15');
|
||
const dedicated = timetableDepartures(await bundle([...schedules, schedule('holiday', [{ departureTime: '10:00' }])], { holidays }), '2026-10-09');
|
||
assert.equal(dedicated[0].scheduledTime, '10:15');
|
||
});
|
||
test('trip restrictions and schedule validity apply to the actual service date', async () => {
|
||
const data = await bundle([schedule('weekday', [{ departureTime: '08:00', daysOfWeek: [4] }, { departureTime: '09:00', exceptDaysOfWeek: [4] }], { valid_until: '2026-10-09' })]);
|
||
assert.equal(timetableDepartures(data, '2026-10-08')[0].scheduledTime, '08:15');
|
||
assert.equal(timetableDepartures(data, '2026-10-09')[0].scheduledTime, '09:15');
|
||
assert.equal(timetableDepartures(data, '2026-10-12').length, 0);
|
||
});
|
||
test('previous service day retains after-midnight arrivals, with no time wrapping', async () => {
|
||
const data = await bundle([schedule('weekday', [{ departureTime: '23:55', daysOfWeek: [4] }])]);
|
||
const departures = timetableDepartures(data, '2026-10-09');
|
||
assert.equal(departures.length, 1);
|
||
assert.equal(departures[0].scheduledTime, '00:10');
|
||
assert.equal(departures[0].scheduledAt, '2026-10-08T21:10:00.000Z');
|
||
assert.equal(departures[0].serviceDate, '2026-10-08');
|
||
assert.equal(departures[0].originTime, '23:55'); assert.equal(departures[0].originStopId, 1);
|
||
});
|
||
test('partial and reverse trips use the start offset and correct terminal', async () => {
|
||
const data = await bundle([schedule('weekday', [{ departureTime: '12:00', startStopId: 3, endStopId: 1, note: 'Обратно' }, { departureTime: '13:00', startStopId: 3, endStopId: 3 }])]);
|
||
const departures = timetableDepartures(data, '2026-10-08').filter(item => localDate(Date.parse(item.scheduledAt)) === '2026-10-08');
|
||
assert.equal(departures.length, 1); assert.equal(departures[0].scheduledTime, '12:15'); assert.equal(departures[0].destination, 'А');
|
||
});
|
||
test('failed calendar, route data, or malformed trips cannot publish a misleading complete cache', async () => {
|
||
await assert.rejects(bundle([schedule('weekday', [{ departureTime: '12:00', startStopId: 999 }])]));
|
||
await assert.rejects(bundle([schedule('weekday', [{ departureTime: '25:00' }])]));
|
||
await assert.rejects(bundle([], { holidays: {} }));
|
||
await assert.rejects(loadTimetable({ cityId: 1, cityName: 'Полоцк', stop: { id: 2, name: 'Б', routes: [route] }, now, upstream: async path => {
|
||
if (path.endsWith('/schedules')) throw new Error('Upstream unavailable'); return path.includes('holidays') ? [] : stops;
|
||
} }));
|
||
});
|
||
test('QR link identifies the exact city and platform, strips credentials and old query state', () => {
|
||
assert.equal(passengerLink('https://pwa.ridebus.by/?demo=1', 1, 107), 'https://pwa.ridebus.by/?city=1&stop=107');
|
||
assert.equal(passengerLink('javascript:alert(1)', 1, 1), 'https://pwa.ridebus.by/?city=1&stop=1');
|
||
assert.equal(passengerLink('https://user:password@pwa.ridebus.by/#old', 2, 88), 'https://pwa.ridebus.by/?city=2&stop=88');
|
||
});
|