diff --git a/apps/server/src/services/__tests__/timerUtils.test.ts b/apps/server/src/services/__tests__/timerUtils.test.ts index eecc5893a..356521f0e 100644 --- a/apps/server/src/services/__tests__/timerUtils.test.ts +++ b/apps/server/src/services/__tests__/timerUtils.test.ts @@ -225,7 +225,7 @@ describe('getExpectedFinish()', () => { expect(calculatedFinish).toBe(10); }); describe('on timers of type time-to-end', () => { - it('finish time is as schedule + added time', () => { + it('finish time is the fixed end, ignoring added time', () => { const state = { eventNow: { timeEnd: 30, @@ -245,6 +245,29 @@ describe('getExpectedFinish()', () => { const calculatedFinish = getExpectedFinish(state); expect(calculatedFinish).toBe(40); }); + + it('finish time is the fixed end, including paused time', () => { + const state = { + eventNow: { + timeEnd: 30, + countToEnd: true, + }, + clock: 25, + timer: { + addedTime: 0, + duration: dayInMs, + startedAt: 10, + }, + _timer: { + pausedAt: 20, // paused 5 ago + hasFinished: false, + }, + } as RuntimeState; + + const calculatedFinish = getExpectedFinish(state); + expect(calculatedFinish).toBe(35); + }); + it('handles events that finish the day after', () => { const state = { eventNow: { @@ -404,6 +427,9 @@ describe('getCurrent()', () => { it('current time is the time to end even if it hasnt started, this is weird, but by design', () => { const state = { eventNow: { + timeStart: 0, + duration: 100, + dayOffset: 0, timeEnd: 100, countToEnd: true, }, @@ -429,6 +455,9 @@ describe('getCurrent()', () => { it('current time is the time to end', () => { const state = { eventNow: { + timeStart: 0, + duration: 100, + dayOffset: 0, timeEnd: 100, countToEnd: true, }, @@ -451,10 +480,13 @@ describe('getCurrent()', () => { expect(current).toBe(70); }); - it('current time is the time to end + added time', () => { + it('current time is the time to end, ignoring added time', () => { const state = { eventNow: { + timeStart: 0, + duration: 100, timeEnd: 100, + dayOffset: 0, countToEnd: true, }, clock: 30, @@ -480,8 +512,10 @@ describe('getCurrent()', () => { const state = { eventNow: { timeStart: 79200000, // 22:00:00 + duration: 2 * MILLIS_PER_HOUR + 10 * MILLIS_PER_MINUTE, timeEnd: 600000, // 00:10:00 countToEnd: true, + dayOffset: 0, }, clock: 79500000, // 22:05:00 timer: { @@ -492,6 +526,7 @@ describe('getCurrent()', () => { rundown: { actualStart: 79200000, plannedEnd: 600000, + currentDay: 0, }, _timer: { pausedAt: null, @@ -503,6 +538,36 @@ describe('getCurrent()', () => { expect(current).toBe(dayInMs - 79500000 + 600000); }); + it('handles overnight count-to-end after midnight', () => { + const state = { + eventNow: { + timeStart: 23 * MILLIS_PER_HOUR, // 23:00:00 + duration: 2 * MILLIS_PER_HOUR, // 2h + timeEnd: 1 * MILLIS_PER_HOUR, // 01:00:00 + countToEnd: true, + dayOffset: 0, + }, + clock: 30 * MILLIS_PER_MINUTE, // 00:30:00 on day 1 + timer: { + addedTime: 0, + duration: Infinity, // not relevant, + startedAt: 23 * MILLIS_PER_HOUR, + }, + rundown: { + actualStart: 23 * MILLIS_PER_HOUR, + plannedEnd: 1 * MILLIS_PER_HOUR, + currentDay: 1, + }, + _timer: { + pausedAt: null, + hasFinished: false, + }, + } as RuntimeState; + + const current = getCurrent(state); + expect(current).toBe(30 * MILLIS_PER_MINUTE); + }); + it('handles events that were started late', () => { const state = { clock: 82000000, // 22:46:40 <--- starting 16 min after the scheduled end @@ -511,6 +576,7 @@ describe('getCurrent()', () => { timeEnd: 81000000, // 22:30:00 duration: 3600000, // 01:00:00 countToEnd: true, + dayOffset: 0, }, timer: { addedTime: 0, @@ -1073,7 +1139,7 @@ describe('getRuntimeOffset()', () => { expect(absolute).toBe(0); }); - it('with time-to-end, offset is the overtime', () => { + it('with time-to-end, offset combines overtime and added time', () => { const state = { clock: 82000000, // 22:46:40 eventNow: { @@ -1126,7 +1192,45 @@ describe('getRuntimeOffset()', () => { } as RuntimeState; const { absolute } = getRuntimeOffset(state); - expect(absolute).toBe(400000 - 200000); // <--- offset is always the overtime + added time + // overtime (400000) plus the operator's added time (-200000) + expect(absolute).toBe(200000); + }); + + it('with time-to-end', () => { + const state = { + clock: 80000000, // 22:13:20 - before the scheduled end, not in overtime + eventNow: { + id: 'd6a2ce', + timeStart: 77400000, // 21:30:00 + timeEnd: 81000000, // 22:30:00 + duration: 3600000, // 01:00:00 + timeStrategy: TimeStrategy.LockEnd, + countToEnd: true, + dayOffset: 0, + delay: 0, + }, + rundown: { + plannedStart: 77400000, // 21:30:00 + plannedEnd: 81000000, // 22:30:00 + actualStart: 78000000, // 21:40:00 + currentDay: 0, + }, + offset: { + absolute: 0, + }, + timer: { + addedTime: 300000, // operator added 5 minutes + current: 1000000, // still counting down, no overtime + duration: 3600000, + startedAt: 78000000, + }, + _startDayOffset: 0, + _timer: { pausedAt: null }, + } as RuntimeState; + + // the end is anchored, so the added 5 minutes shows up as offset + const { absolute } = getRuntimeOffset(state); + expect(absolute).toBe(300000); }); it('handles time-to-end started after the end time', () => { diff --git a/apps/server/src/services/timerUtils.ts b/apps/server/src/services/timerUtils.ts index 8c28a5e8d..b6e186e40 100644 --- a/apps/server/src/services/timerUtils.ts +++ b/apps/server/src/services/timerUtils.ts @@ -62,34 +62,34 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber { */ export function getCurrent(state: RuntimeState): number { + const { timer, eventNow } = state; + // eslint-disable-next-line no-unused-labels -- dev code path DEV: { - if (state.eventNow === null || state.timer.duration === null) { + if (eventNow === null || timer.duration === null) { throw new Error('timerUtils.getCurrent: invalid state received'); } } - const { startedAt, duration, addedTime } = state.timer; - const { countToEnd, timeStart, timeEnd } = state.eventNow; const { pausedAt } = state._timer; const { clock } = state; - if (countToEnd) { - const isEventOverMidnight = timeStart > timeEnd; - const correctDay = isEventOverMidnight ? dayInMs : 0; - return correctDay - clock + timeEnd + addedTime; + if (eventNow.countToEnd) { + const clockDayCorrected = clock + (state.rundown.currentDay ?? 0) * dayInMs; + const eventDayOffset = eventNow.dayOffset * dayInMs; + return eventDayOffset + eventNow.timeStart + eventNow.duration + timer.addedTime - clockDayCorrected; } - if (startedAt === null) { - return duration; + if (timer.startedAt === null) { + return timer.duration; } if (pausedAt != null) { - return startedAt + duration + addedTime - pausedAt; + return timer.startedAt + timer.duration + timer.addedTime - pausedAt; } - const hasPassedMidnight = startedAt > clock; + const hasPassedMidnight = timer.startedAt > clock; const correctDay = hasPassedMidnight ? dayInMs : 0; - return startedAt + duration + addedTime - clock - correctDay; + return timer.startedAt + timer.duration + timer.addedTime - clock - correctDay; } /**