refactor: Significantly refactor how conditions work internally (#1886)
- Much improved API cleanliness and testability to allow further extensibility in future - Simplified code in `live` view This technically contains a small change in how overrides work in the `live` view. Since that change is _closer_ to the documentation, and since this is likely to be rarely used, this is not considered a breaking change. Previously, overrides for a given live camera would always render _as if_ that camera was selected, vs was actually selected. Now, overrides will only apply in the live view when the camera is _actually_ selected. If this is an issue for you in practice, lets discuss.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AutomationsManager } from '../../src/card-controller/automations-manager.js';
|
||||
import { createCardAPI } from '../test-utils.js';
|
||||
import { ActionType } from '../../src/config/types.js';
|
||||
import { AuxillaryActionConfig } from '../../src/card-controller/actions/types.js';
|
||||
import { AutomationsManager } from '../../src/card-controller/automations-manager.js';
|
||||
import { ConditionStateManager } from '../../src/conditions/state-manager.js';
|
||||
import { ActionType } from '../../src/config/types.js';
|
||||
import { createCardAPI } from '../test-utils.js';
|
||||
|
||||
describe('AutomationsManager', () => {
|
||||
const actions = [
|
||||
@@ -25,39 +26,55 @@ describe('AutomationsManager', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should do nothing without hass', () => {
|
||||
const api = createCardAPI();
|
||||
describe('should not execute actions', () => {
|
||||
it('should do nothing without hass', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.execute();
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([automation]);
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
it('should do nothing without being initialized', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().hasHASS).mockReturnValue(true);
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
false,
|
||||
);
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.execute();
|
||||
it('should do nothing without being initialized', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().hasHASS).mockReturnValue(true);
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
false,
|
||||
);
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([automation]);
|
||||
|
||||
it('should do nothing without automations', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().hasHASS).mockReturnValue(true);
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
true,
|
||||
);
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.execute();
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
it('should do nothing with an error message present', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().hasHASS).mockReturnValue(true);
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
true,
|
||||
);
|
||||
vi.mocked(api.getMessageManager().hasErrorMessage).mockReturnValue(true);
|
||||
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([automation]);
|
||||
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute actions', () => {
|
||||
@@ -66,31 +83,25 @@ describe('AutomationsManager', () => {
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
true,
|
||||
);
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([automation]);
|
||||
|
||||
automationsManager.execute();
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(true);
|
||||
|
||||
automationsManager.execute();
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||
|
||||
// Automation will not re-fire when condition continues to evaluate the
|
||||
// same.
|
||||
automationsManager.execute();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(false);
|
||||
|
||||
automationsManager.execute();
|
||||
stateManager.setState({ fullscreen: false });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(true);
|
||||
|
||||
automationsManager.execute();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
@@ -100,14 +111,16 @@ describe('AutomationsManager', () => {
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
true,
|
||||
);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(false);
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([not_automation]);
|
||||
|
||||
automationsManager.execute();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
|
||||
stateManager.setState({ fullscreen: false });
|
||||
expect(api.getActionsManager().executeActions).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -117,12 +130,23 @@ describe('AutomationsManager', () => {
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
true,
|
||||
);
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([automation, not_automation]);
|
||||
automationsManager.addAutomations([
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen' as const, fullscreen: true }],
|
||||
actions: actions,
|
||||
},
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen' as const, fullscreen: false }],
|
||||
actions_not: actions,
|
||||
},
|
||||
]);
|
||||
|
||||
// Create a setup where one automation action causes another...
|
||||
let evaluation = true;
|
||||
let fullscreen = true;
|
||||
|
||||
vi.mocked(api.getActionsManager().executeActions).mockImplementation(
|
||||
async (
|
||||
@@ -131,17 +155,12 @@ describe('AutomationsManager', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_config?: AuxillaryActionConfig,
|
||||
): Promise<void> => {
|
||||
evaluation = !evaluation;
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(
|
||||
evaluation,
|
||||
);
|
||||
automationsManager.execute();
|
||||
fullscreen = !fullscreen;
|
||||
stateManager.setState({ fullscreen: fullscreen });
|
||||
},
|
||||
);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(evaluation);
|
||||
|
||||
automationsManager.execute();
|
||||
stateManager.setState({ fullscreen: fullscreen });
|
||||
|
||||
expect(api.getMessageManager().setMessageIfHigherPriority).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -160,18 +179,40 @@ describe('AutomationsManager', () => {
|
||||
vi.mocked(api.getInitializationManager().isInitializedMandatory).mockReturnValue(
|
||||
true,
|
||||
);
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.addAutomations([automation]);
|
||||
automationsManager.addAutomations([
|
||||
{
|
||||
conditions: [{ condition: 'expand' as const, expand: true }],
|
||||
actions: actions,
|
||||
},
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen' as const, fullscreen: true }],
|
||||
actions: actions,
|
||||
tag: 'fullscreen',
|
||||
},
|
||||
]);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateConditions).mockReturnValue(true);
|
||||
|
||||
automationsManager.execute();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||
|
||||
// Delete the fullscreen automation.
|
||||
automationsManager.deleteAutomations('fullscreen');
|
||||
|
||||
stateManager.setState({ fullscreen: false });
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||
|
||||
stateManager.setState({ expand: true });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
|
||||
|
||||
// Delete all automations.
|
||||
automationsManager.deleteAutomations();
|
||||
|
||||
automationsManager.execute();
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
|
||||
stateManager.setState({ fullscreen: false });
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ZodError } from 'zod';
|
||||
import { getOverriddenConfig } from '../../../src/card-controller/conditions-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 { advancedCameraCardConfigSchema } from '../../../src/config/types';
|
||||
import { createCardAPI, createConfig, flushPromises } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/card-controller/conditions-manager.js');
|
||||
import { createCardAPI, flushPromises } from '../../test-utils';
|
||||
|
||||
describe('ConfigManager', () => {
|
||||
beforeEach(() => {
|
||||
@@ -84,8 +82,7 @@ describe('ConfigManager', () => {
|
||||
expect(manager.getConfig()?.menu.alignment).toBe('left');
|
||||
|
||||
// Verify appropriate API calls are made.
|
||||
expect(api.getConditionsManager().setConditionsFromConfig).toBeCalled();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({
|
||||
view: undefined,
|
||||
displayMode: undefined,
|
||||
camera: undefined,
|
||||
@@ -165,179 +162,222 @@ describe('ConfigManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore overrides without a config', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(manager.getConfig()).toBeNull();
|
||||
expect(api.getStyleManager().updateFromConfig).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should ignore overrides with same config', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = new ConfigManager(api);
|
||||
const cameras = [{ camera_entity: 'camera.office' }];
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
cameras: cameras,
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
set: {
|
||||
// Override with the same.
|
||||
cameras: cameras,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(config);
|
||||
|
||||
manager.setConfig(config);
|
||||
expect(api.getStyleManager().updateFromConfig).toBeCalled();
|
||||
|
||||
vi.mocked(api.getStyleManager().updateFromConfig).mockClear();
|
||||
manager.computeOverrideConfig();
|
||||
expect(api.getStyleManager().updateFromConfig).toBeCalledTimes(1);
|
||||
|
||||
expect(api.getStyleManager().updateFromConfig).not.toBeCalled();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(api.getStyleManager().updateFromConfig).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should override', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
set: { 'menu.style': 'none' },
|
||||
},
|
||||
],
|
||||
};
|
||||
manager.setConfig(config_1);
|
||||
vi.mocked(api.getStyleManager().updateFromConfig).mockClear();
|
||||
|
||||
const config_2 = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.kitchen' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(config_2);
|
||||
manager.computeOverrideConfig();
|
||||
manager.setConfig(config);
|
||||
expect(manager.getConfig()?.menu?.style).toBe('hidden');
|
||||
|
||||
expect(api.getStyleManager().updateFromConfig).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(manager.getConfig()?.menu?.style).toBe('none');
|
||||
expect(manager.getConfig()).not.toEqual(manager.getNonOverriddenConfig());
|
||||
});
|
||||
|
||||
it('should set error on invalid override', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = new ConfigManager(api);
|
||||
manager.setConfig({
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
});
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
delete: ['cameras'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const error = new Error('Invalid override configuration');
|
||||
vi.mocked(getOverriddenConfig).mockImplementation(() => {
|
||||
throw error;
|
||||
});
|
||||
manager.setConfig(config);
|
||||
expect(manager.getConfig()).not.toBeNull();
|
||||
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getMessageManager().setErrorIfHigherPriority).toBeCalledWith(error);
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(manager.getConfig()).not.toBeNull();
|
||||
expect(api.getMessageManager().setErrorIfHigherPriority).toBeCalledWith(
|
||||
expect.objectContaining({ message: 'Invalid override configuration' }),
|
||||
);
|
||||
});
|
||||
|
||||
describe('should uninitialize on override', () => {
|
||||
it('cameras', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
set: {
|
||||
cameras: [{ camera_entity: 'camera.kitchen' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.VIEW,
|
||||
manager.setConfig(config);
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
|
||||
const config_2 = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.kitchen' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
});
|
||||
|
||||
it('cameras_global', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
set: {
|
||||
cameras_global: { live_provider: 'jsmpeg' },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.VIEW,
|
||||
manager.setConfig(config);
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
cameras_global: {
|
||||
live_provider: 'jsmpeg',
|
||||
},
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
});
|
||||
|
||||
it('live.microphone.always_connected', () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: false,
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
set: {
|
||||
'live.microphone.always_connected': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.VIEW,
|
||||
manager.setConfig(config);
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalledWith(
|
||||
InitializationAspect.MICROPHONE_CONNECT,
|
||||
);
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
|
||||
InitializationAspect.MICROPHONE_CONNECT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should initialize background items', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config));
|
||||
describe('should initialize on override', () => {
|
||||
it('should initialize background items', async () => {
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
manager.setConfig(config);
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
overrides: [
|
||||
{
|
||||
conditions: [{ condition: 'fullscreen', fullscreen: true }],
|
||||
set: {
|
||||
cameras: [{ camera_entity: 'camera.kitchen' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await flushPromises();
|
||||
manager.setConfig(config);
|
||||
|
||||
expect(api.getDefaultManager().initializeIfNecessary).toBeCalledWith(null);
|
||||
expect(api.getMediaPlayerManager().initializeIfNecessary).toBeCalledWith(null);
|
||||
await flushPromises();
|
||||
|
||||
expect(api.getDefaultManager().initializeIfNecessary).toBeCalledTimes(1);
|
||||
expect(api.getMediaPlayerManager().initializeIfNecessary).toBeCalledTimes(1);
|
||||
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(api.getDefaultManager().initializeIfNecessary).toBeCalledTimes(2);
|
||||
expect(api.getMediaPlayerManager().initializeIfNecessary).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { z } from 'zod';
|
||||
import { getOverriddenConfig } from '../../../src/card-controller/config/get-overridden-config';
|
||||
import { ConditionsManagerReadonlyInterface } from '../../../src/conditions/types';
|
||||
|
||||
describe('getOverriddenConfig', () => {
|
||||
const config = {
|
||||
menu: {
|
||||
style: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
it('should not override without overrides', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(getOverriddenConfig(manager, config)).toBe(config);
|
||||
});
|
||||
|
||||
it('should not override when conditions do not match', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: false });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
merge: {
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
},
|
||||
delete: ['menu.style'],
|
||||
set: {
|
||||
'menu.style': 'overlay',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toBe(config);
|
||||
});
|
||||
|
||||
describe('should merge', () => {
|
||||
it('with path', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
merge: {
|
||||
'live.controls.thumbnails': {
|
||||
mode: 'none',
|
||||
},
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {
|
||||
style: 'none',
|
||||
},
|
||||
live: {
|
||||
controls: {
|
||||
thumbnails: {
|
||||
mode: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('without path', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
merge: {
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('with invalid merge', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
merge: 6 as unknown as Record<string, unknown>,
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {
|
||||
style: 'none',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should set', () => {
|
||||
it('leaf node', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
set: {
|
||||
'menu.style': 'hidden',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('root node', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
set: {
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should delete', () => {
|
||||
it('leaf node', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
delete: ['menu.style' as const],
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('root node', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
delete: ['menu' as const],
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should validate schema', () => {
|
||||
const testSchema = z.object({
|
||||
menu: z.object({
|
||||
style: z.enum(['none', 'hidden']),
|
||||
}),
|
||||
});
|
||||
|
||||
it('passing', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
set: {
|
||||
'menu.style': 'hidden',
|
||||
},
|
||||
},
|
||||
],
|
||||
schema: testSchema,
|
||||
}),
|
||||
).toEqual({
|
||||
menu: {
|
||||
style: 'hidden',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('failing', () => {
|
||||
const manager = mock<ConditionsManagerReadonlyInterface>();
|
||||
manager.getEvaluation.mockReturnValue({ result: true });
|
||||
|
||||
expect(() =>
|
||||
getOverriddenConfig(manager, config, {
|
||||
configOverrides: [
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
set: {
|
||||
'menu.style': 'NOT_A_STYLE',
|
||||
},
|
||||
},
|
||||
],
|
||||
schema: testSchema,
|
||||
}),
|
||||
).toThrowError(/Invalid override configuration/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
CardElementManager,
|
||||
CardHTMLElement,
|
||||
} from '../../src/card-controller/card-element-manager';
|
||||
import { ConditionsManager } from '../../src/card-controller/conditions-manager';
|
||||
import { ConfigManager } from '../../src/card-controller/config/config-manager';
|
||||
import { CardController } from '../../src/card-controller/controller';
|
||||
import { DefaultManager } from '../../src/card-controller/default-manager';
|
||||
@@ -27,6 +26,7 @@ import { StatusBarItemManager } from '../../src/card-controller/status-bar-item-
|
||||
import { StyleManager } from '../../src/card-controller/style-manager';
|
||||
import { TriggersManager } from '../../src/card-controller/triggers-manager';
|
||||
import { ViewManager } from '../../src/card-controller/view/view-manager';
|
||||
import { ConditionStateManager } from '../../src/conditions/state-manager';
|
||||
import { AdvancedCameraCardEditor } from '../../src/editor';
|
||||
import { DeviceRegistryManager } from '../../src/utils/ha/registry/device';
|
||||
import { EntityRegistryManager } from '../../src/utils/ha/registry/entity';
|
||||
@@ -37,7 +37,6 @@ vi.mock('../../src/card-controller/actions/actions-manager');
|
||||
vi.mock('../../src/card-controller/automations-manager');
|
||||
vi.mock('../../src/card-controller/camera-url-manager');
|
||||
vi.mock('../../src/card-controller/card-element-manager');
|
||||
vi.mock('../../src/card-controller/conditions-manager');
|
||||
vi.mock('../../src/card-controller/config/config-manager');
|
||||
vi.mock('../../src/card-controller/default-manager');
|
||||
vi.mock('../../src/card-controller/download-manager');
|
||||
@@ -56,6 +55,7 @@ vi.mock('../../src/card-controller/status-bar-item-manager');
|
||||
vi.mock('../../src/card-controller/style-manager');
|
||||
vi.mock('../../src/card-controller/triggers-manager');
|
||||
vi.mock('../../src/card-controller/view/view-manager');
|
||||
vi.mock('../../src/conditions/state-manager');
|
||||
vi.mock('../../src/utils/ha/registry/device');
|
||||
vi.mock('../../src/utils/ha/registry/entity');
|
||||
vi.mock('../../src/utils/ha/resolved-media');
|
||||
@@ -67,7 +67,7 @@ const createCardElement = (): CardHTMLElement => {
|
||||
};
|
||||
|
||||
const createController = (): CardController => {
|
||||
return new CardController(createCardElement(), vi.fn(), vi.fn(), vi.fn());
|
||||
return new CardController(createCardElement(), vi.fn(), vi.fn());
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
@@ -80,16 +80,9 @@ describe('CardController', () => {
|
||||
const element = createCardElement();
|
||||
const scrollCallback = vi.fn();
|
||||
const menuToggleCallback = vi.fn();
|
||||
const conditionListener = vi.fn();
|
||||
|
||||
const manager = new CardController(
|
||||
element,
|
||||
scrollCallback,
|
||||
menuToggleCallback,
|
||||
conditionListener,
|
||||
);
|
||||
const manager = new CardController(element, scrollCallback, menuToggleCallback);
|
||||
|
||||
expect(ConditionsManager).toBeCalledWith(manager, conditionListener);
|
||||
expect(CardElementManager).toBeCalledWith(
|
||||
manager,
|
||||
element,
|
||||
@@ -135,9 +128,9 @@ describe('CardController', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('getConditionsManager', () => {
|
||||
expect(createController().getConditionsManager()).toBe(
|
||||
vi.mocked(ConditionsManager).mock.instances[0],
|
||||
it('ConditionStateManager', () => {
|
||||
expect(createController().getConditionStateManager()).toBe(
|
||||
vi.mocked(ConditionStateManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('ExpandManager', () => {
|
||||
const manager = new ExpandManager(api);
|
||||
|
||||
manager.initialize();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({ expand: false });
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({ expand: false });
|
||||
});
|
||||
|
||||
it('should set expanded', () => {
|
||||
@@ -26,7 +26,7 @@ describe('ExpandManager', () => {
|
||||
|
||||
expect(manager.isExpanded()).toBeTruthy();
|
||||
expect(api.getFullscreenManager().setFullscreen).toBeCalledWith(false);
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({ expand: true });
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({ expand: true });
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ describe('FullscreenManager', () => {
|
||||
const manager = new FullscreenManager(api, mock<FullscreenProvider>());
|
||||
|
||||
manager.initialize();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({ fullscreen: false });
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({
|
||||
fullscreen: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly determine whether in fullscreen', () => {
|
||||
@@ -133,7 +135,7 @@ describe('FullscreenManager', () => {
|
||||
|
||||
handler();
|
||||
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({
|
||||
fullscreen: fullscreen,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { CardController } from '../../../../src/card-controller/controller';
|
||||
import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit';
|
||||
import { ConditionStateManager } from '../../../../src/conditions/state-manager';
|
||||
import {
|
||||
AdvancedCameraCardMediaPlayer,
|
||||
WebkitHTMLVideoElement,
|
||||
@@ -37,7 +38,7 @@ describe('WebkitFullScreenProvider', () => {
|
||||
|
||||
provider.connect();
|
||||
|
||||
expect(api.getConditionsManager().addListener).toBeCalledWith(expect.anything());
|
||||
expect(api.getConditionStateManager().addListener).toBeCalledWith(expect.anything());
|
||||
});
|
||||
|
||||
it('should disconnect', () => {
|
||||
@@ -46,7 +47,9 @@ describe('WebkitFullScreenProvider', () => {
|
||||
|
||||
provider.disconnect();
|
||||
|
||||
expect(api.getConditionsManager().removeListener).toBeCalledWith(expect.anything());
|
||||
expect(api.getConditionStateManager().removeListener).toBeCalledWith(
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
describe('should return if in fullscreen', () => {
|
||||
@@ -152,20 +155,19 @@ describe('WebkitFullScreenProvider', () => {
|
||||
(event: string) => {
|
||||
const handler = vi.fn();
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const provider = new WebkitFullScreenProvider(api, handler);
|
||||
|
||||
provider.connect();
|
||||
|
||||
const conditionChangeHandler = vi.mocked(
|
||||
api.getConditionsManager().addListener,
|
||||
).mock.calls[0][0];
|
||||
|
||||
const element_1 = createWebkitVideoElement();
|
||||
const player_1 = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player_1.getFullscreenElement.mockReturnValue(element_1);
|
||||
const mediaLoadedInfo_1 = createMediaLoadedInfo({ player: player_1 });
|
||||
|
||||
conditionChangeHandler({ mediaLoadedInfo: mediaLoadedInfo_1 });
|
||||
stateManager.setState({ mediaLoadedInfo: mediaLoadedInfo_1 });
|
||||
|
||||
element_1.dispatchEvent(new Event(event));
|
||||
|
||||
@@ -176,10 +178,7 @@ describe('WebkitFullScreenProvider', () => {
|
||||
player_2.getFullscreenElement.mockReturnValue(element_2);
|
||||
const mediaLoadedInfo_2 = createMediaLoadedInfo({ player: player_2 });
|
||||
|
||||
conditionChangeHandler(
|
||||
{ mediaLoadedInfo: mediaLoadedInfo_2 },
|
||||
{ mediaLoadedInfo: mediaLoadedInfo_1 },
|
||||
);
|
||||
stateManager.setState({ mediaLoadedInfo: mediaLoadedInfo_2 });
|
||||
|
||||
element_2.dispatchEvent(new Event(event));
|
||||
|
||||
@@ -190,11 +189,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
|
||||
expect(handler).toBeCalledTimes(2);
|
||||
|
||||
// Test the media loaded info not changing.
|
||||
conditionChangeHandler(
|
||||
{ mediaLoadedInfo: mediaLoadedInfo_2 },
|
||||
{ mediaLoadedInfo: mediaLoadedInfo_2 },
|
||||
);
|
||||
// Test the media loaded info changing, but the player not changing.
|
||||
stateManager.setState({
|
||||
mediaLoadedInfo: { ...mediaLoadedInfo_2, width: 101 },
|
||||
});
|
||||
|
||||
// Events on the new element should still be handled.
|
||||
element_2.dispatchEvent(new Event(event));
|
||||
@@ -210,13 +208,13 @@ describe('WebkitFullScreenProvider', () => {
|
||||
|
||||
const handler = vi.fn();
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager);
|
||||
|
||||
const provider = new WebkitFullScreenProvider(api, handler);
|
||||
|
||||
provider.connect();
|
||||
|
||||
const conditionChangeHandler = vi.mocked(api.getConditionsManager().addListener).mock
|
||||
.calls[0][0];
|
||||
|
||||
const element = createWebkitVideoElement();
|
||||
element.play = vi.fn();
|
||||
|
||||
@@ -226,7 +224,7 @@ describe('WebkitFullScreenProvider', () => {
|
||||
player.getFullscreenElement.mockReturnValue(element);
|
||||
const mediaLoadedInfo = createMediaLoadedInfo({ player });
|
||||
|
||||
conditionChangeHandler({ mediaLoadedInfo });
|
||||
stateManager.setState({ mediaLoadedInfo });
|
||||
|
||||
element.dispatchEvent(new Event('webkitendfullscreen'));
|
||||
|
||||
|
||||
@@ -47,35 +47,22 @@ describe('HASSManager', () => {
|
||||
expect(api.getStyleManager().applyTheme).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should set condition manager state', () => {
|
||||
it('positively', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new HASSManager(api);
|
||||
vi.mocked(api.getConditionsManager().hasHAStateConditions).mockReturnValue(true);
|
||||
it('should set condition manager state', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new HASSManager(api);
|
||||
|
||||
const states = { 'switch.foo': createStateEntity() };
|
||||
const user = createUser({ id: 'user_1' });
|
||||
const hass = createHASS(states, user);
|
||||
const states = { 'switch.foo': createStateEntity() };
|
||||
const user = createUser({ id: 'user_1' });
|
||||
const hass = createHASS(states, user);
|
||||
|
||||
manager.setHASS(hass);
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
state: states,
|
||||
user: user,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('negatively', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new HASSManager(api);
|
||||
vi.mocked(api.getConditionsManager().hasHAStateConditions).mockReturnValue(false);
|
||||
|
||||
manager.setHASS(createHASS());
|
||||
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
});
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
state: states,
|
||||
user: user,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('should handle connection state change when', () => {
|
||||
|
||||
@@ -23,7 +23,9 @@ describe('InteractionManager', () => {
|
||||
const manager = new InteractionManager(api);
|
||||
|
||||
manager.initialize();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({ interaction: false });
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({
|
||||
interaction: false,
|
||||
});
|
||||
expect(element.getAttribute('interaction')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -62,11 +64,11 @@ describe('InteractionManager', () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(start);
|
||||
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().setState).not.toBeCalled();
|
||||
|
||||
manager.reportInteraction();
|
||||
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
interaction: true,
|
||||
}),
|
||||
@@ -77,7 +79,7 @@ describe('InteractionManager', () => {
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
interaction: false,
|
||||
}),
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('KeyboardStateManager', () => {
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
|
||||
expect(api.getConditionsManager().setState).toHaveBeenCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenCalledWith({
|
||||
keys: {
|
||||
a: { state: 'down', ctrl: false, alt: false, meta: false, shift: false },
|
||||
},
|
||||
@@ -26,7 +26,7 @@ describe('KeyboardStateManager', () => {
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
|
||||
// Duplicate keydown should not re-set the state.
|
||||
expect(api.getConditionsManager().setState).toBeCalledTimes(1);
|
||||
expect(api.getConditionStateManager().setState).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should set state on keyup', () => {
|
||||
@@ -39,13 +39,13 @@ describe('KeyboardStateManager', () => {
|
||||
element.dispatchEvent(new KeyboardEvent('keyup', { key: 'a' }));
|
||||
|
||||
// Key not held down in the first place should not update the state.
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().setState).not.toBeCalled();
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
element.dispatchEvent(new KeyboardEvent('keyup', { key: 'a' }));
|
||||
|
||||
expect(api.getConditionsManager().setState).toBeCalledTimes(2);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toBeCalledTimes(2);
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith({
|
||||
keys: {
|
||||
a: { state: 'up', ctrl: false, alt: false, meta: false, shift: false },
|
||||
},
|
||||
@@ -60,13 +60,13 @@ describe('KeyboardStateManager', () => {
|
||||
manager.initialize();
|
||||
|
||||
element.dispatchEvent(new FocusEvent('blur'));
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().setState).not.toBeCalled();
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
element.dispatchEvent(new FocusEvent('blur'));
|
||||
|
||||
expect(api.getConditionsManager().setState).toBeCalledTimes(2);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toBeCalledTimes(2);
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith({
|
||||
keys: {},
|
||||
});
|
||||
});
|
||||
@@ -81,6 +81,6 @@ describe('KeyboardStateManager', () => {
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().setState).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('MediaLoadedInfoManager', () => {
|
||||
const manager = new MediaLoadedInfoManager(api);
|
||||
|
||||
manager.initialize();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({
|
||||
mediaLoadedInfo: null,
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,7 @@ describe('MediaLoadedInfoManager', () => {
|
||||
|
||||
expect(manager.has()).toBeTruthy();
|
||||
expect(manager.get()).toBe(mediaInfo);
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({ mediaLoadedInfo: mediaInfo }),
|
||||
);
|
||||
expect(api.getStyleManager().setExpandedMode).toBeCalled();
|
||||
@@ -38,7 +38,7 @@ describe('MediaLoadedInfoManager', () => {
|
||||
|
||||
expect(manager.has()).toBeFalsy();
|
||||
expect(manager.get()).toBeNull();
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().setState).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should get last known', () => {
|
||||
@@ -54,7 +54,7 @@ describe('MediaLoadedInfoManager', () => {
|
||||
|
||||
expect(manager.has()).toBeFalsy();
|
||||
expect(manager.getLastKnown()).toBe(mediaLoadedInfo);
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({ mediaLoadedInfo }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -302,7 +302,7 @@ describe('MicrophoneManager', () => {
|
||||
const manager = new MicrophoneManager(api);
|
||||
|
||||
manager.initialize();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toBeCalledWith({
|
||||
microphone: { connected: false, muted: true, forbidden: false, stream: undefined },
|
||||
});
|
||||
});
|
||||
@@ -313,7 +313,7 @@ describe('MicrophoneManager', () => {
|
||||
const stream = createMockStream();
|
||||
vi.mocked(navigatorMock.mediaDevices.getUserMedia).mockResolvedValue(stream);
|
||||
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
expect(api.getConditionStateManager().setState).not.toBeCalled();
|
||||
|
||||
await manager.connect();
|
||||
|
||||
@@ -325,7 +325,7 @@ describe('MicrophoneManager', () => {
|
||||
};
|
||||
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: expectedState,
|
||||
}),
|
||||
@@ -340,7 +340,7 @@ describe('MicrophoneManager', () => {
|
||||
muted: false,
|
||||
};
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: expectedState,
|
||||
}),
|
||||
@@ -355,7 +355,7 @@ describe('MicrophoneManager', () => {
|
||||
muted: true,
|
||||
};
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: expectedState,
|
||||
}),
|
||||
@@ -370,7 +370,7 @@ describe('MicrophoneManager', () => {
|
||||
muted: true,
|
||||
};
|
||||
expect(manager.getState()).toEqual(expectedState);
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith(
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
microphone: expectedState,
|
||||
}),
|
||||
|
||||
@@ -50,7 +50,7 @@ const createTriggerAPI = (options?: {
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getConditionsManager().getState).mockReturnValue({});
|
||||
vi.mocked(api.getConditionStateManager().getState).mockReturnValue({});
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -319,10 +319,10 @@ describe('TriggersManager', () => {
|
||||
|
||||
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
|
||||
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith({
|
||||
triggered: new Set(['camera_1']),
|
||||
});
|
||||
vi.mocked(api.getConditionsManager().getState).mockReturnValue({
|
||||
vi.mocked(api.getConditionStateManager().getState).mockReturnValue({
|
||||
triggered: new Set(['camera_1']),
|
||||
});
|
||||
|
||||
@@ -331,7 +331,7 @@ describe('TriggersManager', () => {
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
|
||||
expect(api.getConditionStateManager().setState).toHaveBeenLastCalledWith({
|
||||
triggered: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('should act correctly when view is set', () => {
|
||||
expect(api.getCardElementManager().scrollReset).toBeCalled();
|
||||
expect(api.getMessageManager().reset).toBeCalled();
|
||||
expect(api.getStyleManager().setExpandedMode).toBeCalled();
|
||||
expect(api.getConditionsManager()?.setState).toBeCalledWith({
|
||||
expect(api.getConditionStateManager()?.setState).toBeCalledWith({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
displayMode: 'grid',
|
||||
|
||||
@@ -0,0 +1,933 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { MicrophoneState } from '../../src/card-controller/types';
|
||||
import { ConditionsManager } from '../../src/conditions/conditions-manager';
|
||||
import { ConditionStateManager } from '../../src/conditions/state-manager';
|
||||
import { createMediaLoadedInfo, createStateEntity, createUser } from '../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('ConditionsManager', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('should evaluate conditions', () => {
|
||||
describe('with a view condition', () => {
|
||||
it('should match named view change', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'view' as const, views: ['foo'] }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ view: 'foo' });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should match any view change', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'view' as const }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({ view: 'clips' });
|
||||
expect(listener).toHaveBeenLastCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
view: {
|
||||
to: 'clips',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stateManager.setState({ view: 'timeline' });
|
||||
expect(listener).toHaveBeenLastCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
view: {
|
||||
from: 'clips',
|
||||
to: 'timeline',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('with fullscreen condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ fullscreen: true });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ fullscreen: false });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with expand condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'expand' as const, expand: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ expand: true });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ expand: false });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('with camera condition', () => {
|
||||
it('should match named camera change', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'camera' as const, cameras: ['bar'] }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ camera: 'bar' });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ camera: 'will-not-match' });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should match any camera change', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'camera' as const }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({ camera: 'bar' });
|
||||
expect(listener).toHaveBeenLastCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
camera: {
|
||||
to: 'bar',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stateManager.setState({ camera: 'foo' });
|
||||
expect(listener).toHaveBeenLastCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
camera: {
|
||||
from: 'bar',
|
||||
to: 'foo',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with stock HA conditions', () => {
|
||||
describe('with state condition', () => {
|
||||
it('neither positive nor negative', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'state' as const,
|
||||
entity: 'binary_sensor.foo',
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'on' }) },
|
||||
});
|
||||
expect(listener).toBeCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
state: {
|
||||
entity: 'binary_sensor.foo',
|
||||
to: 'on',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(listener).toBeCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
state: {
|
||||
entity: 'binary_sensor.foo',
|
||||
from: 'on',
|
||||
to: 'off',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
describe('positive', () => {
|
||||
it('single state', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'state' as const,
|
||||
entity: 'binary_sensor.foo',
|
||||
state: 'on',
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity() },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('multiple states', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'state' as const,
|
||||
entity: 'binary_sensor.foo',
|
||||
state: ['active', 'on'],
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity() },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'active' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('negative', () => {
|
||||
it('single state', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'state' as const,
|
||||
entity: 'binary_sensor.foo',
|
||||
state_not: 'on',
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity() },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('multiple states', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'state' as const,
|
||||
entity: 'binary_sensor.foo',
|
||||
state_not: ['active', 'on'],
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ state: { 'binary_sensor.foo': createStateEntity() } });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'active' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('implicit state condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
entity: 'binary_sensor.foo',
|
||||
state: 'on',
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ state: { 'binary_sensor.foo': createStateEntity() } });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should match any state change when state and state_not omitted', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{ condition: 'state' as const, entity: 'switch.one' },
|
||||
{ condition: 'state' as const, entity: 'switch.two' },
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({
|
||||
state: {
|
||||
'switch.one': createStateEntity({ state: 'on' }),
|
||||
'switch.two': createStateEntity({ state: 'off' }),
|
||||
},
|
||||
});
|
||||
expect(listener).toHaveBeenLastCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
// Only the last matching state will be included in the data.
|
||||
state: {
|
||||
entity: 'switch.two',
|
||||
to: 'off',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
stateManager.setState({
|
||||
state: {
|
||||
'switch.one': createStateEntity({ state: 'off' }),
|
||||
'switch.two': createStateEntity({ state: 'on' }),
|
||||
},
|
||||
});
|
||||
|
||||
expect(listener).toHaveBeenLastCalledWith({
|
||||
result: true,
|
||||
data: {
|
||||
// Only the last matching state will be included in the data.
|
||||
state: {
|
||||
entity: 'switch.two',
|
||||
from: 'off',
|
||||
to: 'on',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with numeric state condition', () => {
|
||||
it('above', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'numeric_state' as const,
|
||||
entity: 'sensor.foo',
|
||||
above: 10,
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'sensor.foo': createStateEntity({ state: '11' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: '9' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('below', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'numeric_state' as const,
|
||||
entity: 'sensor.foo',
|
||||
below: 10,
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'sensor.foo': createStateEntity({ state: '11' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
state: { 'sensor.foo': createStateEntity({ state: '9' }) },
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call listeners for HA state changes without relevant condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'fullscreen' as const,
|
||||
fullscreen: true,
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({
|
||||
state: { 'sensor.foo': createStateEntity({ state: '11' }) },
|
||||
});
|
||||
|
||||
expect(listener).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('with user condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'user' as const,
|
||||
users: ['user_1', 'user_2'],
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
user: createUser({ id: 'user_1' }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
user: createUser({ id: 'user_WRONG' }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with media loaded condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'media_loaded' as const, media_loaded: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ mediaLoadedInfo: createMediaLoadedInfo() });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ mediaLoadedInfo: null });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('with screen condition', () => {
|
||||
it('on evaluation', () => {
|
||||
vi.spyOn(window, 'matchMedia')
|
||||
.mockReturnValueOnce({
|
||||
addEventListener: vi.fn(),
|
||||
} as unknown as MediaQueryList)
|
||||
.mockReturnValueOnce({
|
||||
matches: true,
|
||||
} as unknown as MediaQueryList);
|
||||
|
||||
const manager = new ConditionsManager([
|
||||
{ condition: 'screen' as const, media_query: 'whatever' },
|
||||
]);
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('on trigger', () => {
|
||||
const addEventListener = vi.fn();
|
||||
const removeEventListener = vi.fn();
|
||||
vi.spyOn(window, 'matchMedia')
|
||||
.mockReturnValueOnce({
|
||||
addEventListener: addEventListener,
|
||||
removeEventListener: removeEventListener,
|
||||
} as unknown as MediaQueryList)
|
||||
.mockReturnValueOnce({
|
||||
matches: false,
|
||||
} as unknown as MediaQueryList)
|
||||
.mockReturnValueOnce({
|
||||
matches: true,
|
||||
} as unknown as MediaQueryList);
|
||||
|
||||
const manager = new ConditionsManager([
|
||||
{
|
||||
condition: 'screen' as const,
|
||||
media_query: 'media query goes here',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(addEventListener).toHaveBeenCalledWith('change', expect.anything());
|
||||
|
||||
const callback = vi.fn();
|
||||
manager.addListener(callback);
|
||||
|
||||
// Call the media query callback and use it to pretend a match happened. The
|
||||
// callback is the 0th mock innvocation and the 1st argument.
|
||||
addEventListener.mock.calls[0][1]();
|
||||
|
||||
// This should result in a callback to our state listener.
|
||||
expect(callback).toBeCalledWith({ result: true, data: {} });
|
||||
|
||||
// Destroy the manager and ensure the event listener is removed.
|
||||
manager.destroy();
|
||||
expect(removeEventListener).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('with display mode condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'display_mode' as const, display_mode: 'grid' as const }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ displayMode: 'grid' });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ displayMode: 'single' });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with triggered condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'triggered' as const, triggered: ['camera_1', 'camera_2'] }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ triggered: new Set(['camera_1']) });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
triggered: new Set(['camera_2', 'camera_1', 'camera_3']),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ triggered: new Set(['camera_3']) });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with interaction condition', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'interaction' as const, interaction: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ interaction: true });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ interaction: false });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('with microphone condition', () => {
|
||||
const createMicrophoneState = (
|
||||
state: Partial<MicrophoneState>,
|
||||
): MicrophoneState => {
|
||||
return {
|
||||
connected: false,
|
||||
muted: false,
|
||||
forbidden: false,
|
||||
...state,
|
||||
};
|
||||
};
|
||||
it('empty', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'microphone' as const }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: true }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: false }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('connected is true', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'microphone' as const, connected: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: true }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: false }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('connected is false', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'microphone' as const, connected: false }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: true }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: false }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('muted is true', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'microphone' as const, muted: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('muted is false', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'microphone' as const, muted: false }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('connected and muted', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'microphone' as const, muted: false, connected: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: true }) });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({ microphone: createMicrophoneState({ muted: false }) });
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: false, muted: false }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
microphone: createMicrophoneState({ connected: true, muted: false }),
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with key condition', () => {
|
||||
it('simple keypress', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'key' as const, key: 'a' }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
keys: {
|
||||
a: { state: 'down', ctrl: false, shift: false, alt: false, meta: false },
|
||||
},
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
keys: {
|
||||
a: { state: 'up', ctrl: false, shift: false, alt: false, meta: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('keypress with modifiers', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'key' as const,
|
||||
key: 'a',
|
||||
state: 'down' as const,
|
||||
ctrl: true,
|
||||
shift: true,
|
||||
alt: true,
|
||||
meta: true,
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
keys: {
|
||||
a: { state: 'down', ctrl: false, shift: false, alt: false, meta: false },
|
||||
},
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
keys: {
|
||||
a: { state: 'down', ctrl: true, shift: true, alt: true, meta: false },
|
||||
},
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
keys: {
|
||||
a: { state: 'down', ctrl: true, shift: true, alt: true, meta: true },
|
||||
},
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with user agent condition', () => {
|
||||
const userAgent =
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
||||
|
||||
it('should match exact user agent', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'user_agent' as const, user_agent: userAgent }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
userAgent: userAgent,
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
userAgent: 'Something else',
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should match user agent regex', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'user_agent' as const, user_agent_re: 'Chrome/' }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
userAgent: userAgent,
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
userAgent: 'Something else',
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should match companion app', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'user_agent' as const, companion: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
userAgent: 'Home Assistant/',
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
userAgent: userAgent,
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should match multiple parameters', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[
|
||||
{
|
||||
condition: 'user_agent' as const,
|
||||
companion: true,
|
||||
user_agent: 'Home Assistant/',
|
||||
user_agent_re: 'Home.Assistant',
|
||||
},
|
||||
],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
stateManager.setState({
|
||||
userAgent: 'Home Assistant/',
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeTruthy();
|
||||
stateManager.setState({
|
||||
userAgent: 'Something else',
|
||||
});
|
||||
expect(manager.getEvaluation().result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle listeners correctly', () => {
|
||||
it('should add listener', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(listener).toBeCalledWith({ result: true, data: {} });
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
stateManager.setState({ fullscreen: false });
|
||||
expect(listener).toBeCalledWith({ result: false });
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
// Re-add the same listener (will still only be called once).
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(listener).toBeCalledWith({ result: true, data: {} });
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should remove listener', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
manager.removeListener(listener);
|
||||
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(listener).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should remove listener on destroy', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'fullscreen' as const, fullscreen: true }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
manager.destroy();
|
||||
|
||||
stateManager.setState({ fullscreen: true });
|
||||
|
||||
expect(listener).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('with not call listeners when condition result does not change', () => {
|
||||
const stateManager = new ConditionStateManager();
|
||||
const manager = new ConditionsManager(
|
||||
[{ condition: 'view' as const, views: ['foo'] }],
|
||||
stateManager,
|
||||
);
|
||||
|
||||
const listener = vi.fn();
|
||||
manager.addListener(listener);
|
||||
|
||||
stateManager.setState({ view: 'foo' });
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
stateManager.setState({ view: 'bar' });
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
stateManager.setState({ view: 'bar' });
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
stateManager.setState({ view: 'foo' });
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
|
||||
stateManager.setState({ view: 'foo' });
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { ConditionStateManager } from '../../src/conditions/state-manager';
|
||||
import {
|
||||
ConditionStateManagerGetEvent,
|
||||
getConditionStateManagerViaEvent,
|
||||
} from '../../src/conditions/state-manager-via-event';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('getConditionStateManagerViaEvent', () => {
|
||||
it('should dispatch event and retrieve state manager', () => {
|
||||
const element = document.createElement('div');
|
||||
const stateManager = mock<ConditionStateManager>();
|
||||
|
||||
const handler = vi.fn().mockImplementation((ev: ConditionStateManagerGetEvent) => {
|
||||
ev.conditionStateManager = stateManager;
|
||||
});
|
||||
element.addEventListener(
|
||||
'advanced-camera-card:condition-state-manager:get',
|
||||
handler,
|
||||
);
|
||||
|
||||
expect(getConditionStateManagerViaEvent(element)).toBe(stateManager);
|
||||
});
|
||||
|
||||
it('should dispatch event and retrieve state manager', () => {
|
||||
const element = document.createElement('div');
|
||||
|
||||
const handler = vi.fn();
|
||||
element.addEventListener(
|
||||
'advanced-camera-card:condition-state-manager:get',
|
||||
handler,
|
||||
);
|
||||
|
||||
expect(getConditionStateManagerViaEvent(element)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ConditionStateManager } from '../../src/conditions/state-manager';
|
||||
import { createStateEntity } from '../test-utils';
|
||||
|
||||
describe('ConditionStateManager', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should get state', () => {
|
||||
const state = { fullscreen: true };
|
||||
|
||||
const manager = new ConditionStateManager();
|
||||
manager.setState(state);
|
||||
expect(manager.getState()).toEqual(state);
|
||||
});
|
||||
|
||||
describe('should set state', () => {
|
||||
it('should set and be able to get it again', () => {
|
||||
const state = {
|
||||
fullscreen: true,
|
||||
};
|
||||
|
||||
const manager = new ConditionStateManager();
|
||||
|
||||
manager.setState(state);
|
||||
expect(manager.getState()).toEqual(state);
|
||||
});
|
||||
|
||||
it('should set but only trigger when necessary', () => {
|
||||
const listener = vi.fn();
|
||||
const manager = new ConditionStateManager();
|
||||
manager.addListener(listener);
|
||||
|
||||
const state = {
|
||||
fullscreen: true,
|
||||
};
|
||||
|
||||
manager.setState(state);
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
manager.setState(state);
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
manager.setState({ ...state });
|
||||
expect(listener).toBeCalledTimes(1);
|
||||
|
||||
manager.setState({
|
||||
state: {
|
||||
'binary_sensor.foo': createStateEntity(),
|
||||
},
|
||||
});
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
manager.setState({ fullscreen: true });
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
manager.setState({
|
||||
state: {
|
||||
'binary_sensor.foo': createStateEntity(),
|
||||
},
|
||||
});
|
||||
expect(listener).toBeCalledTimes(2);
|
||||
|
||||
manager.setState({ fullscreen: false });
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
|
||||
manager.setState({ fullscreen: false });
|
||||
expect(listener).toBeCalledTimes(3);
|
||||
|
||||
manager.setState({
|
||||
state: {
|
||||
'binary_sensor.foo': createStateEntity({ state: 'off' }),
|
||||
},
|
||||
});
|
||||
expect(listener).toBeCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add listener', () => {
|
||||
const listener = vi.fn();
|
||||
const manager = new ConditionStateManager();
|
||||
|
||||
manager.setState({ fullscreen: true });
|
||||
|
||||
manager.addListener(listener);
|
||||
|
||||
manager.setState({ expand: true });
|
||||
|
||||
expect(listener).toBeCalledWith({
|
||||
old: { fullscreen: true },
|
||||
change: { expand: true },
|
||||
new: { fullscreen: true, expand: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove listener', () => {
|
||||
const listener = vi.fn();
|
||||
const manager = new ConditionStateManager();
|
||||
|
||||
manager.addListener(listener);
|
||||
manager.removeListener(listener);
|
||||
|
||||
const state = { fullscreen: true };
|
||||
manager.setState(state);
|
||||
|
||||
expect(listener).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
+2
-8
@@ -19,7 +19,7 @@ import { ActionsManager } from '../src/card-controller/actions/actions-manager';
|
||||
import { AutomationsManager } from '../src/card-controller/automations-manager';
|
||||
import { CameraURLManager } from '../src/card-controller/camera-url-manager';
|
||||
import { CardElementManager } from '../src/card-controller/card-element-manager';
|
||||
import { ConditionsManager } from '../src/card-controller/conditions-manager';
|
||||
import { ConditionStateManager } from '../src/conditions/state-manager';
|
||||
import { ConfigManager } from '../src/card-controller/config/config-manager';
|
||||
import { CardController } from '../src/card-controller/controller';
|
||||
import { DefaultManager } from '../src/card-controller/default-manager';
|
||||
@@ -77,12 +77,6 @@ export const createCameraConfig = (config?: unknown): CameraConfig => {
|
||||
return cameraConfigSchema.parse(config ?? {});
|
||||
};
|
||||
|
||||
export const createCondition = (
|
||||
condition?: Partial<AdvancedCameraCardCondition>,
|
||||
): AdvancedCameraCardCondition => {
|
||||
return advancedCameraCardConditionSchema.parse(condition ?? {});
|
||||
};
|
||||
|
||||
export const createRawConfig = (
|
||||
config?: Partial<RawAdvancedCameraCardConfig>,
|
||||
): RawAdvancedCameraCardConfig => {
|
||||
@@ -487,7 +481,7 @@ export const createCardAPI = (): CardController => {
|
||||
api.getCameraManager.mockReturnValue(mock<CameraManager>());
|
||||
api.getCameraURLManager.mockReturnValue(mock<CameraURLManager>());
|
||||
api.getCardElementManager.mockReturnValue(mock<CardElementManager>());
|
||||
api.getConditionsManager.mockReturnValue(mock<ConditionsManager>());
|
||||
api.getConditionStateManager.mockReturnValue(mock<ConditionStateManager>());
|
||||
api.getConfigManager.mockReturnValue(mock<ConfigManager>());
|
||||
api.getDownloadManager.mockReturnValue(mock<DownloadManager>());
|
||||
api.getEntityRegistryManager.mockReturnValue(mock<EntityRegistryManager>());
|
||||
|
||||
Reference in New Issue
Block a user