* refactor: cleanup routes

* style: smaller base font

* chore: upgrade dependencies

* chore: lock node version to electron

* refactor: pass HTTP to integration controller (#652)

* refactor: deprecate onair control

* refactor: remove playback router

* Several project files user folder (#617)

* chore: automated screenshots (#667)

* feat: app settings (#658)

* refactor: remove deprecated event data (#674)

* Studio clock (#663)

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* Feat: reorder events with alt+ctrl + arrow up/down (#645)

* Warning and danger per event (#677)

---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>

* refactor: stabilise actionHandler (#683)

Co-authored-by: Fabian Posenau <fabian@fphome.de>

* improvement: hide seconds (#675)

* wip: overview (#688)

* fix: focus cursor (#695)

* refactor: update lower third (#665)

* Refactor/time formatting (#696)

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* feat: multiple selection (#703)

---------

Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com>
Co-authored-by: Alex <ac@omnivox.dk>

* fix: test - go to `Edit mode` befor tying to click `Event options` button (#708)

* refactor: runtime service (#715)

* fix: issue with loosing cursor position on message (#719)

* remove info panel (#721)

* Event editor continue (#722)

* update API - part  (#709)

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* refactor: update timers (#729)

* feat: many timers (#706)

---------

Co-authored-by: arc-alex <ac@omnivox.dk>

* refactor: excel cleanup (#734)

* refactor: allow import of blocks and skip import (#735)

* Project manager (#697)

* refactor: UI for linking events (#763)

* upgraded pipeline actions (#777)

* Over under (#771)

* custom fields (#744)


---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* Sheets settings (#774)

---------

Co-authored-by: arc-alex <ac@omnivox.dk>

* style: tweaks to lower thirds (#785)

* refactor: delays account for gaps (#784)

* refactor: partial state updates (#780)

* feat: generate crash report (#787)

* Sheet use limited input device auth flow (#782)

---------

Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com>
Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* Custom fields views (#789)

* refactor: deprecate presenter and subtitle (#795)

* refactor: organise API around resources (#798)

---------

Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com>

* Time to end (#804)

* Skip fixes (#805)

* fix: onair derives from playback

* Param nav (#822)

---------

Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>

* refactor: download files from interface (#831)

* Quick options (#814)

* End pause (#832)

* chore: bump node version in docker (#834)

* refactor: follow in run mode (#840)

* fix: uncaught error in http integration (#837)

* Apply project (#843)

Co-authored-by: Matteo Gheza <matteo.gheza07@gmail.com>
Co-authored-by: Ary <arylmoraesn@gmail.com>
Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>
Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com>
Co-authored-by: Fabian Posenau <fabian@fphome.de>
Co-authored-by: Alex Rohleder <alexrohleder96@gmail.com>
Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com>
Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com>
Co-authored-by: Fabian Posenau <fabianpos99+github@gmail.com>
This commit is contained in:
Carlos Valente
2024-04-13 10:06:46 +02:00
committed by GitHub
parent debdbd1c5a
commit 55d1aca9b6
640 changed files with 26702 additions and 21623 deletions
@@ -0,0 +1,249 @@
import { OntimeBlock, OntimeDelay, OntimeEvent, OntimeRundown, SupportedEvent } from 'ontime-types';
import { apply } from '../delayUtils.js';
describe('apply() ', () => {
describe('in a rundown without the delay field, persisted rundown', () => {
it('applies delays', () => {
const delayId = '1';
const testRundown: OntimeRundown = [
{ id: delayId, type: SupportedEvent.Delay, duration: 10 } as OntimeDelay,
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
];
const expected = [
{ id: '2', type: SupportedEvent.Event, timeStart: 10, timeEnd: 20, duration: 10, revision: 2 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, timeStart: 10, timeEnd: 20, duration: 10, revision: 2 } as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
];
const updatedRundown = apply(delayId, testRundown);
expect(updatedRundown).toStrictEqual(expected);
});
it('applies negative delays', () => {
const delayId = '1';
const testRundown: OntimeRundown = [
{ id: delayId, type: SupportedEvent.Delay, duration: -10 } as OntimeDelay,
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, timeStart: 20, timeEnd: 40, duration: 20, revision: 1 } as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
];
const expected = [
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 2 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, timeStart: 10, timeEnd: 30, duration: 20, revision: 2 } as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
];
const updatedRundown = apply(delayId, testRundown);
expect(updatedRundown).toStrictEqual(expected);
});
it('maintains constant duration', () => {
const delayId = '1';
const testRundown: OntimeRundown = [
{ id: delayId, type: SupportedEvent.Delay, duration: -30 } as OntimeDelay,
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, timeStart: 20, timeEnd: 40, duration: 20, revision: 1 } as OntimeEvent,
];
const expected = [
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 2 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, timeStart: 0, timeEnd: 20, duration: 20, revision: 2 } as OntimeEvent,
];
const updatedRundown = apply(delayId, testRundown);
expect(updatedRundown).toStrictEqual(expected);
});
});
describe('in a rundown with the delay field, cached rundown', () => {
it('applies delays', () => {
const delayId = '1';
const testRundown: OntimeRundown = [
{ id: delayId, type: SupportedEvent.Delay, duration: 10 } as OntimeDelay,
{
id: '2',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: 10,
} as OntimeEvent,
{
id: '3',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: 10,
} as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{
id: '5',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: 0,
} as OntimeEvent,
];
const expected = [
{
id: '2',
type: SupportedEvent.Event,
timeStart: 10,
timeEnd: 20,
duration: 10,
revision: 2,
delay: 0,
} as OntimeEvent,
{
id: '3',
type: SupportedEvent.Event,
timeStart: 10,
timeEnd: 20,
duration: 10,
revision: 2,
delay: 0,
} as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{
id: '5',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: 0,
} as OntimeEvent,
];
const updatedRundown = apply(delayId, testRundown);
expect(updatedRundown).toStrictEqual(expected);
});
it('applies negative delays', () => {
const delayId = '1';
const testRundown: OntimeRundown = [
{ id: delayId, type: SupportedEvent.Delay, duration: -10 } as OntimeDelay,
{
id: '2',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: -10,
} as OntimeEvent,
{
id: '3',
type: SupportedEvent.Event,
timeStart: 20,
timeEnd: 40,
duration: 20,
revision: 1,
delay: -10,
} as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{
id: '5',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: 0,
} as OntimeEvent,
];
const expected = [
{
id: '2',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 2,
delay: 0,
} as OntimeEvent,
{
id: '3',
type: SupportedEvent.Event,
timeStart: 10,
timeEnd: 30,
duration: 20,
revision: 2,
delay: 0,
} as OntimeEvent,
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
{
id: '5',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: 0,
} as OntimeEvent,
];
const updatedRundown = apply(delayId, testRundown);
expect(updatedRundown).toStrictEqual(expected);
});
it('maintains constant duration', () => {
const delayId = '1';
const testRundown: OntimeRundown = [
{ id: delayId, type: SupportedEvent.Delay, duration: -30 } as OntimeDelay,
{
id: '2',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 1,
delay: -30,
} as OntimeEvent,
{
id: '3',
type: SupportedEvent.Event,
timeStart: 20,
timeEnd: 40,
duration: 20,
revision: 1,
delay: -30,
} as OntimeEvent,
];
const expected = [
{
id: '2',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 10,
duration: 10,
revision: 2,
delay: 0,
} as OntimeEvent,
{
id: '3',
type: SupportedEvent.Event,
timeStart: 0,
timeEnd: 20,
duration: 20,
revision: 2,
delay: 0,
} as OntimeEvent,
];
const updatedRundown = apply(delayId, testRundown);
expect(updatedRundown).toStrictEqual(expected);
});
});
});
@@ -1,457 +0,0 @@
import { EndAction, OntimeEvent, OntimeRundown, SupportedEvent, TimerType } from 'ontime-types';
import { calculateRuntimeDelays, calculateRuntimeDelaysFrom, getDelayAt } from '../delayedRundown.utils.js';
describe('calculateRuntimeDelays', () => {
it('calculates all delays in a given rundown', () => {
const rundown: OntimeRundown = [
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '659e1',
cue: '1',
},
{
duration: 600000,
type: SupportedEvent.Delay,
revision: 0,
id: '07986',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 1200000,
timeEnd: 1200000,
duration: 0,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '1c48f',
cue: '2',
},
{
duration: 1200000,
type: SupportedEvent.Delay,
revision: 0,
id: '7db42',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: 'd48c2',
cue: '3',
},
{
title: '',
type: SupportedEvent.Block,
id: '9870d',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 1200000,
timeEnd: 1800000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '2f185',
cue: '4',
},
];
const updatedRundown = calculateRuntimeDelays(rundown);
expect(rundown.length).toBe(updatedRundown.length);
expect((updatedRundown[0] as OntimeEvent).delay).toBe(0);
expect((updatedRundown[2] as OntimeEvent).delay).toBe(600000);
expect((updatedRundown[4] as OntimeEvent).delay).toBe(600000 + 1200000);
expect((updatedRundown[6] as OntimeEvent).delay).toBe(0);
});
});
describe('getDelayAt()', () => {
const delayedRundown: OntimeRundown = [
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '659e1',
delay: 0,
cue: '1',
},
{
duration: 600000,
type: SupportedEvent.Delay,
revision: 0,
id: '07986',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 1200000,
timeEnd: 1200000,
duration: 0,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '1c48f',
delay: 600000,
cue: '2',
},
{
duration: 1200000,
type: SupportedEvent.Delay,
revision: 0,
id: '7db42',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: 'd48c2',
delay: 1800000,
cue: '3',
},
{
title: '',
type: SupportedEvent.Block,
id: '9870d',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 1200000,
timeEnd: 1800000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '2f185',
delay: 0,
cue: '4',
},
];
it('calculates delay in a rundown', () => {
const delayAtStart = getDelayAt(0, delayedRundown);
const delayOnFirstEvent = getDelayAt(2, delayedRundown);
const delayOnSecondEvent = getDelayAt(4, delayedRundown);
const delayOnBlockedEvent = getDelayAt(0, delayedRundown);
expect(delayAtStart).toBe(0);
expect(delayOnFirstEvent).toBe(600000);
expect(delayOnSecondEvent).toBe(600000 + 1200000);
expect(delayOnBlockedEvent).toBe(0);
});
it('finds delay before a delay block', () => {
const valueOnFirstDelayBlock = getDelayAt(1, delayedRundown);
const valueOnSecondDelayBlock = getDelayAt(3, delayedRundown);
const valueAfterSecondDelayBlock = getDelayAt(4, delayedRundown);
expect(valueOnFirstDelayBlock).toBe(0);
expect(valueOnSecondDelayBlock).toBe(600000);
expect(valueAfterSecondDelayBlock).toBe(600000 + 1200000);
});
it('returns 0 after blocks', () => {
const valueOnBlock = getDelayAt(6, delayedRundown);
expect(valueOnBlock).toBe(0);
});
});
describe('calculateRuntimeDelaysFrom()', () => {
it('updates delays from given id', () => {
const delayedRundown: OntimeRundown = [
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '659e1',
delay: 0,
cue: '1',
},
{
duration: 600000,
type: SupportedEvent.Delay,
revision: 0,
id: '07986',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 1200000,
timeEnd: 1200000,
duration: 0,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '1c48f',
delay: 0,
cue: '2',
},
{
duration: 1200000,
type: SupportedEvent.Delay,
revision: 0,
id: '7db42',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: 'd48c2',
delay: 1800000,
cue: '3',
},
{
title: '',
type: SupportedEvent.Block,
id: '9870d',
},
{
title: '',
subtitle: '',
presenter: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStart: 1200000,
timeEnd: 1800000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
user0: '',
user1: '',
user2: '',
user3: '',
user4: '',
user5: '',
user6: '',
user7: '',
user8: '',
user9: '',
type: SupportedEvent.Event,
revision: 0,
id: '2f185',
delay: 0,
cue: '4',
},
];
const updatedRundown = calculateRuntimeDelaysFrom('07986', delayedRundown);
// we only update from the 4th on
expect((updatedRundown[0] as OntimeEvent).delay).toBe(0);
// 1 + 3
expect((updatedRundown[4] as OntimeEvent).delay).toBe(600000 + 1200000);
});
});
@@ -0,0 +1,893 @@
import {
CustomFields,
EndAction,
EventCustomFields,
OntimeBlock,
OntimeDelay,
OntimeEvent,
OntimeRundown,
SupportedEvent,
TimeStrategy,
TimerType,
} from 'ontime-types';
import { MILLIS_PER_HOUR, dayInMs, millisToString } from 'ontime-utils';
import { calculateRuntimeDelays, getDelayAt, calculateRuntimeDelaysFrom } from '../delayUtils.js';
import {
add,
batchEdit,
edit,
generate,
remove,
reorder,
swap,
createCustomField,
editCustomField,
removeCustomField,
} from '../rundownCache.js';
describe('generate()', () => {
it('creates normalised versions of a given rundown', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Event, id: '1' } as OntimeEvent,
{ type: SupportedEvent.Block, id: '2' } as OntimeBlock,
{ type: SupportedEvent.Delay, id: '3' } as OntimeDelay,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(3);
expect(initResult.order).toStrictEqual(['1', '2', '3']);
expect(initResult.rundown['1'].type).toBe(SupportedEvent.Event);
expect(initResult.rundown['2'].type).toBe(SupportedEvent.Block);
expect(initResult.rundown['3'].type).toBe(SupportedEvent.Delay);
});
it('calculates delays versions of a given rundown', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Delay, id: '1', duration: 100 } as OntimeDelay,
{ type: SupportedEvent.Event, id: '2', timeStart: 1, timeEnd: 100 } as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(2);
expect((initResult.rundown['2'] as OntimeEvent).delay).toBe(100);
expect(initResult.totalDelay).toBe(100);
});
it('accounts for gaps in rundown when calculating delays', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Event, id: '1', timeStart: 100, timeEnd: 200 } as OntimeEvent,
{ type: SupportedEvent.Delay, id: 'delay', duration: 200 } as OntimeDelay,
{ type: SupportedEvent.Event, id: '2', timeStart: 200, timeEnd: 300 } as OntimeEvent,
{ type: SupportedEvent.Block, id: 'block', title: 'break' } as OntimeBlock,
{ type: SupportedEvent.Event, id: '3', timeStart: 400, timeEnd: 500 } as OntimeEvent,
{ type: SupportedEvent.Block, id: 'another-block', title: 'another-break' } as OntimeBlock,
{ type: SupportedEvent.Event, id: '4', timeStart: 600, timeEnd: 700 } as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(7);
expect((initResult.rundown['1'] as OntimeEvent).delay).toBe(0);
expect((initResult.rundown['2'] as OntimeEvent).delay).toBe(200);
expect((initResult.rundown['3'] as OntimeEvent).delay).toBe(100);
expect((initResult.rundown['4'] as OntimeEvent).delay).toBe(0);
expect(initResult.totalDelay).toBe(0);
expect(initResult.totalDuration).toBe(700 - 100);
});
it('handles negative delays', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Event, id: '1', timeStart: 100, timeEnd: 200 } as OntimeEvent,
{ type: SupportedEvent.Delay, id: 'delay', duration: -200 } as OntimeDelay,
{ type: SupportedEvent.Event, id: '2', timeStart: 200, timeEnd: 300 } as OntimeEvent,
{ type: SupportedEvent.Block, id: 'block', title: 'break' } as OntimeBlock,
{ type: SupportedEvent.Event, id: '3', timeStart: 400, timeEnd: 500 } as OntimeEvent,
{ type: SupportedEvent.Block, id: 'another-block', title: 'another-break' } as OntimeBlock,
{ type: SupportedEvent.Event, id: '4', timeStart: 600, timeEnd: 700 } as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(7);
expect((initResult.rundown['1'] as OntimeEvent).delay).toBe(0);
expect((initResult.rundown['2'] as OntimeEvent).delay).toBe(-200);
expect((initResult.rundown['3'] as OntimeEvent).delay).toBe(-200);
expect((initResult.rundown['4'] as OntimeEvent).delay).toBe(-200);
expect(initResult.totalDelay).toBe(-200);
expect(initResult.totalDuration).toBe(700 - 100);
});
it('links times across events', () => {
const testRundown: OntimeRundown = [
{
type: SupportedEvent.Event,
id: '1',
timeStart: 1,
duration: 1,
timeEnd: 2,
timeStrategy: TimeStrategy.LockEnd,
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: '2',
timeStart: 11,
duration: 1,
timeEnd: 12,
linkStart: '1',
timeStrategy: TimeStrategy.LockEnd,
} as OntimeEvent,
{ type: SupportedEvent.Block, id: 'block' } as OntimeBlock,
{ type: SupportedEvent.Delay, id: 'delay' } as OntimeDelay,
{
type: SupportedEvent.Event,
id: '3',
timeStart: 21,
duration: 1,
timeEnd: 22,
linkStart: '2',
timeStrategy: TimeStrategy.LockEnd,
} as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(5);
expect((initResult.rundown['2'] as OntimeEvent).timeStart).toBe(2);
expect((initResult.rundown['2'] as OntimeEvent).timeEnd).toBe(12);
expect((initResult.rundown['2'] as OntimeEvent).duration).toBe(10);
expect((initResult.rundown['3'] as OntimeEvent).timeStart).toBe(12);
expect((initResult.rundown['3'] as OntimeEvent).timeEnd).toBe(22);
expect((initResult.rundown['3'] as OntimeEvent).duration).toBe(10);
expect(initResult.links['1']).toBe('2');
expect(initResult.links['2']).toBe('3');
});
it('links times across events, reordered', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Event, id: '1', timeStart: 1, timeEnd: 2 } as OntimeEvent,
{ type: SupportedEvent.Event, id: '3', timeStart: 21, timeEnd: 22, linkStart: '2' } as OntimeEvent,
{ type: SupportedEvent.Event, id: '2', timeStart: 11, timeEnd: 12, linkStart: '1' } as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(3);
expect((initResult.rundown['3'] as OntimeEvent).timeStart).toBe(2);
expect(initResult.links['1']).toBe('3');
expect(initResult.links['3']).toBe('2');
});
it('calculates total duration', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Event, id: '1', timeStart: 100, timeEnd: 200 } as OntimeEvent,
{ type: SupportedEvent.Event, id: '2', timeStart: 200, timeEnd: 300 } as OntimeEvent,
{ type: SupportedEvent.Event, id: '3', timeStart: 300, timeEnd: 400 } as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(3);
expect(initResult.totalDuration).toBe(400 - 100);
});
it('calculates total duration across days with gap', () => {
const testRundown: OntimeRundown = [
{
type: SupportedEvent.Event,
id: '1',
timeStart: new Date(0).setHours(9),
timeEnd: new Date(0).setHours(23),
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: '2',
timeStart: new Date(0).setHours(9),
timeEnd: new Date(0).setHours(23),
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: '3',
timeStart: new Date(0).setHours(9),
timeEnd: new Date(0).setHours(23),
} as OntimeEvent,
];
const initResult = generate(testRundown);
const expectedDuration = (23 - 9 + 48) * MILLIS_PER_HOUR;
expect(millisToString(initResult.totalDuration)).toBe('62:00:00');
expect(initResult.totalDuration).toBe(expectedDuration);
});
it('calculates total duration across days', () => {
const testRundown: OntimeRundown = [
{
type: SupportedEvent.Event,
id: '1',
timeStart: new Date(0).setHours(12),
timeEnd: new Date(0).setHours(22),
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: '2',
timeStart: new Date(0).setHours(22),
timeEnd: new Date(0).setHours(8),
} as OntimeEvent,
];
const initResult = generate(testRundown);
const expectedDuration = 8 * MILLIS_PER_HOUR + (dayInMs - 12 * MILLIS_PER_HOUR);
expect(millisToString(initResult.totalDuration)).toBe('20:00:00');
expect(initResult.totalDuration).toBe(expectedDuration);
});
it('handles updating event sequence', () => {
const testRundown: OntimeRundown = [
{
type: SupportedEvent.Event,
id: '97cc3e',
timeStart: 0,
timeEnd: 600000,
duration: 600000,
timeStrategy: TimeStrategy.LockDuration,
linkStart: null,
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: 'e01948',
timeStart: 600000,
timeEnd: 601000,
duration: 85801000, // <------------- value out of sync
timeStrategy: TimeStrategy.LockEnd,
linkStart: '97cc3e',
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: '25c1af',
timeStart: 100, // <------------- value out of sync
timeEnd: 602000,
duration: 0,
timeStrategy: TimeStrategy.LockEnd,
linkStart: 'e01948',
} as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.rundown).toMatchObject({
'97cc3e': {
timeStart: 0,
timeEnd: 600000,
duration: 600000,
timeStrategy: 'lock-duration',
linkStart: null,
},
e01948: {
timeStart: 600000,
timeEnd: 601000,
duration: 1000,
timeStrategy: 'lock-end',
linkStart: '97cc3e',
},
'25c1af': {
timeStart: 601000,
timeEnd: 602000,
duration: 1000,
timeStrategy: 'lock-end',
linkStart: 'e01948',
},
});
});
it('deletes links if invalid', () => {
const testRundown: OntimeRundown = [
{ type: SupportedEvent.Event, id: '1', timeStart: 1, linkStart: '10' } as OntimeEvent,
];
const initResult = generate(testRundown);
expect(initResult.order.length).toBe(1);
expect((initResult.rundown['1'] as OntimeEvent).timeStart).toBe(1);
expect(Object.keys(initResult.links).length).toBe(0);
});
describe('custom properties feature', () => {
it('creates a map of custom properties', () => {
const customProperties: CustomFields = {
lighting: {
label: 'lighting',
type: 'string',
colour: 'red',
},
sound: {
label: 'sound',
type: 'string',
colour: 'red',
},
};
const testRundown: OntimeRundown = [
{
type: SupportedEvent.Event,
id: '1',
custom: {
lighting: { value: 'event 1 lx' },
} as EventCustomFields,
} as OntimeEvent,
{
type: SupportedEvent.Event,
id: '2',
custom: {
lighting: { value: 'event 2 lx' },
sound: { value: 'event 2 sound' },
} as EventCustomFields,
} as OntimeEvent,
];
const initResult = generate(testRundown, customProperties);
expect(initResult.order.length).toBe(2);
expect(initResult.assignedCustomProperties).toMatchObject({
lighting: ['1', '2'],
sound: ['2'],
});
expect((initResult.rundown['1'] as OntimeEvent).custom).toMatchObject({ lighting: { value: 'event 1 lx' } });
expect((initResult.rundown['2'] as OntimeEvent).custom).toMatchObject({
lighting: { value: 'event 2 lx' },
sound: { value: 'event 2 sound' },
});
});
});
});
describe('add() mutation', () => {
test('adds an event to the rundown', () => {
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
const testRundown: OntimeRundown = [];
const { newRundown } = add({ atIndex: 0, event: mockEvent, persistedRundown: testRundown });
expect(newRundown.length).toBe(1);
expect(newRundown[0]).toMatchObject(mockEvent);
});
});
describe('remove() mutation', () => {
test('deletes an event from the rundown', () => {
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
const testRundown: OntimeRundown = [mockEvent];
const { newRundown } = remove({ eventId: mockEvent.id, persistedRundown: testRundown });
expect(newRundown.length).toBe(0);
});
});
describe('edit() mutation', () => {
test('edits an event in the rundown', () => {
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
const mockEventPatch = { cue: 'patched' } as OntimeEvent;
const testRundown: OntimeRundown = [mockEvent];
const { newRundown, newEvent } = edit({
eventId: mockEvent.id,
patch: mockEventPatch,
persistedRundown: testRundown,
});
expect(newRundown.length).toBe(1);
expect(newEvent).toMatchObject({
id: 'mock',
cue: 'patched',
type: SupportedEvent.Event,
});
});
});
describe('batchEdit() mutation', () => {
it('should correctly apply the patch to the events with the given IDs', () => {
const persistedRundown: OntimeRundown = [
{ id: '1', type: SupportedEvent.Event, cue: 'data1' } as OntimeEvent,
{ id: '2', type: SupportedEvent.Event, cue: 'data2' } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, cue: 'data3' } as OntimeEvent,
];
const eventIds = ['1', '3'];
const patch = { cue: 'newData' };
const { newRundown } = batchEdit({ persistedRundown, eventIds, patch });
expect(newRundown).toMatchObject([
{ id: '1', type: SupportedEvent.Event, cue: 'newData' },
{ id: '2', type: SupportedEvent.Event, cue: 'data2' },
{ id: '3', type: SupportedEvent.Event, cue: 'newData' },
]);
});
});
describe('reorder() mutation', () => {
it('should correctly reorder two events', () => {
const persistedRundown: OntimeRundown = [
{ id: '1', type: SupportedEvent.Event, cue: 'data1', revision: 0 } as OntimeEvent,
{ id: '2', type: SupportedEvent.Event, cue: 'data2', revision: 0 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, cue: 'data3', revision: 0 } as OntimeEvent,
];
const { newRundown } = reorder({
persistedRundown,
eventId: persistedRundown[0].id,
from: 0,
to: persistedRundown.length - 1,
});
expect(newRundown).toMatchObject([
{ id: '2', type: SupportedEvent.Event, cue: 'data2', revision: 1 },
{ id: '3', type: SupportedEvent.Event, cue: 'data3', revision: 1 },
{ id: '1', type: SupportedEvent.Event, cue: 'data1', revision: 1 },
]);
});
});
describe('swap() mutation', () => {
it('should correctly swap data between events', () => {
const persistedRundown: OntimeRundown = [
{ id: '1', type: SupportedEvent.Event, cue: 'data1', timeStart: 1, revision: 0 } as OntimeEvent,
{ id: '2', type: SupportedEvent.Event, cue: 'data2', timeStart: 2, revision: 0 } as OntimeEvent,
{ id: '3', type: SupportedEvent.Event, cue: 'data3', timeStart: 3, revision: 0 } as OntimeEvent,
];
const { newRundown } = swap({
persistedRundown,
fromId: persistedRundown[0].id,
toId: persistedRundown[1].id,
});
expect((newRundown[0] as OntimeEvent).id).toBe('1');
expect((newRundown[0] as OntimeEvent).cue).toBe('data2');
expect((newRundown[0] as OntimeEvent).timeStart).toBe(1);
expect((newRundown[0] as OntimeEvent).revision).toBe(1);
expect((newRundown[1] as OntimeEvent).id).toBe('2');
expect((newRundown[1] as OntimeEvent).cue).toBe('data1');
expect((newRundown[1] as OntimeEvent).timeStart).toBe(2);
expect((newRundown[1] as OntimeEvent).revision).toBe(1);
expect((newRundown[2] as OntimeEvent).id).toBe('3');
expect((newRundown[2] as OntimeEvent).cue).toBe('data3');
expect((newRundown[2] as OntimeEvent).timeStart).toBe(3);
expect((newRundown[2] as OntimeEvent).revision).toBe(0);
});
});
/**
*
*
*
*
*
*
*
*
*
*
*
*/
describe('calculateRuntimeDelays', () => {
it('calculates all delays in a given rundown', () => {
const rundown: OntimeRundown = [
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
cue: '1',
custom: {},
},
{
duration: 600000,
type: SupportedEvent.Delay,
id: '07986',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 1200000,
timeEnd: 1200000,
duration: 0,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
cue: '2',
custom: {},
},
{
duration: 1200000,
type: SupportedEvent.Delay,
id: '7db42',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
cue: '3',
custom: {},
},
{
title: '',
type: SupportedEvent.Block,
id: '9870d',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 1200000,
timeEnd: 1800000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
cue: '4',
custom: {},
},
];
const updatedRundown = calculateRuntimeDelays(rundown);
expect(rundown.length).toBe(updatedRundown.length);
expect((updatedRundown[0] as OntimeEvent).delay).toBe(0);
expect((updatedRundown[2] as OntimeEvent).delay).toBe(600000);
expect((updatedRundown[4] as OntimeEvent).delay).toBe(600000 + 1200000);
expect((updatedRundown[6] as OntimeEvent).delay).toBe(0);
});
});
describe('getDelayAt()', () => {
const delayedRundown: OntimeRundown = [
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
delay: 0,
cue: '1',
custom: {},
},
{
duration: 600000,
type: SupportedEvent.Delay,
id: '07986',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 1200000,
timeEnd: 1200000,
duration: 0,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
delay: 600000,
cue: '2',
custom: {},
},
{
duration: 1200000,
type: SupportedEvent.Delay,
id: '7db42',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
delay: 1800000,
cue: '3',
custom: {},
},
{
title: '',
type: SupportedEvent.Block,
id: '9870d',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 1200000,
timeEnd: 1800000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
delay: 0,
cue: '4',
custom: {},
},
];
it('calculates delay in a rundown', () => {
const delayAtStart = getDelayAt(0, delayedRundown);
const delayOnFirstEvent = getDelayAt(2, delayedRundown);
const delayOnSecondEvent = getDelayAt(4, delayedRundown);
const delayOnBlockedEvent = getDelayAt(0, delayedRundown);
expect(delayAtStart).toBe(0);
expect(delayOnFirstEvent).toBe(600000);
expect(delayOnSecondEvent).toBe(600000 + 1200000);
expect(delayOnBlockedEvent).toBe(0);
});
it('finds delay before a delay block', () => {
const valueOnFirstDelayBlock = getDelayAt(1, delayedRundown);
const valueOnSecondDelayBlock = getDelayAt(3, delayedRundown);
const valueAfterSecondDelayBlock = getDelayAt(4, delayedRundown);
expect(valueOnFirstDelayBlock).toBe(0);
expect(valueOnSecondDelayBlock).toBe(600000);
expect(valueAfterSecondDelayBlock).toBe(600000 + 1200000);
});
it('returns 0 after blocks', () => {
const valueOnBlock = getDelayAt(6, delayedRundown);
expect(valueOnBlock).toBe(0);
});
});
describe('calculateRuntimeDelaysFrom()', () => {
it('updates delays from given id', () => {
const delayedRundown: OntimeRundown = [
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '659e1',
delay: 0,
cue: '1',
custom: {},
},
{
duration: 600000,
type: SupportedEvent.Delay,
id: '07986',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 1200000,
timeEnd: 1200000,
duration: 0,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '1c48f',
delay: 0,
cue: '2',
custom: {},
},
{
duration: 1200000,
type: SupportedEvent.Delay,
id: '7db42',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 600000,
timeEnd: 1200000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: 'd48c2',
delay: 1800000,
cue: '3',
custom: {},
},
{
title: '',
type: SupportedEvent.Block,
id: '9870d',
},
{
title: '',
note: '',
endAction: EndAction.None,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
timeStart: 1200000,
timeEnd: 1800000,
duration: 600000,
isPublic: true,
skip: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
timeWarning: 120000,
timeDanger: 60000,
id: '2f185',
delay: 0,
cue: '4',
custom: {},
},
];
const updatedRundown = calculateRuntimeDelaysFrom('07986', delayedRundown);
// we only update from the 4th on
expect((updatedRundown[0] as OntimeEvent).delay).toBe(0);
// 1 + 3
expect((updatedRundown[4] as OntimeEvent).delay).toBe(600000 + 1200000);
});
});
describe('custom fields', () => {
describe('createCustomField()', () => {
beforeEach(() => {
vi.mock('../../classes/data-provider/DataProvider.js', () => {
return {
DataProvider: {
...vi.fn().mockImplementation(() => {
return {};
}),
getCustomFields: vi.fn().mockReturnValue({}),
setCustomFields: vi.fn().mockImplementation((newData) => {
return newData;
}),
persist: vi.fn().mockReturnValue({}),
},
};
});
});
it('creates a field from given parameters', async () => {
const expected = {
lighting: {
label: 'Lighting',
type: 'string',
colour: 'blue',
},
};
const customField = await createCustomField({ label: 'Lighting', type: 'string', colour: 'blue' });
expect(customField).toStrictEqual(expected);
});
});
describe('editCustomField()', () => {
it('edits a field with a given label', async () => {
await createCustomField({ label: 'Sound', type: 'string', colour: 'blue' });
const expected = {
lighting: {
label: 'Lighting',
type: 'string',
colour: 'blue',
},
sound: {
label: 'Sound',
type: 'string',
colour: 'green',
},
};
const customField = await editCustomField('sound', { label: 'Sound', type: 'string', colour: 'green' });
expect(customField).toStrictEqual(expected);
});
});
describe('removeCustomField()', () => {
it('deletes a field with a given label', async () => {
const expected = {
lighting: {
label: 'Lighting',
type: 'string',
colour: 'blue',
},
};
const customField = await removeCustomField('sound');
expect(customField).toStrictEqual(expected);
});
});
});
@@ -0,0 +1,253 @@
import {
CustomFields,
EndAction,
OntimeEvent,
OntimeRundown,
SupportedEvent,
TimeStrategy,
TimerType,
} from 'ontime-types';
import {
addToCustomAssignment,
getLink,
handleCustomField,
handleLink,
hasChanges,
isDataStale,
} from '../rundownCacheUtils.js';
describe('getLink()', () => {
it('should return null if there is no link', () => {
const rundown = [
{ type: SupportedEvent.Block, id: 'block' },
{ type: SupportedEvent.Event, id: '1' },
] as OntimeRundown;
const result = getLink(1, rundown);
expect(result).toBeNull();
});
it('returns previous event', () => {
const rundown = [
{ type: SupportedEvent.Event, id: '1', timeEnd: 100 },
{ type: SupportedEvent.Event, id: '2', timeStart: 0, linkStart: '1' },
] as OntimeRundown;
const result = getLink(1, rundown);
expect(result.id).toBe('1');
});
});
describe('handleLink()', () => {
it('populates data in object and updates link map', () => {
const rundown = [
{ type: SupportedEvent.Event, id: '1', timeEnd: 100 },
{ type: SupportedEvent.Event, id: '2', timeStart: 0, linkStart: '1' },
] as OntimeRundown;
const mutableEvent = { ...rundown[1] } as OntimeEvent;
const links = {};
const result = handleLink(1, rundown, mutableEvent, links);
expect(result).toBeUndefined();
expect(mutableEvent.timeStart).toBe(100);
expect(mutableEvent.linkStart).toBe('1');
expect(links).toStrictEqual({ '1': '2' });
});
it('removes link if linked event is not found', () => {
const rundown = [
{ type: SupportedEvent.Block, id: '1' },
{ type: SupportedEvent.Event, id: '2', timeStart: 0, linkStart: '1' },
] as OntimeRundown;
const mutableEvent = { ...rundown[1] } as OntimeEvent;
const links = {};
const result = handleLink(1, rundown, mutableEvent, links);
expect(result).toBeUndefined();
expect(mutableEvent.timeStart).toBe(0);
expect(mutableEvent.linkStart).toBe(null);
expect(links).toStrictEqual({});
});
});
describe('addToCustomAssignment()', () => {
it('adds given entry to assignedCustomFields', () => {
const assignedCustomFields = {};
addToCustomAssignment('label1', 'eventId 1', assignedCustomFields);
expect(assignedCustomFields).toStrictEqual({ label1: ['eventId 1'] });
addToCustomAssignment('label1', 'eventId 2', assignedCustomFields);
expect(assignedCustomFields).toStrictEqual({ label1: ['eventId 1', 'eventId 2'] });
});
});
describe('handleCustomField()', () => {
it('creates a map of where custom fields are used', () => {
const customFields = {
lighting: {
type: 'string',
colour: 'red',
label: 'lighting',
},
sound: {
type: 'string',
colour: 'red',
label: 'sound',
},
} as CustomFields;
const customFieldChangelog = {};
// @ts-expect-error -- partial event for testing
const event: OntimeEvent = {
type: SupportedEvent.Event,
id: '2',
timeStart: 0,
linkStart: '1',
custom: {
lighting: { value: 'on' },
},
};
const assignedCustomFields = {};
const result = handleCustomField(customFields, customFieldChangelog, event, assignedCustomFields);
expect(result).toBeUndefined();
expect(assignedCustomFields).toStrictEqual({ lighting: ['2'] });
expect(event.custom).toStrictEqual({
lighting: { value: 'on' },
});
});
it('renames a field if in changelog', () => {
const customFields = {
lighting: {
type: 'string',
colour: 'red',
label: 'lighting',
},
video: {
type: 'string',
colour: 'red',
label: 'video',
},
} as CustomFields;
const customFieldChangelog = {
sound: 'video',
};
// @ts-expect-error -- partial event for testing
const event: OntimeEvent = {
type: SupportedEvent.Event,
id: '2',
timeStart: 0,
linkStart: '1',
custom: {
sound: { value: 'on' },
},
};
const assignedCustomFields = {};
const result = handleCustomField(customFields, customFieldChangelog, event, assignedCustomFields);
expect(result).toBeUndefined();
expect(assignedCustomFields).toStrictEqual({ video: ['2'] });
expect(event.custom).toStrictEqual({
video: { value: 'on' },
});
});
it('processes all fields', () => {
const customFields = {
field1: {
type: 'string',
colour: 'red',
label: 'field1',
},
field2: {
type: 'string',
colour: 'red',
label: 'field2',
},
} as CustomFields;
const customFieldChangelog = {
field1: 'newField1',
};
// @ts-expect-error -- partial event for testing
const mutableEvent: OntimeEvent = {
type: SupportedEvent.Event,
id: 'event1',
custom: {
field1: { value: 'value1' },
field2: { value: 'value2' },
},
};
const assignedCustomFields = {};
handleCustomField(customFields, customFieldChangelog, mutableEvent, assignedCustomFields);
// Check that field1 has been renamed to newField1 and the value reassigned
expect(mutableEvent.custom['newField1']).toStrictEqual({ value: 'value1' });
expect(mutableEvent.custom['field1']).toBeUndefined();
// Check that field2 has been processed
expect(mutableEvent.custom['field2']).toStrictEqual({ value: 'value2' });
// Check that assignedCustomFields has been updated correctly
expect(assignedCustomFields).toStrictEqual({
newField1: ['event1'],
field2: ['event1'],
});
});
});
describe('isDataStale()', () => {
it('is stale if data contains timers', () => {
const needsRecompute = [
{ timeStart: 10 },
{ timeEnd: 10 },
{ duration: 10 },
{ linkStart: '1' },
{ timerStrategy: TimeStrategy.LockDuration },
];
for (const testCase of needsRecompute) {
expect(isDataStale(testCase)).toBe(true);
}
expect.assertions(needsRecompute.length);
});
it('is not stale if data contains auxiliary dataset', () => {
expect(
isDataStale({
cue: 'cue',
title: 'title',
note: 'note',
endAction: EndAction.LoadNext,
timerType: TimerType.Clock,
isPublic: false,
colour: 'colour',
timeWarning: 1,
timeDanger: 2,
custom: {
lighting: { value: '3' },
},
}),
).toBe(false);
});
});
describe('hasChanges()', () => {
it('identifies objects with new values', () => {
const newEvent = { id: '1', title: 'new-title' } as OntimeEvent;
const existing = { id: '1', cue: 'cue', title: 'title' } as OntimeEvent;
expect(hasChanges(existing, newEvent)).toBe(true);
});
it('identifies objects with all same values', () => {
const newEvent = { id: '1', title: 'title' } as OntimeEvent;
const existing = { id: '1', cue: 'cue', title: 'title' } as OntimeEvent;
expect(hasChanges(existing, newEvent)).toBe(false);
});
});