mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 00:43:54 +00:00
722e045b20
* Revert "refactor: add version to session endpoint" This reverts commitcd40a6e55e. * Revert "Remove public event feature (#1645)" This reverts commit08d9e24871. * Revert "Fix: rearrange playing event (#1640)" This reverts commit0649678dca. * Revert "refactor: style tweaks to edit css modal" This reverts commita00ec2d02a. * Revert "refactor: remove usages of framer-motion" This reverts commit54a74ccc2a. * Revert "Upgrade expressjs (#1633)" This reverts commit6f3ab274bd. * Revert "Refactor: require trigger in all events objects (#1636)" This reverts commit90870ecfb6. * Revert "Fix: Correct boundary condition in applyDelay" This reverts commitb640e0e181. * Revert "let vite be the proxy to the dev server (#1630)" This reverts commitc41fe824cf. * Revert "refactor: migrate custom fields to transactions" This reverts commit62c8319d70. * Revert "refactor: simplify validations" This reverts commitb1d23467a2. * Revert "refactor: create transaction system and apply to adding entry (#1620)" This reverts commit9a62daf047. * Revert "Refactor: better rounding (#1594)" This reverts commitb9ab1c6fd7. * Revert "refactor: improve reorder logic" This reverts commitc1054711b0. * Revert "chore: simplify URLs" This reverts commita4d4f29a37. * Revert "refactor: order is single source of truth" This reverts commit2793aadea0. * Revert "refactor: refetch targets is enum" This reverts commite7cfb7d9d9. * Revert "refactor: small ux improvements" This reverts commit256a851c9b. * Revert "refactor: remove trivially inferred numEvents" This reverts commit4ab9c81cb8. * Revert "feat: duplicate groups" This reverts commit2f13d6c89e. * Revert "feat: create group from entry selection" This reverts commitf1f7bad25e. * Revert "feat: create block from rundown empty" This reverts commita8b52a48f7. * Revert "fix: collapsed blocks dont render children" This reverts commitcd0999b2ab. * Revert "refactor: type cleanup and test improvements" This reverts commitb4c60f3f04. * Revert "feat: allow dissolving a block" This reverts commit5cefad3666. * Revert "fix: uncontrolled prop on controlled component" This reverts commit7a6ecd8c34. * Revert "refactor: improve return of reorder" This reverts commitba96ecfd91. * Revert "refactor: mutations on batch elements must have IDs" This reverts commit7bed3757f2. * Revert "chore: upgrade dependencies" This reverts commite2e755b1d2. * Revert "refactor: extract utility to merge two arrays" This reverts commita77d23109d. * Revert "refactor: change network mode defaults" This reverts commit0eb3b8d382. * Revert "fix: delete nested events" This reverts commit0021185288. * Revert "fix: add event at end of block" This reverts commit94c72ff4f6. * Revert "refactor: make finder available in exported rundown" This reverts commite4c08dc9b2. * Revert "assert non null and update test (#1604)" This reverts commitec74af0d62. * Revert "Fix project renumber (#1597)" This reverts commitb6d72dd082. * Revert "Refactor: WebSocket from flush queue to one patch (#1595)" This reverts commit31c311daf0. * Revert "Refactor: ms for api calls (#1593)" This reverts commite9b3cc6090. * Revert "fix test (#1601)" This reverts commit543b04a097. * Revert "fix: rebase master" This reverts commitd39b85b6e6. * Revert "chore: correct test path" This reverts commitbbe107bb2b. * Revert "refactor: extract rundown parsing" This reverts commitc616240db1. * Revert "chore: improve convention entry <> event" This reverts commit166be66ce3. * Revert "refactor: maintain flat orders" This reverts commit4180d0a337. * Revert "refactor: implement operations on nested events" This reverts commit78108e316c. * Revert "refactor: process events in rundown" This reverts commit3bb8b70915. * Revert "chore: improve convention entry <> event" This reverts commit1c4f13a0ed. * Revert "chore: rename currentBlock > parent" This reverts commit3ca0abad53. * Revert "refactor: fix delay positioning in gaps" This reverts commit030c8f897f. * Revert "refactor(e2e): skip flaky test" This reverts commit68175cfa3b. * Revert "refactor: improve project loading" This reverts commitfd8f757851. * Revert "refactor: gather group metadata" This reverts commit876d111c61. * Revert "refactor: swap maintains schedule" This reverts commit730cb95c04. * Revert "chore: rename files" This reverts commit3c388d4fb5. * Revert "refactor: restructure model to contain an object of rundowns" This reverts commit2e23718d73. * Revert "refactor: clearer relationship on rundown elements" This reverts commit89ea8c470b. * Revert "refactor: use strict typing" This reverts commit178640bfc4. * Revert "refactor: remove stop as a possible end action" This reverts commit4ed38340e0. * Revert "refactor: restructure model to contain an object of rundowns" This reverts commit69eb9a5eff. * Revert "chore: remove IDE files" This reverts commit351425127a. * Revert "refactor: remove unused and legacy code" This reverts commit95f2ba37cc.
1299 lines
31 KiB
TypeScript
1299 lines
31 KiB
TypeScript
import { dayInMs, millisToString } from 'ontime-utils';
|
|
import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime-types';
|
|
|
|
import {
|
|
getCurrent,
|
|
getExpectedFinish,
|
|
getRuntimeOffset,
|
|
getTimerPhase,
|
|
normaliseEndTime,
|
|
skippedOutOfEvent,
|
|
} from '../timerUtils.js';
|
|
import type { RuntimeState } from '../../stores/runtimeState.js';
|
|
|
|
describe('getExpectedFinish()', () => {
|
|
it('is null if we havent started', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 10,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 10,
|
|
finishedAt: null,
|
|
startedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(null);
|
|
});
|
|
it('is finishedAt if defined', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 20,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 10,
|
|
finishedAt: 20, // <---- finished at
|
|
startedAt: 10,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(20);
|
|
});
|
|
it('calculates the finish time', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 11,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 10,
|
|
finishedAt: null,
|
|
startedAt: 1,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(11);
|
|
});
|
|
it('adds paused and added times', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 11,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: 20,
|
|
duration: 10,
|
|
finishedAt: null,
|
|
startedAt: 1,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(31);
|
|
});
|
|
it('added time could be negative', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 11,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: -10,
|
|
duration: 10,
|
|
finishedAt: null,
|
|
startedAt: 1,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(1);
|
|
});
|
|
it('user could add enough time for it to be negative', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 11,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: -100,
|
|
duration: 10,
|
|
finishedAt: null,
|
|
startedAt: 1,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(1);
|
|
});
|
|
it('timer can have no duration', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 0,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 0,
|
|
finishedAt: null,
|
|
startedAt: 1,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(1);
|
|
});
|
|
it('finish can be the day after', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 10,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: dayInMs,
|
|
finishedAt: null,
|
|
startedAt: 10,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(10);
|
|
});
|
|
describe('on timers of type time-to-end', () => {
|
|
it('finish time is as schedule + added time', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 30,
|
|
countToEnd: true,
|
|
},
|
|
timer: {
|
|
addedTime: 10,
|
|
duration: dayInMs,
|
|
finishedAt: null,
|
|
startedAt: 10,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
expect(calculatedFinish).toBe(40);
|
|
});
|
|
it('handles events that finish the day after', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 600000, // 00:10:00
|
|
countToEnd: true,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
finishedAt: null,
|
|
startedAt: 79200000, // 22:00:00
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 79200000,
|
|
plannedEnd: 600000,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const calculatedFinish = getExpectedFinish(state);
|
|
// expected finish is not a duration but a point in time
|
|
expect(calculatedFinish).toBe(600000);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getCurrent()', () => {
|
|
it('is duration if it hasnt started', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 30,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 0,
|
|
timer: {
|
|
addedTime: 10,
|
|
duration: 111, // <-- we take the duration value
|
|
startedAt: null,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(111);
|
|
});
|
|
it('is the remaining time in clock', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 10,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 1,
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 10,
|
|
startedAt: 0,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(9);
|
|
});
|
|
it('accounts for added times', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 10,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 1,
|
|
timer: {
|
|
addedTime: 10,
|
|
duration: 10,
|
|
startedAt: 0,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(19);
|
|
});
|
|
it('counts over midnight', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 20,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 10,
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: dayInMs + 10,
|
|
startedAt: 10,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(dayInMs + 10);
|
|
});
|
|
it('rolls over midnight', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 20,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 5,
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: dayInMs + 10,
|
|
startedAt: 10,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(15);
|
|
});
|
|
it('midnight holds delays', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 20,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 5,
|
|
timer: {
|
|
addedTime: 20,
|
|
duration: dayInMs + 10,
|
|
startedAt: 10,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(35);
|
|
});
|
|
|
|
describe('on timers of type count-to-end', () => {
|
|
it('current time is the time to end even if it hasnt started, this is weird, but by design', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 100,
|
|
countToEnd: true,
|
|
},
|
|
clock: 30,
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 100,
|
|
startedAt: null,
|
|
finishedAt: null,
|
|
},
|
|
runtime: {
|
|
plannedEnd: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(70);
|
|
});
|
|
|
|
it('current time is the time to end', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 100,
|
|
countToEnd: true,
|
|
},
|
|
clock: 30,
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: 100,
|
|
startedAt: 10,
|
|
finishedAt: null,
|
|
},
|
|
runtime: {
|
|
plannedEnd: 100,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(70);
|
|
});
|
|
|
|
it('current time is the time to end + added time', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 100,
|
|
countToEnd: true,
|
|
},
|
|
clock: 30,
|
|
timer: {
|
|
addedTime: 7,
|
|
duration: 100,
|
|
startedAt: 10,
|
|
finishedAt: null,
|
|
},
|
|
runtime: {
|
|
plannedEnd: 100,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(77);
|
|
});
|
|
|
|
it('handles events that finish the day after', () => {
|
|
const state = {
|
|
eventNow: {
|
|
timeStart: 79200000, // 22:00:00
|
|
timeEnd: 600000, // 00:10:00
|
|
countToEnd: true,
|
|
},
|
|
clock: 79500000, // 22:05:00
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: Infinity, // not relevant,
|
|
startedAt: 79200000, // 22:00:00
|
|
finishedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 79200000,
|
|
plannedEnd: 600000,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(dayInMs - 79500000 + 600000);
|
|
});
|
|
|
|
it('handles events that were started late', () => {
|
|
const state = {
|
|
clock: 82000000, // 22:46:40 <--- starting 16 min after the scheduled end
|
|
eventNow: {
|
|
timeStart: 77400000, // 21:30:00
|
|
timeEnd: 81000000, // 22:30:00
|
|
duration: 3600000, // 01:00:00
|
|
countToEnd: true,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
duration: Infinity, // not relevant,
|
|
startedAt: 79200000, // 22:00:00
|
|
finishedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 82000000, // 22:46:40 <--- started now
|
|
plannedEnd: 81000000, // 22:30:00
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const current = getCurrent(state);
|
|
expect(current).toBe(81000000 - 82000000); // <-- planned end - now
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getExpectedFinish() and getCurrentTime() combined', () => {
|
|
it('without added times, they combine to be duration', () => {
|
|
const duration = 10;
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 10,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 0,
|
|
timer: {
|
|
addedTime: 0,
|
|
duration,
|
|
startedAt: 0,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const expectedFinish = getExpectedFinish(state);
|
|
const current = getCurrent(state);
|
|
|
|
const elapsed = duration - current;
|
|
expect(expectedFinish).toBe(10);
|
|
expect(elapsed).toBe(0);
|
|
expect(current).toBe(10);
|
|
expect(elapsed + current).toBe(10);
|
|
});
|
|
it('added times influence expected finish', () => {
|
|
const duration = 10;
|
|
const state = {
|
|
eventNow: {
|
|
timeEnd: 10,
|
|
timerType: TimerType.CountDown,
|
|
},
|
|
clock: 5,
|
|
timer: {
|
|
addedTime: 3,
|
|
duration,
|
|
startedAt: 0,
|
|
finishedAt: null,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const expectedFinish = getExpectedFinish(state);
|
|
const current = getCurrent(state);
|
|
|
|
const elapsed = duration - current;
|
|
expect(expectedFinish).toBe(13);
|
|
expect(elapsed).toBe(2);
|
|
expect(current).toBe(8);
|
|
});
|
|
});
|
|
|
|
describe('skippedOutOfEvent()', () => {
|
|
const testSkipLimit = 32;
|
|
it('does not consider an event end as a skip', () => {
|
|
const startedAt = 1000;
|
|
const duration = 1000;
|
|
const expectedFinish = startedAt + duration;
|
|
const previousTime = expectedFinish - testSkipLimit / 2;
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock += testSkipLimit;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
});
|
|
|
|
it('allows rolling backwards in an event', () => {
|
|
const startedAt = 1000;
|
|
const duration = 1000;
|
|
const expectedFinish = startedAt + duration;
|
|
const previousTime = startedAt + testSkipLimit / 2;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock += testSkipLimit;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
});
|
|
|
|
it('accounts for crossing midnight', () => {
|
|
const startedAt = dayInMs - testSkipLimit;
|
|
const expectedFinish = 10;
|
|
const previousTime = dayInMs - 1;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock = testSkipLimit - 2;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
});
|
|
|
|
it('allows rolling backwards in an event across midnight', () => {
|
|
const startedAt = dayInMs - testSkipLimit;
|
|
const expectedFinish = 10;
|
|
const previousTime = startedAt + 1;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock -= testSkipLimit;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
});
|
|
|
|
it('finds skip forwards out of event', () => {
|
|
const startedAt = 1000;
|
|
const duration = 1000;
|
|
const expectedFinish = startedAt + duration;
|
|
const previousTime = expectedFinish - testSkipLimit / 2;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock += testSkipLimit + 1;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
|
|
});
|
|
|
|
it('finds skip backwards out of event', () => {
|
|
const startedAt = 1000;
|
|
const duration = 1000;
|
|
const expectedFinish = startedAt + duration;
|
|
const previousTime = startedAt + testSkipLimit / 2;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock -= testSkipLimit + 1;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
|
|
});
|
|
|
|
it('finds skip forwards out of event across midnight', () => {
|
|
const startedAt = dayInMs - testSkipLimit;
|
|
const expectedFinish = 10;
|
|
const previousTime = dayInMs - 3;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock = testSkipLimit - 2;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
|
|
});
|
|
|
|
it('finds skip backwards out of event across midnight', () => {
|
|
const startedAt = dayInMs - testSkipLimit;
|
|
const expectedFinish = 10;
|
|
const previousTime = startedAt + 1;
|
|
|
|
const state = {
|
|
clock: previousTime,
|
|
timer: {
|
|
expectedFinish,
|
|
startedAt,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(false);
|
|
|
|
state.clock -= testSkipLimit + 1;
|
|
expect(skippedOutOfEvent(state, previousTime, testSkipLimit)).toBe(true);
|
|
});
|
|
});
|
|
|
|
test('normaliseEndTime()', () => {
|
|
const t1 = {
|
|
start: 10,
|
|
end: 20,
|
|
};
|
|
const t1_expected = 20;
|
|
|
|
expect(normaliseEndTime(t1.start, t1.end)).toBe(t1_expected);
|
|
|
|
const t2 = {
|
|
start: 10 + dayInMs,
|
|
end: 20,
|
|
};
|
|
const t2_expected = 20 + dayInMs;
|
|
|
|
expect(normaliseEndTime(t2.start, t2.end)).toBe(t2_expected);
|
|
|
|
const t3 = {
|
|
start: 10,
|
|
end: 10,
|
|
};
|
|
const t3_expected = 10;
|
|
|
|
expect(normaliseEndTime(t3.start, t3.end)).toBe(t3_expected);
|
|
});
|
|
|
|
describe('getRuntimeOffset()', () => {
|
|
it('is the difference between scheduled and when we actually started', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 100,
|
|
},
|
|
timer: {
|
|
startedAt: 150,
|
|
addedTime: 0,
|
|
current: 0,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 150,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(-50);
|
|
});
|
|
|
|
it('added time subtracts time offset (positive offset)', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 100,
|
|
},
|
|
timer: {
|
|
startedAt: 150, // we started 50ms delayed
|
|
addedTime: 10, // we compensated with 10ms
|
|
current: 10, // we are 10ms into the timer
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 150,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(-60);
|
|
});
|
|
|
|
it('considers running overtime (negative offset)', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 100,
|
|
timeEnd: 140,
|
|
},
|
|
timer: {
|
|
startedAt: 100, // we started ontime
|
|
current: -10, // we are 10 seconds over
|
|
addedTime: 0, // we have not compensated with added time
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 100,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(-10);
|
|
});
|
|
|
|
it('paused time is delayed time (negative offset)', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 100,
|
|
timeEnd: 150,
|
|
},
|
|
clock: 150,
|
|
timer: {
|
|
startedAt: 100, // started on time
|
|
current: 25, // are 25ms into it
|
|
addedTime: 0,
|
|
},
|
|
_timer: {
|
|
pausedAt: 125, // we have been paused for 25ms (see clock)
|
|
},
|
|
runtime: {
|
|
actualStart: 100,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(-25);
|
|
});
|
|
|
|
it('offset doesnt exist if we havent started', () => {
|
|
const state = {
|
|
clock: 78480789,
|
|
eventNow: {
|
|
id: 'd6a2ce',
|
|
timeStart: 77400000,
|
|
timeEnd: 81000000,
|
|
duration: 3600000,
|
|
timeStrategy: 'lock-duration',
|
|
linkStart: null,
|
|
},
|
|
runtime: {
|
|
selectedEventIndex: 0,
|
|
numEvents: 2,
|
|
offset: -77400000,
|
|
plannedStart: 77400000,
|
|
plannedEnd: 84600000,
|
|
actualStart: null,
|
|
expectedEnd: null,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 3600000,
|
|
duration: 3600000,
|
|
elapsed: null,
|
|
expectedFinish: null,
|
|
finishedAt: null,
|
|
playback: 'armed',
|
|
secondaryTimer: null,
|
|
startedAt: null,
|
|
},
|
|
_timer: { pausedAt: null },
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(0);
|
|
});
|
|
|
|
it('handles loaded event', () => {
|
|
const state = {
|
|
clock: 79521653,
|
|
eventNow: {
|
|
id: '835242',
|
|
timeStart: 81000000,
|
|
timeEnd: 84600000,
|
|
duration: 3600000,
|
|
timeStrategy: 'lock-duration',
|
|
linkStart: null,
|
|
endAction: 'none',
|
|
timerType: 'count-down',
|
|
delay: 0,
|
|
},
|
|
runtime: {
|
|
selectedEventIndex: 1,
|
|
numEvents: 2,
|
|
offset: -81000000,
|
|
plannedStart: 77400000,
|
|
plannedEnd: 84600000,
|
|
actualStart: 79443403,
|
|
expectedEnd: null,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 3600000,
|
|
duration: 3600000,
|
|
elapsed: null,
|
|
expectedFinish: null,
|
|
finishedAt: null,
|
|
playback: 'armed',
|
|
secondaryTimer: null,
|
|
startedAt: null,
|
|
},
|
|
_timer: { pausedAt: null },
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(81000000 - 79521653); // clock - timestart
|
|
});
|
|
|
|
it('with time-to-end, offsets dont exist if we are not in overtime', () => {
|
|
const state = {
|
|
clock: 80000000, // 22:13:20
|
|
eventNow: {
|
|
id: 'd6a2ce',
|
|
type: 'event',
|
|
title: '',
|
|
timeStart: 77400000, // 21:30:00
|
|
timeEnd: 81000000, // 22:30:00
|
|
duration: 3600000, // 01:00:00
|
|
timeStrategy: TimeStrategy.LockEnd,
|
|
linkStart: null,
|
|
endAction: EndAction.None,
|
|
timerType: TimerType.CountDown,
|
|
countToEnd: true,
|
|
isPublic: true,
|
|
skip: false,
|
|
note: '',
|
|
colour: '',
|
|
cue: '1',
|
|
revision: 0,
|
|
timeWarning: 120000,
|
|
timeDanger: 60000,
|
|
custom: {},
|
|
delay: 0,
|
|
},
|
|
runtime: {
|
|
selectedEventIndex: 0,
|
|
numEvents: 1,
|
|
offset: 0,
|
|
plannedStart: 77400000, // 21:30:00
|
|
plannedEnd: 81000000, // 22:30:00
|
|
actualStart: 78000000, // 21:40:00
|
|
expectedEnd: 81600000, // 22:40:00
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 1600000,
|
|
duration: 3600000,
|
|
elapsed: 2000000,
|
|
expectedFinish: 81600000,
|
|
finishedAt: null,
|
|
playback: Playback.Play,
|
|
secondaryTimer: null,
|
|
startedAt: 78000000,
|
|
},
|
|
_timer: { pausedAt: null },
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(0);
|
|
});
|
|
|
|
it('with time-to-end, offset is the overtime', () => {
|
|
const state = {
|
|
clock: 82000000, // 22:46:40
|
|
eventNow: {
|
|
id: 'd6a2ce',
|
|
type: 'event',
|
|
title: '',
|
|
timeStart: 77400000, // 21:30:00
|
|
timeEnd: 81000000, // 22:30:00
|
|
duration: 3600000, // 01:00:00
|
|
timeStrategy: TimeStrategy.LockEnd,
|
|
linkStart: null,
|
|
endAction: EndAction.None,
|
|
timerType: TimerType.CountDown,
|
|
countToEnd: true,
|
|
isPublic: true,
|
|
skip: false,
|
|
note: '',
|
|
colour: '',
|
|
cue: '1',
|
|
revision: 0,
|
|
timeWarning: 120000,
|
|
timeDanger: 60000,
|
|
custom: {},
|
|
delay: 0,
|
|
},
|
|
runtime: {
|
|
selectedEventIndex: 0,
|
|
numEvents: 1,
|
|
offset: 0,
|
|
plannedStart: 77400000, // 21:30:00
|
|
plannedEnd: 81000000, // 22:30:00
|
|
actualStart: 78000000, // 21:40:00
|
|
expectedEnd: 81600000, // 22:40:00
|
|
},
|
|
timer: {
|
|
addedTime: -200000,
|
|
current: -400000,
|
|
duration: 3600000,
|
|
elapsed: 4000000,
|
|
expectedFinish: 81600000,
|
|
finishedAt: null,
|
|
playback: Playback.Play,
|
|
secondaryTimer: null,
|
|
startedAt: 78000000,
|
|
},
|
|
_timer: { pausedAt: null },
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(-400000); // <--- offset is always the overtime
|
|
});
|
|
|
|
it('handles time-to-end started after the end time', () => {
|
|
const state = {
|
|
clock: 82000000, // 22:46:40 <--- starting 16m 40s after the scheduled end
|
|
eventNow: {
|
|
id: 'd6a2ce',
|
|
timeStart: 77400000, // 21:30:00
|
|
timeEnd: 81000000, // 22:30:00
|
|
duration: 3600000, // 01:00:00
|
|
timeStrategy: TimeStrategy.LockEnd,
|
|
linkStart: null,
|
|
endAction: EndAction.None,
|
|
timerType: TimerType.CountDown,
|
|
countToEnd: true,
|
|
},
|
|
runtime: {
|
|
selectedEventIndex: 0,
|
|
numEvents: 1,
|
|
offset: 0,
|
|
plannedStart: 77400000, // 21:30:00
|
|
plannedEnd: 81000000, // 22:30:00
|
|
actualStart: 82000000, // 22:46:40 <--- started now
|
|
expectedEnd: 82000000 + 3600000, // <--- now + duration
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 0,
|
|
duration: 3600000,
|
|
elapsed: 0,
|
|
expectedFinish: 82000000 + 3600000, // <--- now + duration
|
|
finishedAt: null,
|
|
playback: Playback.Play,
|
|
secondaryTimer: null,
|
|
startedAt: 82000000, // <--- started now
|
|
},
|
|
_timer: { pausedAt: null },
|
|
} as RuntimeState;
|
|
|
|
const updateCurrent = getCurrent(state);
|
|
state.timer.current = updateCurrent;
|
|
const { absoluteOffset } = getRuntimeOffset(state);
|
|
expect(millisToString(absoluteOffset)).toBe('-00:16:40');
|
|
expect(absoluteOffset).toBe(81000000 - 82000000); // <-- planned end - now
|
|
});
|
|
});
|
|
|
|
describe('getRelativeOffset()', () => {
|
|
it('relative offset is 0 when starting at the planed time', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 150,
|
|
},
|
|
timer: {
|
|
startedAt: 150,
|
|
addedTime: 0,
|
|
current: 0,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 150,
|
|
plannedStart: 150,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset, relativeOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(0);
|
|
expect(relativeOffset).toBe(0);
|
|
});
|
|
it('relative offset is 0 when starting after the planed time', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 100,
|
|
},
|
|
timer: {
|
|
startedAt: 150,
|
|
addedTime: 0,
|
|
current: 0,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 150,
|
|
plannedStart: 100,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset, relativeOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(-50);
|
|
expect(relativeOffset).toBe(0);
|
|
});
|
|
it('relative offset is 0 when starting before the planed time', () => {
|
|
const state = {
|
|
eventNow: {
|
|
id: '1',
|
|
timeStart: 150,
|
|
},
|
|
timer: {
|
|
startedAt: 100,
|
|
addedTime: 0,
|
|
current: 0,
|
|
},
|
|
_timer: {
|
|
pausedAt: null,
|
|
},
|
|
runtime: {
|
|
actualStart: 100,
|
|
plannedStart: 150,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const { absoluteOffset, relativeOffset } = getRuntimeOffset(state);
|
|
expect(absoluteOffset).toBe(50);
|
|
expect(relativeOffset).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getTimerPhase()', () => {
|
|
it('should be None if the timer is not running', () => {
|
|
const state = {
|
|
timer: {
|
|
addedTime: 0,
|
|
current: null,
|
|
duration: null,
|
|
elapsed: null,
|
|
expectedFinish: null,
|
|
finishedAt: null,
|
|
playback: Playback.Stop,
|
|
phase: TimerPhase.None,
|
|
secondaryTimer: null,
|
|
startedAt: null,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.None);
|
|
});
|
|
|
|
it('can be in overtime', () => {
|
|
const state = {
|
|
timer: {
|
|
addedTime: 0,
|
|
current: -50,
|
|
duration: 1000,
|
|
playback: Playback.Play,
|
|
},
|
|
eventNow: {
|
|
timeDanger: 100,
|
|
timeWarning: 200,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.Overtime);
|
|
});
|
|
|
|
it('can be danger', () => {
|
|
const state = {
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 0,
|
|
duration: 1000,
|
|
playback: Playback.Play,
|
|
},
|
|
eventNow: {
|
|
timeDanger: 100,
|
|
timeWarning: 200,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.Danger);
|
|
});
|
|
|
|
it('can be warning', () => {
|
|
const state = {
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 150,
|
|
duration: 1000,
|
|
playback: Playback.Play,
|
|
},
|
|
eventNow: {
|
|
timeDanger: 100,
|
|
timeWarning: 200,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.Warning);
|
|
});
|
|
|
|
it('it default if the timer is playing and there is none of the above', () => {
|
|
const state = {
|
|
timer: {
|
|
addedTime: 0,
|
|
current: 250,
|
|
duration: 1000,
|
|
playback: Playback.Play,
|
|
},
|
|
eventNow: {
|
|
timeDanger: 100,
|
|
timeWarning: 200,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.Default);
|
|
});
|
|
|
|
it('#1042 identifies waiting to roll', () => {
|
|
const state = {
|
|
clock: 55691050,
|
|
eventNow: null,
|
|
publicEventNow: null,
|
|
eventNext: null,
|
|
publicEventNext: null,
|
|
runtime: {
|
|
selectedEventIndex: null,
|
|
numEvents: 1,
|
|
offset: 0,
|
|
plannedStart: 55860000,
|
|
plannedEnd: 55880000,
|
|
actualStart: null,
|
|
expectedEnd: null,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
current: null,
|
|
duration: null,
|
|
elapsed: 0,
|
|
expectedFinish: null,
|
|
finishedAt: null,
|
|
phase: 'none',
|
|
playback: 'roll',
|
|
secondaryTimer: 168950,
|
|
startedAt: null,
|
|
},
|
|
_timer: {
|
|
forceFinish: null,
|
|
pausedAt: null,
|
|
},
|
|
_rundown: {
|
|
totalDelay: 0,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.Pending);
|
|
});
|
|
|
|
it('#1042 identifies waiting to roll', () => {
|
|
const state = {
|
|
clock: 55691050,
|
|
eventNow: null,
|
|
publicEventNow: null,
|
|
eventNext: null,
|
|
publicEventNext: null,
|
|
runtime: {
|
|
selectedEventIndex: null,
|
|
numEvents: 1,
|
|
offset: 0,
|
|
plannedStart: 55860000,
|
|
plannedEnd: 55880000,
|
|
actualStart: null,
|
|
expectedEnd: null,
|
|
},
|
|
timer: {
|
|
addedTime: 0,
|
|
current: null,
|
|
duration: null,
|
|
elapsed: 0,
|
|
expectedFinish: null,
|
|
finishedAt: null,
|
|
phase: 'none',
|
|
playback: 'roll',
|
|
secondaryTimer: 168950,
|
|
startedAt: null,
|
|
},
|
|
_timer: {
|
|
forceFinish: null,
|
|
pausedAt: null,
|
|
},
|
|
_rundown: {
|
|
totalDelay: 0,
|
|
},
|
|
} as RuntimeState;
|
|
|
|
const phase = getTimerPhase(state);
|
|
expect(phase).toBe(TimerPhase.Pending);
|
|
});
|
|
});
|