Files
ridebus-board/test/timetable.test.mjs
Zakhar Timoshenko e3074555a7 feat: add service window component and related functionality
- 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.
2026-10-09 23:55:47 +03:00

67 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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');
});