mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 09:23:51 +00:00
422 lines
13 KiB
TypeScript
422 lines
13 KiB
TypeScript
import { CustomFields, OntimeBlock, OntimeEvent, Rundown, Settings, SupportedEvent, URLPreset } from 'ontime-types';
|
|
|
|
import { defaultRundown } from '../../models/dataModel.js';
|
|
|
|
import {
|
|
parseCustomFields,
|
|
parseProject,
|
|
parseRundown,
|
|
parseRundowns,
|
|
parseSettings,
|
|
parseUrlPresets,
|
|
parseViewSettings,
|
|
sanitiseCustomFields,
|
|
} from '../parserFunctions.js';
|
|
import { makeOntimeBlock, makeOntimeEvent } from '../../services/rundown-service/__mocks__/rundown.mocks.js';
|
|
|
|
describe('parseRundowns()', () => {
|
|
it('returns a default project rundown if nothing is given', () => {
|
|
const errorEmitter = vi.fn();
|
|
const result = parseRundowns({}, errorEmitter);
|
|
expect(result.customFields).toEqual({});
|
|
expect(result.rundowns).toStrictEqual({ default: defaultRundown });
|
|
// one for not having custom fields
|
|
// one for not having a rundown
|
|
expect(errorEmitter).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
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.rundowns).toMatchObject({
|
|
'1': r1,
|
|
'2': r2,
|
|
});
|
|
// one for not having a rundown
|
|
expect(errorEmitter).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('parseRundown()', () => {
|
|
it('parses data, skipping invalid results', () => {
|
|
const errorEmitter = vi.fn();
|
|
const rundown = {
|
|
id: '',
|
|
title: '',
|
|
order: ['1', '2', '3', '4'],
|
|
entries: {
|
|
'1': { id: '1', type: SupportedEvent.Event, title: 'test', skip: false } as OntimeEvent, // OK
|
|
'2': { id: '1', type: SupportedEvent.Block, title: 'test 2', skip: false } as OntimeBlock, // 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: SupportedEvent.Event,
|
|
title: 'test',
|
|
skip: false,
|
|
},
|
|
});
|
|
expect(errorEmitter).toHaveBeenCalled();
|
|
});
|
|
|
|
it('stringifies necessary values', () => {
|
|
const rundown = {
|
|
id: '',
|
|
title: '',
|
|
order: ['1', '2'],
|
|
entries: {
|
|
// @ts-expect-error -- testing external data which could be incorrect
|
|
'1': { id: '1', type: SupportedEvent.Event, cue: 101 } as OntimeEvent,
|
|
// @ts-expect-error -- testing external data which could be incorrect
|
|
'2': { id: '2', type: SupportedEvent.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'],
|
|
entries: {
|
|
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
|
|
'2': { id: '2', type: SupportedEvent.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'],
|
|
entries: {
|
|
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
|
|
'2': { id: '2', type: SupportedEvent.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'],
|
|
entries: {
|
|
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
|
|
'2': { id: '2', type: SupportedEvent.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'],
|
|
entries: {
|
|
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
|
|
'2': { id: '2', type: SupportedEvent.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 events nested in blocks', () => {
|
|
const rundown = {
|
|
id: 'test',
|
|
title: '',
|
|
order: ['block'],
|
|
entries: {
|
|
block: makeOntimeBlock({ id: 'block', events: ['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.block).toMatchObject({ events: ['1', '2'] });
|
|
expect(Object.keys(parsedRundown.entries).length).toEqual(3);
|
|
});
|
|
});
|
|
|
|
describe('parseProject()', () => {
|
|
it('returns an a base model if nothing is given', () => {
|
|
const errorEmitter = vi.fn();
|
|
const result = parseProject({}, errorEmitter);
|
|
expect(result).toBeTypeOf('object');
|
|
expect(errorEmitter).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('test migration with adding the logo field v3.8.0', () => {
|
|
const errorEmitter = vi.fn();
|
|
const result = parseProject(
|
|
{
|
|
//@ts-expect-error -- checking migration when the logo field is added
|
|
project: {
|
|
title: 'title',
|
|
description: 'description',
|
|
publicUrl: 'publicUrl',
|
|
publicInfo: 'publicInfo',
|
|
backstageUrl: 'backstageUrl',
|
|
backstageInfo: 'backstageInfo',
|
|
custom: [],
|
|
},
|
|
},
|
|
errorEmitter,
|
|
);
|
|
expect(result).toStrictEqual({
|
|
title: 'title',
|
|
description: 'description',
|
|
publicUrl: 'publicUrl',
|
|
publicInfo: 'publicInfo',
|
|
backstageUrl: 'backstageUrl',
|
|
backstageInfo: 'backstageInfo',
|
|
projectLogo: null,
|
|
custom: [],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('parseSettings()', () => {
|
|
it('throws if settings object does not exist', () => {
|
|
expect(() => parseSettings({})).toThrow();
|
|
});
|
|
|
|
it('returns an a base model as long as we have the app and version', () => {
|
|
const result = parseSettings({ settings: { app: 'ontime', version: '1' } as Settings });
|
|
expect(result).toBeTypeOf('object');
|
|
expect(result).toMatchObject({
|
|
app: 'ontime',
|
|
version: expect.any(String),
|
|
serverPort: 4001,
|
|
editorKey: null,
|
|
operatorKey: null,
|
|
timeFormat: '24',
|
|
language: 'en',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('parseViewSettings()', () => {
|
|
it('returns an a base model if nothing is given', () => {
|
|
const errorEmitter = vi.fn();
|
|
const result = parseViewSettings({}, errorEmitter);
|
|
expect(result).toBeTypeOf('object');
|
|
expect(errorEmitter).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
describe('parseUrlPresets()', () => {
|
|
it('returns an a base model if nothing is given', () => {
|
|
const errorEmitter = vi.fn();
|
|
const result = parseUrlPresets({}, errorEmitter);
|
|
expect(result).toBeTypeOf('object');
|
|
expect(errorEmitter).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('parses data, skipping invalid results', () => {
|
|
const errorEmitter = vi.fn();
|
|
const urlPresets = [{ enabled: true, alias: 'alias', pathAndParams: 'ss' }] as URLPreset[];
|
|
const result = parseUrlPresets({ urlPresets }, errorEmitter);
|
|
expect(result.length).toEqual(1);
|
|
expect(result.at(0)).toMatchObject({
|
|
enabled: true,
|
|
alias: 'alias',
|
|
pathAndParams: 'ss',
|
|
});
|
|
expect(errorEmitter).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('parseCustomFields()', () => {
|
|
it('returns an a base model if nothing is given', () => {
|
|
const errorEmitter = vi.fn();
|
|
const result = parseCustomFields({}, errorEmitter);
|
|
expect(result).toBeTypeOf('object');
|
|
expect(errorEmitter).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('parses data, skipping invalid results', () => {
|
|
const errorEmitter = vi.fn();
|
|
// @ts-expect-error -- data is external, we check bad types
|
|
const customFields = {
|
|
1: { label: 'test', type: 'string', colour: 'red' }, // ok
|
|
2: { label: 'test', type: 'string' }, // duplicate label
|
|
3: { label: '', type: 'string' }, // missing colour
|
|
4: { type: 'string', colour: '' }, // missing label
|
|
} as CustomFields;
|
|
|
|
const result = parseCustomFields({ customFields }, errorEmitter);
|
|
expect(result).toMatchObject({
|
|
test: {
|
|
label: 'test',
|
|
type: 'string',
|
|
colour: 'red',
|
|
},
|
|
});
|
|
expect(errorEmitter).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('sanitiseCustomFields()', () => {
|
|
it('returns an empty object the type is incorrect', () => {
|
|
expect(sanitiseCustomFields({})).toEqual({});
|
|
});
|
|
|
|
it('returns an object of valid entries', () => {
|
|
const customFields: CustomFields = {
|
|
test: { label: 'test', type: 'string', colour: 'red' },
|
|
test2: { label: 'test2', type: 'string', colour: 'green' },
|
|
Test3: { label: 'Test3', type: 'string', colour: '' },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual(customFields);
|
|
});
|
|
|
|
it('type should be one of (image | string)', () => {
|
|
const testTypes = sanitiseCustomFields({
|
|
test1: { label: 'test1', type: 'another', colour: 'red' },
|
|
test2: { label: 'test2', type: 'image', colour: 'red' },
|
|
test3: { label: 'test3', type: 'string', colour: 'red' },
|
|
});
|
|
expect(testTypes).toMatchObject({
|
|
test2: { label: 'test2', type: 'image', colour: 'red' },
|
|
test3: { label: 'test3', type: 'string', colour: 'red' },
|
|
});
|
|
});
|
|
|
|
it('colour must be a string', () => {
|
|
const customFields: CustomFields = {
|
|
// @ts-expect-error intentional bad data
|
|
test: { label: 'test', type: 'string', colour: 5 },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual({});
|
|
});
|
|
|
|
it('label can not be empty', () => {
|
|
const customFields: CustomFields = {
|
|
'': { label: '', type: 'string', colour: 'red' },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual({});
|
|
});
|
|
|
|
it('remove extra stuff', () => {
|
|
const customFields: CustomFields = {
|
|
// @ts-expect-error intentional bad data
|
|
test: { label: 'test', type: 'string', colour: 'red', extra: 'should be removed' },
|
|
};
|
|
const expectedCustomFields: CustomFields = {
|
|
test: { label: 'test', type: 'string', colour: 'red' },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
|
});
|
|
|
|
it('enforce name cohesion', () => {
|
|
const customFields: CustomFields = {
|
|
test: { label: 'NewName', type: 'string', colour: 'red' },
|
|
};
|
|
const expectedCustomFields: CustomFields = {
|
|
NewName: { label: 'NewName', type: 'string', colour: 'red' },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
|
});
|
|
|
|
it('labels with space', () => {
|
|
const customFields: CustomFields = {
|
|
Test_with_Space: { label: 'Test with Space', type: 'string', colour: 'red' },
|
|
};
|
|
const expectedCustomFields: CustomFields = {
|
|
Test_with_Space: { label: 'Test with Space', type: 'string', colour: 'red' },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
|
});
|
|
|
|
it('filters invalid entries', () => {
|
|
const customFields: CustomFields = {
|
|
test: { label: 'test', type: 'string', colour: 'red' },
|
|
test2: { label: 'test2', type: 'string', colour: 'green' },
|
|
bad: { label: '', type: 'string', colour: '' },
|
|
Test3: { label: 'Test3', type: 'string', colour: '' },
|
|
};
|
|
const expectedCustomFields: CustomFields = {
|
|
test: { label: 'test', type: 'string', colour: 'red' },
|
|
test2: { label: 'test2', type: 'string', colour: 'green' },
|
|
Test3: { label: 'Test3', type: 'string', colour: '' },
|
|
};
|
|
const sanitationResult = sanitiseCustomFields(customFields);
|
|
expect(sanitationResult).toStrictEqual(expectedCustomFields);
|
|
});
|
|
});
|