- Added NightScreen component for displaying night mode information and alerts. - Introduced NextStops component to show upcoming stops during a journey. - Implemented logic for determining night mode activation based on schedule and user settings. - Created journey utility functions to manage onward journey data and stop information. - Updated tests to cover new night mode features and journey logic. - Added fixture for testing night mode scenarios with specific timestamps. - Removed QR code from night screen for a cleaner UI experience. - Enhanced admin settings to manage night mode configurations.
35 lines
6.2 KiB
JavaScript
35 lines
6.2 KiB
JavaScript
// Explicit local fixture using the production App and timetable logic.
|
|
import express from 'express';
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { createApp } from '../../server/index.mjs';
|
|
import { createDeviceBridge } from '../../server/device.mjs';
|
|
import { localDate, shiftDate, timetableDepartures } from '../../src/lib/timetable.mjs';
|
|
const scenarios = { evening: '2026-10-10T23:20:00+03:00', overnight: '2026-10-11T00:14:00+03:00', after: '2026-10-11T00:20:00+03:00', night: '2026-10-11T02:00:00+03:00', ninety: '2026-10-11T04:10:00+03:00', wake: '2026-10-11T04:40:00+03:00', wake45: '2026-10-11T04:55:00+03:00', day: '2026-10-11T12:00:00+03:00', avtovokzal: '2026-10-11T03:31:00+03:00', avtovokzalWake60: '2026-10-11T04:15:00+03:00', avtovokzalWake45: '2026-10-11T04:30:00+03:00' };
|
|
// Optional public timetable snapshot, with explicit fixture clock and fresh TTL.
|
|
const snapshot = process.env.NIGHT_TIMETABLE_PATH ? JSON.parse(readFileSync(process.env.NIGHT_TIMETABLE_PATH, 'utf8')) : null;
|
|
const defaultStop = snapshot?.stop || {id:1,name:'Швейная фабрика',direction:'Тестовое расписание'};
|
|
let scenario = scenarios[process.env.NIGHT_SCENARIO] ? process.env.NIGHT_SCENARIO : 'evening', started = Date.now(), alertsOn = false, expired = false;
|
|
const now = () => Date.parse(scenarios[scenario]) + Date.now() - started;
|
|
function timetable(stopId=defaultStop.id) {
|
|
const updated = now() - (expired ? 8*86400000 : 0);
|
|
if (snapshot) return {...snapshot,stop:{...snapshot.stop,id:stopId,direction:`Тестовые часы · ${snapshot.stop.direction}`},updatedAt:new Date(updated).toISOString(),expiresAt:new Date(updated+7*86400000).toISOString()};
|
|
const route={id:6,number:'6',name:'Швейная фабрика — Больница (Новка)',type:'bus'};
|
|
const trip=(seconds)=>({arrivalSeconds:seconds,destination:'Больница (Новка) (через Аэропорт)',note:'',daysOfWeek:[],exceptDaysOfWeek:[],originStopId:1,originTime:seconds>=86400?'23:45':`${String(Math.floor(seconds/3600)).padStart(2,'0')}:${String(Math.floor(seconds%3600/60)).padStart(2,'0')}`});
|
|
return {version:1,cityId:1,cityName:'Полоцк',stop:{id:stopId,name:'Швейная фабрика',direction:'Тестовое расписание'},routes:[{...route,schedules:[
|
|
{dayType:'saturday',validFrom:null,validUntil:null,trips:[trip(23*3600),...(scenario==='overnight'||scenario==='after'?[trip(86400+15*60)]:[])]},
|
|
{dayType:'sunday',validFrom:null,validUntil:null,trips:[trip(5*3600+40*60),trip(8*3600),trip(19*3600),trip(23*3600)]},
|
|
{dayType:'weekday',validFrom:null,validUntil:null,trips:[trip(6*3600),trip(23*3600)]}]}],holidays:[],calendarYears:[2026,2027],updatedAt:new Date(updated).toISOString(),expiresAt:new Date(updated+7*86400000).toISOString()};
|
|
}
|
|
const client={ config:()=>({cityId:1,cityName:'Полоцк',stopName:defaultStop.name,defaultStopId:defaultStop.id,configured:true,refreshSeconds:5,timeZone:'Europe/Minsk',passengerBaseUrl:'https://pwa.ridebus.by/'}),stops:async()=>[defaultStop],timetable:async id=>timetable(id),coordinates:async()=>({latitude:55,longitude:28}),
|
|
board:async id=>{const t=timetable(id);return {cityId:1,cityName:'Полоцк',stop:t.stop,routes:t.routes.map(({schedules,...r})=>r),departures:timetableDepartures(t,localDate(now())).filter(d=>Date.parse(d.scheduledAt)+60000>now()),alerts:alertsOn?[{id:'fixture-cancel',type:'cancellation',routeNumbers:['6'],routeIds:[6],title:'Изменение утренних рейсов',message:'Рейс №6 в 05:40 отменён. Первое отправление — в 08:00. Остальные рейсы выполняются по расписанию.',startAt:'2026-10-11T00:00:00+03:00',endAt:'2026-10-12T00:00:00+03:00',cancellations:[{routeNumber:'6',stopId:1,serviceDate:'2026-10-11',time:'05:40'}]}]:[],alertsAvailable:true,partial:false,updatedAt:new Date(now()).toISOString(),serviceDate:localDate(now()),mode:'live',serverTime:new Date(now()).toISOString()};}};
|
|
// createApp supplies real wall time by default. This fixture explicitly replaces
|
|
// serverTime so the production client exercises schedule boundaries deterministically.
|
|
const app=express();app.use('/api/board',async(req,res,next)=>{if(req.method==='GET')res.setHeader('Cache-Control','no-store');if(req.method==='GET'&&req.query.stopId===String(defaultStop.id))return res.json(await client.board(defaultStop.id));next();});
|
|
app.use(createApp(client,{weather:async(cityId,stopId)=>({cityId,stopId,updatedAt:new Date(now()).toISOString(),observedAt:new Date(now()).toISOString(),temperature:7,apparentTemperature:5,code:3,isDay:false,windSpeed:2,hourly:[]})},createDeviceBridge({enabled:false})));
|
|
app.use('/__fixture',express.urlencoded({extended:false}));
|
|
app.post('/__fixture',(req,res)=>{if(scenarios[req.body.scenario]){scenario=req.body.scenario;started=Date.now();}if(req.body.alerts!==undefined)alertsOn=req.body.alerts==='true';if(req.body.expired!==undefined)expired=req.body.expired==='true';res.redirect(303,'/__fixture');});
|
|
app.get('/__fixture',(_req,res)=>res.type('html').send(`<html lang="ru"><body style="font:20px system-ui;padding:32px"><h1>Тестовые сценарии ночного режима</h1><p>Текущий: ${scenario}; уведомление: ${alertsOn}; истёкший кеш: ${expired}</p><form method="post">${Object.keys(scenarios).map(s=>`<button style="padding:14px;margin:6px" name="scenario" value="${s}">${s}</button>`).join('')}<button name="alerts" value="true">Показать отмену</button><button name="alerts" value="false">Убрать отмену</button><button name="expired" value="true">Истёкший кеш</button><button name="expired" value="false">Свежий кеш</button></form><p>Часы приложения обновятся при опросе (5 секунд). После смены полного расписания обновите страницу табло.</p></body></html>`));
|
|
const dist=fileURLToPath(new URL('../../dist/',import.meta.url));app.use(express.static(dist));app.get('/{*path}',(_req,res)=>res.sendFile(`${dist}/index.html`));
|
|
await new Promise(resolve=>app.listen(5193,'127.0.0.1',resolve));console.log('Night fixture http://127.0.0.1:5193/ ; controls /__fixture');
|