Add update trigger action.

This commit is contained in:
Dermot Duffy
2024-06-11 19:48:39 -07:00
parent 4c73c8faa0
commit f193f9925f
9 changed files with 266 additions and 137 deletions
+234 -122
View File
@@ -2,7 +2,11 @@ import { add } from 'date-fns';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { CardController } from '../../src/card-controller/controller';
import { TriggersManager } from '../../src/card-controller/triggers-manager';
import { TriggersOptions, triggersSchema } from '../../src/config/types';
import {
FrigateCardView,
TriggersOptions,
triggersSchema,
} from '../../src/config/types';
import {
createCameraConfig,
createCameraManager,
@@ -16,11 +20,12 @@ vi.mock('lodash-es/throttle', () => ({
default: vi.fn((fn) => fn),
}));
const baseTriggersConfig: Partial<TriggersOptions> = {
const baseTriggersConfig: TriggersOptions = {
untrigger_seconds: 10,
filter_selected_camera: false,
show_trigger_status: false,
actions: {
trigger: 'live' as const,
trigger: 'update' as const,
untrigger: 'default' as const,
interaction_mode: 'inactive' as const,
},
@@ -30,6 +35,7 @@ const baseTriggersConfig: Partial<TriggersOptions> = {
// function reduces it.
const createTriggerAPI = (options?: {
config?: Partial<TriggersOptions>;
default?: FrigateCardView;
interaction?: boolean;
}): CardController => {
const api = createCardAPI();
@@ -39,6 +45,7 @@ const createTriggerAPI = (options?: {
triggers: options?.config
? triggersSchema.parse(options.config)
: baseTriggersConfig,
...(options?.default && { default: options.default }),
},
}),
);
@@ -101,161 +108,266 @@ describe('TriggersManager', () => {
expect(manager.isTriggered()).toBeFalsy();
});
it('should trigger and untrigger based on low fidelity event', () => {
const api = createTriggerAPI();
const manager = new TriggersManager(api);
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'new',
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
triggered: new Set(['camera_1']),
});
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
viewName: 'live' as const,
cameraID: 'camera_1' as const,
});
vi.mocked(api.getConditionsManager().getState).mockReturnValue({
triggered: new Set(['camera_1']),
});
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'end',
});
// Will still be triggered, but untrigger timer will be running.
expect(manager.isTriggered()).toBeTruthy();
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
triggered: undefined,
});
expect(api.getViewManager().setViewDefault).toBeCalled();
});
describe('should treat high fidelity events appropriately', () => {
it('with no media', () => {
const api = createTriggerAPI();
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
default: 'clips',
describe('trigger actions', () => {
it('update', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'update',
},
}),
);
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'new',
fidelity: 'high',
// Intentionally blank.
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setView).toBeCalled();
});
it('default', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'default',
},
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefault).toBeCalledWith({
cameraID: 'camera_1',
});
});
it('live', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'live',
},
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
viewName: 'live',
cameraID: 'camera_1',
});
});
describe('media', () => {
it.each([
[false, false, null],
[false, true, 'clip' as const],
[true, false, 'snapshot' as const],
[true, true, 'clip' as const],
])(
'with snapshot %s and clip %s',
async (
hasSnapshot: boolean,
hasClip: boolean,
viewName: 'clip' | 'snapshot' | null,
) => {
const api = createTriggerAPI({
config: {
actions: {
interaction_mode: 'all',
trigger: 'media',
untrigger: 'none',
},
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'new',
fidelity: 'high',
snapshot: hasSnapshot,
clip: hasClip,
});
if (!viewName) {
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
} else {
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
cameraID: 'camera_1',
viewName: viewName,
});
}
},
);
});
it('none', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'none',
},
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setView).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
});
describe('untrigger actions', () => {
it('none', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'none',
untrigger: 'none',
},
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'end' });
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setView).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
it('with media', () => {
const api = createTriggerAPI();
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
view: {
default: 'clips',
it('default', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'none',
untrigger: 'default',
},
}),
);
const manager = new TriggersManager(api);
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'new',
fidelity: 'high',
clip: true,
},
});
expect(manager.isTriggered()).toBeTruthy();
const manager = new TriggersManager(api);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'end' });
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefault).toBeCalled();
});
});
it('should change to default view when suitably configured', () => {
it('should manage condition state', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
interaction_mode: 'all',
trigger: 'default',
...baseTriggersConfig.actions,
trigger: 'none',
untrigger: 'none',
},
},
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'new',
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new' });
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
triggered: new Set(['camera_1']),
});
vi.mocked(api.getConditionsManager().getState).mockReturnValue({
triggered: new Set(['camera_1']),
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefault).toBeCalledWith({
cameraID: 'camera_1',
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'end' });
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
expect(api.getConditionsManager().setState).toHaveBeenLastCalledWith({
triggered: undefined,
});
});
describe('should change to media view', () => {
it.each([
[false, false, null],
[false, true, 'clip' as const],
[true, false, 'snapshot' as const],
[true, true, 'clip' as const],
])(
'with snapshot %s and clip %s',
async (
hasSnapshot: boolean,
hasClip: boolean,
viewName: 'clip' | 'snapshot' | null,
) => {
const api = createTriggerAPI({
config: {
actions: {
interaction_mode: 'all',
trigger: 'media',
untrigger: 'none',
},
describe('should take no actions with high-fidelity event', () => {
it('with non-live action', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'media',
},
});
const manager = new TriggersManager(api);
},
default: 'live',
});
manager.handleCameraEvent({
cameraID: 'camera_1',
type: 'new',
fidelity: 'high',
snapshot: hasSnapshot,
clip: hasClip,
});
const manager = new TriggersManager(api);
if (!viewName) {
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
} else {
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
cameraID: 'camera_1',
viewName: viewName,
});
}
},
);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new', fidelity: 'high' });
expect(api.getViewManager().setView).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
it('with non-live default', () => {
const api = createTriggerAPI({
config: {
...baseTriggersConfig,
actions: {
...baseTriggersConfig.actions,
trigger: 'default',
},
},
default: 'clips',
});
const manager = new TriggersManager(api);
manager.handleCameraEvent({ cameraID: 'camera_1', type: 'new', fidelity: 'high' });
expect(api.getViewManager().setView).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
});
});
it('should take no actions with human interactions', () => {