fix(report): harden empty report check

This commit is contained in:
Carlos Valente
2026-09-05 20:44:54 +02:00
committed by Carlos Valente
parent afc3a44455
commit 1175b3c641
2 changed files with 60 additions and 3 deletions
@@ -62,6 +62,27 @@ it('falls back to the current event when a stop arrives without a start', () =>
});
});
it('captures the rundown plan when a stop is the first report entry', () => {
const rundown = makeRundown({
id: 'run-1',
title: 'Stopped without start',
order: [eventA.id],
entries: { [eventA.id]: eventA },
});
const stop = makeRuntimeStateData({
eventNow: eventA,
clock: MILLIS_PER_MINUTE,
rundown: { plannedStart: 0, plannedEnd: MILLIS_PER_MINUTE },
});
triggerReportEntry(TimerLifeCycle.onStop, stop, rundown);
expect(generateReport()).toMatchObject({
eventReports: { [eventA.id]: { endedAt: MILLIS_PER_MINUTE } },
rundown: { id: rundown.id, title: rundown.title },
});
});
it('accumulates entries until the report is explicitly cleared', () => {
const firstRun = makeRuntimeStateData({ eventNow: eventA, timer: { startedAt: 0 }, _startEpoch: 1 });
triggerReportEntry(TimerLifeCycle.onStart, firstRun);
@@ -111,3 +132,33 @@ it('clears the retained report and rundown snapshot together', () => {
expect(sendRefetch).toHaveBeenCalledOnce();
expect(sendRefetch).toHaveBeenCalledWith(RefetchKey.Report);
});
it('captures a new plan after removing the final event by id', () => {
const firstRundown = makeRundown({
id: 'run-1',
title: 'First run',
order: [eventA.id],
entries: { [eventA.id]: eventA },
});
const secondRundown = makeRundown({
id: 'run-2',
title: 'Second run',
order: [eventB.id],
entries: { [eventB.id]: eventB },
});
const firstStart = makeRuntimeStateData({ eventNow: eventA, timer: { startedAt: 0 }, _startEpoch: 1 });
const secondStart = makeRuntimeStateData({
eventNow: eventB,
timer: { startedAt: MILLIS_PER_MINUTE },
_startEpoch: 2,
});
triggerReportEntry(TimerLifeCycle.onStart, firstStart, firstRundown);
clear(eventA.id);
triggerReportEntry(TimerLifeCycle.onStart, secondStart, secondRundown);
expect(generateReport()).toMatchObject({
eventReports: { [eventB.id]: { startedAt: MILLIS_PER_MINUTE } },
rundown: { id: secondRundown.id, title: secondRundown.title },
});
});
@@ -43,11 +43,11 @@ export function clear(id?: string) {
formattedReport = null;
if (id) {
report.delete(id);
if (report.size === 0) resetReportPlan();
} else {
// A full clear makes the next event start a new report instead of resuming this run.
report.clear();
plannedTimes = emptyPlannedTimes;
rundownSnapshot = null;
resetReportPlan();
}
sendRefetch(RefetchKey.Report);
@@ -86,6 +86,7 @@ export function triggerReportEntry(
}
if (cycle === TimerLifeCycle.onStop) {
captureReportPlan(state, rundown);
const previous = report.get(eventId);
const schedule = previous ?? getScheduleSnapshot(state.eventNow);
report.set(eventId, {
@@ -113,7 +114,7 @@ function getScheduleSnapshot(
}
/**
* Captures the plan once, when the first event in the report starts.
* Captures the plan once, when the first event in the report is recorded.
*/
function captureReportPlan(state: DeepReadonly<RuntimeState>, rundown: Readonly<Rundown>) {
if (rundownSnapshot !== null) return;
@@ -126,6 +127,11 @@ function captureReportPlan(state: DeepReadonly<RuntimeState>, rundown: Readonly<
};
}
function resetReportPlan() {
plannedTimes = emptyPlannedTimes;
rundownSnapshot = null;
}
/**
* Show level times for the report.
* Planned times are the ones captured when the show started, actual times are