Files
advanced-camera-card/tests/components-lib/editor/schema/simple.test.ts
T

247 lines
8.0 KiB
TypeScript

import { assert, describe, expect, it } from 'vitest';
import {
applyConfigChanges,
computeConfigChanges,
computeDisplayedData,
} from '../../../../src/components-lib/editor/form-data';
import {
getSimpleMenuForms,
getSimpleTopLevelForms,
} from '../../../../src/components-lib/editor/schema/simple';
import {
findBinding,
isComputedFieldBinding,
} from '../../../../src/components-lib/editor/types';
import { copyConfig, getConfigValue } from '../../../../src/config/management';
import { setProfiles } from '../../../../src/config/profiles/set-profiles';
import { menuConfigDefault } from '../../../../src/config/schema/menu';
import type { ProfileType } from '../../../../src/config/schema/profiles';
import { configDefaults } from '../../../../src/config/schema/types';
import type { RawAdvancedCameraCardConfig } from '../../../../src/config/types';
const [MENU_FORM] = getSimpleMenuForms();
const [TOP_LEVEL_FORM] = getSimpleTopLevelForms();
// The paths the buttons field says it stands for, taken from the field itself
// rather than from a walk of the form, so that the two can be compared.
const getMenuButtonsConfigPaths = () => {
const binding = findBinding(MENU_FORM, ['menu_buttons']);
assert(binding && isComputedFieldBinding(binding));
return binding.configPaths;
};
// The values a field shows when the configuration does not set it, built the
// way the editor builds them so that profiles are taken into account. The
// configuration given to `setProfiles` must be a valid one: it does not apply a
// profile over a configuration it cannot parse.
const createDefaults = (profiles: ProfileType[] = []): RawAdvancedCameraCardConfig => {
const defaults = copyConfig(configDefaults);
setProfiles(
{
type: 'custom:advanced-camera-card',
cameras: [{ camera_entity: 'camera.office' }],
},
defaults,
profiles,
);
return defaults;
};
const getMenuButtons = (
config: RawAdvancedCameraCardConfig,
defaults: RawAdvancedCameraCardConfig = createDefaults(),
): unknown =>
getConfigValue(computeDisplayedData(MENU_FORM, config, defaults), 'menu_buttons');
// Edit the buttons as the form does: the form emits everything it displays,
// with the edited field changed.
const changeMenuButtons = (
config: RawAdvancedCameraCardConfig,
buttons: unknown,
defaults: RawAdvancedCameraCardConfig = createDefaults(),
) => {
const displayed = computeDisplayedData(MENU_FORM, config, defaults);
return computeConfigChanges(
MENU_FORM,
displayed,
{ ...displayed, menu_buttons: buttons },
config,
defaults,
);
};
describe('the simple editor forms', () => {
it('should show the value of the configuration key a field is bound to', () => {
expect(
computeDisplayedData(
MENU_FORM,
{ menu: { style: 'outside', position: 'left' } },
createDefaults(),
),
).toMatchObject({ menu_style: 'outside', menu_position: 'left' });
expect(
computeDisplayedData(
TOP_LEVEL_FORM,
{ view: { default: 'clips' } },
createDefaults(),
),
).toMatchObject({ default_view: 'clips' });
});
it('should write an edit to the configuration key a field is bound to', () => {
expect(
computeConfigChanges(
MENU_FORM,
{ menu_style: 'hidden' },
{ menu_style: 'outside' },
{},
createDefaults(),
),
).toEqual([{ path: ['menu', 'style'], type: 'set', value: 'outside' }]);
expect(
computeConfigChanges(
TOP_LEVEL_FORM,
{ default_view: 'live' },
{ default_view: 'clips' },
{},
createDefaults(),
),
).toEqual([{ path: ['view', 'default'], type: 'set', value: 'clips' }]);
});
});
describe('the menu buttons field', () => {
it('should select the buttons the menu shows by default', () => {
// `iris` is shown by default, `clips` is not.
expect(getMenuButtons({})).toContain('iris');
expect(getMenuButtons({})).not.toContain('clips');
});
it('should select buttons by what the configuration sets, not the defaults', () => {
const buttons = getMenuButtons({
menu: { buttons: { iris: { enabled: false }, clips: { enabled: true } } },
});
expect(buttons).not.toContain('iris');
expect(buttons).toContain('clips');
});
it('should not select a button that a profile hides', () => {
// The low performance profile hides `iris` and `timeline`.
expect(getMenuButtons({}, createDefaults(['low-performance']))).not.toContain(
'iris',
);
});
it('should store false for a default-shown button that is turned off', () => {
expect(changeMenuButtons({}, ['clips'])).toContainEqual({
path: ['menu', 'buttons', 'iris', 'enabled'],
type: 'set',
value: false,
});
});
it('should store true for a default-hidden button that is turned on', () => {
expect(changeMenuButtons({}, ['clips'])).toContainEqual({
path: ['menu', 'buttons', 'clips', 'enabled'],
type: 'set',
value: true,
});
});
it('should remove the stored value of a button returned to its default', () => {
const config = { menu: { buttons: { clips: { enabled: true } } } };
expect(changeMenuButtons(config, [])).toContainEqual({
path: ['menu', 'buttons', 'clips', 'enabled'],
type: 'delete',
});
});
it('should change nothing for the buttons that stay as they are', () => {
// Only `clips` moves: everything else is already as asked for.
const config = { menu: { buttons: { iris: { enabled: false } } } };
const buttons = getMenuButtons(config);
const wanted = Array.isArray(buttons) ? [...buttons, 'clips'] : ['clips'];
expect(changeMenuButtons(config, wanted)).toEqual([
{ path: ['menu', 'buttons', 'clips', 'enabled'], type: 'set', value: true },
]);
});
it('should treat a value that is not a list of buttons as none selected', () => {
expect(changeMenuButtons({}, undefined)).toContainEqual({
path: ['menu', 'buttons', 'iris', 'enabled'],
type: 'set',
value: false,
});
});
it('should stand for the enabled setting of every menu button the card has', () => {
// Taken from the card's own menu defaults rather than from the editor, so
// that a button the card gains and the editor does not is a failure.
const expected = Object.keys(menuConfigDefault.buttons).map(
(button) => `menu.buttons.${button}.enabled`,
);
expect(
getMenuButtonsConfigPaths()
.map((path) => path.join('.'))
.sort(),
).toEqual(expected.sort());
});
it('should change only the configuration keys it stands for', () => {
const declared = new Set(
Object.keys(menuConfigDefault.buttons).map(
(button) => `menu.buttons.${button}.enabled`,
),
);
const changes = changeMenuButtons({}, ['clips', 'iris']);
expect(changes.length).toBeGreaterThan(0);
for (const change of changes) {
expect(declared).toContain(change.path.join('.'));
}
});
it('should leave the rest of the configuration as it was', () => {
const config = {
cameras: [{ camera_entity: 'camera.office' }],
menu: {
style: 'outside',
buttons: { iris: { enabled: false, priority: 7, icon: 'mdi:cow' } },
},
live: { preload: true },
timeline: { window_seconds: 7200 },
};
// Turn `iris` back on, which returns it to its default, and `clips` on,
// leaving every other button as it is.
const shown = getMenuButtons(config);
assert(Array.isArray(shown));
const edited = applyConfigChanges(
config,
changeMenuButtons(config, [...shown, 'iris', 'clips']),
);
assert(edited);
expect(edited).toEqual({
cameras: [{ camera_entity: 'camera.office' }],
menu: {
style: 'outside',
buttons: {
// The button's other settings survive its `enabled` being removed.
iris: { priority: 7, icon: 'mdi:cow' },
clips: { enabled: true },
},
},
live: { preload: true },
timeline: { window_seconds: 7200 },
});
});
});