feat: Trigger cameras on HA bus events (#2512)
This commit is contained in:
committed by
dermotduffy
parent
406176eebc
commit
0a36358394
@@ -1,25 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { BrowseMediaCameraManagerEngine } from '../../../src/camera-manager/browse-media/engine-browse-media';
|
||||
import { CameraManagerReadOnlyConfigStore } from '../../../src/camera-manager/store';
|
||||
import {
|
||||
CameraManagerRequestCache,
|
||||
CameraQuery,
|
||||
QueryType,
|
||||
} from '../../../src/camera-manager/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { BROWSE_MEDIA_CACHE_SECONDS } from '../../../src/ha/browse-media/types';
|
||||
import { BrowseMediaWalker } from '../../../src/ha/browse-media/walker';
|
||||
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
|
||||
import { QuerySource } from '../../../src/query-source';
|
||||
import { ViewMedia } from '../../../src/view/item';
|
||||
import { CameraManagerReadOnlyConfigStore } from '../../../src/camera-manager/store';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import { createCameraConfig, createHASS } from '../../test-utils';
|
||||
|
||||
const createEngine = (): BrowseMediaCameraManagerEngine => {
|
||||
return new BrowseMediaCameraManagerEngine(
|
||||
new EntityRegistryManagerMock(),
|
||||
mock<StateWatcher>(),
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
new BrowseMediaWalker(),
|
||||
new ResolvedMediaCache(),
|
||||
new CameraManagerRequestCache(),
|
||||
|
||||
@@ -3,10 +3,12 @@ import { mock } from 'vitest-mock-extended';
|
||||
import { Camera } from '../../src/camera-manager/camera.js';
|
||||
import { GenericCameraManagerEngine } from '../../src/camera-manager/generic/engine-generic.js';
|
||||
import { CameraProxyConfig } from '../../src/camera-manager/types.js';
|
||||
import { EventWatcherSubscriptionInterface } from '../../src/card-controller/hass/event-watcher.js';
|
||||
import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
|
||||
import { liveProviderSupports2WayAudio } from '../../src/utils/live-provider.js';
|
||||
import { EntityRegistryManagerMock } from '../ha/registry/entity/mock.js';
|
||||
import {
|
||||
callEventWatcherCallback,
|
||||
callStateWatcherCallback,
|
||||
createCameraConfig,
|
||||
createCapabilities,
|
||||
@@ -23,7 +25,10 @@ describe('Camera', () => {
|
||||
const config = createCameraConfig();
|
||||
const camera = new Camera(
|
||||
config,
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getConfig()).toBe(config);
|
||||
});
|
||||
@@ -33,7 +38,10 @@ describe('Camera', () => {
|
||||
const capabilities = createCapabilities();
|
||||
const camera = await createInitializedCamera(
|
||||
createCameraConfig(),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
capabilities,
|
||||
);
|
||||
expect(camera.getCapabilities()).toBe(capabilities);
|
||||
@@ -42,7 +50,10 @@ describe('Camera', () => {
|
||||
it('when unpopulated', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig(),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getCapabilities()).toBeNull();
|
||||
});
|
||||
@@ -51,6 +62,7 @@ describe('Camera', () => {
|
||||
it('should get engine', async () => {
|
||||
const engine = new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
);
|
||||
const camera = new Camera(createCameraConfig(), engine);
|
||||
expect(camera.getEngine()).toBe(engine);
|
||||
@@ -59,7 +71,10 @@ describe('Camera', () => {
|
||||
it('should set and get id', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig(),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
camera.setID('foo');
|
||||
expect(camera.getID()).toBe('foo');
|
||||
@@ -69,7 +84,10 @@ describe('Camera', () => {
|
||||
it('should throw without id', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig(),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(() => camera.getID()).toThrowError(
|
||||
'Could not determine camera id for the following ' +
|
||||
@@ -89,13 +107,17 @@ describe('Camera', () => {
|
||||
entities: ['camera.foo'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
const stateWatcher = mock<StateWatcherSubscriptionInterface>();
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: stateWatcher,
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
|
||||
});
|
||||
|
||||
@@ -116,7 +138,10 @@ describe('Camera', () => {
|
||||
stream: 'stream',
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
vi.mocked(liveProviderSupports2WayAudio).mockResolvedValue(true);
|
||||
@@ -124,6 +149,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).toHaveBeenCalledWith(
|
||||
@@ -154,7 +180,10 @@ describe('Camera', () => {
|
||||
stream: 'stream',
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
vi.mocked(liveProviderSupports2WayAudio).mockResolvedValue(false);
|
||||
@@ -162,6 +191,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.has('2-way-audio')).toBe(false);
|
||||
@@ -176,7 +206,10 @@ describe('Camera', () => {
|
||||
metadata_fetch_timeout_seconds: 20,
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
vi.mocked(liveProviderSupports2WayAudio).mockResolvedValue(true);
|
||||
@@ -184,6 +217,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).toHaveBeenCalledWith(
|
||||
@@ -206,7 +240,10 @@ describe('Camera', () => {
|
||||
live: true,
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
vi.mocked(liveProviderSupports2WayAudio).mockResolvedValue(true);
|
||||
@@ -217,6 +254,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass,
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).toHaveBeenCalledWith(
|
||||
@@ -243,7 +281,10 @@ describe('Camera', () => {
|
||||
createCameraConfig({
|
||||
proxy: { live: true },
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getLiveProxyConfig()).toEqual(
|
||||
expect.objectContaining({ enabled: true, enforce: true }),
|
||||
@@ -255,7 +296,10 @@ describe('Camera', () => {
|
||||
createCameraConfig({
|
||||
proxy: { media: true },
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getMediaProxyConfig()).toEqual(
|
||||
expect.objectContaining({ enabled: true, enforce: true }),
|
||||
@@ -268,7 +312,10 @@ describe('Camera', () => {
|
||||
live_provider: 'go2rtc',
|
||||
go2rtc: { url: 'http://go2rtc' },
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getLiveProxyConfig()).toEqual(
|
||||
expect.objectContaining({ enabled: true, enforce: false }),
|
||||
@@ -280,7 +327,10 @@ describe('Camera', () => {
|
||||
createCameraConfig({
|
||||
proxy: { media: 'auto' },
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getMediaProxyConfig()).toEqual(
|
||||
expect.objectContaining({ enabled: false, enforce: false }),
|
||||
@@ -294,12 +344,16 @@ describe('Camera', () => {
|
||||
force: ['2-way-audio'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).not.toHaveBeenCalled();
|
||||
@@ -314,12 +368,16 @@ describe('Camera', () => {
|
||||
force: ['2-way-audio'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).not.toHaveBeenCalled();
|
||||
@@ -334,12 +392,16 @@ describe('Camera', () => {
|
||||
force: ['2-way-audio'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).not.toHaveBeenCalled();
|
||||
@@ -353,12 +415,16 @@ describe('Camera', () => {
|
||||
disable: ['2-way-audio'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).not.toHaveBeenCalled();
|
||||
@@ -372,12 +438,16 @@ describe('Camera', () => {
|
||||
disable_except: ['substream'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).not.toHaveBeenCalled();
|
||||
@@ -391,13 +461,17 @@ describe('Camera', () => {
|
||||
disable_except: ['substream', '2-way-audio'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
vi.mocked(liveProviderSupports2WayAudio).mockResolvedValue(true);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).toHaveBeenCalled();
|
||||
@@ -411,13 +485,17 @@ describe('Camera', () => {
|
||||
disable_except: [],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
vi.mocked(liveProviderSupports2WayAudio).mockResolvedValue(true);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(liveProviderSupports2WayAudio).toHaveBeenCalled();
|
||||
@@ -432,12 +510,16 @@ describe('Camera', () => {
|
||||
});
|
||||
const camera = new Camera(
|
||||
createCameraConfig({ camera_entity: 'camera.front_door' }),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
});
|
||||
|
||||
@@ -447,12 +529,16 @@ describe('Camera', () => {
|
||||
it('should leave entity null when camera_entity is unset', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig(),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock(),
|
||||
});
|
||||
|
||||
@@ -462,12 +548,16 @@ describe('Camera', () => {
|
||||
it('should leave entity null when entityRegistryManager is not provided', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig({ camera_entity: 'camera.front_door' }),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getEntity()).toBeNull();
|
||||
@@ -504,7 +594,10 @@ describe('Camera', () => {
|
||||
...(options?.userEntities && { entities: options.userEntities }),
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
const stateWatcher = mock<StateWatcherSubscriptionInterface>();
|
||||
const hass = createHASS(
|
||||
@@ -518,6 +611,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass,
|
||||
stateWatcher,
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
...(!options?.omitRegistryManager && {
|
||||
entityRegistryManager: new EntityRegistryManagerMock(
|
||||
options?.registryEntities ?? [cameraEntity, doorbellEntity],
|
||||
@@ -643,7 +737,10 @@ describe('Camera', () => {
|
||||
entities: ['binary_sensor.foo'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
{
|
||||
eventCallback: eventCallback,
|
||||
},
|
||||
@@ -653,6 +750,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: stateWatcher,
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
|
||||
});
|
||||
|
||||
@@ -673,6 +771,100 @@ describe('Camera', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('should subscribe to configured triggers.events and dispatch momentary events', async () => {
|
||||
const eventCallback = vi.fn();
|
||||
const camera = new Camera(
|
||||
createCameraConfig({
|
||||
id: 'camera_1',
|
||||
triggers: {
|
||||
events: [{ event_type: 'zha_event' }],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
{ eventCallback },
|
||||
);
|
||||
|
||||
const eventWatcher = mock<EventWatcherSubscriptionInterface>();
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher,
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
|
||||
});
|
||||
|
||||
expect(eventWatcher.subscribe).toBeCalledTimes(1);
|
||||
const request = vi.mocked(eventWatcher.subscribe).mock.calls[0][1];
|
||||
expect(request.event_type).toBe('zha_event');
|
||||
expect(request.matcher).toBeUndefined();
|
||||
|
||||
callEventWatcherCallback(eventWatcher, { command: 'press' });
|
||||
|
||||
expect(eventCallback).toBeCalledWith({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event:zha_event',
|
||||
type: 'momentary',
|
||||
});
|
||||
|
||||
await camera.destroy();
|
||||
expect(eventWatcher.unsubscribe).toBeCalled();
|
||||
});
|
||||
|
||||
it('should attach a matcher when triggers.events entry has data filter', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig({
|
||||
id: 'camera_1',
|
||||
triggers: {
|
||||
events: [{ event_type: 'zha_event', event_data: { command: 'press' } }],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
const eventWatcher = mock<EventWatcherSubscriptionInterface>();
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher,
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
|
||||
});
|
||||
|
||||
const matcher = vi.mocked(eventWatcher.subscribe).mock.calls[0][1].matcher;
|
||||
expect(matcher).toBeDefined();
|
||||
expect(matcher?.({ command: 'press', extra: 1 })).toBe(true);
|
||||
expect(matcher?.({ command: 'release' })).toBe(false);
|
||||
});
|
||||
|
||||
it('should not subscribe to events when trigger capability is disabled', async () => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig({
|
||||
id: 'camera_1',
|
||||
triggers: {
|
||||
events: [{ event_type: 'zha_event' }],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
const eventWatcher = mock<EventWatcherSubscriptionInterface>();
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher,
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: false }) },
|
||||
});
|
||||
|
||||
expect(eventWatcher.subscribe).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not dispatch when the helper returns null', async () => {
|
||||
vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
@@ -684,7 +876,10 @@ describe('Camera', () => {
|
||||
entities: ['event.front_door_doorbell'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
{
|
||||
eventCallback: eventCallback,
|
||||
},
|
||||
@@ -694,6 +889,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: stateWatcher,
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
|
||||
});
|
||||
|
||||
@@ -706,7 +902,7 @@ describe('Camera', () => {
|
||||
expect(eventCallback).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should dispatch a signal for an event entity fire', async () => {
|
||||
it('should dispatch a momentary event for an event entity fire', async () => {
|
||||
vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
const eventCallback = vi.fn();
|
||||
@@ -717,7 +913,10 @@ describe('Camera', () => {
|
||||
entities: ['event.front_door_doorbell'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
{
|
||||
eventCallback: eventCallback,
|
||||
},
|
||||
@@ -727,6 +926,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: stateWatcher,
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
|
||||
});
|
||||
|
||||
@@ -740,7 +940,7 @@ describe('Camera', () => {
|
||||
expect(eventCallback).toBeCalledWith({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event.front_door_doorbell',
|
||||
type: 'signal',
|
||||
type: 'momentary',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -753,7 +953,10 @@ describe('Camera', () => {
|
||||
entities: ['binary_sensor.foo'],
|
||||
},
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
{
|
||||
eventCallback: eventCallback,
|
||||
},
|
||||
@@ -763,6 +966,7 @@ describe('Camera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: stateWatcher,
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
capabilityOptions: { capabilities: createCapabilities({ trigger: false }) },
|
||||
});
|
||||
|
||||
@@ -924,7 +1128,10 @@ describe('Camera', () => {
|
||||
(_name: string, cameraConfig: unknown, expectedResult: CameraProxyConfig) => {
|
||||
const camera = new Camera(
|
||||
createCameraConfig(cameraConfig),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getProxyConfig()).toEqual(expectedResult);
|
||||
},
|
||||
@@ -938,7 +1145,10 @@ describe('Camera', () => {
|
||||
go2rtc: { stream: '' },
|
||||
camera_entity: '',
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
expect(camera.getEndpoints()).toBeNull();
|
||||
});
|
||||
@@ -952,7 +1162,10 @@ describe('Camera', () => {
|
||||
},
|
||||
camera_entity: 'camera.foo',
|
||||
}),
|
||||
new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>()),
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(camera.getEndpoints()).toEqual({
|
||||
|
||||
@@ -7,10 +7,11 @@ import { MotionEyeCameraManagerEngine } from '../../src/camera-manager/motioneye
|
||||
import { ReolinkCameraManagerEngine } from '../../src/camera-manager/reolink/engine-reolink.js';
|
||||
import { TPLinkCameraManagerEngine } from '../../src/camera-manager/tplink/engine-tplink.js';
|
||||
import { Engine } from '../../src/camera-manager/types.js';
|
||||
import { EventWatcherSubscriptionInterface } from '../../src/card-controller/hass/event-watcher.js';
|
||||
import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
|
||||
import { CardWideConfig } from '../../src/config/schema/types.js';
|
||||
import { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
|
||||
import { DeviceRegistryManager } from '../../src/ha/registry/device';
|
||||
import { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
|
||||
import { ResolvedMediaCache } from '../../src/ha/resolved-media.js';
|
||||
import { EntityRegistryManagerMock } from '../ha/registry/entity/mock.js';
|
||||
import {
|
||||
@@ -232,6 +233,7 @@ describe('createEngine()', () => {
|
||||
expect(
|
||||
await createFactory().createEngine(Engine.Generic, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
}),
|
||||
).toBeInstanceOf(GenericCameraManagerEngine);
|
||||
@@ -240,6 +242,7 @@ describe('createEngine()', () => {
|
||||
expect(
|
||||
await createFactory().createEngine(Engine.Frigate, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
}),
|
||||
).toBeInstanceOf(FrigateCameraManagerEngine);
|
||||
@@ -248,6 +251,7 @@ describe('createEngine()', () => {
|
||||
expect(
|
||||
await createFactory().createEngine(Engine.MotionEye, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
}),
|
||||
).toBeInstanceOf(MotionEyeCameraManagerEngine);
|
||||
@@ -256,6 +260,7 @@ describe('createEngine()', () => {
|
||||
expect(
|
||||
await createFactory().createEngine(Engine.Reolink, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
}),
|
||||
).toBeInstanceOf(ReolinkCameraManagerEngine);
|
||||
@@ -264,6 +269,7 @@ describe('createEngine()', () => {
|
||||
expect(
|
||||
await createFactory().createEngine(Engine.TPLink, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
}),
|
||||
).toBeInstanceOf(TPLinkCameraManagerEngine);
|
||||
|
||||
@@ -18,9 +18,10 @@ import {
|
||||
FrigateReviewWatcher,
|
||||
} from '../../../src/camera-manager/frigate/watcher';
|
||||
import { ActionsExecutor } from '../../../src/card-controller/actions/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { PTZAction } from '../../../src/config/schema/actions/custom/ptz';
|
||||
import { CameraTriggerEventType } from '../../../src/config/schema/cameras';
|
||||
import { CameraTriggerMediaEventType } from '../../../src/config/schema/cameras';
|
||||
import { Entity, EntityRegistryManager } from '../../../src/ha/registry/entity/types';
|
||||
import { ViewMediaType } from '../../../src/view/item';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
@@ -69,7 +70,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -91,7 +93,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
}),
|
||||
@@ -116,7 +119,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -141,7 +145,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -166,7 +171,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -191,7 +197,8 @@ describe('FrigateCamera', () => {
|
||||
}),
|
||||
}),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -214,7 +221,8 @@ describe('FrigateCamera', () => {
|
||||
}),
|
||||
}),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -231,7 +239,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -254,7 +263,8 @@ describe('FrigateCamera', () => {
|
||||
}),
|
||||
}),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -278,7 +288,8 @@ describe('FrigateCamera', () => {
|
||||
}),
|
||||
}),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -301,7 +312,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -329,7 +341,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -361,7 +374,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -391,7 +405,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -428,7 +443,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -460,7 +476,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -496,7 +513,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -814,7 +832,7 @@ describe('FrigateCamera', () => {
|
||||
camera_name: 'CAMERA',
|
||||
},
|
||||
triggers: {
|
||||
events: ['events'],
|
||||
media_events: ['events'],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -825,7 +843,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -845,7 +864,7 @@ describe('FrigateCamera', () => {
|
||||
camera_name: 'CAMERA',
|
||||
},
|
||||
triggers: {
|
||||
events: [],
|
||||
media_events: [],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -856,7 +875,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -882,7 +902,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -896,7 +917,7 @@ describe('FrigateCamera', () => {
|
||||
client_id: 'CLIENT_ID',
|
||||
},
|
||||
triggers: {
|
||||
events: ['events'],
|
||||
media_events: ['events'],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -907,7 +928,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -919,7 +941,7 @@ describe('FrigateCamera', () => {
|
||||
createCameraConfig({
|
||||
frigate: { camera_name: 'front_door' },
|
||||
triggers: {
|
||||
events: ['events'],
|
||||
media_events: ['events'],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -932,7 +954,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -1002,7 +1025,7 @@ describe('FrigateCamera', () => {
|
||||
])(
|
||||
'with events %s when snapshot %s and clip %s',
|
||||
async (
|
||||
events: CameraTriggerEventType[],
|
||||
events: CameraTriggerMediaEventType[],
|
||||
hasSnapshot: boolean,
|
||||
hasClip: boolean,
|
||||
call: boolean,
|
||||
@@ -1015,7 +1038,7 @@ describe('FrigateCamera', () => {
|
||||
camera_name: 'camera.front_door',
|
||||
},
|
||||
triggers: {
|
||||
events: events,
|
||||
media_events: events,
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -1029,7 +1052,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -1087,7 +1111,7 @@ describe('FrigateCamera', () => {
|
||||
zones: ['front_steps'],
|
||||
},
|
||||
triggers: {
|
||||
events: ['events'],
|
||||
media_events: ['events'],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -1101,7 +1125,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -1146,7 +1171,7 @@ describe('FrigateCamera', () => {
|
||||
labels: ['person'],
|
||||
},
|
||||
triggers: {
|
||||
events: ['events'],
|
||||
media_events: ['events'],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -1160,7 +1185,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -1202,7 +1228,7 @@ describe('FrigateCamera', () => {
|
||||
camera_name: 'camera.front_door',
|
||||
},
|
||||
triggers: {
|
||||
events: ['events'],
|
||||
media_events: ['events'],
|
||||
},
|
||||
}),
|
||||
mock<CameraManagerEngine>(),
|
||||
@@ -1216,7 +1242,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -1269,7 +1296,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1302,7 +1330,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1331,7 +1360,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1365,7 +1395,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1429,7 +1460,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1501,7 +1533,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1573,7 +1606,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1631,7 +1665,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1688,7 +1723,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1749,7 +1785,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1807,7 +1844,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1873,7 +1911,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: reviewWatcher,
|
||||
});
|
||||
@@ -1951,7 +1990,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -1981,7 +2021,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2005,7 +2046,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: new EntityRegistryManagerMock(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2030,7 +2072,8 @@ describe('FrigateCamera', () => {
|
||||
camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
}),
|
||||
@@ -2056,7 +2099,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2081,7 +2125,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2105,7 +2150,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: new EntityRegistryManagerMock(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2138,7 +2184,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2173,7 +2220,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2193,7 +2241,8 @@ describe('FrigateCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: hass,
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2226,7 +2275,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: new EntityRegistryManagerMock([
|
||||
createRegistryEntity({ entity_id: 'camera.office_frigate' }),
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2264,7 +2314,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: new EntityRegistryManagerMock([
|
||||
createRegistryEntity({ entity_id: 'camera.office_frigate' }),
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2317,7 +2368,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: new EntityRegistryManagerMock([
|
||||
createRegistryEntity({ entity_id: 'camera.office_frigate' }),
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
@@ -2353,7 +2405,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: new EntityRegistryManagerMock([
|
||||
createRegistryEntity({ entity_id: 'camera.office_frigate' }),
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, assert, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { RecordingSegmentsCache } from '../../../src/camera-manager/cache';
|
||||
import { Camera } from '../../../src/camera-manager/camera';
|
||||
import {
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
QueryResultsType,
|
||||
QueryType,
|
||||
} from '../../../src/camera-manager/types';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { CameraConfig } from '../../../src/config/schema/cameras';
|
||||
import { RawAdvancedCameraCardConfig } from '../../../src/config/types';
|
||||
@@ -58,6 +60,7 @@ const createEngine = (options?: {
|
||||
return new FrigateCameraManagerEngine(
|
||||
new EntityRegistryManagerMock(),
|
||||
new StateWatcher(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
options?.cache ?? new RecordingSegmentsCache(),
|
||||
options?.requestCache ?? new CameraManagerRequestCache(),
|
||||
);
|
||||
|
||||
@@ -2,10 +2,11 @@ import { describe, expect, it } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { GenericCameraManagerEngine } from '../../../src/camera-manager/generic/engine-generic';
|
||||
import { Engine, QueryResultsType, QueryType } from '../../../src/camera-manager/types';
|
||||
import { QuerySource } from '../../../src/query-source';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { CameraConfig } from '../../../src/config/schema/cameras';
|
||||
import { RawAdvancedCameraCardConfig } from '../../../src/config/types';
|
||||
import { QuerySource } from '../../../src/query-source';
|
||||
import {
|
||||
TestViewMedia,
|
||||
createCameraConfig,
|
||||
@@ -15,7 +16,10 @@ import {
|
||||
} from '../../test-utils';
|
||||
|
||||
const createEngine = (): GenericCameraManagerEngine => {
|
||||
return new GenericCameraManagerEngine(mock<StateWatcherSubscriptionInterface>());
|
||||
return new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
);
|
||||
};
|
||||
|
||||
const createGenericCameraConfig = (
|
||||
|
||||
@@ -574,15 +574,15 @@ describe('CameraManager', () => {
|
||||
config: createCameraConfig({
|
||||
...baseCameraConfig,
|
||||
triggers: {
|
||||
events: ['snapshots'],
|
||||
media_events: ['snapshots'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
await manager.initializeCamerasFromConfig();
|
||||
expect(manager.getStore().getCamera('id')?.getConfig().triggers.events).toEqual([
|
||||
'snapshots',
|
||||
]);
|
||||
expect(
|
||||
manager.getStore().getCamera('id')?.getConfig().triggers.media_events,
|
||||
).toEqual(['snapshots']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { describe, expect, it } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { CameraManagerEngine } from '../../../src/camera-manager/engine';
|
||||
import { MotionEyeCamera } from '../../../src/camera-manager/motioneye/camera';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import { createCameraConfig, createHASS, createRegistryEntity } from '../../test-utils';
|
||||
|
||||
@@ -48,7 +49,8 @@ describe('MotionEyeCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
const endpoints = camera.getEndpoints();
|
||||
@@ -65,7 +67,8 @@ describe('MotionEyeCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
const endpoints = camera.getEndpoints();
|
||||
@@ -82,7 +85,8 @@ describe('MotionEyeCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
const capabilities = camera.getCapabilities();
|
||||
@@ -107,7 +111,8 @@ describe('MotionEyeCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
const capabilities = camera.getCapabilities();
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
QueryResultsType,
|
||||
QueryType,
|
||||
} from '../../../src/camera-manager/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { BrowseMediaMetadata } from '../../../src/ha/browse-media/types';
|
||||
import { BrowseMediaStep, BrowseMediaWalker } from '../../../src/ha/browse-media/walker';
|
||||
import { Entity } from '../../../src/ha/registry/entity/types';
|
||||
@@ -50,7 +51,8 @@ const createEngine = (options?: {
|
||||
}): MotionEyeCameraManagerEngine => {
|
||||
return new MotionEyeCameraManagerEngine(
|
||||
new EntityRegistryManagerMock(options?.entities ?? [createEntity()]),
|
||||
mock<StateWatcher>(),
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
options?.walker ?? new BrowseMediaWalker(),
|
||||
new ResolvedMediaCache(),
|
||||
options?.requestCache ?? new CameraManagerRequestCache(),
|
||||
@@ -73,7 +75,8 @@ const createMotionEyeStore = async (
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([entity]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
camera.setID(options?.cameraID ?? 'camera-1');
|
||||
const store = new CameraManagerStore();
|
||||
@@ -497,7 +500,8 @@ describe('MotionEyeCameraManagerEngine', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([createEntity()]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
camera.setID('camera-1');
|
||||
const store = new CameraManagerStore();
|
||||
@@ -545,7 +549,8 @@ describe('MotionEyeCameraManagerEngine', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([createEntity()]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
camera.setID('camera-1');
|
||||
const store = new CameraManagerStore();
|
||||
@@ -599,7 +604,8 @@ describe('MotionEyeCameraManagerEngine', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([createEntity()]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
camera.setID('camera-1');
|
||||
const store = new CameraManagerStore();
|
||||
|
||||
@@ -4,7 +4,8 @@ import { CameraManagerEngine } from '../../../src/camera-manager/engine';
|
||||
import { ReolinkCamera } from '../../../src/camera-manager/reolink/camera';
|
||||
import { CameraProxyConfig } from '../../../src/camera-manager/types';
|
||||
import { ActionsExecutor } from '../../../src/card-controller/actions/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { DeviceRegistryManager } from '../../../src/ha/registry/device';
|
||||
import { EntityRegistryManagerLive } from '../../../src/ha/registry/entity';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
@@ -105,7 +106,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: mock<EntityRegistryManagerLive>(),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
}),
|
||||
).rejects.toThrowError('Could not find camera entity');
|
||||
});
|
||||
@@ -129,7 +131,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
}),
|
||||
).rejects.toThrowError('Could not initialize Reolink camera');
|
||||
});
|
||||
@@ -153,7 +156,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
}),
|
||||
).rejects.toThrowError('Could not initialize Reolink camera');
|
||||
});
|
||||
@@ -169,7 +173,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(0);
|
||||
@@ -201,7 +206,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(3);
|
||||
@@ -224,7 +230,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(7);
|
||||
@@ -247,7 +254,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(0);
|
||||
@@ -270,7 +278,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(7);
|
||||
@@ -295,7 +304,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(0);
|
||||
@@ -327,7 +337,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(0);
|
||||
@@ -359,7 +370,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(0);
|
||||
@@ -391,7 +403,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
deviceRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getChannel()).toBe(0);
|
||||
@@ -410,7 +423,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -449,7 +463,8 @@ describe('ReolinkCamera', () => {
|
||||
}),
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -475,7 +490,8 @@ describe('ReolinkCamera', () => {
|
||||
}),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -508,7 +524,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -695,7 +712,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -727,7 +745,8 @@ describe('ReolinkCamera', () => {
|
||||
buttonEntityPTZLeft,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
await camera.executePTZAction(executor, 'left', { phase: 'start' });
|
||||
@@ -754,7 +773,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -795,7 +815,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -821,7 +842,8 @@ describe('ReolinkCamera', () => {
|
||||
}),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -852,7 +874,8 @@ describe('ReolinkCamera', () => {
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -875,7 +898,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -902,7 +926,8 @@ describe('ReolinkCamera', () => {
|
||||
}),
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toBeNull();
|
||||
@@ -924,7 +949,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -946,7 +972,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -984,7 +1011,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1022,7 +1050,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1060,7 +1089,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1098,7 +1128,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1127,7 +1158,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1155,7 +1187,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1180,7 +1213,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1211,7 +1245,8 @@ describe('ReolinkCamera', () => {
|
||||
buttonEntityPTZStop,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -1233,7 +1268,8 @@ describe('ReolinkCamera', () => {
|
||||
numberEntityZoom,
|
||||
]),
|
||||
deviceRegistryManager: mock<DeviceRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
|
||||
@@ -23,19 +23,20 @@ import {
|
||||
QueryReturnType,
|
||||
QueryType,
|
||||
} from '../../../src/camera-manager/types';
|
||||
import { QuerySource } from '../../../src/query-source';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { BrowseMedia, browseMediaSchema } from '../../../src/ha/browse-media/types';
|
||||
import { BrowseMediaWalker } from '../../../src/ha/browse-media/walker';
|
||||
import { DeviceRegistryManager } from '../../../src/ha/registry/device';
|
||||
import { EntityRegistryManager } from '../../../src/ha/registry/entity/types';
|
||||
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
|
||||
import { homeAssistantWSRequest } from '../../../src/ha/ws-request';
|
||||
import { QuerySource } from '../../../src/query-source';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import {
|
||||
createInitializedCamera,
|
||||
createCameraConfig,
|
||||
createHASS,
|
||||
createInitializedCamera,
|
||||
createRegistryEntity,
|
||||
createStore,
|
||||
} from '../../test-utils';
|
||||
@@ -186,7 +187,8 @@ const createEngine = (options?: {
|
||||
return new ReolinkCameraManagerEngine(
|
||||
options?.entityRegistryManager ?? new EntityRegistryManagerMock(),
|
||||
mock<DeviceRegistryManager>(),
|
||||
mock<StateWatcher>(),
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
options?.browseMediaManager ?? new BrowseMediaWalker(),
|
||||
new ResolvedMediaCache(),
|
||||
new CameraManagerRequestCache(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Capabilities } from '../../src/camera-manager/capabilities.js';
|
||||
import { CameraManagerEngineFactory } from '../../src/camera-manager/engine-factory.js';
|
||||
import { CameraManagerStore } from '../../src/camera-manager/store.js';
|
||||
import { Engine } from '../../src/camera-manager/types.js';
|
||||
import { EventWatcherSubscriptionInterface } from '../../src/card-controller/hass/event-watcher.js';
|
||||
import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
|
||||
import { DeviceRegistryManager } from '../../src/ha/registry/device/index.js';
|
||||
import { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
|
||||
@@ -31,10 +32,12 @@ describe('CameraManagerStore', async () => {
|
||||
|
||||
const engineGeneric = await engineFactory.createEngine(Engine.Generic, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
});
|
||||
const engineFrigate = await engineFactory.createEngine(Engine.Frigate, {
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
resolvedMediaCache: mock<ResolvedMediaCache>(),
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { mock } from 'vitest-mock-extended';
|
||||
import { CameraManagerEngine } from '../../../src/camera-manager/engine';
|
||||
import { TPLinkCamera } from '../../../src/camera-manager/tplink/camera';
|
||||
import { ActionsExecutor } from '../../../src/card-controller/actions/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import { createCameraConfig, createHASS, createRegistryEntity } from '../../test-utils';
|
||||
|
||||
@@ -78,7 +79,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
}),
|
||||
).rejects.toThrowError('Could not find camera entity');
|
||||
});
|
||||
@@ -94,7 +96,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
}),
|
||||
).rejects.toThrowError('Could not find camera entity');
|
||||
});
|
||||
@@ -112,7 +115,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getEntity()).toBe(cameraEntity);
|
||||
@@ -129,7 +133,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getEntity()).toBe(cameraEntity);
|
||||
@@ -146,7 +151,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -175,7 +181,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
@@ -198,7 +205,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: new EntityRegistryManagerMock([cameraEntity]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -219,7 +227,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -242,7 +251,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -262,7 +272,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -290,7 +301,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -318,7 +330,8 @@ describe('TPLinkCamera', () => {
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
entityRegistryManager: ptzPopulatedEntityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
@@ -357,7 +370,8 @@ describe('TPLinkCamera', () => {
|
||||
cameraEntity,
|
||||
buttonEntityPanLeft,
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
await camera.executePTZAction(executor, 'left', { phase: 'start' });
|
||||
@@ -395,7 +409,8 @@ describe('TPLinkCamera', () => {
|
||||
cameraWithDifferentUniqueId,
|
||||
buttonEntityPanLeft,
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
// Should not find PTZ entities since unique_id doesn't end with _live_view
|
||||
@@ -420,7 +435,8 @@ describe('TPLinkCamera', () => {
|
||||
cameraWithoutUniqueId,
|
||||
buttonEntityPanLeft,
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
|
||||
// Should not find PTZ entities since camera has no unique_id
|
||||
@@ -442,7 +458,8 @@ describe('TPLinkCamera', () => {
|
||||
cameraEntity,
|
||||
buttonEntityPanLeft, // Only left button available
|
||||
]),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
});
|
||||
const executor = mock<ActionsExecutor>();
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { describe, expect, it } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { TPLinkCameraManagerEngine } from '../../../src/camera-manager/tplink/engine-tplink';
|
||||
import { Engine } from '../../../src/camera-manager/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EventWatcherSubscriptionInterface } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import { createCameraConfig, createHASS, createRegistryEntity } from '../../test-utils';
|
||||
|
||||
@@ -11,7 +12,8 @@ const createEngine = (options?: {
|
||||
}): TPLinkCameraManagerEngine => {
|
||||
return new TPLinkCameraManagerEngine(
|
||||
options?.entityRegistryManager ?? new EntityRegistryManagerMock(),
|
||||
mock<StateWatcher>(),
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { HassEvent } from 'home-assistant-js-websocket';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { EventWatcher } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { HomeAssistant } from '../../../src/ha/types';
|
||||
import { createHASS } from '../../test-utils';
|
||||
|
||||
const fireEvent = (hass: HomeAssistant, event: HassEvent, n = 0): void => {
|
||||
const mock = vi.mocked(hass.connection.subscribeEvents).mock;
|
||||
expect(mock.calls.length).greaterThan(n);
|
||||
|
||||
// subscribeEvents(callback, event_type) -- callback is the first argument.
|
||||
mock.calls[n][0]?.(event);
|
||||
};
|
||||
|
||||
const createHassEvent = (event_type: string, data: object = {}): HassEvent => ({
|
||||
event_type,
|
||||
data: data as { [key: string]: string },
|
||||
origin: 'LOCAL',
|
||||
time_fired: '2026-05-25T00:00:00Z',
|
||||
context: { id: 'ctx', user_id: null, parent_id: null },
|
||||
});
|
||||
|
||||
describe('EventWatcher', () => {
|
||||
it('opens a single WS subscription per event_type regardless of subscribers', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', callback: vi.fn() });
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', callback: vi.fn() });
|
||||
|
||||
expect(hass.connection.subscribeEvents).toBeCalledTimes(1);
|
||||
expect(vi.mocked(hass.connection.subscribeEvents).mock.calls[0][1]).toBe(
|
||||
'zha_event',
|
||||
);
|
||||
});
|
||||
|
||||
it('opens separate WS subscriptions for distinct event_types', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', callback: vi.fn() });
|
||||
await watcher.subscribe(hass, { event_type: 'deconz_event', callback: vi.fn() });
|
||||
|
||||
expect(hass.connection.subscribeEvents).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('only tears down the WS subscription when the last subscriber unsubscribes', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
const unsub = vi.fn();
|
||||
vi.mocked(hass.connection.subscribeEvents).mockResolvedValue(unsub);
|
||||
|
||||
const req1 = { event_type: 'zha_event', callback: vi.fn() };
|
||||
const req2 = { event_type: 'zha_event', callback: vi.fn() };
|
||||
await watcher.subscribe(hass, req1);
|
||||
await watcher.subscribe(hass, req2);
|
||||
|
||||
await watcher.unsubscribe(req1);
|
||||
expect(unsub).not.toBeCalled();
|
||||
|
||||
await watcher.unsubscribe(req2);
|
||||
expect(unsub).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('dispatches to all subscribers whose event_type matches', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
const cb1 = vi.fn();
|
||||
const cb2 = vi.fn();
|
||||
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', callback: cb1 });
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', callback: cb2 });
|
||||
|
||||
fireEvent(hass, createHassEvent('zha_event', { command: 'press' }));
|
||||
|
||||
expect(cb1).toBeCalledWith({ command: 'press' });
|
||||
expect(cb2).toBeCalledWith({ command: 'press' });
|
||||
});
|
||||
|
||||
it('drops events whose event_type does not match the request', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
const cb = vi.fn();
|
||||
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', callback: cb });
|
||||
// Inject an unrelated event into the shared dispatcher.
|
||||
fireEvent(hass, createHassEvent('other_event', { x: 1 }));
|
||||
|
||||
expect(cb).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('gates dispatch on the request matcher when provided', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
const cb = vi.fn();
|
||||
const matcher = vi.fn((data: unknown) => (data as { x?: number }).x === 1);
|
||||
|
||||
await watcher.subscribe(hass, { event_type: 'zha_event', matcher, callback: cb });
|
||||
|
||||
fireEvent(hass, createHassEvent('zha_event', { x: 1 }));
|
||||
fireEvent(hass, createHassEvent('zha_event', { x: 2 }));
|
||||
|
||||
expect(matcher).toBeCalledTimes(2);
|
||||
expect(cb).toBeCalledTimes(1);
|
||||
expect(cb).toBeCalledWith({ x: 1 });
|
||||
});
|
||||
|
||||
it('handles unsubscribe during a still-pending subscribe without leaking', async () => {
|
||||
const watcher = new EventWatcher();
|
||||
const hass = createHASS();
|
||||
const unsub = vi.fn();
|
||||
|
||||
let resolveSubscription: ((cb: () => Promise<void>) => void) | undefined;
|
||||
const subscriptionPromise = new Promise<() => Promise<void>>((resolve) => {
|
||||
resolveSubscription = resolve;
|
||||
});
|
||||
vi.mocked(hass.connection.subscribeEvents).mockReturnValue(subscriptionPromise);
|
||||
|
||||
const req = { event_type: 'zha_event', callback: vi.fn() };
|
||||
const subscribePromise = watcher.subscribe(hass, req);
|
||||
|
||||
// Unsubscribe before the underlying connection has resolved.
|
||||
const unsubscribePromise = watcher.unsubscribe(req);
|
||||
|
||||
// Resolve the connection -- the watcher should now have the unsub fn and
|
||||
// call it as part of completing the unsubscribe.
|
||||
resolveSubscription?.(unsub);
|
||||
await subscribePromise;
|
||||
await unsubscribePromise;
|
||||
|
||||
expect(unsub).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { STATE_RUNNING, STATE_STARTING } from 'home-assistant-js-websocket';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { EventWatcher } from '../../../src/card-controller/hass/event-watcher';
|
||||
import { HASSManager } from '../../../src/card-controller/hass/hass-manager';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import {
|
||||
@@ -29,6 +30,11 @@ describe('HASSManager', () => {
|
||||
expect(manager.getStateWatcher()).toEqual(expect.any(StateWatcher));
|
||||
});
|
||||
|
||||
it('should get event watcher', () => {
|
||||
const manager = new HASSManager(createCardAPI());
|
||||
expect(manager.getEventWatcher()).toEqual(expect.any(EventWatcher));
|
||||
});
|
||||
|
||||
it('should get hass after set', () => {
|
||||
const manager = new HASSManager(createCardAPI());
|
||||
const hass = createHASS();
|
||||
|
||||
@@ -25,7 +25,7 @@ vi.mock('lodash-es', async () => ({
|
||||
const baseTriggersConfig: TriggersOptions = {
|
||||
untrigger_delay_seconds: 10,
|
||||
untrigger_force_seconds: 0,
|
||||
signal_hold_seconds: 0,
|
||||
event_hold_seconds: 0,
|
||||
filter_selected_camera: false,
|
||||
show_trigger_status: false,
|
||||
actions: {
|
||||
@@ -810,7 +810,7 @@ describe('TriggersManager', () => {
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should auto-untrigger after the delay on a signal event', async () => {
|
||||
it('should auto-untrigger after the delay on a momentary event', async () => {
|
||||
const api = createTriggerAPI({
|
||||
config: {
|
||||
actions: { trigger: 'none', untrigger: 'default' },
|
||||
@@ -821,7 +821,7 @@ describe('TriggersManager', () => {
|
||||
await manager.handleCameraEvent({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event.doorbell',
|
||||
type: 'signal',
|
||||
type: 'momentary',
|
||||
});
|
||||
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
@@ -835,7 +835,7 @@ describe('TriggersManager', () => {
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
|
||||
});
|
||||
|
||||
it('should auto-untrigger immediately on a signal when delay is 0', async () => {
|
||||
it('should auto-untrigger immediately on a momentary event when delay is 0', async () => {
|
||||
const api = createTriggerAPI({
|
||||
config: {
|
||||
untrigger_delay_seconds: 0,
|
||||
@@ -847,7 +847,7 @@ describe('TriggersManager', () => {
|
||||
await manager.handleCameraEvent({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event.doorbell',
|
||||
type: 'signal',
|
||||
type: 'momentary',
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
@@ -855,7 +855,7 @@ describe('TriggersManager', () => {
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not auto-untrigger from a signal while a continuous source remains active', async () => {
|
||||
it('should not auto-untrigger from a momentary event while a continuous source remains active', async () => {
|
||||
const api = createTriggerAPI({
|
||||
config: {
|
||||
actions: { trigger: 'none', untrigger: 'default' },
|
||||
@@ -874,7 +874,7 @@ describe('TriggersManager', () => {
|
||||
await manager.handleCameraEvent({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event.doorbell',
|
||||
type: 'signal',
|
||||
type: 'momentary',
|
||||
});
|
||||
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
@@ -886,11 +886,11 @@ describe('TriggersManager', () => {
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should add signal_hold_seconds on top of untrigger_delay_seconds for signals', async () => {
|
||||
it('should add event_hold_seconds on top of untrigger_delay_seconds for momentary events', async () => {
|
||||
const api = createTriggerAPI({
|
||||
config: {
|
||||
untrigger_delay_seconds: 5,
|
||||
signal_hold_seconds: 30,
|
||||
event_hold_seconds: 30,
|
||||
actions: { trigger: 'none', untrigger: 'default' },
|
||||
},
|
||||
});
|
||||
@@ -899,7 +899,7 @@ describe('TriggersManager', () => {
|
||||
await manager.handleCameraEvent({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event.doorbell',
|
||||
type: 'signal',
|
||||
type: 'momentary',
|
||||
});
|
||||
|
||||
// At 34s the additive 35s window is still active.
|
||||
@@ -916,11 +916,11 @@ describe('TriggersManager', () => {
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not apply signal_hold_seconds to non-signal events', async () => {
|
||||
it('should not apply event_hold_seconds to non-momentary events', async () => {
|
||||
const api = createTriggerAPI({
|
||||
config: {
|
||||
untrigger_delay_seconds: 5,
|
||||
signal_hold_seconds: 30,
|
||||
event_hold_seconds: 30,
|
||||
actions: { trigger: 'none', untrigger: 'default' },
|
||||
},
|
||||
});
|
||||
@@ -1305,9 +1305,9 @@ describe('TriggersManager', () => {
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not reset the untrigger timer when a filtered-out signal arrives', async () => {
|
||||
// Guards the `if (handled)` branch on the signal synthesis: a
|
||||
// filter-rejected signal must not call the internal 'end', which would
|
||||
it('should not reset the untrigger timer when a filtered-out momentary event arrives', async () => {
|
||||
// Guards the `if (handled)` branch on the momentary-event synthesis: a
|
||||
// filter-rejected momentary event must not call the internal 'end', which would
|
||||
// otherwise reset an already-running untrigger-delay timer.
|
||||
const api = createTriggerAPI({
|
||||
config: {
|
||||
@@ -1341,7 +1341,7 @@ describe('TriggersManager', () => {
|
||||
await manager.handleCameraEvent({
|
||||
cameraID: 'camera_1',
|
||||
id: 'event.doorbell',
|
||||
type: 'signal',
|
||||
type: 'momentary',
|
||||
});
|
||||
|
||||
// At t=10 (the original delay's deadline), the timer should fire.
|
||||
|
||||
@@ -4052,6 +4052,154 @@ describe('should handle version specific upgrades', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cameras[].triggers.events string[] → triggers.media_events', () => {
|
||||
it('migrates a legacy string array to media_events', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [
|
||||
{
|
||||
camera_entity: 'camera.office',
|
||||
triggers: { events: ['events', 'clips'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(config.cameras[0].triggers).toEqual({
|
||||
media_events: ['events', 'clips'],
|
||||
});
|
||||
postUpgradeChecks(config);
|
||||
});
|
||||
|
||||
it('migrates an empty legacy array', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office', triggers: { events: [] } }],
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(config.cameras[0].triggers).toEqual({ media_events: [] });
|
||||
postUpgradeChecks(config);
|
||||
});
|
||||
|
||||
it('leaves the new object-array shape untouched', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [
|
||||
{
|
||||
camera_entity: 'camera.office',
|
||||
triggers: { events: [{ event_type: 'zha_event' }] },
|
||||
},
|
||||
],
|
||||
};
|
||||
// The new shape might still trigger OTHER upgrades to fire on the
|
||||
// config, so we don't assert the overall upgrade result -- only that
|
||||
// this specific field is not touched.
|
||||
upgradeConfig(config);
|
||||
expect(config.cameras[0].triggers).toEqual({
|
||||
events: [{ event_type: 'zha_event' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('drops legacy events but keeps an existing media_events untouched', () => {
|
||||
// Both fields present is implausible in real user configs, but if it
|
||||
// happens we still must remove the legacy `events: string[]` because
|
||||
// the new schema would reject it; the explicit `media_events` wins.
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [
|
||||
{
|
||||
camera_entity: 'camera.office',
|
||||
triggers: {
|
||||
events: ['snapshots'],
|
||||
media_events: ['clips'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(config.cameras[0].triggers).toEqual({
|
||||
media_events: ['clips'],
|
||||
});
|
||||
postUpgradeChecks(config);
|
||||
});
|
||||
|
||||
it('is idempotent across repeated runs', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [
|
||||
{
|
||||
camera_entity: 'camera.office',
|
||||
triggers: { events: ['events'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(upgradeConfig(config)).toBeFalsy();
|
||||
expect(config.cameras[0].triggers).toEqual({ media_events: ['events'] });
|
||||
});
|
||||
|
||||
it('migrates per camera independently', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [
|
||||
{ camera_entity: 'camera.a', triggers: { events: ['clips'] } },
|
||||
{
|
||||
camera_entity: 'camera.b',
|
||||
triggers: { events: [{ event_type: 'zha_event' }] },
|
||||
},
|
||||
{ camera_entity: 'camera.c' },
|
||||
],
|
||||
};
|
||||
upgradeConfig(config);
|
||||
expect(config.cameras[0].triggers).toEqual({ media_events: ['clips'] });
|
||||
expect(config.cameras[1].triggers).toEqual({
|
||||
events: [{ event_type: 'zha_event' }],
|
||||
});
|
||||
expect(config.cameras[2].triggers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('migrates a legacy cameras_global.triggers.events string array', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
cameras_global: {
|
||||
triggers: {
|
||||
events: ['clips', 'snapshots'],
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(upgradeConfig(config)).toBeTruthy();
|
||||
expect(config.cameras_global.triggers).toEqual({
|
||||
media_events: ['clips', 'snapshots'],
|
||||
});
|
||||
postUpgradeChecks(config);
|
||||
});
|
||||
|
||||
it('leaves cameras_global new-shape events untouched', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
cameras_global: {
|
||||
triggers: {
|
||||
events: [{ event_type: 'zha_event' }],
|
||||
},
|
||||
},
|
||||
};
|
||||
upgradeConfig(config);
|
||||
expect(config.cameras_global.triggers).toEqual({
|
||||
events: [{ event_type: 'zha_event' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('is a no-op when triggers is not an object (malformed user config)', () => {
|
||||
const config = {
|
||||
type: 'custom:advanced-camera-card',
|
||||
cameras: [{ camera_entity: 'camera.office', triggers: 'not-an-object' }],
|
||||
};
|
||||
upgradeConfig(config);
|
||||
expect(config.cameras[0].triggers).toBe('not-an-object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('live_substream_{on,off,select} → substream_{on,off}', () => {
|
||||
it('rewrites live_substream_on to substream_on in an automation', () => {
|
||||
const config = {
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('config defaults', () => {
|
||||
doorbell: false,
|
||||
entities: [],
|
||||
events: [],
|
||||
media_events: [],
|
||||
motion: false,
|
||||
occupancy: false,
|
||||
reviews: {
|
||||
@@ -550,7 +551,7 @@ describe('config defaults', () => {
|
||||
},
|
||||
filter_selected_camera: true,
|
||||
show_trigger_status: false,
|
||||
signal_hold_seconds: 30,
|
||||
event_hold_seconds: 30,
|
||||
untrigger_delay_seconds: 0,
|
||||
untrigger_force_seconds: 0,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { matchesEventData } from '../../src/ha/event-data-match';
|
||||
|
||||
describe('matchesEventData', () => {
|
||||
describe('non-object data', () => {
|
||||
it.each([['string'], [42], [true], [null], [undefined]])(
|
||||
'rejects non-object data (%s) with a non-empty filter',
|
||||
(data) => {
|
||||
expect(matchesEventData({ a: 1 }, data)).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('matches when every filter key matches', () => {
|
||||
expect(matchesEventData({ command: 'press' }, { command: 'press', extra: 1 })).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects when a filter value differs', () => {
|
||||
expect(matchesEventData({ command: 'press' }, { command: 'release' })).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when a filter key is missing from data', () => {
|
||||
expect(matchesEventData({ command: 'press' }, { other: 'press' })).toBe(false);
|
||||
});
|
||||
|
||||
it('ignores extra keys in data', () => {
|
||||
expect(matchesEventData({ a: 1 }, { a: 1, b: 2, c: 3 })).toBe(true);
|
||||
});
|
||||
|
||||
it('matches nested objects as a subset', () => {
|
||||
expect(
|
||||
matchesEventData(
|
||||
{ device: { id: 'abc' } },
|
||||
{ device: { id: 'abc', name: 'Front Door' } },
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects nested objects when a nested key differs', () => {
|
||||
expect(matchesEventData({ device: { id: 'abc' } }, { device: { id: 'xyz' } })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('matches arrays element-wise', () => {
|
||||
expect(matchesEventData({ tags: ['a', 'b'] }, { tags: ['a', 'b'] })).toBe(true);
|
||||
});
|
||||
|
||||
it('matches arrays as a subset by index (filter shorter than data)', () => {
|
||||
// Same partial-match semantics lodash uses for objects: a shorter filter
|
||||
// array matches if every index it specifies matches in data. Useful when
|
||||
// the user only cares about the first N values.
|
||||
expect(matchesEventData({ tags: ['a'] }, { tags: ['a', 'b'] })).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects arrays with mismatched elements at the same index', () => {
|
||||
expect(matchesEventData({ tags: ['a', 'c'] }, { tags: ['a', 'b'] })).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when filter array is longer than data array', () => {
|
||||
expect(matchesEventData({ tags: ['a', 'b'] }, { tags: ['a'] })).toBe(false);
|
||||
});
|
||||
|
||||
it('distinguishes null from undefined', () => {
|
||||
expect(matchesEventData({ a: null }, { a: null })).toBe(true);
|
||||
expect(matchesEventData({ a: null }, { a: 0 })).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts an empty filter against any object', () => {
|
||||
expect(matchesEventData({}, {})).toBe(true);
|
||||
expect(matchesEventData({}, { anything: 1, nested: { x: 2 } })).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -36,14 +36,14 @@ describe('getTriggerEventType', () => {
|
||||
});
|
||||
|
||||
describe('for event entities', () => {
|
||||
it('returns "signal" for a transition between two real timestamps', () => {
|
||||
it('returns "momentary" for a transition between two real timestamps', () => {
|
||||
expect(
|
||||
getTriggerEventType({
|
||||
entityID: 'event.front_door_doorbell',
|
||||
oldState: createStateEntity({ state: '2026-05-24T12:00:00.000+00:00' }),
|
||||
newState: createStateEntity({ state: '2026-05-24T12:00:05.123+00:00' }),
|
||||
}),
|
||||
).toBe('signal');
|
||||
).toBe('momentary');
|
||||
});
|
||||
|
||||
it('returns null when there is no old state', () => {
|
||||
@@ -65,14 +65,14 @@ describe('getTriggerEventType', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('returns "signal" when the old state is unknown (first fire after startup)', () => {
|
||||
it('returns "momentary" when the old state is unknown (first fire after startup)', () => {
|
||||
expect(
|
||||
getTriggerEventType({
|
||||
entityID: 'event.front_door_doorbell',
|
||||
oldState: createStateEntity({ state: 'unknown' }),
|
||||
newState: createStateEntity({ state: '2026-05-24T12:00:05.123+00:00' }),
|
||||
}),
|
||||
).toBe('signal');
|
||||
).toBe('momentary');
|
||||
});
|
||||
|
||||
it.each([['unavailable'], ['unknown']])(
|
||||
|
||||
@@ -38,6 +38,7 @@ import { FoldersManager } from '../src/card-controller/folders/manager';
|
||||
import { FolderQuery } from '../src/card-controller/folders/types';
|
||||
import { FullscreenManager } from '../src/card-controller/fullscreen/fullscreen-manager';
|
||||
import { HASSManager } from '../src/card-controller/hass/hass-manager';
|
||||
import { EventWatcherSubscriptionInterface } from '../src/card-controller/hass/event-watcher';
|
||||
import { StateWatcherSubscriptionInterface } from '../src/card-controller/hass/state-watcher';
|
||||
import { InitializationManager } from '../src/card-controller/initialization-manager';
|
||||
import { InteractionManager } from '../src/card-controller/interaction-manager';
|
||||
@@ -124,6 +125,7 @@ export const createInitializedCamera = async (
|
||||
await camera.initialize({
|
||||
hass: createHASS(),
|
||||
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
|
||||
eventWatcher: mock<EventWatcherSubscriptionInterface>(),
|
||||
...(capabilities ? { capabilityOptions: { capabilities } } : {}),
|
||||
});
|
||||
return camera;
|
||||
@@ -143,6 +145,7 @@ export const createHASS = (states?: HassEntities, user?: CurrentUser): HomeAssis
|
||||
// startup state still represent a "ready" instance.
|
||||
hass.config.state = STATE_RUNNING;
|
||||
hass.connection.subscribeMessage = vi.fn();
|
||||
hass.connection.subscribeEvents = vi.fn();
|
||||
|
||||
// ha-nunjucks calls sendMessagePromise to fetch label registry; return empty array to prevent crash.
|
||||
hass.connection.sendMessagePromise = vi.fn().mockResolvedValue([]);
|
||||
@@ -282,6 +285,7 @@ export const createStore = (
|
||||
cameraProps.engine ??
|
||||
new GenericCameraManagerEngine(
|
||||
mock<StateWatcherSubscriptionInterface>(),
|
||||
mock<EventWatcherSubscriptionInterface>(),
|
||||
mock<EntityRegistryManager>(),
|
||||
eventCallback,
|
||||
),
|
||||
@@ -737,6 +741,16 @@ export const callStateWatcherCallback = (
|
||||
mock.calls[n][0](diff);
|
||||
};
|
||||
|
||||
export const callEventWatcherCallback = (
|
||||
eventWatcher: EventWatcherSubscriptionInterface,
|
||||
data: unknown,
|
||||
n = 0,
|
||||
): void => {
|
||||
const mock = vi.mocked(eventWatcher.subscribe).mock;
|
||||
expect(mock.calls.length).greaterThan(n);
|
||||
mock.calls[n][1].callback(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Flush resolved promises.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user