mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 04:13:47 +00:00
fix: backdate rundown start when rolling
This commit is contained in:
committed by
Carlos Valente
parent
5ff7861968
commit
6f1f10ccff
@@ -1,4 +1,4 @@
|
||||
import { Instant, PlayableEvent, Playback, TimerPhase } from 'ontime-types';
|
||||
import { Instant, PlayableEvent, Playback, SupportedEntry, TimerPhase } from 'ontime-types';
|
||||
import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||
|
||||
import { makeOntimeGroup, makeOntimeEvent, makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
||||
@@ -575,6 +575,95 @@ describe('roll mode', () => {
|
||||
expect(stateAfterMidnight.timer.secondaryTimer).toBe(50 * MILLIS_PER_MINUTE);
|
||||
});
|
||||
|
||||
test('rolling into overnight event after midnight has correct offset and expected times', async () => {
|
||||
// Simulates a rundown with:
|
||||
// - A group starting at 13:00, containing an overnight event
|
||||
// - Overnight event: 23:50 to 01:50 (2 hours)
|
||||
// Roll into the event at 00:21 (31 minutes into the event)
|
||||
const groupId = 'group-1';
|
||||
const eventId = 'event-1';
|
||||
const eventStart = 23 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE; // 23:50
|
||||
const eventEnd = 1 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE; // 01:50
|
||||
const eventDuration = 2 * MILLIS_PER_HOUR; // 2 hours
|
||||
const groupStart = 13 * MILLIS_PER_HOUR; // 13:00
|
||||
const groupDuration = 12 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE; // 12h 50m (ends at 01:50)
|
||||
|
||||
const mockRundown = makeRundown({
|
||||
entries: {
|
||||
[groupId]: {
|
||||
id: groupId,
|
||||
type: SupportedEntry.Group,
|
||||
title: 'Test Group',
|
||||
timeStart: groupStart,
|
||||
timeEnd: eventEnd,
|
||||
duration: groupDuration,
|
||||
entries: [eventId],
|
||||
colour: '',
|
||||
note: '',
|
||||
custom: {},
|
||||
revision: 0,
|
||||
isFirstLinked: false,
|
||||
targetDuration: null,
|
||||
},
|
||||
[eventId]: {
|
||||
...mockEvent,
|
||||
id: eventId,
|
||||
timeStart: eventStart,
|
||||
timeEnd: eventEnd,
|
||||
duration: eventDuration,
|
||||
dayOffset: 0,
|
||||
parent: groupId,
|
||||
},
|
||||
},
|
||||
order: [groupId],
|
||||
});
|
||||
|
||||
await initRundown(mockRundown, {});
|
||||
vi.runAllTimers();
|
||||
|
||||
const { rundown, metadata } = rundownCache.get();
|
||||
|
||||
// Roll into the event AFTER midnight at 00:21 (31 minutes into the 2-hour event)
|
||||
const rollTime = 21 * MILLIS_PER_MINUTE; // 00:21
|
||||
vi.setSystemTime('jan 2 00:21');
|
||||
const result = roll(rundown, metadata);
|
||||
|
||||
expect(result.eventId).toBe(eventId);
|
||||
expect(result.didStart).toBe(true);
|
||||
|
||||
// Call update to recalculate all state (like runtime service does)
|
||||
update();
|
||||
|
||||
const state = getState();
|
||||
|
||||
// currentDay should be 1 (we're on the next day after midnight)
|
||||
expect(state.rundown.currentDay).toBe(1);
|
||||
|
||||
// offset.absolute should be 0 (event started on time, backdated to planned start)
|
||||
expect(state.offset.absolute).toBe(0);
|
||||
|
||||
// Timer should show correct remaining time
|
||||
// Event started at 23:50, duration is 2 hours, clock is 00:21
|
||||
// Time elapsed = 31 minutes, time remaining = 2h - 31m = 1h 29m = 89 minutes
|
||||
const expectedRemaining = eventDuration - (rollTime + (24 * MILLIS_PER_HOUR - eventStart));
|
||||
expect(state.timer.current).toBe(expectedRemaining);
|
||||
|
||||
// expectedFinish should be 01:50 (event end time)
|
||||
expect(state.timer.expectedFinish).toBe(eventEnd);
|
||||
|
||||
// expectedRundownEnd should be 01:50 (same as event end, only one event)
|
||||
expect(state.offset.expectedRundownEnd).toBe(eventEnd);
|
||||
|
||||
// expectedGroupEnd should be 01:50 (group ends when the event ends)
|
||||
expect(state.offset.expectedGroupEnd).toBe(eventEnd);
|
||||
|
||||
// Verify timer started at correct backdated time
|
||||
expect(state.timer.startedAt).toBe(eventStart);
|
||||
|
||||
// Verify actualStart is backdated to planned start
|
||||
expect(state.rundown.actualStart).toBe(eventStart);
|
||||
});
|
||||
|
||||
test('rundown loops back after finishing and resets currentDay when first event starts again', async () => {
|
||||
// Rundown: 09:00-23:00, then overnight event 23:00-01:00
|
||||
const mockRundown = makeRundown({
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
Day,
|
||||
Duration,
|
||||
isOntimeEvent,
|
||||
Instant,
|
||||
MaybeNumber,
|
||||
@@ -69,7 +71,7 @@ export type RuntimeState = {
|
||||
_flag: ExpectedMetadata;
|
||||
_end: ExpectedMetadata;
|
||||
_startEpoch: Maybe<Instant>;
|
||||
_startDayOffset: MaybeNumber;
|
||||
_startDayOffset: Maybe<Day>;
|
||||
};
|
||||
|
||||
const runtimeState: RuntimeState = {
|
||||
@@ -435,7 +437,7 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
||||
state.timer.elapsed = 0;
|
||||
|
||||
if (state.rundown.actualStart === null) {
|
||||
state._startDayOffset = findDayOffset(state.eventNow.timeStart, state.clock) + state.eventNow.dayOffset;
|
||||
state._startDayOffset = (findDayOffset(state.eventNow.timeStart, state.clock) + state.eventNow.dayOffset) as Day;
|
||||
state.rundown.currentDay = state._startDayOffset;
|
||||
state._startEpoch = epoch;
|
||||
state.rundown.actualStart = state.clock;
|
||||
@@ -688,12 +690,21 @@ export function roll(
|
||||
runtimeState.rundown.actualGroupStart = plannedStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* we need to backdate the actual start and start metadata
|
||||
* to prevent adding unintended offset
|
||||
*/
|
||||
if (runtimeState.rundown.actualStart === null) {
|
||||
runtimeState.rundown.actualStart = plannedStart;
|
||||
runtimeState._startDayOffset =
|
||||
findDayOffset(runtimeState.eventNow.timeStart, runtimeState.clock) + runtimeState.eventNow.dayOffset;
|
||||
runtimeState.rundown.currentDay = runtimeState._startDayOffset;
|
||||
runtimeState._startEpoch = epoch;
|
||||
// use plannedStart (not clock) because actualStart is backdated to plannedStart
|
||||
runtimeState._startDayOffset = (findDayOffset(runtimeState.eventNow.timeStart, plannedStart) +
|
||||
runtimeState.eventNow.dayOffset) as Day;
|
||||
// backdate _startEpoch to when the event conceptually started
|
||||
const timeElapsed = timeCore.elapsedTime(runtimeState.clock, plannedStart as TimeOfDay);
|
||||
runtimeState._startEpoch = timeCore.addDuration(epoch, -timeElapsed as Duration);
|
||||
// calculate currentDay from the backdated epoch
|
||||
runtimeState.rundown.currentDay =
|
||||
runtimeState._startDayOffset + timeCore.daysSinceStart(runtimeState._startEpoch, epoch);
|
||||
}
|
||||
} else {
|
||||
runtimeState._timer.secondaryTarget = normaliseRollStart(
|
||||
@@ -789,10 +800,15 @@ export function roll(
|
||||
}
|
||||
|
||||
// update metadata
|
||||
runtimeState._startDayOffset =
|
||||
findDayOffset(runtimeState.eventNow.timeStart, runtimeState.clock) + runtimeState.eventNow.dayOffset;
|
||||
runtimeState.rundown.currentDay = runtimeState._startDayOffset;
|
||||
runtimeState._startEpoch = epoch;
|
||||
// use plannedStart (not clock) because actualStart is backdated to plannedStart
|
||||
runtimeState._startDayOffset = (findDayOffset(runtimeState.eventNow.timeStart, plannedStart) +
|
||||
runtimeState.eventNow.dayOffset) as Day;
|
||||
// backdate _startEpoch to when the event conceptually started
|
||||
const timeElapsed = timeCore.elapsedTime(runtimeState.clock, plannedStart as TimeOfDay);
|
||||
runtimeState._startEpoch = timeCore.addDuration(epoch, -timeElapsed as Duration);
|
||||
// calculate currentDay from the backdated epoch
|
||||
runtimeState.rundown.currentDay = (runtimeState._startDayOffset +
|
||||
timeCore.daysSinceStart(runtimeState._startEpoch, epoch)) as Day;
|
||||
|
||||
return { eventId: runtimeState.eventNow.id, didStart: true };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user