247 lines
8.0 KiB
TypeScript
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 },
|
|
});
|
|
});
|
|
});
|