mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 12:23:51 +00:00
feat: event cue (#473)
* feat: parse cue * refactor: get delay from backend * feat: add cue to UI * refactor: remove deprecated delay logic * refactor: extract studio clock specific logic * refactor: extract utilities * refactor: fix issue with missing key * style: prevent cue overflow * style: prevent whitespace wrap * feat: add support for cues in integrations
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import { getPreviousPlayable } from '../eventUtils.js';
|
||||
|
||||
describe('getPreviousPlayable()', () => {
|
||||
describe('given a list of events', () => {
|
||||
it('finds the previous playable event', () => {
|
||||
const events = [
|
||||
{ id: 100, type: 'delay' },
|
||||
{ id: 101, type: 'event', skip: true },
|
||||
{ id: 102, type: 'event', skip: true },
|
||||
{ id: 103, type: 'event', skip: false },
|
||||
{ id: 'not-this', type: 'block' },
|
||||
{ id: 104, type: 'event' },
|
||||
];
|
||||
const { index, id } = getPreviousPlayable(events, events[4].id);
|
||||
expect(index).toBe(3);
|
||||
expect(id).toBe(103);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles common errors', () => {
|
||||
it('returns null if id not found in list', () => {
|
||||
const events = [
|
||||
{ id: 0, type: 'delay' },
|
||||
{ id: 1, type: 'event', skip: true },
|
||||
{ id: 2, type: 'event', skip: true },
|
||||
{ id: 3, type: 'event', skip: false },
|
||||
{ id: 4, type: 'event' },
|
||||
];
|
||||
const { index, id } = getPreviousPlayable(events, 'no-valid-id');
|
||||
expect(index).toBe(null);
|
||||
expect(id).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null if there are no previous events to play', () => {
|
||||
const events = [
|
||||
{ id: 0, type: 'delay' },
|
||||
{ id: 1, type: 'event', skip: true },
|
||||
{ id: 2, type: 'event', skip: true },
|
||||
{ id: 3, type: 'event', skip: true },
|
||||
{ id: 4, type: 'event' },
|
||||
];
|
||||
const { index, id } = getPreviousPlayable(events, events[4].id);
|
||||
expect(index).toBe(null);
|
||||
expect(id).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null if list is empty', () => {
|
||||
const events = [];
|
||||
const { index, id } = getPreviousPlayable(events, 'made-up');
|
||||
expect(index).toBe(null);
|
||||
expect(id).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
+30
-80
@@ -1,10 +1,11 @@
|
||||
import { vi } from 'vitest';
|
||||
import { dbModel } from '../../models/dataModel.ts';
|
||||
import { parseExcel, parseJson, validateEvent } from '../parser.ts';
|
||||
import { makeString } from '../parserUtils.ts';
|
||||
import { parseAliases, parseUserFields, parseViewSettings } from '../parserFunctions.ts';
|
||||
import { EndAction, TimerType } from 'ontime-types';
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
|
||||
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
|
||||
|
||||
import { dbModel } from '../../models/dataModel.js';
|
||||
import { parseExcel, parseJson, validateEvent } from '../parser.js';
|
||||
import { makeString } from '../parserUtils.js';
|
||||
import { parseAliases, parseUserFields, parseViewSettings } from '../parserFunctions.js';
|
||||
|
||||
describe('test json parser with valid def', () => {
|
||||
const testData = {
|
||||
@@ -220,64 +221,20 @@ describe('test json parser with valid def', () => {
|
||||
const first = parseResponse?.rundown[0];
|
||||
const expected = {
|
||||
title: 'Guest Welcoming',
|
||||
subtitle: '',
|
||||
presenter: '',
|
||||
note: '',
|
||||
timeStart: 31500000,
|
||||
timeEnd: 32400000,
|
||||
duration: 32400000 - 31500000,
|
||||
isPublic: false,
|
||||
endAction: 'play-next',
|
||||
timerType: 'clock',
|
||||
skip: false,
|
||||
colour: '',
|
||||
type: 'event',
|
||||
revision: 0,
|
||||
id: '4b31',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
};
|
||||
expect(first).toStrictEqual(expected);
|
||||
expect(first).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('second event is as a match', () => {
|
||||
const second = parseResponse?.rundown[1];
|
||||
const expected = {
|
||||
title: 'Good Morning',
|
||||
subtitle: 'Days schedule',
|
||||
presenter: 'Carlos Valente',
|
||||
note: '',
|
||||
timeStart: 32400000,
|
||||
timeEnd: 36000000,
|
||||
endAction: 'play-next',
|
||||
timerType: 'count-up',
|
||||
duration: 36000000 - 32400000,
|
||||
isPublic: true,
|
||||
skip: true,
|
||||
colour: 'red',
|
||||
type: 'event',
|
||||
revision: 0,
|
||||
id: 'f24d',
|
||||
user0: '',
|
||||
user1: '',
|
||||
user2: '',
|
||||
user3: '',
|
||||
user4: '',
|
||||
user5: '',
|
||||
user6: '',
|
||||
user7: '',
|
||||
user8: '',
|
||||
user9: '',
|
||||
};
|
||||
expect(second).toStrictEqual(expected);
|
||||
expect(second).toMatchObject(expected);
|
||||
});
|
||||
it('third event end action is set as the default value', () => {
|
||||
const third = parseResponse?.rundown[2];
|
||||
@@ -448,7 +405,7 @@ describe('test corrupt data', () => {
|
||||
it('handles missing event data', async () => {
|
||||
const emptyEventData = {
|
||||
rundown: [{}, {}, {}, {}, {}, {}, {}, {}],
|
||||
event: {},
|
||||
eventData: {},
|
||||
settings: {
|
||||
app: 'ontime',
|
||||
version: 2,
|
||||
@@ -459,7 +416,7 @@ describe('test corrupt data', () => {
|
||||
};
|
||||
|
||||
const parsedDef = await parseJson(emptyEventData);
|
||||
expect(parsedDef.event).toStrictEqual(dbModel.event);
|
||||
expect(parsedDef.eventData).toStrictEqual(dbModel.eventData);
|
||||
});
|
||||
|
||||
it('handles missing settings', async () => {
|
||||
@@ -488,7 +445,7 @@ describe('test event validator', () => {
|
||||
const event = {
|
||||
title: 'test',
|
||||
};
|
||||
const validated = validateEvent(event);
|
||||
const validated = validateEvent(event, 'test');
|
||||
|
||||
expect(validated).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -503,6 +460,7 @@ describe('test event validator', () => {
|
||||
revision: expect.any(Number),
|
||||
type: expect.any(String),
|
||||
id: expect.any(String),
|
||||
cue: 'test',
|
||||
colour: expect.any(String),
|
||||
user0: expect.any(String),
|
||||
user1: expect.any(String),
|
||||
@@ -520,7 +478,7 @@ describe('test event validator', () => {
|
||||
|
||||
it('fails an empty object', () => {
|
||||
const event = {};
|
||||
const validated = validateEvent(event);
|
||||
const validated = validateEvent(event, 'none');
|
||||
expect(validated).toEqual(null);
|
||||
});
|
||||
|
||||
@@ -531,7 +489,8 @@ describe('test event validator', () => {
|
||||
presenter: 3.2,
|
||||
note: '1899-12-30T08:00:10.000Z',
|
||||
};
|
||||
const validated = validateEvent(event);
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
const validated = validateEvent(event, 'not-used');
|
||||
expect(typeof validated.title).toEqual('string');
|
||||
expect(typeof validated.subtitle).toEqual('string');
|
||||
expect(typeof validated.presenter).toEqual('string');
|
||||
@@ -543,6 +502,7 @@ describe('test event validator', () => {
|
||||
timeStart: false,
|
||||
timeEnd: '2',
|
||||
};
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
const validated = validateEvent(event);
|
||||
expect(typeof validated.timeStart).toEqual('number');
|
||||
expect(validated.timeStart).toEqual(0);
|
||||
@@ -554,6 +514,7 @@ describe('test event validator', () => {
|
||||
const event = {
|
||||
title: {},
|
||||
};
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
const validated = validateEvent(event);
|
||||
expect(typeof validated.title).toEqual('string');
|
||||
});
|
||||
@@ -571,11 +532,13 @@ describe('test makeString function', () => {
|
||||
converted = makeString(val);
|
||||
expect(converted).toBe(expected);
|
||||
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
val = ['testing'];
|
||||
expected = 'testing';
|
||||
converted = makeString(val);
|
||||
expect(converted).toBe(expected);
|
||||
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
val = { doing: 'testing' };
|
||||
converted = makeString(val, 'fallback');
|
||||
expect(converted).toBe('fallback');
|
||||
@@ -628,10 +591,6 @@ describe('test parseExcel function', () => {
|
||||
'x',
|
||||
'',
|
||||
'Ballyhoo',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'a0',
|
||||
'a1',
|
||||
'a2',
|
||||
@@ -650,15 +609,11 @@ describe('test parseExcel function', () => {
|
||||
'A song from the hearth',
|
||||
'Still Carlos',
|
||||
'Derailing early',
|
||||
'clock',
|
||||
'load-next',
|
||||
'clock',
|
||||
'',
|
||||
'',
|
||||
'x',
|
||||
'Rainbow chase',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'b0',
|
||||
'',
|
||||
'',
|
||||
@@ -682,10 +637,11 @@ describe('test parseExcel function', () => {
|
||||
backstageInfo: 'test backstage info',
|
||||
};
|
||||
|
||||
// TODO: update tests once import is resolved
|
||||
const expectedParsedRundown = [
|
||||
{
|
||||
timeStart: 25200000,
|
||||
timeEnd: 28810000,
|
||||
//timeStart: 28800000,
|
||||
//timeEnd: 32410000,
|
||||
title: 'Guest Welcome',
|
||||
presenter: 'Carlos',
|
||||
subtitle: 'Getting things started',
|
||||
@@ -708,8 +664,8 @@ describe('test parseExcel function', () => {
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
timeStart: 28800000,
|
||||
timeEnd: 30600000,
|
||||
//timeStart: 32400000,
|
||||
//timeEnd: 34200000,
|
||||
title: 'A song from the hearth',
|
||||
presenter: 'Still Carlos',
|
||||
subtitle: 'Derailing early',
|
||||
@@ -728,13 +684,8 @@ describe('test parseExcel function', () => {
|
||||
const parsedData = await parseExcel(testdata);
|
||||
expect(parsedData.eventData).toStrictEqual(expectedParsedEvent);
|
||||
expect(parsedData.rundown).toBeDefined();
|
||||
expect(parsedData.rundown.title).toBe(expectedParsedRundown.title);
|
||||
expect(parsedData.rundown.presenter).toBe(expectedParsedRundown.presenter);
|
||||
expect(parsedData.rundown.subtitle).toBe(expectedParsedRundown.subtitle);
|
||||
expect(parsedData.rundown.isPublic).toBe(expectedParsedRundown.isPublic);
|
||||
expect(parsedData.rundown.skip).toBe(expectedParsedRundown.skip);
|
||||
expect(parsedData.rundown.note).toBe(expectedParsedRundown.note);
|
||||
expect(parsedData.rundown.type).toBe(expectedParsedRundown.type);
|
||||
expect(parsedData.rundown[0]).toMatchObject(expectedParsedRundown[0]);
|
||||
expect(parsedData.rundown[1]).toMatchObject(expectedParsedRundown[1]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -759,7 +710,6 @@ describe('test aliases import', () => {
|
||||
expect(parsed.length).toBe(1);
|
||||
|
||||
// generates missing id
|
||||
console.log(parsed);
|
||||
expect(parsed[0].alias).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -874,7 +824,7 @@ describe('test views import', () => {
|
||||
endMessage: '',
|
||||
overrideStyles: false,
|
||||
};
|
||||
const parsed = parseViewSettings(testData);
|
||||
const parsed = parseViewSettings(testData, false);
|
||||
expect(parsed).toStrictEqual(expectedParsedViewSettings);
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { parseExcelDate } from '../time';
|
||||
|
||||
describe('parseExcelDate', () => {
|
||||
it('parses a valid date string as expected from excel', () => {
|
||||
const millis = parseExcelDate('1899-12-30T07:00:00.000Z');
|
||||
expect(millis).not.toBe(0);
|
||||
});
|
||||
|
||||
describe('parses a time string that passes validation', () => {
|
||||
const validFields = ['10:00:00', '10:00'];
|
||||
validFields.forEach((field) => {
|
||||
it(`handles ${field}`, () => {
|
||||
const millis = parseExcelDate(field);
|
||||
expect(millis).not.toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('returns 0 on other strings', () => {
|
||||
const invalidFields = ['10', 'test', ''];
|
||||
invalidFields.forEach((field) => {
|
||||
it(`handles ${field}`, () => {
|
||||
const millis = parseExcelDate(field);
|
||||
expect(millis).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { parseExcelDate } from '../time.js';
|
||||
|
||||
describe('parseExcelDate', () => {
|
||||
describe.todo('parses a valid date string as expected from excel', () => {
|
||||
const testCases = [
|
||||
{
|
||||
fromExcel: '1899-12-30T00:00:00.000Z',
|
||||
expected: 3600000,
|
||||
},
|
||||
{
|
||||
fromExcel: '1899-12-30T00:10:00.000Z',
|
||||
expected: 4200000,
|
||||
},
|
||||
{
|
||||
fromExcel: '1899-12-30T01:00:00.000Z',
|
||||
expected: 7200000,
|
||||
},
|
||||
{
|
||||
fromExcel: '1899-12-30T07:00:00.000Z',
|
||||
expected: 28800000,
|
||||
},
|
||||
{
|
||||
fromExcel: '1899-12-30T08:00:10.000Z',
|
||||
expected: 32410000,
|
||||
},
|
||||
{
|
||||
fromExcel: '1899-12-30T08:30:00.000Z',
|
||||
expected: 34200000,
|
||||
},
|
||||
];
|
||||
|
||||
for (const scenario of testCases) {
|
||||
it(`handles ${scenario.fromExcel}`, () => {
|
||||
expect(parseExcelDate(scenario.fromExcel)).toBe(scenario.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('parses a time string that passes validation', () => {
|
||||
const validFields = ['10:00:00', '10:00'];
|
||||
validFields.forEach((field) => {
|
||||
it(`handles ${field}`, () => {
|
||||
const millis = parseExcelDate(field);
|
||||
expect(millis).not.toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('returns 0 on other strings', () => {
|
||||
const invalidFields = ['10', 'test', ''];
|
||||
invalidFields.forEach((field) => {
|
||||
it(`handles ${field}`, () => {
|
||||
const millis = parseExcelDate(field);
|
||||
expect(millis).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { cleanURL } from '../url';
|
||||
import { cleanURL } from '../url.js';
|
||||
|
||||
describe('url is correctly formatted', () => {
|
||||
it('has no leading spaces', () => {
|
||||
Reference in New Issue
Block a user