Files
ontime/apps/server/src/api-data/rundown/__tests__/rundown.parser.test.ts
T
2025-08-18 06:16:53 +02:00

342 lines
10 KiB
TypeScript

import { SupportedEntry, OntimeEvent, OntimeGroup, Rundown, CustomFields } from 'ontime-types';
import { defaultRundown } from '../../../models/dataModel.js';
import { makeOntimeGroup, makeOntimeEvent, makeOntimeMilestone } from '../__mocks__/rundown.mocks.js';
import { parseRundowns, parseRundown, handleCustomField, addToCustomAssignment } from '../rundown.parser.js';
describe('parseRundowns()', () => {
it('returns a default project rundown if nothing is given', () => {
const errorEmitter = vi.fn();
const result = parseRundowns({}, {}, errorEmitter);
expect(result).toStrictEqual({ default: defaultRundown });
// one for not having custom fields
// one for not having a rundown
expect(errorEmitter).toHaveBeenCalledTimes(1);
});
it('ensures the rundown IDs are consistent', () => {
const errorEmitter = vi.fn();
const r1 = { ...defaultRundown, id: '1' };
const r2 = { ...defaultRundown, id: '2' };
const result = parseRundowns(
{
rundowns: {
'1': r1,
'3': r2,
},
},
{},
errorEmitter,
);
expect(result).toMatchObject({
'1': r1,
'2': r2,
});
expect(errorEmitter).toHaveBeenCalledTimes(0);
});
});
describe('parseRundown()', () => {
it('parses data, skipping invalid results', () => {
const errorEmitter = vi.fn();
const rundown = {
id: '',
title: '',
order: ['1', '2', '3', '4'],
flatOrder: ['1', '2', '3', '4'],
entries: {
'1': { id: '1', type: SupportedEntry.Event, title: 'test', skip: false } as OntimeEvent, // OK
'2': { id: '1', type: SupportedEntry.Group, title: 'test 2' } as OntimeGroup, // duplicate ID
'3': {} as OntimeEvent, // no data
'4': { id: '4', title: 'test 2', skip: false } as OntimeEvent, // no type
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {}, errorEmitter);
expect(parsedRundown.id).not.toBe('');
expect(parsedRundown.id).toBeTypeOf('string');
expect(parsedRundown.order.length).toEqual(1);
expect(parsedRundown.order).toEqual(['1']);
expect(parsedRundown.entries).toMatchObject({
'1': {
id: '1',
type: SupportedEntry.Event,
title: 'test',
skip: false,
},
});
expect(errorEmitter).toHaveBeenCalled();
});
it('stringifies necessary values', () => {
const rundown = {
id: '',
title: '',
order: ['1', '2'],
flatOrder: ['1', '2'],
entries: {
// @ts-expect-error -- testing external data which could be incorrect
'1': { id: '1', type: SupportedEntry.Event, cue: 101 } as OntimeEvent,
// @ts-expect-error -- testing external data which could be incorrect
'2': { id: '2', type: SupportedEntry.Event, cue: 101.1 } as OntimeEvent,
},
revision: 1,
} as Rundown;
expect(parseRundown(rundown, {})).toMatchObject({
entries: {
'1': {
cue: '101',
},
'2': {
cue: '101.1',
},
},
});
});
it('detects duplicate Ids', () => {
const rundown = {
id: '',
title: '',
order: ['1', '1'],
flatOrder: ['1', '1'],
entries: {
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order.length).toEqual(1);
expect(Object.keys(parsedRundown.entries).length).toEqual(1);
});
it('completes partial datasets', () => {
const rundown = {
id: 'test',
title: '',
order: ['1', '2'],
flatOrder: ['1', '2'],
entries: {
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order.length).toEqual(2);
expect(parsedRundown.entries).toMatchObject({
'1': {
title: '',
cue: '1',
custom: {},
},
'2': {
title: '',
cue: '2',
custom: {},
},
});
});
it('handles empty events', () => {
const rundown = {
id: 'test',
title: '',
order: ['1', '2', '3', '4'],
flatOrder: ['1', '2', '3', '4'],
entries: {
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
'not-mentioned': {} as OntimeEvent,
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order.length).toEqual(2);
expect(Object.keys(parsedRundown.entries).length).toEqual(2);
});
it('handles empty events', () => {
const rundown = {
id: 'test',
title: '',
order: ['1', '2', '3', '4'],
flatOrder: ['1', '2', '3', '4'],
entries: {
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
'not-mentioned': {} as OntimeEvent,
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order.length).toEqual(2);
expect(Object.keys(parsedRundown.entries).length).toEqual(2);
});
it('parses customFields', () => {
const rundown = {
id: 'test',
title: '',
order: ['1', '2'],
flatOrder: ['1', '2'],
entries: {
'1': makeOntimeEvent({ id: '1', custom: { lighting: 'on' } }),
'2': makeOntimeEvent({ id: '2', custom: { sound: 'loud' } }),
},
revision: 1,
} as Rundown;
const customFields: CustomFields = {
lighting: {
type: 'text',
colour: 'red',
label: 'lighting',
},
sound: {
type: 'text',
colour: 'red',
label: 'sound',
},
};
const parsedRundown = parseRundown(rundown, customFields);
expect((parsedRundown.entries['1'] as OntimeEvent).custom).toStrictEqual({ lighting: 'on' });
expect((parsedRundown.entries['2'] as OntimeEvent).custom).toStrictEqual({ sound: 'loud' });
});
it('removes empty custom fields', () => {
const rundown = {
id: 'test',
title: '',
order: ['1', '2'],
flatOrder: ['1', '2', '21'],
entries: {
'1': makeOntimeEvent({ id: '1', custom: { lighting: 'yes' } }),
'2': makeOntimeGroup({ id: '2', entries: ['21'], custom: { lighting: '' } }),
'21': makeOntimeEvent({ id: '21', custom: { lighting: '' } }),
},
revision: 1,
} as Rundown;
const customFields: CustomFields = {
lighting: {
type: 'text',
colour: 'red',
label: 'lighting',
},
};
const parsedRundown = parseRundown(rundown, customFields);
expect((parsedRundown.entries['1'] as OntimeEvent).custom).toStrictEqual({ lighting: 'yes' });
expect((parsedRundown.entries['2'] as OntimeGroup).custom).not.toHaveProperty('lighting');
expect((parsedRundown.entries['21'] as OntimeEvent).custom).not.toHaveProperty('lighting');
});
it('parses data in groups', () => {
const rundown = {
id: 'test',
title: '',
order: ['group'],
flatOrder: ['group'],
isNextDay: false,
entries: {
group: makeOntimeGroup({
id: 'group',
title: 'group-title',
note: 'group-note',
colour: 'red',
entries: ['1', '2', '3'],
}),
'1': makeOntimeEvent({ id: '1', parent: 'group' }),
'2': makeOntimeMilestone({ id: '2', parent: 'group' }),
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order).toStrictEqual(['group']);
expect(parsedRundown.flatOrder).toStrictEqual(['group', '1', '2']);
expect(parsedRundown.entries).toMatchObject({
group: { id: 'group', type: SupportedEntry.Group, entries: ['1', '2'] },
'1': { id: '1', type: SupportedEntry.Event },
'2': { id: '2', type: SupportedEntry.Milestone },
});
});
it('parses events nested in groups', () => {
const rundown = {
id: 'test',
title: '',
order: ['group'],
flatOrder: ['group'],
entries: {
group: makeOntimeGroup({ id: 'group', entries: ['1', '2'] }),
'1': makeOntimeEvent({ id: '1' }),
'2': makeOntimeEvent({ id: '2' }),
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order.length).toEqual(1);
expect(parsedRundown.entries.group).toMatchObject({ entries: ['1', '2'] });
expect(Object.keys(parsedRundown.entries).length).toEqual(3);
});
});
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: 'text',
colour: 'red',
label: 'lighting',
},
sound: {
type: 'text',
colour: 'red',
label: 'sound',
},
} as CustomFields;
const event = makeOntimeEvent({
type: SupportedEntry.Event,
id: '2',
timeStart: 0,
linkStart: true,
custom: {
lighting: 'on',
},
});
const assignedCustomFields = {};
const result = handleCustomField(customFields, event, assignedCustomFields);
expect(result).toBeUndefined();
expect(assignedCustomFields).toStrictEqual({ lighting: ['2'] });
expect(event.custom).toStrictEqual({
lighting: 'on',
});
});
});