fix: add current day when ticking over midnight

This commit is contained in:
Carlos Valente
2026-03-07 00:10:19 +01:00
committed by Carlos Valente
parent 27b179276d
commit a29a493678
2 changed files with 123 additions and 13 deletions
@@ -256,8 +256,8 @@ describe('mutation on runtimeState', () => {
order: ['event1'], order: ['event1'],
}); });
const startEpoch = new Date('2024-01-01T00:00:00Z').getTime(); const startEpoch = new Date('jan 1 00:00').getTime() as Instant;
vi.setSystemTime(new Date('2024-01-03T00:00:00Z')); vi.setSystemTime('jan 3 23:59:59');
await initRundown(mockRundown, {}); await initRundown(mockRundown, {});
vi.runAllTimers(); vi.runAllTimers();
@@ -280,7 +280,8 @@ describe('mutation on runtimeState', () => {
expect(newState.rundown.actualStart).toBe(60 * 1000); expect(newState.rundown.actualStart).toBe(60 * 1000);
expect(newState.rundown.currentDay).toBe(2); expect(newState.rundown.currentDay).toBe(2);
vi.setSystemTime(new Date('2024-01-04T00:00:00Z')); // crossing midnight increments currentDay
vi.setSystemTime('jan 4 00:00:01');
update(); update();
expect(getState().rundown.currentDay).toBe(3); expect(getState().rundown.currentDay).toBe(3);
}); });
@@ -297,6 +298,104 @@ describe('roll mode', () => {
vi.useRealTimers(); vi.useRealTimers();
}); });
describe('overnight event sets correct currentDay', () => {
test('roll into overnight event after midnight sets currentDay correctly', async () => {
// Event spans 23:30 to 00:30 (overnight)
const mockRundown = makeRundown({
entries: {
1: {
...mockEvent,
id: '1',
timeStart: 23 * 60 * 60 * 1000 + 30 * 60 * 1000, // 23:30
timeEnd: 30 * 60 * 1000, // 00:30
duration: 60 * 60 * 1000, // 1 hour
dayOffset: 0,
},
},
order: ['1'],
});
await initRundown(mockRundown, {});
vi.runAllTimers();
// Clock is at 00:10 (morning part of overnight event)
vi.setSystemTime('jan 1 00:10');
const { rundown, metadata } = rundownCache.get();
const result = roll(rundown, metadata);
expect(result.eventId).toBe('1');
expect(result.didStart).toBe(true);
const state = getState();
// currentDay should be 1 because we're in the "next day" part of the overnight event
expect(state.rundown.currentDay).toBe(1);
});
test('roll into overnight event before midnight sets currentDay correctly', async () => {
// Event spans 23:30 to 00:30 (overnight)
const mockRundown = makeRundown({
entries: {
1: {
...mockEvent,
id: '1',
timeStart: 23 * 60 * 60 * 1000 + 30 * 60 * 1000, // 23:30
timeEnd: 30 * 60 * 1000, // 00:30
duration: 60 * 60 * 1000, // 1 hour
dayOffset: 0,
},
},
order: ['1'],
});
await initRundown(mockRundown, {});
vi.runAllTimers();
// Clock is at 23:40 (evening part of overnight event)
vi.setSystemTime('jan 1 23:40');
const { rundown, metadata } = rundownCache.get();
const result = roll(rundown, metadata);
expect(result.eventId).toBe('1');
expect(result.didStart).toBe(true);
const state = getState();
// currentDay should be 0 because we're still on the same day as the event start
expect(state.rundown.currentDay).toBe(0);
});
test('currentDay increments when crossing midnight during roll', async () => {
const mockRundown = makeRundown({
entries: {
1: {
...mockEvent,
id: '1',
timeStart: 23 * 60 * 60 * 1000 + 30 * 60 * 1000, // 23:30
timeEnd: 30 * 60 * 1000, // 00:30
duration: 60 * 60 * 1000, // 1 hour
dayOffset: 0,
},
},
order: ['1'],
});
await initRundown(mockRundown, {});
vi.runAllTimers();
// Start at 23:40
vi.setSystemTime('jan 1 23:40');
const { rundown, metadata } = rundownCache.get();
roll(rundown, metadata);
expect(getState().rundown.currentDay).toBe(0);
// Cross midnight
vi.setSystemTime('jan 2 00:10');
update();
expect(getState().rundown.currentDay).toBe(1);
});
});
describe('normal roll', async () => { describe('normal roll', async () => {
beforeEach(async () => { beforeEach(async () => {
vi.useFakeTimers(); vi.useFakeTimers();
+21 -10
View File
@@ -40,7 +40,11 @@ import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
import { getPlayableIndexFromTimedIndex } from '../api-data/rundown/rundown.utils.js'; import { getPlayableIndexFromTimedIndex } from '../api-data/rundown/rundown.utils.js';
import * as timeCore from '../lib/time-core/timeCore.js'; import * as timeCore from '../lib/time-core/timeCore.js';
type ExpectedMetadata = { event: OntimeEvent; accumulatedGap: number; isLinkedToLoaded: boolean } | null; type ExpectedMetadata = {
event: OntimeEvent;
accumulatedGap: number;
isLinkedToLoaded: boolean;
} | null;
export type RuntimeState = { export type RuntimeState = {
clock: TimeOfDay; clock: TimeOfDay;
@@ -541,15 +545,10 @@ export function update(): UpdateResult {
return updateIfIdle(); return updateIfIdle();
} }
// if we are playing and playback changes. we tick the current runtime day const hasCrossedMidnight = previousClock > now;
if (runtimeState._startDayOffset !== null && runtimeState._startEpoch) {
runtimeState.rundown.currentDay =
runtimeState._startDayOffset + Math.floor((epoch - runtimeState._startEpoch) / dayInMs);
}
// 2. are we waiting to roll? // 2. are we waiting to roll?
if (runtimeState.timer.playback === Playback.Roll && runtimeState.timer.secondaryTimer !== null) { if (runtimeState.timer.playback === Playback.Roll && runtimeState.timer.secondaryTimer !== null) {
const hasCrossedMidnight = previousClock > runtimeState.clock;
return updateIfWaitingToRoll(hasCrossedMidnight); return updateIfWaitingToRoll(hasCrossedMidnight);
} }
@@ -557,6 +556,11 @@ export function update(): UpdateResult {
// reset data // reset data
runtimeState.timer.secondaryTimer = null; runtimeState.timer.secondaryTimer = null;
// increment day if we crossed midnight
if (hasCrossedMidnight && runtimeState.rundown.currentDay !== null) {
runtimeState.rundown.currentDay++;
}
// eslint-disable-next-line no-unused-labels -- dev code path // eslint-disable-next-line no-unused-labels -- dev code path
DEV: { DEV: {
if (runtimeState.timer.duration === null) { if (runtimeState.timer.duration === null) {
@@ -616,7 +620,10 @@ export function update(): UpdateResult {
} }
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget! - offsetClock; runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget! - offsetClock;
return { hasTimerFinished: false, hasSecondaryTimerFinished: runtimeState.timer.secondaryTimer <= 0 }; return {
hasTimerFinished: false,
hasSecondaryTimerFinished: runtimeState.timer.secondaryTimer <= 0,
};
} }
} }
@@ -683,7 +690,9 @@ export function roll(
if (runtimeState.rundown.actualStart === null) { if (runtimeState.rundown.actualStart === null) {
runtimeState.rundown.actualStart = plannedStart; runtimeState.rundown.actualStart = plannedStart;
runtimeState._startDayOffset = 0; runtimeState._startDayOffset =
findDayOffset(runtimeState.eventNow.timeStart, runtimeState.clock) + runtimeState.eventNow.dayOffset;
runtimeState.rundown.currentDay = runtimeState._startDayOffset;
runtimeState._startEpoch = epoch; runtimeState._startEpoch = epoch;
} }
} else { } else {
@@ -773,7 +782,9 @@ export function roll(
} }
// update metadata // update metadata
runtimeState._startDayOffset = 0; runtimeState._startDayOffset =
findDayOffset(runtimeState.eventNow.timeStart, runtimeState.clock) + runtimeState.eventNow.dayOffset;
runtimeState.rundown.currentDay = runtimeState._startDayOffset;
runtimeState._startEpoch = epoch; runtimeState._startEpoch = epoch;
return { eventId: runtimeState.eventNow.id, didStart: true }; return { eventId: runtimeState.eventNow.id, didStart: true };