- Introduced `PassengerInfo.tsx` for displaying trip details, including upcoming trips and fare information. - Added `PassengerSettings.tsx` for managing user-defined important places and fare settings. - Implemented validation functions for passenger settings in `passenger-settings.mjs`. - Created utility functions for transliterating stop names in `transliteration.ts`. - Developed weather hint functionality in `weather-hint.ts` to provide precipitation probabilities. - Established a mock server for testing passenger-related features in `passenger-server.mjs`. - Added comprehensive tests for passenger guide logic and transliteration functionality.
97 lines
7.8 KiB
JavaScript
97 lines
7.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readSnapshot, writeSnapshot, readTimetable, writeTimetable, readWeather, writeWeather, readSettings, saveSettings } from '../src/lib/storage.ts';
|
|
|
|
test('transliteration is opt-in for old settings and persists as a boolean', () => {
|
|
const values = new Map([['ridebus-board-settings-v1', '{}']]);
|
|
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
|
const old = readSettings();
|
|
assert.equal(old.journeyTranslitEnabled, false);
|
|
saveSettings({ ...old, journeyTranslitEnabled: true });
|
|
assert.equal(readSettings().journeyTranslitEnabled, true);
|
|
saveSettings({ ...readSettings(), journeyTranslitEnabled: false });
|
|
assert.equal(readSettings().journeyTranslitEnabled, false);
|
|
values.set('ridebus-board-settings-v1', JSON.stringify({ journeyTranslitEnabled: 'true' }));
|
|
assert.equal(readSettings().journeyTranslitEnabled, false);
|
|
});
|
|
test('passenger preferences migrate with confirmed city-bound fares and preserve operator overrides', () => {
|
|
const values = new Map(); globalThis.localStorage = { getItem:key=>values.get(key)||null, setItem:(key,value)=>values.set(key,value) };
|
|
const old = readSettings(); assert.deepEqual(old.places, []); assert.deepEqual(old.fareRules, []);
|
|
assert.equal(old.fareByType[0].price, '1,05 BYN'); assert.equal(old.fareByType[0].cityId, 1);
|
|
const edited = { ...old, places: [{stopId:28,name:'Вокзал'}], fareRules: [{routeId:7,price:'Особый тариф',payment:'Подтверждённый способ',checkedAt:'2026-10-11'}],
|
|
infoPlacesEnabled:false, infoFareEnabled:false, lastTripNoticeEnabled:false, weatherHintEnabled:false, fareByType:[] };
|
|
saveSettings(edited); assert.deepEqual(readSettings(),edited);
|
|
values.set('ridebus-board-settings-v1',JSON.stringify({places:[{stopId:-1,name:'Неверно'}],fareByType:null}));
|
|
assert.deepEqual(readSettings().places,[]);assert.deepEqual(readSettings().fareByType,[]);
|
|
});
|
|
|
|
test('night settings migrate and persist, with invalid values falling back to supported defaults', () => {
|
|
const values = new Map(); globalThis.localStorage = { getItem:key=>values.get(key)||null, setItem:(key,value)=>values.set(key,value) };
|
|
const old = readSettings();assert.equal(old.nightMode,'auto');assert.equal(old.nightWakeMinutes,60);
|
|
saveSettings({...old,nightMode:'off',nightWakeMinutes:45});assert.equal(readSettings().nightMode,'off');assert.equal(readSettings().nightWakeMinutes,45);
|
|
values.set('ridebus-board-settings-v1',JSON.stringify({nightMode:'always',nightWakeMinutes:5}));assert.equal(readSettings().nightMode,'auto');assert.equal(readSettings().nightWakeMinutes,60);
|
|
});
|
|
|
|
const data = { cityId: 1, stop: { id: 1, name: 'Швейная фабрика' }, mode: 'live', serviceDate: '2026-10-08', updatedAt: '2026-10-08T12:00:00+03:00', routes: [], alerts: [],
|
|
departures: [{ id: '1:12:30', scheduledAt: '2026-10-08T12:30:00+03:00' }] };
|
|
|
|
test('offline snapshot is restored when IndexedDB is unavailable in a kiosk WebView', async () => {
|
|
const values = new Map();
|
|
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
|
globalThis.indexedDB = { open: () => { throw new Error('IndexedDB unavailable'); } };
|
|
await writeSnapshot(data);
|
|
assert.deepEqual(await readSnapshot(1, 1), data);
|
|
assert.equal(await readSnapshot(2, 1), null);
|
|
assert.equal(await readSnapshot(1, 2), null);
|
|
});
|
|
test('damaged saved data is rejected, and total storage failure is reported to the UI', async () => {
|
|
globalThis.localStorage = { getItem: () => '{invalid', setItem: () => { throw new Error('Quota exceeded'); } };
|
|
globalThis.indexedDB = { open: () => { throw new Error('IndexedDB unavailable'); } };
|
|
assert.equal(await readSnapshot(1, 1), null);
|
|
await assert.rejects(writeSnapshot(data));
|
|
});
|
|
test('full timetable and weather survive storage fallback and stay isolated by city and stop', async () => {
|
|
const values = new Map();
|
|
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
|
globalThis.indexedDB = { open: () => { throw new Error('IndexedDB unavailable'); } };
|
|
const timetable = { version: 1, cityId: 1, cityName: 'Полоцк', stop: { id: 1, name: 'Швейная фабрика', direction: '' }, routes: [], holidays: [], calendarYears: [2026], updatedAt: data.updatedAt, expiresAt: '2026-10-15T12:00:00+03:00' };
|
|
const weather = { cityId: 1, stopId: 1, updatedAt: data.updatedAt, observedAt: data.updatedAt, temperature: 12, apparentTemperature: 10, code: 3, isDay: true, windSpeed: 3, hourly: [] };
|
|
await writeTimetable(timetable); await writeWeather(weather); await writeSnapshot(data);
|
|
assert.deepEqual(await readTimetable(1, 1), timetable); assert.deepEqual(await readWeather(1, 1), weather); assert.deepEqual(await readSnapshot(1, 1), data);
|
|
assert.equal(await readTimetable(1, 2), null); assert.equal(await readWeather(2, 1), null);
|
|
});
|
|
test('old theme preferences migrate safely and each theme retains its own accent', () => {
|
|
const values = new Map([['ridebus-board-settings-v1', JSON.stringify({ colorScheme: 'cyan' })]]);
|
|
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
|
const old = readSettings();
|
|
assert.equal(old.theme, 'dark'); assert.equal(old.colorScheme, 'cyan'); assert.equal(old.lightColorScheme, 'blue');
|
|
saveSettings({ ...old, theme: 'light', lightColorScheme: 'crimson' });
|
|
const saved = readSettings();
|
|
assert.equal(saved.theme, 'light'); assert.equal(saved.lightColorScheme, 'crimson'); assert.equal(saved.colorScheme, 'cyan');
|
|
values.set('ridebus-board-settings-v1', JSON.stringify({ theme: 'light', lightColorScheme: 'lime' }));
|
|
assert.equal(readSettings().lightColorScheme, 'blue');
|
|
});
|
|
test('information switches migrate from old settings and persist every disabled block', () => {
|
|
const values = new Map([['ridebus-board-settings-v1', JSON.stringify({ rowCount: 6 })]]);
|
|
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
|
const old = readSettings();
|
|
assert.equal(old.infoServiceEnabled, true); assert.equal(old.infoWeatherEnabled, true); assert.equal(old.infoQrEnabled, true);
|
|
saveSettings({ ...old, infoServiceEnabled: false, infoWeatherEnabled: false, infoQrEnabled: false });
|
|
const disabled = readSettings();
|
|
assert.equal(disabled.infoServiceEnabled, false); assert.equal(disabled.infoWeatherEnabled, false); assert.equal(disabled.infoQrEnabled, false);
|
|
assert.equal(disabled.rowCount, 6);
|
|
saveSettings({ ...disabled, infoQrEnabled: true });
|
|
assert.equal(readSettings().infoQrEnabled, true); assert.equal(readSettings().infoWeatherEnabled, false);
|
|
});
|
|
test('readability and departure display preferences migrate safely and survive reload', () => {
|
|
const values = new Map([['ridebus-board-settings-v1', '{}']]);
|
|
globalThis.localStorage = { getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value) };
|
|
const old = readSettings();
|
|
assert.equal(old.displayProfile, 'tv'); assert.equal(old.viewingDistance, 'near'); assert.equal(old.showNextSameRoute, true); assert.equal(old.longWaitEnabled, true);
|
|
saveSettings({ ...old, displayProfile: 'street', viewingDistance: 'far', showNextSameRoute: false, longWaitEnabled: false });
|
|
const saved = readSettings();
|
|
assert.equal(saved.displayProfile, 'street'); assert.equal(saved.viewingDistance, 'far'); assert.equal(saved.showNextSameRoute, false); assert.equal(saved.longWaitEnabled, false);
|
|
values.set('ridebus-board-settings-v1', JSON.stringify({ displayProfile: 'invalid', viewingDistance: 'invalid' }));
|
|
assert.equal(readSettings().displayProfile, 'tv'); assert.equal(readSettings().viewingDistance, 'near');
|
|
});
|