Initial version of more advanced scan mode control.

This commit is contained in:
Dermot Duffy
2024-01-17 22:07:30 -08:00
parent ad1c323cb0
commit 278f4f372d
24 changed files with 648 additions and 327 deletions
+108 -30
View File
@@ -9,6 +9,7 @@ import {
import { FrigateCardMediaPlayer } from '../../src/types';
import {
convertActionToFrigateCardCustomAction,
frigateCardHandleAction,
frigateCardHandleActionConfig,
getActionConfigGivenAction,
} from '../../src/utils/action.js';
@@ -245,7 +246,7 @@ describe('ActionsManager.executeAction', () => {
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
card_id: 'NOT_foo',
frigate_card_action: 'default',
@@ -259,7 +260,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'default',
})!,
@@ -283,7 +284,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: viewName,
})!,
@@ -301,7 +302,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'download',
})!,
@@ -314,7 +315,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_ui',
})!,
@@ -327,7 +328,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'expand',
})!,
@@ -340,7 +341,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'fullscreen',
})!,
@@ -353,7 +354,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'menu_toggle',
})!,
@@ -370,7 +371,7 @@ describe('ActionsManager.executeAction', () => {
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
camera: 'camera',
@@ -397,7 +398,7 @@ describe('ActionsManager.executeAction', () => {
);
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
camera: 'camera',
@@ -430,7 +431,7 @@ describe('ActionsManager.executeAction', () => {
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
camera: 'camera',
@@ -445,11 +446,55 @@ describe('ActionsManager.executeAction', () => {
);
});
it('with triggered camera', async () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
vi.mocked(
api.getTriggersManager().getMostRecentlyTriggeredCameraID,
).mockReturnValue('camera');
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
triggered: true,
})!,
);
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
expect.objectContaining({
viewName: 'live',
cameraID: 'camera',
}),
);
});
it('without camera or triggered camera', async () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
vi.mocked(
api.getTriggersManager().getMostRecentlyTriggeredCameraID,
).mockReturnValue('camera');
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
})!,
);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
it('without a current view', async () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
camera: 'camera',
@@ -469,7 +514,7 @@ describe('ActionsManager.executeAction', () => {
}),
);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'camera_select',
camera: 'camera',
@@ -490,7 +535,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'live_substream_select',
camera: 'substream',
@@ -504,7 +549,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'live_substream_off',
})!,
@@ -517,7 +562,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'live_substream_on',
})!,
@@ -531,7 +576,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'media_player',
media_player_action: 'stop',
@@ -552,7 +597,7 @@ describe('ActionsManager.executeAction', () => {
);
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'media_player',
media_player_action: 'play',
@@ -576,7 +621,7 @@ describe('ActionsManager.executeAction', () => {
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'media_player',
media_player_action: 'play',
@@ -599,7 +644,7 @@ describe('ActionsManager.executeAction', () => {
);
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'media_player',
media_player_action: 'play',
@@ -615,7 +660,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'diagnostics',
})!,
@@ -632,7 +677,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'microphone_mute',
})!,
@@ -645,7 +690,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'microphone_unmute',
})!,
@@ -670,7 +715,7 @@ describe('ActionsManager.executeAction', () => {
);
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: action,
})!,
@@ -684,7 +729,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'screenshot',
})!,
@@ -697,7 +742,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'display_mode_select',
display_mode: 'grid',
@@ -715,7 +760,7 @@ describe('ActionsManager.executeAction', () => {
createView({ camera: 'camera.office' }),
);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'ptz',
ptz_action: 'left',
@@ -736,7 +781,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'ptz',
ptz_action: 'left',
@@ -751,7 +796,7 @@ describe('ActionsManager.executeAction', () => {
const api = createCardAPI();
const manager = new ActionsManager(api);
await manager.executeAction(
await manager.executeFrigateAction(
createAction({
frigate_card_action: 'show_ptz',
show_ptz: true,
@@ -768,7 +813,7 @@ describe('ActionsManager.executeAction', () => {
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
await manager.executeAction(
await manager.executeFrigateAction(
// Have to manually create the action (vs using `createAction()`) since
// it's malformed.
{
@@ -781,3 +826,36 @@ describe('ActionsManager.executeAction', () => {
);
});
});
describe('ActionsManager.executeActions', () => {
it('should execute actions', async () => {
const api = createCardAPI();
const hass = createHASS();
const element = document.createElement('div');
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
const manager = new ActionsManager(api);
const action = createAction({
frigate_card_action: 'default',
})!;
manager.executeActions(action);
expect(frigateCardHandleAction).toBeCalledWith(element, hass, {}, action);
});
it('should not execute actions without hass', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
const manager = new ActionsManager(api);
manager.executeActions(
createAction({
frigate_card_action: 'default',
})!,
);
expect(api.getViewManager().setViewDefault).not.toBeCalled();
});
});
+2 -2
View File
@@ -72,10 +72,10 @@ describe('HASSManager', () => {
const originalHASS = createHASS();
manager.setHASS(originalHASS);
expect(api.getTriggersManager().updateTriggeredCameras).toBeCalledWith(null);
expect(api.getTriggersManager().updateTriggerHAState).toBeCalledWith(null);
manager.setHASS(createHASS());
expect(api.getTriggersManager().updateTriggeredCameras).toBeCalledWith(originalHASS);
expect(api.getTriggersManager().updateTriggerHAState).toBeCalledWith(originalHASS);
});
describe('should handle connection state change when', () => {
@@ -30,7 +30,6 @@ describe('InteractionManager', () => {
manager.reportInteraction();
expect(api.getTriggersManager().untrigger).toBeCalled();
expect(manager.hasInteraction()).toBeTruthy();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
@@ -271,7 +271,6 @@ describe('MediaPlayerManager', () => {
},
]),
);
vi.mocked(api.getQueryStringManager().generateQueryString).mockReturnValue('');
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
const manager = new MediaPlayerManager(api);
@@ -28,7 +28,7 @@ describe('QueryStringManager', () => {
manager.executeAll();
expect(manager.hasViewRelatedActions()).toBeFalsy();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
@@ -76,7 +76,7 @@ describe('QueryStringManager', () => {
manager.executeAll();
expect(manager.hasViewRelatedActions()).toBeFalsy();
expect(api.getActionsManager().executeAction).toBeCalledWith({
expect(api.getActionsManager().executeFrigateAction).toBeCalledWith({
action: 'fire-dom-event',
card_id: 'id',
frigate_card_action: action,
@@ -97,7 +97,7 @@ describe('QueryStringManager', () => {
expect(api.getViewManager().setViewDefault).toBeCalled();
expect(manager.hasViewRelatedActions()).toBeTruthy();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
@@ -114,7 +114,7 @@ describe('QueryStringManager', () => {
});
expect(manager.hasViewRelatedActions()).toBeTruthy();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
});
@@ -131,7 +131,7 @@ describe('QueryStringManager', () => {
});
expect(manager.hasViewRelatedActions()).toBeTruthy();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
});
@@ -147,7 +147,7 @@ describe('QueryStringManager', () => {
manager.executeAll();
expect(manager.hasViewRelatedActions()).toBeFalsy();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
},
@@ -165,7 +165,7 @@ describe('QueryStringManager', () => {
manager.executeAll();
expect(manager.hasViewRelatedActions()).toBeFalsy();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(consoleSpy).toBeCalled();
@@ -212,7 +212,7 @@ describe('QueryStringManager', () => {
manager.executeAll();
expect(api.getActionsManager().executeAction).not.toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
@@ -296,77 +296,7 @@ describe('QueryStringManager', () => {
manager.executeViewRelated();
expect(api.getActionsManager().executeAction).not.toBeCalled();
});
});
describe('should generate query string', () => {
describe('that require no arguments', () => {
it.each([
['camera_ui' as const],
['clip' as const],
['clips' as const],
['default' as const],
['diagnostics' as const],
['download' as const],
['expand' as const],
['image' as const],
['live' as const],
['menu_toggle' as const],
['recording' as const],
['recordings' as const],
['snapshot' as const],
['snapshots' as const],
['timeline' as const],
])('%s', (actionName: FrigateCardGeneralAction | FrigateCardUserSpecifiedView) => {
const manager = new QueryStringManager(createCardAPI());
expect(
manager.generateQueryString({
action: 'fire-dom-event',
frigate_card_action: actionName,
}),
).toBe(`frigate-card-action.${actionName}=`);
});
});
describe('that require camera argument', () => {
it.each([['camera_select' as const], ['live_substream_select' as const]])(
'%s',
(actionName: 'camera_select' | 'live_substream_select') => {
const manager = new QueryStringManager(createCardAPI());
expect(
manager.generateQueryString({
action: 'fire-dom-event',
frigate_card_action: actionName,
camera: 'camera',
}),
).toBe(`frigate-card-action.${actionName}=camera`);
},
);
});
it('that include a card_id', () => {
const manager = new QueryStringManager(createCardAPI());
expect(
manager.generateQueryString({
action: 'fire-dom-event',
frigate_card_action: 'clips',
card_id: 'card-id',
}),
).toBe(`frigate-card-action.card-id.clips=`);
});
it('that include an unsupported action', () => {
const spy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
const manager = new QueryStringManager(createCardAPI());
expect(
manager.generateQueryString({
action: 'fire-dom-event',
frigate_card_action: 'microphone_unmute',
}),
).toBeNull();
expect(spy).toBeCalled();
expect(api.getActionsManager().executeFrigateAction).not.toBeCalled();
});
});
});
+238 -56
View File
@@ -15,6 +15,28 @@ import {
createView,
} from '../test-utils';
const baseScanConfig: Partial<ScanOptions> = {
enabled: true,
untrigger_seconds: 10,
interaction_mode: 'inactive' as const,
trigger_filter_camera: 'all' as const,
trigger_action: [
{
action: 'fire-dom-event' as const,
frigate_card_action: 'camera_select' as const,
triggered: true,
},
{
action: 'fire-dom-event' as const,
frigate_card_action: 'live' as const,
},
],
untrigger_action: {
action: 'fire-dom-event' as const,
frigate_card_action: 'default' as const,
},
};
// Creating and mocking a trigger API is a lot of boilerplate, this convenience
// function reduces it.
const createTriggerAPI = (options?: {
@@ -26,11 +48,7 @@ const createTriggerAPI = (options?: {
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
scan: options?.config ?? {
enabled: true,
untrigger_reset: true,
untrigger_seconds: 10,
},
scan: options?.config ?? baseScanConfig,
},
}),
);
@@ -83,11 +101,28 @@ describe('TriggersManager', () => {
});
const manager = new TriggersManager(api);
manager.updateTriggeredCameras(null);
manager.updateTriggerHAState(null);
expect(manager.isTriggered()).toBeFalsy();
});
it('should not trigger without a scan config', () => {
const api = createTriggerAPI({
hassStates: hassActiveState,
});
vi.mocked(api.getViewManager().getView).mockReturnValue(
createView({
camera: 'camera_1' as const,
}),
);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
const manager = new TriggersManager(api);
manager.updateView(null);
expect(manager.isTriggered()).toBeFalsy();
});
it('should trigger and untrigger based on entity state', () => {
const start = new Date('2023-10-01T17:14');
vi.setSystemTime(start);
@@ -96,22 +131,29 @@ describe('TriggersManager', () => {
});
const manager = new TriggersManager(api);
manager.updateTriggeredCameras(createHASS(hassInactiveState));
manager.updateTriggerHAState(createHASS(hassInactiveState));
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
viewName: 'live',
cameraID: 'camera_1',
});
expect(api.getActionsManager().executeActions).toBeCalledWith([
{
action: 'fire-dom-event' as const,
frigate_card_action: 'camera_select' as const,
triggered: true,
},
{
action: 'fire-dom-event' as const,
frigate_card_action: 'live' as const,
},
]);
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS(hassInactiveState),
);
manager.updateTriggeredCameras(createHASS(hassActiveState));
manager.updateTriggerHAState(createHASS(hassActiveState));
// Intentional state update with no change.
manager.updateTriggeredCameras(createHASS(hassActiveState));
manager.updateTriggerHAState(createHASS(hassActiveState));
// Will still be triggered, but untrigger timer will be running.
expect(manager.isTriggered()).toBeTruthy();
@@ -120,26 +162,10 @@ describe('TriggersManager', () => {
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefault).toBeCalled();
});
it('should trigger and set view if current view is wrong', () => {
const api = createTriggerAPI({
hassStates: hassActiveState,
});
vi.mocked(api.getViewManager().getView).mockReturnValue(
createView({
// Correct camera, but wrong view.
view: 'clips',
camera: 'camera_1',
}),
);
const manager = new TriggersManager(api);
manager.updateTriggeredCameras(null);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
viewName: 'live',
cameraID: 'camera_1',
expect(api.getActionsManager().executeActions).toBeCalledWith({
action: 'fire-dom-event' as const,
frigate_card_action: 'default' as const,
});
});
@@ -150,40 +176,196 @@ describe('TriggersManager', () => {
const manager = new TriggersManager(api);
expect(manager.isTriggered()).toBeFalsy();
manager.updateTriggeredCameras(null);
manager.updateTriggerHAState(null);
expect(manager.isTriggered()).toBeTruthy();
});
it('should untrigger manually', () => {
const api = createTriggerAPI({
hassStates: hassActiveState,
});
const manager = new TriggersManager(api);
// Untriggering when not triggered.
manager.untrigger();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
manager.updateTriggeredCameras(null);
expect(manager.isTriggered()).toBeTruthy();
manager.untrigger();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefault).toBeCalled();
});
it('should take no actions when automated actions are not allowed', () => {
it('should take no actions with human interactions', () => {
const start = new Date('2023-10-01T17:14');
const api = createTriggerAPI({
hassStates: hassActiveState,
// Interaction present.
interaction: true,
});
const manager = new TriggersManager(api);
manager.updateTriggeredCameras(null);
manager.updateTriggerHAState(createHASS(hassInactiveState));
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
manager.untrigger();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toBeCalled();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS(hassInactiveState),
);
manager.updateTriggerHAState(createHASS(hassActiveState));
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getActionsManager().executeActions).not.toBeCalled();
});
it('should take actions with human interactions when interaction mode is active', () => {
const start = new Date('2023-10-01T17:14');
const api = createTriggerAPI({
hassStates: hassActiveState,
// Interaction present.
interaction: true,
config: {
...baseScanConfig,
interaction_mode: 'active',
},
});
const manager = new TriggersManager(api);
manager.updateTriggerHAState(createHASS(hassInactiveState));
expect(manager.isTriggered()).toBeTruthy();
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS(hassInactiveState),
);
manager.updateTriggerHAState(createHASS(hassActiveState));
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
});
it('should report multiple triggered cameras', () => {
const start = new Date('2023-10-01T17:14');
const bothOnState = {
'binary_sensor.one': createStateEntity({ state: 'on' }),
'binary_sensor.two': createStateEntity({ state: 'on' }),
};
const api = createTriggerAPI({
hassStates: bothOnState,
});
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
createStore([
{
cameraID: 'camera_1',
config: createCameraConfig({
triggers: {
entities: ['binary_sensor.one'],
},
}),
},
{
cameraID: 'camera_2',
config: createCameraConfig({
triggers: {
entities: ['binary_sensor.two'],
},
}),
},
]),
);
const manager = new TriggersManager(api);
expect(manager.isTriggered()).toBeFalsy();
expect(manager.getMostRecentlyTriggeredCameraID()).toBeNull();
expect(manager.getTriggeredCameraIDs()).toEqual(new Set());
manager.updateTriggerHAState(null);
expect(manager.isTriggered()).toBeTruthy();
expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_1', 'camera_2']));
// Either is the most recently triggered.
expect(['camera_1', 'camera_2']).toContain(
manager.getMostRecentlyTriggeredCameraID(),
);
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
createHASS({
'binary_sensor.one': createStateEntity({ state: 'off' }),
'binary_sensor.two': createStateEntity({ state: 'on' }),
}),
);
manager.updateTriggerHAState(createHASS(bothOnState));
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_2']));
expect(manager.getMostRecentlyTriggeredCameraID()).toBe('camera_2');
});
it('should filter triggers by camera', () => {
const start = new Date('2023-10-01T17:14');
const api = createTriggerAPI({
config: {
...baseScanConfig,
// Filter triggers to selected camera only.
trigger_filter_camera: 'selected' as const,
},
hassStates: hassActiveState,
});
const manager = new TriggersManager(api);
manager.updateTriggerHAState(createHASS(hassInactiveState));
expect(manager.isTriggered()).toBeFalsy();
const otherCameraSelected = createView({
camera: 'camera_SOME_OTHER_CAMERA' as const,
});
vi.mocked(api.getViewManager().getView).mockReturnValue(otherCameraSelected);
manager.updateView(null);
expect(manager.isTriggered()).toBeFalsy();
const thisCameraSelected = createView({
camera: 'camera_1' as const,
});
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
manager.updateView(otherCameraSelected);
expect(manager.isTriggered()).toBeTruthy();
// Ensure a view change to the same camera stays triggered.
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
manager.updateView(thisCameraSelected);
expect(manager.isTriggered()).toBeTruthy();
});
it('should not untrigger triggers by camera', () => {
const start = new Date('2023-10-01T17:14');
const api = createTriggerAPI({
config: {
...baseScanConfig,
// Filter triggers to selected camera only.
trigger_filter_camera: 'selected' as const,
},
hassStates: hassActiveState,
});
const manager = new TriggersManager(api);
manager.updateTriggerHAState(createHASS(hassInactiveState));
expect(manager.isTriggered()).toBeFalsy();
const otherCameraSelected = createView({
camera: 'camera_SOME_OTHER_CAMERA' as const,
});
vi.mocked(api.getViewManager().getView).mockReturnValue(otherCameraSelected);
manager.updateView(null);
expect(manager.isTriggered()).toBeFalsy();
const thisCameraSelected = createView({
camera: 'camera_1' as const,
});
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
manager.updateView(otherCameraSelected);
expect(manager.isTriggered()).toBeTruthy();
// Ensure a view change to the same camera stays triggered.
vi.mocked(api.getViewManager().getView).mockReturnValue(thisCameraSelected);
manager.updateView(thisCameraSelected);
expect(manager.isTriggered()).toBeTruthy();
});
});