feat: Make cameras optional (#2343)
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ZodError } from 'zod';
|
||||
import { AutomationsManager } from '../../../src/card-controller/automations-manager';
|
||||
import { ConfigManager } from '../../../src/card-controller/config/config-manager';
|
||||
import { InitializationAspect } from '../../../src/card-controller/initialization-manager';
|
||||
import { ConditionStateManager } from '../../../src/conditions/state-manager';
|
||||
import { AutomationsManager } from '../../../src/card-controller/automations-manager';
|
||||
import { Automation } from '../../../src/config/schema/automations';
|
||||
import { AdvancedCameraCardCondition } from '../../../src/config/schema/conditions/types';
|
||||
import { advancedCameraCardConfigSchema } from '../../../src/config/schema/types';
|
||||
import { createCardAPI, createConfig, flushPromises } from '../../test-utils';
|
||||
import { createGeneralAction } from '../../../src/utils/action';
|
||||
import { createCardAPI, createConfig, flushPromises } from '../../test-utils';
|
||||
|
||||
/**
|
||||
* Create a ConfigManager test setup with real AutomationsManager and ConditionStateManager.
|
||||
@@ -92,7 +94,7 @@ describe('ConfigManager', () => {
|
||||
it('invalid configuration with hint', () => {
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
expect(() => manager.setConfig({})).toThrowError(
|
||||
'Invalid configuration: [\n "cameras",\n "type"\n]',
|
||||
'Invalid configuration: [\n "type"\n]',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -290,7 +292,7 @@ describe('ConfigManager', () => {
|
||||
overrides: [
|
||||
{
|
||||
conditions: [TEST_CONDITIONS.FULLSCREEN_ON],
|
||||
delete: ['cameras'],
|
||||
delete: ['type'],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -472,7 +474,7 @@ describe('ConfigManager', () => {
|
||||
],
|
||||
});
|
||||
|
||||
manager.setConfig(config as any);
|
||||
manager.setConfig(config);
|
||||
await flushPromises();
|
||||
|
||||
// Verify keyboard shortcuts automations were added initially with ptz_home
|
||||
@@ -501,9 +503,10 @@ describe('ConfigManager', () => {
|
||||
// This confirms the override removed them (directly verified through add calls)
|
||||
const addCalls = addAutomationsSpy.mock.calls;
|
||||
const hasKeyboardShortcut = addCalls.some((call) =>
|
||||
call[0].some((automation: any) =>
|
||||
call[0].some((automation: Automation) =>
|
||||
automation.conditions?.some(
|
||||
(cond: any) => cond.condition === 'key' && cond.key === 'h',
|
||||
(cond: AdvancedCameraCardCondition) =>
|
||||
cond.condition === 'key' && cond.key === 'h',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -524,7 +527,7 @@ describe('ConfigManager', () => {
|
||||
],
|
||||
});
|
||||
|
||||
manager.setConfig(config as any);
|
||||
manager.setConfig(config);
|
||||
await flushPromises();
|
||||
|
||||
// Verify initial payload (folders may be populated with defaults, so
|
||||
@@ -594,7 +597,7 @@ describe('ConfigManager', () => {
|
||||
],
|
||||
});
|
||||
|
||||
manager.setConfig(config as any);
|
||||
manager.setConfig(config);
|
||||
await flushPromises();
|
||||
|
||||
// Verify automations were added initially
|
||||
@@ -652,7 +655,7 @@ describe('ConfigManager', () => {
|
||||
});
|
||||
|
||||
// Initial set should register remote control automations
|
||||
manager.setConfig(config as any);
|
||||
manager.setConfig(config);
|
||||
await flushPromises();
|
||||
|
||||
// Verify remote-control automations were added initially with config condition
|
||||
@@ -682,9 +685,9 @@ describe('ConfigManager', () => {
|
||||
// Verify new automations don't contain remote-control config conditions
|
||||
const addCalls = addAutomationsSpy.mock.calls;
|
||||
const hasRemoteControl = addCalls.some((call) =>
|
||||
call[0].some((automation: any) =>
|
||||
call[0].some((automation: Automation) =>
|
||||
automation.conditions?.some(
|
||||
(cond: any) =>
|
||||
(cond: AdvancedCameraCardCondition) =>
|
||||
cond.condition === 'config' &&
|
||||
cond.paths?.includes('remote_control.entities.camera'),
|
||||
),
|
||||
|
||||
@@ -313,13 +313,16 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
});
|
||||
|
||||
describe('should select option on entity', () => {
|
||||
it('should select option when camera differs from entity state', () => {
|
||||
it('should select option when camera differs from entity state', async () => {
|
||||
const hass = createHASS({
|
||||
'input_select.camera': createStateEntity({
|
||||
state: 'camera.one',
|
||||
}),
|
||||
});
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
||||
createStore([{ cameraID: 'camera.two' }]),
|
||||
);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
@@ -349,7 +352,7 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
INTERNAL_CALLBACK_ACTION,
|
||||
);
|
||||
|
||||
cameraSyncAction.callback(api);
|
||||
await cameraSyncAction.callback(api);
|
||||
expect(hass.callService).toBeCalledWith(
|
||||
'input_select',
|
||||
'select_option',
|
||||
@@ -369,6 +372,9 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
}),
|
||||
});
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
||||
createStore([{ cameraID: 'camera.one' }]),
|
||||
);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
@@ -441,7 +447,47 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
expect(hass.callService).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not throw when hass is undefined', () => {
|
||||
it('should not select option when view exists but has no camera', () => {
|
||||
const hass = createHASS({
|
||||
'input_select.camera': createStateEntity({
|
||||
state: 'camera.one',
|
||||
}),
|
||||
});
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
remote_control: {
|
||||
entities: {
|
||||
camera: 'input_select.camera',
|
||||
camera_priority: 'card',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: null,
|
||||
view: 'live',
|
||||
}),
|
||||
);
|
||||
|
||||
setRemoteControlEntityFromConfig(api);
|
||||
|
||||
// Test the 'camera' condition callback (automation index 1)
|
||||
const cameraSyncAction = vi.mocked(api.getAutomationsManager().addAutomations).mock
|
||||
.calls[0][0][1].actions?.[0] as InternalCallbackActionConfig;
|
||||
cameraSyncAction.callback(api);
|
||||
expect(hass.callService).not.toBeCalled();
|
||||
|
||||
// Also test the 'initialized' condition callback (automation index 2)
|
||||
const initializedSyncAction = vi.mocked(api.getAutomationsManager().addAutomations)
|
||||
.mock.calls[0][0][2].actions?.[0] as InternalCallbackActionConfig;
|
||||
initializedSyncAction.callback(api);
|
||||
expect(hass.callService).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not throw when hass is undefined', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
@@ -472,12 +518,15 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
);
|
||||
|
||||
// Should not throw and obviously not call service (as hass is null)
|
||||
cameraSyncAction.callback(api);
|
||||
await cameraSyncAction.callback(api);
|
||||
});
|
||||
|
||||
it('should select option when entity state is undefined', () => {
|
||||
it('should select option when entity state is undefined', async () => {
|
||||
const hass = createHASS({});
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
||||
createStore([{ cameraID: 'camera.two' }]),
|
||||
);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
@@ -506,7 +555,7 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
INTERNAL_CALLBACK_ACTION,
|
||||
);
|
||||
|
||||
cameraSyncAction.callback(api);
|
||||
await cameraSyncAction.callback(api);
|
||||
expect(hass.callService).toBeCalledWith(
|
||||
'input_select',
|
||||
'select_option',
|
||||
@@ -519,13 +568,16 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should select option on initialization with card priority', () => {
|
||||
it('should select option on initialization with card priority', async () => {
|
||||
const hass = createHASS({
|
||||
'input_select.camera': createStateEntity({
|
||||
state: 'camera.one',
|
||||
}),
|
||||
});
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
||||
createStore([{ cameraID: 'camera.two' }]),
|
||||
);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
@@ -553,7 +605,7 @@ describe('setRemoteControlEntityFromConfig', () => {
|
||||
expect(isAdvancedCameraCardCustomAction(initAction)).toBeTruthy();
|
||||
expect(initAction.advanced_camera_card_action).toBe(INTERNAL_CALLBACK_ACTION);
|
||||
|
||||
initAction.callback(api);
|
||||
await initAction.callback(api);
|
||||
expect(hass.callService).toBeCalledWith(
|
||||
'input_select',
|
||||
'select_option',
|
||||
|
||||
Reference in New Issue
Block a user