test: Split test utilities to improve test times (#2622)

This commit is contained in:
Dermot Duffy
2026-07-26 16:29:53 -07:00
committed by GitHub
parent 8d44fcecf1
commit ad89aa9538
234 changed files with 2730 additions and 2516 deletions
@@ -8,6 +8,10 @@ import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout';
const ROTATED_ATTRIBUTE = 'rotated';
// Resizes arrive in bursts as the browser settles the layout, so only the last
// one in a burst is acted upon.
export const RESIZE_DEBOUNCE_SECONDS = 0.1;
interface MediaDimensions {
width: number;
height: number;
@@ -32,7 +36,9 @@ export class MediaDimensionsContainerController implements ReactiveController {
private _innerContainer: HTMLElement | null = null;
private _outerContainer: HTMLElement | null = null;
public resize = debounce(this._resize.bind(this), 100, { trailing: true });
public resize = debounce(this._resize.bind(this), RESIZE_DEBOUNCE_SECONDS * 1000, {
trailing: true,
});
private _resizeObserver = new ResizeObserver(this.resize);
private _mediaDimensions: MediaDimensions | null = null;
@@ -1,5 +1,9 @@
import { debounce, isEqual } from 'lodash-es';
// Balancing act: Debounce to avoid excessive calls to setHeight, when new media
// is loading the player may be a much smaller height momentarily.
export const SET_HEIGHT_DEBOUNCE_SECONDS = 0.3;
export class MediaHeightController {
private _host: HTMLElement;
private _selector: string;
@@ -13,9 +17,7 @@ export class MediaHeightController {
private _debouncedSetHeight = debounce(
() => this._setHeight(),
// Balancing act: Debounce to avoid excessive calls to setHeight, when new
// media is loading the player may be a much smaller height momentarily.
300,
SET_HEIGHT_DEBOUNCE_SECONDS * 1000,
{
trailing: true,
leading: false,
-3
View File
@@ -25,9 +25,6 @@ export async function homeAssistantSignPath(
signedPathSchema,
request,
);
if (!response) {
return null;
}
return hass.hassUrl(response.path);
}
@@ -13,8 +13,9 @@ import { BrowseMediaWalker } from '../../../src/ha/browse-media/walker';
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
import { QuerySource } from '../../../src/query-source';
import type { ViewMedia } from '../../../src/view/item';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import { createCameraConfig, createHASS, createHASSManager } from '../../test-utils';
import { createHASS, createHASSManager } from '../../test-utils';
const createEngine = (): BrowseMediaCameraManagerEngine => {
return new BrowseMediaCameraManagerEngine(
+18 -19
View File
@@ -8,19 +8,18 @@ import type { EventWatcherSubscriptionInterface } from '../../src/card-controlle
import type { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
import { liveProviderSupports2WayAudio } from '../../src/utils/live-provider.js';
import type * as LiveProviderUtils from '../../src/utils/live-provider.js';
import { createCameraConfig } from '../config/test-utils';
import { EntityRegistryManagerMock } from '../ha/registry/entity/mock.js';
import {
callEventWatcherCallback,
callStateWatcherCallback,
createCameraConfig,
createCapabilities,
createHASS,
createHASSEvent,
createHASSManager,
createInitializedCamera,
createRegistryEntity,
createStateEntity,
} from '../test-utils.js';
import { createCapabilities, createInitializedCamera } from './test-utils';
// Partially mock to keep the real pure helpers (e.g. `isGo2RTCLiveProvider`
// used by `getProxyConfig`) while mocking the async metadata fetch.
@@ -80,7 +79,7 @@ describe('Camera', () => {
createCameraConfig(),
new GenericCameraManagerEngine(createHASSManager()),
);
expect(() => camera.getID()).toThrowError(
expect(() => camera.getID()).toThrow(
'Could not determine camera id for the following ' +
"camera, may need to set 'id' parameter manually",
);
@@ -109,13 +108,13 @@ describe('Camera', () => {
});
expect(camera.isInitialized()).toBe(true);
expect(stateWatcher.subscribe).toBeCalledWith(expect.any(Function), [
expect(stateWatcher.subscribe).toHaveBeenCalledWith(expect.any(Function), [
'camera.foo',
]);
await camera.destroy();
expect(stateWatcher.unsubscribe).toBeCalled();
expect(stateWatcher.unsubscribe).toHaveBeenCalled();
});
it('should skip initialization when hass is unavailable', async () => {
@@ -134,7 +133,7 @@ describe('Camera', () => {
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
});
expect(stateWatcher.subscribe).not.toBeCalled();
expect(stateWatcher.subscribe).not.toHaveBeenCalled();
expect(camera.getCapabilities()).toBeNull();
expect(camera.isInitialized()).toBe(false);
});
@@ -630,7 +629,7 @@ describe('Camera', () => {
it('should subscribe to discovered doorbell entities for state changes', async () => {
const { stateWatcher } = await initializeDoorbellCamera();
expect(stateWatcher.subscribe).toBeCalledWith(expect.any(Function), [
expect(stateWatcher.subscribe).toHaveBeenCalledWith(expect.any(Function), [
'event.front_door_doorbell',
]);
});
@@ -671,7 +670,7 @@ describe('Camera', () => {
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
});
expect(stateWatcher.subscribe).toBeCalled();
expect(stateWatcher.subscribe).toHaveBeenCalled();
const diff = {
entityID: 'sensor.force_update',
@@ -680,7 +679,7 @@ describe('Camera', () => {
};
callStateWatcherCallback(stateWatcher, diff);
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
cameraID: 'camera_1',
id: 'sensor.force_update',
type: eventType,
@@ -707,7 +706,7 @@ describe('Camera', () => {
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
});
expect(eventWatcher.subscribe).toBeCalledTimes(1);
expect(eventWatcher.subscribe).toHaveBeenCalledTimes(1);
const request = vi.mocked(eventWatcher.subscribe).mock.calls[0][0];
expect(request.event_type).toBe('zha_event');
expect(request.matcher).toBeUndefined();
@@ -717,14 +716,14 @@ describe('Camera', () => {
createHASSEvent('zha_event', { command: 'press' }),
);
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
cameraID: 'camera_1',
id: 'event:zha_event',
type: 'momentary',
});
await camera.destroy();
expect(eventWatcher.unsubscribe).toBeCalled();
expect(eventWatcher.unsubscribe).toHaveBeenCalled();
});
it('should attach a context-only matcher when only a context filter is set', async () => {
@@ -775,7 +774,7 @@ describe('Camera', () => {
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
});
expect(eventWatcher.subscribe).toBeCalledTimes(2);
expect(eventWatcher.subscribe).toHaveBeenCalledTimes(2);
expect(vi.mocked(eventWatcher.subscribe).mock.calls[0][0].event_type).toBe(
'zha_event',
);
@@ -828,7 +827,7 @@ describe('Camera', () => {
capabilityOptions: { capabilities: createCapabilities({ trigger: false }) },
});
expect(eventWatcher.subscribe).not.toBeCalled();
expect(eventWatcher.subscribe).not.toHaveBeenCalled();
});
it('should not dispatch when the helper returns null', async () => {
@@ -860,7 +859,7 @@ describe('Camera', () => {
newState: createStateEntity({ state: '2026-05-24T12:00:05.123+00:00' }),
});
expect(eventCallback).not.toBeCalled();
expect(eventCallback).not.toHaveBeenCalled();
});
it('should dispatch a momentary event for an event entity fire', async () => {
@@ -892,8 +891,8 @@ describe('Camera', () => {
newState: createStateEntity({ state: '2026-05-24T12:00:05.123+00:00' }),
});
expect(eventCallback).toBeCalledTimes(1);
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledTimes(1);
expect(eventCallback).toHaveBeenCalledWith({
cameraID: 'camera_1',
id: 'event.front_door_doorbell',
type: 'momentary',
@@ -921,7 +920,7 @@ describe('Camera', () => {
capabilityOptions: { capabilities: createCapabilities({ trigger: false }) },
});
expect(stateWatcher.subscribe).not.toBeCalled();
expect(stateWatcher.subscribe).not.toHaveBeenCalled();
});
});
+1 -1
View File
@@ -12,9 +12,9 @@ import type { CardWideConfig } from '../../src/config/schema/types.js';
import type { DeviceRegistryManager } from '../../src/ha/registry/device';
import type { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
import type { ResolvedMediaCache } from '../../src/ha/resolved-media.js';
import { createCameraConfig } from '../config/test-utils';
import { EntityRegistryManagerMock } from '../ha/registry/entity/mock.js';
import {
createCameraConfig,
createHASS,
createHASSManager,
createRegistryEntity,
+32 -32
View File
@@ -26,15 +26,15 @@ import type {
EntityRegistryManager,
} from '../../../src/ha/registry/entity/types';
import { ViewMediaType } from '../../../src/view/item';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createCapabilities,
createHASS,
createHASSManager,
createRegistryEntity,
createStateEntity,
} from '../../test-utils';
import { createCapabilities } from '../test-utils';
vi.mock('../../../src/camera-manager/frigate/requests');
@@ -98,7 +98,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: mock<FrigateEventWatcher>(),
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
}),
).rejects.toThrowError(/Could not find camera entity/);
).rejects.toThrow(/Could not find camera entity/);
});
it('with a valid camera_entity', async () => {
@@ -315,7 +315,7 @@ describe('FrigateCamera', () => {
expect(camera.getCapabilities()?.has('snapshots')).toBeTruthy();
expect(camera.getCapabilities()?.has('recordings')).toBeTruthy();
expect(camera.getCapabilities()?.has('trigger')).toBeTruthy();
expect(vi.mocked(getPTZInfo)).toBeCalled();
expect(vi.mocked(getPTZInfo)).toHaveBeenCalled();
});
it('basic birdseye', async () => {
@@ -342,7 +342,7 @@ describe('FrigateCamera', () => {
expect(camera.getCapabilities()?.has('snapshots')).toBeFalsy();
expect(camera.getCapabilities()?.has('recordings')).toBeFalsy();
expect(camera.getCapabilities()?.has('trigger')).toBeTruthy();
expect(vi.mocked(getPTZInfo)).not.toBeCalled();
expect(vi.mocked(getPTZInfo)).not.toHaveBeenCalled();
});
describe('with ptz', () => {
@@ -368,7 +368,7 @@ describe('FrigateCamera', () => {
expect(camera.getCapabilities()?.has('ptz')).toBeFalsy();
expect(camera.getCapabilities()?.hasPTZCapability()).toBeFalsy();
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
it('when getPTZInfo call succeeds with continuous motion', async () => {
@@ -824,7 +824,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: eventWatcher,
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
});
expect(eventWatcher.subscribe).toBeCalledWith(
expect(eventWatcher.subscribe).toHaveBeenCalledWith(
expect.objectContaining({
instanceID: 'CLIENT_ID',
}),
@@ -853,7 +853,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: eventWatcher,
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
});
expect(eventWatcher.subscribe).not.toBeCalled();
expect(eventWatcher.subscribe).not.toHaveBeenCalled();
});
it('should not subscribe without trigger capability', async () => {
@@ -878,7 +878,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: eventWatcher,
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
});
expect(eventWatcher.subscribe).not.toBeCalled();
expect(eventWatcher.subscribe).not.toHaveBeenCalled();
});
it('should not subscribe with no camera name', async () => {
@@ -902,7 +902,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: eventWatcher,
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
});
expect(eventWatcher.subscribe).not.toBeCalled();
expect(eventWatcher.subscribe).not.toHaveBeenCalled();
});
it('should unsubscribe on destroy', async () => {
@@ -926,10 +926,10 @@ describe('FrigateCamera', () => {
frigateEventWatcher: eventWatcher,
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
});
expect(eventWatcher.unsubscribe).not.toBeCalled();
expect(eventWatcher.unsubscribe).not.toHaveBeenCalled();
await camera.destroy();
expect(eventWatcher.unsubscribe).toBeCalled();
expect(eventWatcher.unsubscribe).toHaveBeenCalled();
});
it('should not subscribe when destroyed while base initialization is pending', async () => {
@@ -965,22 +965,22 @@ describe('FrigateCamera', () => {
frigateReviewWatcher: reviewWatcher,
capabilityOptions: { capabilities: createCapabilities({ trigger: true }) },
});
await vi.waitFor(() => expect(entityRegistryManager.getEntity).toBeCalled());
await vi.waitFor(() => expect(entityRegistryManager.getEntity).toHaveBeenCalled());
await camera.destroy();
// `_destroyed` short-circuits initialize() after the pending await, so
// neither watcher is ever subscribed.
expect(eventWatcher.subscribe).not.toBeCalled();
expect(reviewWatcher.subscribe).not.toBeCalled();
expect(eventWatcher.subscribe).not.toHaveBeenCalled();
expect(reviewWatcher.subscribe).not.toHaveBeenCalled();
resolveEntity();
await initializePromise;
expect(eventWatcher.subscribe).not.toBeCalled();
expect(eventWatcher.unsubscribe).not.toBeCalled();
expect(reviewWatcher.subscribe).not.toBeCalled();
expect(reviewWatcher.unsubscribe).not.toBeCalled();
expect(eventWatcher.subscribe).not.toHaveBeenCalled();
expect(eventWatcher.unsubscribe).not.toHaveBeenCalled();
expect(reviewWatcher.subscribe).not.toHaveBeenCalled();
expect(reviewWatcher.unsubscribe).not.toHaveBeenCalled();
});
describe('should call handler correctly', () => {
@@ -1097,7 +1097,7 @@ describe('FrigateCamera', () => {
});
if (call) {
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
type: 'new',
cameraID: 'CAMERA_1',
id: 'event-1',
@@ -1106,7 +1106,7 @@ describe('FrigateCamera', () => {
fidelity: 'high',
});
} else {
expect(eventCallback).not.toBeCalled();
expect(eventCallback).not.toHaveBeenCalled();
}
},
);
@@ -1170,7 +1170,7 @@ describe('FrigateCamera', () => {
},
});
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
type: 'end',
cameraID: 'CAMERA_1',
id: 'event-1',
@@ -1378,7 +1378,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: mock<FrigateEventWatcher>(),
frigateReviewWatcher: reviewWatcher,
});
expect(reviewWatcher.subscribe).toBeCalledWith(
expect(reviewWatcher.subscribe).toHaveBeenCalledWith(
expect.objectContaining({
instanceID: 'CLIENT_ID',
}),
@@ -1409,7 +1409,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: mock<FrigateEventWatcher>(),
frigateReviewWatcher: reviewWatcher,
});
expect(reviewWatcher.subscribe).not.toBeCalled();
expect(reviewWatcher.subscribe).not.toHaveBeenCalled();
});
it('should not subscribe to reviews with description only (no severities)', async () => {
@@ -1438,7 +1438,7 @@ describe('FrigateCamera', () => {
frigateReviewWatcher: reviewWatcher,
});
// Severities are required - description alone is not enough
expect(reviewWatcher.subscribe).not.toBeCalled();
expect(reviewWatcher.subscribe).not.toHaveBeenCalled();
});
describe('should call handler correctly', () => {
@@ -1495,7 +1495,7 @@ describe('FrigateCamera', () => {
},
});
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
type: 'new',
cameraID: 'CAMERA_1',
id: '123',
@@ -1566,7 +1566,7 @@ describe('FrigateCamera', () => {
},
});
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
type: 'update',
cameraID: 'CAMERA_1',
id: '123',
@@ -1637,7 +1637,7 @@ describe('FrigateCamera', () => {
},
});
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
type: 'update',
cameraID: 'CAMERA_1',
id: '123',
@@ -1995,7 +1995,7 @@ describe('FrigateCamera', () => {
},
});
expect(eventCallback).toBeCalledWith({
expect(eventCallback).toHaveBeenCalledWith({
type: 'end',
cameraID: 'CAMERA_1',
id: '123',
@@ -2123,7 +2123,7 @@ describe('FrigateCamera', () => {
frigateEventWatcher: mock<FrigateEventWatcher>(),
frigateReviewWatcher: mock<FrigateReviewWatcher>(),
}),
).rejects.toThrowError(/Could not find camera entity/);
).rejects.toThrow(/Could not find camera entity/);
});
});
@@ -2284,7 +2284,7 @@ describe('FrigateCamera', () => {
const executor = mock<ActionsExecutor>();
await camera.executePTZAction(executor, 'preset');
expect(executor.executeActions).not.toBeCalled();
expect(executor.executeActions).not.toHaveBeenCalled();
});
it('should ignore actions with configured action', async () => {
@@ -2316,7 +2316,7 @@ describe('FrigateCamera', () => {
const executor = mock<ActionsExecutor>();
await camera.executePTZAction(executor, 'left', { phase: 'start' });
expect(executor.executeActions).toBeCalledTimes(1);
expect(executor.executeActions).toHaveBeenCalledTimes(1);
expect(executor.executeActions).toHaveBeenLastCalledWith({
actions: {
action: 'perform-action',
@@ -38,17 +38,17 @@ import type { RawAdvancedCameraCardConfig } from '../../../src/config/types';
import { QuerySource } from '../../../src/query-source';
import type { Severity } from '../../../src/severity';
import { ViewMedia, ViewMediaType } from '../../../src/view/item';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createFrigateEvent,
createFrigateRecording,
createFrigateReview,
createHASS,
createHASSManager,
createStore,
TestViewMedia,
} from '../../test-utils';
import { TestViewMedia } from '../../view/test-utils';
import { createStore } from '../test-utils';
vi.mock('../../../src/camera-manager/frigate/requests');
+1 -1
View File
@@ -7,8 +7,8 @@ import {
FrigateViewMediaFactory,
} from '../../../src/camera-manager/frigate/media';
import { ViewMediaType } from '../../../src/view/item';
import { createCameraConfig } from '../../config/test-utils';
import {
createCameraConfig,
createFrigateEvent,
createFrigateRecording,
createFrigateReview,
+12 -12
View File
@@ -46,7 +46,7 @@ describe('frigate requests', () => {
expect(await getRecordingsSummary(hass, 'clientID', 'camera.office')).toBe(
recordingSummary,
);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
recordingSummarySchema,
expect.objectContaining({
@@ -77,7 +77,7 @@ describe('frigate requests', () => {
before: 0,
}),
).toBe(recordingSegments);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
recordingSegmentsSchema,
expect.objectContaining({
@@ -101,7 +101,7 @@ describe('frigate requests', () => {
const hass = createHASS();
retainEvent(hass, 'clientID', 'eventID', true);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
retainResultSchema,
expect.objectContaining({
@@ -121,10 +121,10 @@ describe('frigate requests', () => {
});
const hass = createHASS();
await expect(retainEvent(hass, 'clientID', 'eventID', true)).rejects.toThrowError(
await expect(retainEvent(hass, 'clientID', 'eventID', true)).rejects.toThrow(
/Could not retain event/,
);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
retainResultSchema,
expect.objectContaining({
@@ -157,7 +157,7 @@ describe('frigate requests', () => {
favorites: true,
}),
).toBe(events);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
frigateEventsSchema,
expect.objectContaining({
@@ -193,7 +193,7 @@ describe('frigate requests', () => {
vi.mocked(homeAssistantWSRequest).mockResolvedValue(eventSummary);
expect(await getEventSummary(hass, 'clientID')).toBe(eventSummary);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
eventSummarySchema,
expect.objectContaining({
@@ -216,7 +216,7 @@ describe('frigate requests', () => {
const hass = createHASS();
vi.mocked(homeAssistantWSRequest).mockResolvedValue(ptzInfo);
expect(await getPTZInfo(hass, 'clientID', 'camera.office')).toBe(ptzInfo);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
ptzInfoSchema,
expect.objectContaining({
@@ -259,7 +259,7 @@ describe('frigate requests', () => {
reviewed: false,
}),
).toBe(reviews);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
frigateReviewsSchema,
expect.objectContaining({
@@ -288,7 +288,7 @@ describe('frigate requests', () => {
const hass = createHASS();
setReviewsReviewed(hass, 'clientID', ['review_id'], true);
expect(homeAssistantWSRequest).toBeCalledWith(
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
reviewResultSchema,
expect.objectContaining({
@@ -309,8 +309,8 @@ describe('frigate requests', () => {
const hass = createHASS();
await expect(
setReviewsReviewed(hass, 'clientID', ['review_id'], true),
).rejects.toThrowError(/Failed to receive response from Home Assistant/);
expect(homeAssistantWSRequest).toBeCalledWith(
).rejects.toThrow(/Failed to receive response from Home Assistant/);
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
hass,
reviewResultSchema,
expect.objectContaining({
+1 -1
View File
@@ -15,8 +15,8 @@ import {
getReviewTitle,
} from '../../../src/camera-manager/frigate/util';
import type { CameraConfig } from '../../../src/config/schema/cameras';
import { createCameraConfig } from '../../config/test-utils';
import {
createCameraConfig,
createFrigateEvent,
createFrigateRecording,
createFrigateReview,
+12 -12
View File
@@ -93,7 +93,7 @@ describe('FrigateEventWatcher', () => {
watcher.subscribe({ instanceID: 'frigate', callback: vi.fn() });
await flushPromises();
expect(hass.connection.subscribeMessage).toBeCalledWith(
expect(hass.connection.subscribeMessage).toHaveBeenCalledWith(
expect.any(Function),
expect.objectContaining({
type: 'frigate/events/subscribe',
@@ -116,7 +116,7 @@ describe('FrigateEventWatcher', () => {
watcher.unsubscribe(request);
await flushPromises();
expect(unsub).toBeCalledTimes(1);
expect(unsub).toHaveBeenCalledTimes(1);
});
it('should drop messages from an old-connection subscription after a swap', async () => {
@@ -140,7 +140,7 @@ describe('FrigateEventWatcher', () => {
await flushPromises();
oldDispatcher(JSON.stringify(createEventChange()));
expect(callback).not.toBeCalled();
expect(callback).not.toHaveBeenCalled();
});
describe('should call handler', () => {
@@ -156,8 +156,8 @@ describe('FrigateEventWatcher', () => {
await flushPromises();
fireMessage(hass, 'NOT_JSON');
expect(callback).not.toBeCalled();
expect(spy).toBeCalledWith(
expect(callback).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(
'Received non-JSON payload from subscription: frigate/events/subscribe',
'NOT_JSON',
);
@@ -176,8 +176,8 @@ describe('FrigateEventWatcher', () => {
const data = JSON.stringify({});
fireMessage(hass, data);
expect(callback).not.toBeCalled();
expect(spy).toBeCalledWith(
expect(callback).not.toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(
'Received malformed message from subscription: frigate/events/subscribe',
data,
);
@@ -194,7 +194,7 @@ describe('FrigateEventWatcher', () => {
const eventChange = createEventChange();
fireMessage(hass, JSON.stringify(eventChange));
expect(callback).toBeCalledWith(eventChange);
expect(callback).toHaveBeenCalledWith(eventChange);
});
it('with a matcher', async () => {
@@ -219,8 +219,8 @@ describe('FrigateEventWatcher', () => {
const eventChange = createEventChange();
fireMessage(hass, JSON.stringify(eventChange));
expect(non_matching_callback).not.toBeCalledWith(eventChange);
expect(matching_callback).toBeCalledWith(eventChange);
expect(non_matching_callback).not.toHaveBeenCalledWith(eventChange);
expect(matching_callback).toHaveBeenCalledWith(eventChange);
});
});
});
@@ -239,7 +239,7 @@ describe('FrigateReviewWatcher', () => {
watcher.subscribe({ instanceID: 'frigate', callback });
await flushPromises();
expect(hass.connection.subscribeMessage).toBeCalledWith(
expect(hass.connection.subscribeMessage).toHaveBeenCalledWith(
expect.any(Function),
expect.objectContaining({
type: 'frigate/reviews/subscribe',
@@ -249,6 +249,6 @@ describe('FrigateReviewWatcher', () => {
const reviewChange = createReviewChange();
fireMessage(hass, JSON.stringify(reviewChange));
expect(callback).toBeCalledWith(reviewChange);
expect(callback).toHaveBeenCalledWith(reviewChange);
});
});
@@ -5,14 +5,10 @@ import { Engine, QueryResultsType, QueryType } from '../../../src/camera-manager
import type { CameraConfig } from '../../../src/config/schema/cameras';
import type { RawAdvancedCameraCardConfig } from '../../../src/config/types';
import { QuerySource } from '../../../src/query-source';
import {
createCameraConfig,
createHASS,
createHASSManager,
createStateEntity,
createStore,
TestViewMedia,
} from '../../test-utils';
import { createCameraConfig } from '../../config/test-utils';
import { createHASS, createHASSManager, createStateEntity } from '../../test-utils';
import { TestViewMedia } from '../../view/test-utils';
import { createStore } from '../test-utils';
const createEngine = (): GenericCameraManagerEngine => {
return new GenericCameraManagerEngine(createHASSManager());
+27 -22
View File
@@ -36,17 +36,10 @@ import { QuerySource } from '../../src/query-source.js';
import { PTZMovementType, type Endpoint } from '../../src/types.js';
import { ViewFolder, type ViewItem, type ViewMedia } from '../../src/view/item.js';
import type { ViewItemCapabilities } from '../../src/view/types.js';
import {
createCameraConfig,
createCapabilities,
createCardAPI,
createConfig,
createFolder,
createHASS,
createInitializedCamera,
generateViewMediaArray,
TestViewMedia,
} from '../test-utils.js';
import { createCameraConfig, createConfig } from '../config/test-utils';
import { createCardAPI, createFolder, createHASS } from '../test-utils.js';
import { generateViewMediaArray, TestViewMedia } from '../view/test-utils';
import { createCapabilities, createInitializedCamera } from './test-utils';
describe('QueryClassifier', () => {
it('should classify event query', () => {
@@ -481,7 +474,7 @@ describe('CameraManager', () => {
type: 'new',
};
eventCallback?.(cameraEvent);
expect(api.getCameraTriggersManager().handleCameraEvent).toBeCalledWith(
expect(api.getCameraTriggersManager().handleCameraEvent).toHaveBeenCalledWith(
cameraEvent,
);
});
@@ -505,7 +498,7 @@ describe('CameraManager', () => {
]);
await manager.initializeCamerasFromConfig();
expect(api.getEntityRegistryManager().fetchEntityList).toBeCalled();
expect(api.getEntityRegistryManager().fetchEntityList).toHaveBeenCalled();
},
);
@@ -516,7 +509,7 @@ describe('CameraManager', () => {
const manager = createCameraManager(api);
await manager.initializeCamerasFromConfig();
expect(api.getEntityRegistryManager().fetchEntityList).not.toBeCalled();
expect(api.getEntityRegistryManager().fetchEntityList).not.toHaveBeenCalled();
});
});
@@ -710,7 +703,7 @@ describe('CameraManager', () => {
const results = new Map([[baseEventQuery, baseEventQueryResults]]);
engine.getEvents.mockResolvedValue(results);
expect(await manager.getEvents(baseEventQuery, engineOptions)).toEqual(results);
expect(engine.getEvents).toBeCalledWith(
expect(engine.getEvents).toHaveBeenCalledWith(
hass,
expect.anything(),
baseEventQuery,
@@ -743,7 +736,12 @@ describe('CameraManager', () => {
await manager.reviewMedia(media, true);
expect(engine.reviewMedia).toBeCalledWith(hass, expect.anything(), media, true);
expect(engine.reviewMedia).toHaveBeenCalledWith(
hass,
expect.anything(),
media,
true,
);
});
});
@@ -1210,7 +1208,7 @@ describe('CameraManager', () => {
const manager = createCameraManager(createCardAPI(), engine);
manager.favoriteMedia(new TestViewMedia(), true);
expect(engine.favoriteMedia).not.toBeCalled();
expect(engine.favoriteMedia).not.toHaveBeenCalled();
});
it('should succeed', async () => {
@@ -1225,7 +1223,12 @@ describe('CameraManager', () => {
const media = new TestViewMedia({ cameraID: 'id' });
manager.favoriteMedia(media, true);
expect(engine.favoriteMedia).toBeCalledWith(hass, expect.anything(), media, true);
expect(engine.favoriteMedia).toHaveBeenCalledWith(
hass,
expect.anything(),
media,
true,
);
});
});
@@ -1396,7 +1399,7 @@ describe('CameraManager', () => {
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
manager.executePTZAction('another', 'left');
expect(api.getActionsManager().executeActions).toBeCalledWith({
expect(api.getActionsManager().executeActions).toHaveBeenCalledWith({
actions: action,
});
});
@@ -1425,7 +1428,9 @@ describe('CameraManager', () => {
manager.executePTZAction('another', 'left');
expect(api.getActionsManager().executeActions).toBeCalledWith({ actions: action });
expect(api.getActionsManager().executeActions).toHaveBeenCalledWith({
actions: action,
});
});
describe('with rotation', () => {
@@ -1544,7 +1549,7 @@ describe('CameraManager', () => {
preset: presetAction,
};
expect(api.getActionsManager().executeActions).toBeCalledWith({
expect(api.getActionsManager().executeActions).toHaveBeenCalledWith({
actions: expectedActionMap[expectedAction],
});
},
@@ -1657,7 +1662,7 @@ describe('CameraManager', () => {
});
expect(await manager.getMediaSeekTime(media, middleTime)).toBe(42);
expect(engine.getMediaSeekTime).toBeCalledWith(
expect(engine.getMediaSeekTime).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
media,
@@ -3,12 +3,9 @@ import { mock } from 'vitest-mock-extended';
import type { CameraManagerEngine } from '../../../src/camera-manager/engine';
import { MotionEyeCamera } from '../../../src/camera-manager/motioneye/camera';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASSManager,
createRegistryEntity,
} from '../../test-utils';
import { createHASSManager, createRegistryEntity } from '../../test-utils';
const cameraEntity = createRegistryEntity({
entity_id: 'camera.motioneye',
@@ -22,9 +22,9 @@ import {
import type { Entity } from '../../../src/ha/registry/entity/types';
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
import { QuerySource } from '../../../src/query-source';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASS,
createHASSManager,
createRegistryEntity,
+6 -6
View File
@@ -7,9 +7,9 @@ import type { CameraProxyConfig } from '../../../src/camera-manager/types';
import type { ActionsExecutor } from '../../../src/card-controller/actions/types';
import type { DeviceRegistryManager } from '../../../src/ha/registry/device';
import type { EntityRegistryManagerLive } from '../../../src/ha/registry/entity';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASS,
createHASSManager,
createRegistryEntity,
@@ -107,7 +107,7 @@ describe('ReolinkCamera', () => {
entityRegistryManager: mock<EntityRegistryManagerLive>(),
deviceRegistryManager: mock<DeviceRegistryManager>(),
}),
).rejects.toThrowError('Could not find camera entity');
).rejects.toThrow('Could not find camera entity');
});
it('without a unique_id', async () => {
@@ -130,7 +130,7 @@ describe('ReolinkCamera', () => {
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
}),
).rejects.toThrowError('Could not initialize Reolink camera');
).rejects.toThrow('Could not initialize Reolink camera');
});
it('without a valid unique_id', async () => {
@@ -153,7 +153,7 @@ describe('ReolinkCamera', () => {
entityRegistryManager,
deviceRegistryManager: mock<DeviceRegistryManager>(),
}),
).rejects.toThrowError('Could not initialize Reolink camera');
).rejects.toThrow('Could not initialize Reolink camera');
});
it('successfully with a directly connected camera', async () => {
@@ -729,7 +729,7 @@ describe('ReolinkCamera', () => {
await camera.executePTZAction(executor, 'left');
await camera.executePTZAction(executor, 'left', { phase: 'start' });
expect(executor.executeActions).not.toBeCalled();
expect(executor.executeActions).not.toHaveBeenCalled();
});
it('should ignore actions with configured action', async () => {
@@ -758,7 +758,7 @@ describe('ReolinkCamera', () => {
const executor = mock<ActionsExecutor>();
await camera.executePTZAction(executor, 'left', { phase: 'start' });
expect(executor.executeActions).toBeCalledTimes(1);
expect(executor.executeActions).toHaveBeenCalledTimes(1);
expect(executor.executeActions).toHaveBeenLastCalledWith({
actions: {
action: 'perform-action',
@@ -31,15 +31,10 @@ import type { EntityRegistryManager } from '../../../src/ha/registry/entity/type
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
import { homeAssistantWSRequest } from '../../../src/ha/ws-request';
import { QuerySource } from '../../../src/query-source';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASS,
createHASSManager,
createInitializedCamera,
createRegistryEntity,
createStore,
} from '../../test-utils';
import { createHASS, createHASSManager, createRegistryEntity } from '../../test-utils';
import { createInitializedCamera, createStore } from '../test-utils';
vi.mock('../../../src/ha/ws-request');
+10 -12
View File
@@ -9,12 +9,10 @@ import { Engine } from '../../src/camera-manager/types.js';
import type { DeviceRegistryManager } from '../../src/ha/registry/device/index.js';
import type { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
import type { ResolvedMediaCache } from '../../src/ha/resolved-media.js';
import {
createCameraConfig,
createHASSManager,
createInitializedCamera,
TestViewMedia,
} from '../test-utils.js';
import { createCameraConfig } from '../config/test-utils';
import { createHASSManager } from '../test-utils.js';
import { TestViewMedia } from '../view/test-utils';
import { createInitializedCamera } from './test-utils';
describe('CameraManagerStore', async () => {
const configVisible = createCameraConfig({
@@ -144,7 +142,7 @@ describe('CameraManagerStore', async () => {
expect(store.getCameraCount()).toBe(0);
for (const camera of cameras) {
expect(camera.destroy).toBeCalled();
expect(camera.destroy).toHaveBeenCalled();
}
});
@@ -381,10 +379,10 @@ describe('CameraManagerStore', async () => {
expect(store.getCamera('camera-3')).toBe(camera_3_new);
expect(store.getCamera('camera-4')).toBe(camera_4);
expect(camera_1.destroy).toBeCalled();
expect(camera_2.destroy).not.toBeCalled();
expect(camera_3.destroy).toBeCalled();
expect(camera_3_new.destroy).not.toBeCalled();
expect(camera_4.destroy).not.toBeCalled();
expect(camera_1.destroy).toHaveBeenCalled();
expect(camera_2.destroy).not.toHaveBeenCalled();
expect(camera_3.destroy).toHaveBeenCalled();
expect(camera_3_new.destroy).not.toHaveBeenCalled();
expect(camera_4.destroy).not.toHaveBeenCalled();
});
});
+90
View File
@@ -0,0 +1,90 @@
import { vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { Camera } from '../../src/camera-manager/camera';
import { Capabilities } from '../../src/camera-manager/capabilities';
import type { CameraManagerEngine } from '../../src/camera-manager/engine';
import { GenericCameraManagerEngine } from '../../src/camera-manager/generic/engine-generic';
import type { CameraManager } from '../../src/camera-manager/manager';
import { CameraManagerStore } from '../../src/camera-manager/store';
import { type CameraEventCallback } from '../../src/camera-manager/types';
import type { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher';
import { type CameraConfig } from '../../src/config/schema/cameras';
import type { EntityRegistryManager } from '../../src/ha/registry/entity/types';
import type { CapabilitiesRaw } from '../../src/types';
import { createCameraConfig } from '../config/test-utils';
import { createHASSManager } from '../test-utils';
export const createCapabilities = (capabilities?: CapabilitiesRaw): Capabilities => {
return new Capabilities({
'favorite-events': false,
'favorite-recordings': false,
'remote-control-entity': true,
clips: false,
live: false,
recordings: false,
seek: false,
snapshots: false,
trigger: true,
...capabilities,
});
};
export const createInitializedCamera = async (
config: CameraConfig,
engine: CameraManagerEngine,
capabilities?: Capabilities,
stateWatcher?: StateWatcherSubscriptionInterface,
): Promise<Camera> => {
const camera = new Camera(config, engine);
await camera.initialize({
hassManager: createHASSManager({ stateWatcher }),
...(capabilities ? { capabilityOptions: { capabilities } } : {}),
});
return camera;
};
export const createStore = (
cameras?: {
cameraID: string;
engine?: CameraManagerEngine;
config?: CameraConfig;
capabilities?: Capabilities | null;
eventCallback?: CameraEventCallback;
}[],
): CameraManagerStore => {
const store = new CameraManagerStore();
for (const cameraProps of cameras ?? []) {
const eventCallback = cameraProps.eventCallback ?? vi.fn();
const capabilities =
cameraProps.capabilities === undefined
? createCapabilities()
: cameraProps.capabilities ?? undefined;
const camera = new Camera(
cameraProps.config ?? createCameraConfig(),
cameraProps.engine ??
new GenericCameraManagerEngine(
createHASSManager(),
mock<EntityRegistryManager>(),
eventCallback,
),
{ eventCallback, capabilities },
);
camera.setID(cameraProps.cameraID);
store.addCamera(camera);
}
return store;
};
export const createCameraManager = (store?: CameraManagerStore): CameraManager => {
const cameraStore = store ?? createStore();
const cameraManager = mock<CameraManager>();
vi.mocked(cameraManager.getStore).mockReturnValue(cameraStore);
vi.mocked(cameraManager.getCameraCapabilities).mockImplementation(
(cameraID: string): Capabilities | null => {
return cameraStore.getCamera(cameraID)?.getCapabilities() ?? null;
},
);
return cameraManager;
};
+9 -12
View File
@@ -4,12 +4,9 @@ import { mock } from 'vitest-mock-extended';
import type { CameraManagerEngine } from '../../../src/camera-manager/engine';
import { TPLinkCamera } from '../../../src/camera-manager/tplink/camera';
import type { ActionsExecutor } from '../../../src/card-controller/actions/types';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASSManager,
createRegistryEntity,
} from '../../test-utils';
import { createHASSManager, createRegistryEntity } from '../../test-utils';
describe('TPLinkCamera', () => {
// Entity patterns from: https://github.com/dermotduffy/advanced-camera-card/issues/2183
@@ -83,7 +80,7 @@ describe('TPLinkCamera', () => {
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock(),
}),
).rejects.toThrowError('Could not find camera entity');
).rejects.toThrow('Could not find camera entity');
});
it('without a matching entity in registry', async () => {
@@ -98,7 +95,7 @@ describe('TPLinkCamera', () => {
hassManager: createHASSManager(),
entityRegistryManager: new EntityRegistryManagerMock(),
}),
).rejects.toThrowError('Could not find camera entity');
).rejects.toThrow('Could not find camera entity');
});
it('successfully with webrtc_card.entity fallback', async () => {
@@ -204,7 +201,7 @@ describe('TPLinkCamera', () => {
await camera.executePTZAction(executor, 'left', { phase: 'start' }),
).toBeFalsy();
expect(executor.executeActions).not.toBeCalled();
expect(executor.executeActions).not.toHaveBeenCalled();
});
it('should ignore zoom actions (not supported by TPLink)', async () => {
@@ -226,7 +223,7 @@ describe('TPLinkCamera', () => {
await camera.executePTZAction(executor, 'zoom_out', { phase: 'start' }),
).toBeFalsy();
expect(executor.executeActions).not.toBeCalled();
expect(executor.executeActions).not.toHaveBeenCalled();
});
it('should ignore preset actions (not supported by TPLink)', async () => {
@@ -245,7 +242,7 @@ describe('TPLinkCamera', () => {
await camera.executePTZAction(executor, 'preset', { preset: 'home' }),
).toBeFalsy();
expect(executor.executeActions).not.toBeCalled();
expect(executor.executeActions).not.toHaveBeenCalled();
});
it('should handle stop phase (no-op for TPLink)', async () => {
@@ -265,7 +262,7 @@ describe('TPLinkCamera', () => {
await camera.executePTZAction(executor, 'left', { phase: 'stop' }),
).toBeTruthy();
expect(executor.executeActions).not.toBeCalled();
expect(executor.executeActions).not.toHaveBeenCalled();
});
it.each([
@@ -353,7 +350,7 @@ describe('TPLinkCamera', () => {
const executor = mock<ActionsExecutor>();
await camera.executePTZAction(executor, 'left', { phase: 'start' });
expect(executor.executeActions).toBeCalledTimes(1);
expect(executor.executeActions).toHaveBeenCalledTimes(1);
expect(executor.executeActions).toHaveBeenLastCalledWith({
actions: {
action: 'perform-action',
@@ -2,13 +2,9 @@ import { describe, expect, it } from 'vitest';
import { TPLinkCameraManagerEngine } from '../../../src/camera-manager/tplink/engine-tplink';
import { Engine } from '../../../src/camera-manager/types';
import { createCameraConfig } from '../../config/test-utils';
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
import {
createCameraConfig,
createHASS,
createHASSManager,
createRegistryEntity,
} from '../../test-utils';
import { createHASS, createHASSManager, createRegistryEntity } from '../../test-utils';
const createEngine = (options?: {
entityRegistryManager?: EntityRegistryManagerMock;
@@ -4,7 +4,7 @@ import {
getGo2RTCMetadataEndpoint,
getGo2RTCStreamEndpoint,
} from '../../../src/camera-manager/utils/go2rtc/endpoint.js';
import { createCameraConfig } from '../../test-utils.js';
import { createCameraConfig } from '../../config/test-utils';
describe('getGo2RTCStreamEndpoint', () => {
it('with local configuration', () => {
+1 -1
View File
@@ -8,7 +8,7 @@ import {
} from '../../../src/camera-manager/utils/ptz';
import type { PTZAction } from '../../../src/config/schema/actions/custom/ptz';
import { PTZMovementType } from '../../../src/types';
import { createCameraConfig } from '../../test-utils';
import { createCameraConfig } from '../../config/test-utils';
const action = {
action: 'perform-action' as const,
@@ -22,14 +22,14 @@ import {
createLogAction,
} from '../../../src/utils/action';
import { arrayify } from '../../../src/utils/basic';
import { createConfig } from '../../config/test-utils';
import {
createCardAPI,
createConfig,
createHASS,
createMockTemplateRenderer,
createView,
stubConnectedHomeAssistant,
} from '../../test-utils';
import { createView } from '../../view/test-utils';
const createAPI = (): CardController => {
const api = createCardAPI();
@@ -215,7 +215,7 @@ describe('ActionsManager', () => {
await manager.handleInteractionEvent(
new CustomEvent<Interaction>('event', { detail: { action: 'tap' } }),
);
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
describe('should handle unexpected interactions', () => {
@@ -246,7 +246,7 @@ describe('ActionsManager', () => {
detail: { action: interaction as unknown as InteractionName },
}),
);
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
},
);
});
@@ -271,7 +271,7 @@ describe('ActionsManager', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await manager.handleCustomActionEvent(event);
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
it('should not handle generic event', async () => {
@@ -292,7 +292,7 @@ describe('ActionsManager', () => {
await manager.handleCustomActionEvent(event);
expect(handler).not.toBeCalled();
expect(handler).not.toHaveBeenCalled();
});
it('should not handle event without detail', async () => {
@@ -300,7 +300,7 @@ describe('ActionsManager', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await manager.handleCustomActionEvent(new Event('ll-custom'));
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
});
});
@@ -319,7 +319,7 @@ describe('ActionsManager', () => {
detail: { actions: createLogAction('Hello, world!') },
}),
);
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
});
@@ -334,7 +334,7 @@ describe('ActionsManager', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await manager.executeActions({ actions: createLogAction('Hello, world!') });
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
it('should execute actions', async () => {
@@ -343,7 +343,7 @@ describe('ActionsManager', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await manager.executeActions({ actions: createLogAction('Hello, world!') });
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
it('should render templates', async () => {
@@ -372,14 +372,12 @@ describe('ActionsManager', () => {
await manager.executeActions({ actions: action, config, triggerData });
expect(vi.mocked(api.getTemplateManager().renderRecursivelyAsType)).toBeCalledWith(
hass,
action,
{
conditionState,
triggerData,
},
);
expect(
vi.mocked(api.getTemplateManager().renderRecursivelyAsType),
).toHaveBeenCalledWith(hass, action, {
conditionState,
triggerData,
});
});
it('should filter actions through the lock manager before rendering them', async () => {
@@ -403,9 +401,9 @@ describe('ActionsManager', () => {
// The lock manager sees the raw (unrendered) action; only the action it
// returns is rendered and run.
expect(api.getLockManager().getAllowedActions).toBeCalledWith([rawAction]);
expect(allowedRan).toBeCalled();
expect(rawRan).not.toBeCalled();
expect(api.getLockManager().getAllowedActions).toHaveBeenCalledWith([rawAction]);
expect(allowedRan).toHaveBeenCalled();
expect(rawRan).not.toHaveBeenCalled();
});
it('should render each action against the state at its turn', async () => {
@@ -481,7 +479,7 @@ describe('ActionsManager', () => {
expect(ran).toEqual(['one', 'two']);
expect(
vi.mocked(api.getTemplateManager().renderRecursivelyAsType),
).toBeCalledTimes(1);
).toHaveBeenCalledTimes(1);
});
it('should abort the remaining actions when one fails to render', async () => {
@@ -519,7 +517,7 @@ describe('ActionsManager', () => {
// The first action ran; the second's render threw, aborting the rest. The
// error was caught by executeActions().
expect(ran).toEqual(['first']);
expect(warnSpy).toBeCalled();
expect(warnSpy).toHaveBeenCalled();
});
it('should hand an if-action branch to the executor unrendered, with the trigger data', async () => {
@@ -549,7 +547,7 @@ describe('ActionsManager', () => {
// The branch is left raw (template intact) and forwarded with the trigger
// data, so the nested executor renders it per-step when it runs -- not
// frozen against the state at the `if` step.
expect(api.getActionsManager().executeNestedActions).toBeCalledWith({
expect(api.getActionsManager().executeNestedActions).toHaveBeenCalledWith({
actions: [thenAction],
config: undefined,
triggerData: { platform: 'state', entity_id: 'binary_sensor.door' },
@@ -557,7 +555,7 @@ describe('ActionsManager', () => {
// The log action is handed to the (mocked) nested executor, not run here,
// so it must not actually log.
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
});
it('should render if-action branch actions per-step', async () => {
@@ -633,7 +631,7 @@ describe('ActionsManager', () => {
await manager.executeActions({ actions: createLogAction('Blocked') });
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
});
describe('should forward haptics', () => {
@@ -651,7 +649,9 @@ describe('ActionsManager', () => {
await manager.executeActions({ actions: { action: 'none' } });
expect(handler).toBeCalledWith(expect.objectContaining({ detail: 'success' }));
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({ detail: 'success' }),
);
});
it('should forward warning haptic', async () => {
@@ -669,7 +669,9 @@ describe('ActionsManager', () => {
actions: { action: 'none', confirmation: true },
});
expect(handler).toBeCalledWith(expect.objectContaining({ detail: 'warning' }));
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({ detail: 'warning' }),
);
});
});
});
@@ -712,7 +714,7 @@ describe('ActionsManager', () => {
await promise;
// Action set will not continue.
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
});
});
});
@@ -44,7 +44,7 @@ describe('should handle base action', () => {
await action.execute(api);
expect(confirm).not.toBeCalled();
expect(confirm).not.toHaveBeenCalled();
});
it('should continue execution when confirmed', async () => {
@@ -61,7 +61,7 @@ describe('should handle base action', () => {
await action.execute(api);
expect(confirm).toBeCalled();
expect(confirm).toHaveBeenCalled();
});
it('should abort execution when not confirmed', async () => {
@@ -76,7 +76,7 @@ describe('should handle base action', () => {
vi.mocked(confirm).mockReturnValue(false);
expect(async () => await action.execute(api)).rejects.toThrowError(/Aborted action/);
expect(async () => await action.execute(api)).rejects.toThrow(/Aborted action/);
});
it('should not confirm when exempted', async () => {
@@ -100,7 +100,7 @@ describe('should handle base action', () => {
await action.execute(api);
expect(confirm).not.toBeCalled();
expect(confirm).not.toHaveBeenCalled();
});
describe('should show correct confirmation text', () => {
@@ -121,7 +121,7 @@ describe('should handle base action', () => {
await action.execute(api);
expect(confirm).toBeCalledWith(
expect(confirm).toHaveBeenCalledWith(
'Are you sure you want to perform this action: more-info',
);
});
@@ -143,7 +143,7 @@ describe('should handle base action', () => {
await action.execute(api);
expect(confirm).toBeCalledWith(
expect(confirm).toHaveBeenCalledWith(
'Are you sure you want to perform this action: clips',
);
});
@@ -167,7 +167,7 @@ describe('should handle base action', () => {
await action.execute(api);
expect(confirm).toBeCalledWith('Test text');
expect(confirm).toHaveBeenCalledWith('Test text');
});
});
});
@@ -15,5 +15,5 @@ it('should handle call_answer action', async () => {
await action.execute(api);
expect(api.getCallManager().answer).toBeCalled();
expect(api.getCallManager().answer).toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle call_end action', async () => {
await action.execute(api);
expect(api.getCallManager().end).toBeCalled();
expect(api.getCallManager().end).toHaveBeenCalled();
});
@@ -20,7 +20,7 @@ describe('CallServiceAction', () => {
);
await action.execute(api);
expect(hass.callService).toBeCalledWith(
expect(hass.callService).toHaveBeenCalledWith(
'light',
'turn_on',
{
@@ -15,7 +15,7 @@ it('should handle call_start action without a camera or stream', async () => {
await action.execute(api);
expect(api.getCallManager().start).toBeCalledWith({
expect(api.getCallManager().start).toHaveBeenCalledWith({
cameraID: undefined,
streamID: undefined,
});
@@ -35,7 +35,7 @@ it('should handle call_start action with a camera and stream', async () => {
await action.execute(api);
expect(api.getCallManager().start).toBeCalledWith({
expect(api.getCallManager().start).toHaveBeenCalledWith({
cameraID: 'camera.front',
streamID: 'camera.front_doorbell',
});
@@ -1,7 +1,9 @@
import { describe, expect, it, vi } from 'vitest';
import { CameraSelectAction } from '../../../../src/card-controller/actions/actions/camera-select';
import { createCardAPI, createConfig, createView } from '../../../test-utils';
import { createConfig } from '../../../config/test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView } from '../../../view/test-utils';
describe('should handle camera_select action', () => {
it('with valid camera and view', async () => {
@@ -19,7 +21,7 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith(
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith(
expect.objectContaining({
params: {
view: 'live',
@@ -49,7 +51,7 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
});
it('without config', async () => {
@@ -72,7 +74,7 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith(
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith(
expect.objectContaining({
params: {
view: 'timeline',
@@ -110,7 +112,7 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith(
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith(
expect.objectContaining({
params: {
view: 'clips',
@@ -139,7 +141,7 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith(
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith(
expect.objectContaining({
params: {
view: 'live',
@@ -167,7 +169,7 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
});
it('without a current view', async () => {
@@ -184,6 +186,6 @@ describe('should handle camera_select action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
});
});
@@ -15,5 +15,5 @@ it('should handle camera_ui action', async () => {
await action.execute(api);
expect(api.getCameraURLManager().openURL).toBeCalled();
expect(api.getCameraURLManager().openURL).toHaveBeenCalled();
});
@@ -29,7 +29,7 @@ describe('CustomAction', () => {
await action.execute(api);
expect(handler).toBeCalledWith(
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
detail: { action: 'fire-dom-event', foo: 'bar', 1: 2 },
}),
@@ -15,5 +15,5 @@ it('should handle default action', async () => {
await action.execute(api);
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
@@ -16,7 +16,7 @@ it('should handle default action', async () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({
params: {
displayMode: 'grid',
},
@@ -2,7 +2,8 @@ import { expect, it, vi } from 'vitest';
import { DownloadAction } from '../../../../src/card-controller/actions/actions/download';
import { QueryResults } from '../../../../src/view/query-results';
import { createCardAPI, createView, TestViewMedia } from '../../../test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView, TestViewMedia } from '../../../view/test-utils';
it('should handle download action with selected media', async () => {
const api = createCardAPI();
@@ -23,7 +24,7 @@ it('should handle download action with selected media', async () => {
await action.execute(api);
expect(api.getViewItemManager().download).toBeCalledWith(selectedMedia);
expect(api.getViewItemManager().download).toHaveBeenCalledWith(selectedMedia);
});
it('should handle download action without selected media', async () => {
@@ -40,5 +41,5 @@ it('should handle download action without selected media', async () => {
await action.execute(api);
expect(api.getViewItemManager().download).not.toBeCalled();
expect(api.getViewItemManager().download).not.toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle expand action', async () => {
await action.execute(api);
expect(api.getExpandManager().toggleExpanded).toBeCalled();
expect(api.getExpandManager().toggleExpanded).toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle fullscreen action', async () => {
await action.execute(api);
expect(api.getFullscreenManager().toggleFullscreen).toBeCalled();
expect(api.getFullscreenManager().toggleFullscreen).toHaveBeenCalled();
});
@@ -17,7 +17,7 @@ describe('GeneratedAction', () => {
await action.execute(api);
expect(api.getActionsManager().executeNestedActions).toBeCalledWith({
expect(api.getActionsManager().executeNestedActions).toHaveBeenCalledWith({
actions: generated,
config: undefined,
triggerData: undefined,
@@ -37,7 +37,7 @@ describe('GeneratedAction', () => {
await action.execute(api);
expect(api.getActionsManager().executeNestedActions).toBeCalledWith({
expect(api.getActionsManager().executeNestedActions).toHaveBeenCalledWith({
actions: generated,
config: undefined,
triggerData: undefined,
@@ -53,7 +53,7 @@ describe('GeneratedAction', () => {
await action.execute(api);
expect(api.getActionsManager().executeNestedActions).not.toBeCalled();
expect(api.getActionsManager().executeNestedActions).not.toHaveBeenCalled();
});
it('should pass the api and trigger data to the generator', async () => {
@@ -72,6 +72,6 @@ describe('GeneratedAction', () => {
await action.execute(api);
expect(generator).toBeCalledWith({ api, triggerData });
expect(generator).toHaveBeenCalledWith({ api, triggerData });
});
});
@@ -35,7 +35,7 @@ describe('IfAction', () => {
await action.execute(api);
expect(api.getActionsManager().executeNestedActions).toBeCalledWith({
expect(api.getActionsManager().executeNestedActions).toHaveBeenCalledWith({
actions: thenActions,
config: undefined,
triggerData: undefined,
@@ -59,7 +59,7 @@ describe('IfAction', () => {
await action.execute(api);
expect(api.getActionsManager().executeNestedActions).toBeCalledWith({
expect(api.getActionsManager().executeNestedActions).toHaveBeenCalledWith({
actions: elseActions,
config: undefined,
triggerData: undefined,
@@ -82,6 +82,6 @@ describe('IfAction', () => {
await action.execute(api);
expect(api.getActionsManager().executeNestedActions).not.toBeCalled();
expect(api.getActionsManager().executeNestedActions).not.toHaveBeenCalled();
});
});
@@ -3,7 +3,8 @@ import { describe, expect, it, vi } from 'vitest';
import { InfoAction } from '../../../../src/card-controller/actions/actions/info';
import { QueryResults } from '../../../../src/view/query-results';
import { View } from '../../../../src/view/view';
import { createCardAPI, TestViewMedia } from '../../../test-utils';
import { createCardAPI } from '../../../test-utils';
import { TestViewMedia } from '../../../view/test-utils';
describe('InfoAction', () => {
it('should handle info action with media', async () => {
@@ -29,7 +30,7 @@ describe('InfoAction', () => {
await action.execute(api);
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should not handle info action without media', async () => {
@@ -46,6 +47,6 @@ describe('InfoAction', () => {
await action.execute(api);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
});
@@ -18,5 +18,5 @@ it('should handle internal callback action', async () => {
await action.execute(api);
expect(callback).toBeCalledWith(api);
expect(callback).toHaveBeenCalledWith(api);
});
@@ -21,5 +21,5 @@ it('should handle log action', async () => {
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
await action.execute(api);
expect(spy).toBeCalledWith('Hello, world!');
expect(spy).toHaveBeenCalledWith('Hello, world!');
});
@@ -1,7 +1,8 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { MediaPlayerAction } from '../../../../src/card-controller/actions/actions/media-player';
import { createCardAPI, createView, createViewWithMedia } from '../../../test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView, createViewWithMedia } from '../../../view/test-utils';
afterEach(() => {
vi.resetAllMocks();
@@ -23,7 +24,9 @@ describe('should handle media_player action', () => {
await action.execute(api);
expect(api.getMediaPlayerManager().stop).toBeCalledWith('this_is_a_media_player');
expect(api.getMediaPlayerManager().stop).toHaveBeenCalledWith(
'this_is_a_media_player',
);
});
it('to play live', async () => {
@@ -47,7 +50,7 @@ describe('should handle media_player action', () => {
await action.execute(api);
expect(api.getMediaPlayerManager().playLive).toBeCalledWith(
expect(api.getMediaPlayerManager().playLive).toHaveBeenCalledWith(
'this_is_a_media_player',
'camera',
);
@@ -74,7 +77,7 @@ describe('should handle media_player action', () => {
await action.execute(api);
expect(api.getMediaPlayerManager().playMedia).toBeCalledWith(
expect(api.getMediaPlayerManager().playMedia).toHaveBeenCalledWith(
'this_is_a_media_player',
view.queryResults?.getSelectedResult(),
);
@@ -100,7 +103,7 @@ describe('should handle media_player action', () => {
await action.execute(api);
expect(api.getMediaPlayerManager().playMedia).not.toBeCalled();
expect(api.getMediaPlayerManager().playMedia).not.toHaveBeenCalled();
});
it('to not play live without camera', async () => {
@@ -124,6 +127,6 @@ describe('should handle media_player action', () => {
await action.execute(api);
expect(api.getMediaPlayerManager().playLive).not.toBeCalled();
expect(api.getMediaPlayerManager().playLive).not.toHaveBeenCalled();
});
});
@@ -15,5 +15,5 @@ it('should handle menu toggle action', async () => {
await action.execute(api);
expect(api.getCardElementManager().toggleMenu).toBeCalled();
expect(api.getCardElementManager().toggleMenu).toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle microphone_connect action', async () => {
await action.execute(api);
expect(api.getMicrophoneManager().connect).toBeCalled();
expect(api.getMicrophoneManager().connect).toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle microphone_disconnect action', async () => {
await action.execute(api);
expect(api.getMicrophoneManager().disconnect).toBeCalled();
expect(api.getMicrophoneManager().disconnect).toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle microphone_mute action', async () => {
await action.execute(api);
expect(api.getMicrophoneManager().mute).toBeCalled();
expect(api.getMicrophoneManager().mute).toHaveBeenCalled();
});
@@ -15,5 +15,5 @@ it('should handle microphone_unmute action', async () => {
await action.execute(api);
expect(api.getMicrophoneManager().unmute).toBeCalled();
expect(api.getMicrophoneManager().unmute).toHaveBeenCalled();
});
@@ -24,7 +24,7 @@ describe('should handle more-info action', () => {
await action.execute(api);
expect(handler).toBeCalledWith(
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
detail: { entityId: 'light.office' },
}),
@@ -51,7 +51,7 @@ describe('should handle more-info action', () => {
await action.execute(api);
expect(handler).toBeCalledWith(
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
detail: { entityId: 'light.office' },
}),
@@ -76,6 +76,6 @@ describe('should handle more-info action', () => {
await action.execute(api);
expect(handler).not.toBeCalled();
expect(handler).not.toHaveBeenCalled();
});
});
@@ -23,5 +23,5 @@ it('should handle mute action', async () => {
await action.execute(api);
expect(mediaPlayerController.mute).toBeCalled();
expect(mediaPlayerController.mute).toHaveBeenCalled();
});
@@ -23,7 +23,7 @@ describe('should handle navigate action', () => {
await action.execute(createCardAPI());
expect(history.length).toBe(historyLength + 1);
expect(handler).toBeCalledWith(
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
detail: { replace: false },
}),
@@ -49,7 +49,7 @@ describe('should handle navigate action', () => {
await action.execute(createCardAPI());
expect(history.length).toBe(historyLength);
expect(handler).toBeCalledWith(
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({
detail: { replace: true },
}),
@@ -25,5 +25,5 @@ it('should handle pause action', async () => {
await action.execute(api);
expect(mediaPlayerController.playback?.pause).toBeCalled();
expect(mediaPlayerController.playback?.pause).toHaveBeenCalled();
});
@@ -20,7 +20,7 @@ describe('PerformActionAction', () => {
);
await action.execute(api);
expect(hass.callService).toBeCalledWith(
expect(hass.callService).toHaveBeenCalledWith(
'light',
'turn_on',
{
@@ -15,5 +15,5 @@ it('should toggle PIP', async () => {
await action.execute(api);
expect(api.getPIPManager().togglePIP).toBeCalled();
expect(api.getPIPManager().togglePIP).toHaveBeenCalled();
});
@@ -25,5 +25,5 @@ it('should handle play action', async () => {
await action.execute(api);
expect(mediaPlayerController.playback?.play).toBeCalled();
expect(mediaPlayerController.playback?.play).toHaveBeenCalled();
});
@@ -20,7 +20,7 @@ describe('PTZControlsAction', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
ptzControls: { enabled: true, type: 'buttons' },
});
});
@@ -41,7 +41,7 @@ describe('PTZControlsAction', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
ptzControls: { enabled: false },
});
});
@@ -59,7 +59,7 @@ describe('PTZControlsAction', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
ptzControls: {},
});
});
@@ -78,7 +78,7 @@ describe('PTZControlsAction', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
ptzControls: { type: 'gestures' },
});
});
@@ -100,7 +100,7 @@ describe('PTZControlsAction', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
ptzControls: { type: 'buttons' },
});
});
@@ -6,7 +6,8 @@ import type {
ZoomSettingsObserved,
} from '../../../../src/components-lib/zoom/types';
import type { PTZAction } from '../../../../src/config/schema/actions/custom/ptz';
import { createCardAPI, createView } from '../../../test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView } from '../../../view/test-utils';
describe('should handle ptz digital action', () => {
const defaultSettings = {
@@ -47,7 +48,7 @@ describe('should handle ptz digital action', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
zoom: {
camera: {
observed: undefined,
@@ -77,7 +78,7 @@ describe('should handle ptz digital action', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
zoom: {
camera: {
observed: undefined,
@@ -101,7 +102,7 @@ describe('should handle ptz digital action', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalledWith();
expect(api.getViewManager().setViewWithMergedContext).not.toHaveBeenCalledWith();
});
it('should do nothing without a camera', async () => {
@@ -125,7 +126,7 @@ describe('should handle ptz digital action', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
expect(api.getViewManager().setViewWithMergedContext).not.toHaveBeenCalled();
});
describe('should honor ptz_action', () => {
@@ -338,7 +339,7 @@ describe('should handle ptz digital action', () => {
await action.execute(api);
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledWith({
zoom: {
camera: {
observed: undefined,
@@ -428,12 +429,12 @@ describe('should handle ptz digital action', () => {
},
},
});
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(2);
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledTimes(2);
action.stop();
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(2);
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledTimes(2);
});
it('stop', async () => {
@@ -463,7 +464,7 @@ describe('should handle ptz digital action', () => {
},
},
});
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(1);
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledTimes(1);
const stopAction = new PTZDigitalAction(context, {
action: 'fire-dom-event',
@@ -474,7 +475,7 @@ describe('should handle ptz digital action', () => {
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(1);
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenCalledTimes(1);
});
});
});
@@ -3,12 +3,9 @@ import { describe, expect, it, vi } from 'vitest';
import { Capabilities } from '../../../../src/camera-manager/capabilities';
import { PTZMultiAction } from '../../../../src/card-controller/actions/actions/ptz-multi';
import { PTZMovementType } from '../../../../src/types';
import {
createCameraManager,
createCardAPI,
createStore,
createView,
} from '../../../test-utils';
import { createCameraManager, createStore } from '../../../camera-manager/test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView } from '../../../view/test-utils';
describe('should handle ptz multi action', () => {
describe.each([
@@ -42,7 +39,7 @@ describe('should handle ptz multi action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
@@ -50,7 +47,7 @@ describe('should handle ptz multi action', () => {
preset: undefined,
},
);
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
expect(api.getViewManager().setViewWithMergedContext).not.toHaveBeenCalled();
});
it('should use digital ptz when camera does not have ptz support', async () => {
@@ -79,7 +76,7 @@ describe('should handle ptz multi action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
expect(api.getCameraManager().executePTZAction).not.toHaveBeenCalled();
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenLastCalledWith({
zoom: {
'camera.office': {
@@ -117,8 +114,8 @@ describe('should handle ptz multi action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
expect(api.getCameraManager().executePTZAction).not.toHaveBeenCalled();
expect(api.getViewManager().setViewWithMergedContext).not.toHaveBeenCalled();
});
it('should do nothing with a media-less view without an explicit target_id', async () => {
@@ -146,7 +143,7 @@ describe('should handle ptz multi action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
expect(api.getCameraManager().executePTZAction).not.toHaveBeenCalled();
expect(api.getViewManager().setViewWithMergedContext).not.toHaveBeenCalled();
});
});
@@ -3,13 +3,10 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { Capabilities } from '../../../../src/camera-manager/capabilities';
import { PTZAction } from '../../../../src/card-controller/actions/actions/ptz';
import { PTZMovementType } from '../../../../src/types';
import {
createCameraConfig,
createCameraManager,
createCardAPI,
createStore,
createView,
} from '../../../test-utils';
import { createCameraManager, createStore } from '../../../camera-manager/test-utils';
import { createCameraConfig } from '../../../config/test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView } from '../../../view/test-utils';
describe('should handle ptz action', () => {
it('should execute simple action', async () => {
@@ -39,7 +36,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
@@ -74,7 +71,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
@@ -121,7 +118,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office_hd',
'left',
{
@@ -149,7 +146,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
expect(api.getCameraManager().executePTZAction).not.toHaveBeenCalled();
});
});
@@ -168,7 +165,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
expect(api.getCameraManager().executePTZAction).not.toHaveBeenCalled();
});
describe('when there is no action', () => {
@@ -195,7 +192,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'preset',
{
@@ -244,7 +241,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'preset',
{
@@ -290,7 +287,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'preset',
{
@@ -328,7 +325,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
expect(api.getCameraManager().executePTZAction).not.toHaveBeenCalled();
});
});
@@ -361,7 +358,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'preset',
{
@@ -400,7 +397,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
@@ -447,7 +444,7 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
@@ -456,7 +453,7 @@ describe('should handle ptz action', () => {
);
vi.runOnlyPendingTimers();
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
@@ -491,12 +488,12 @@ describe('should handle ptz action', () => {
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
action.stop();
vi.runOnlyPendingTimers();
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
});
});
@@ -535,20 +532,20 @@ describe('should handle ptz action', () => {
});
await startAction.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledWith(
'camera.office',
'left',
{
phase: undefined,
},
);
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
await vi.runOnlyPendingTimersAsync();
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(2);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(2);
await vi.runOnlyPendingTimersAsync();
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(3);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(3);
const stopAction = new PTZAction(context, {
action: 'fire-dom-event',
@@ -560,7 +557,7 @@ describe('should handle ptz action', () => {
// There should be no additional calls.
await vi.runOnlyPendingTimersAsync();
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(3);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(3);
});
it('should honor stop', async () => {
@@ -590,10 +587,10 @@ describe('should handle ptz action', () => {
});
await action.execute(api);
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(1);
await vi.runOnlyPendingTimersAsync();
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(2);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(2);
// Emulate the stop being called while the action is running, but before
// the *next* timer is scheduled.
@@ -604,7 +601,7 @@ describe('should handle ptz action', () => {
vi.mocked(cameraManager.executePTZAction).mockReturnValueOnce(promise);
await vi.runOnlyPendingTimersAsync();
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(3);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(3);
action.stop();
@@ -612,7 +609,7 @@ describe('should handle ptz action', () => {
await vi.runOnlyPendingTimersAsync();
// There should be no additional calls.
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(3);
expect(api.getCameraManager().executePTZAction).toHaveBeenCalledTimes(3);
});
});
});
@@ -25,6 +25,6 @@ describe('should handle reload action', async () => {
await action.execute(api);
expect(location.reload).toBeCalledTimes(1);
expect(location.reload).toHaveBeenCalledTimes(1);
});
});
@@ -34,7 +34,7 @@ describe('should handle screenshot action', async () => {
await action.execute(api);
expect(downloadURL).toBeCalledWith('screenshot-url', 'screenshot.jpg');
expect(downloadURL).toHaveBeenCalledWith('screenshot-url', 'screenshot.jpg');
});
it('should handle screenshot action without screenshot URL', async () => {
@@ -58,6 +58,6 @@ describe('should handle screenshot action', async () => {
await action.execute(api);
expect(downloadURL).not.toBeCalled();
expect(downloadURL).not.toHaveBeenCalled();
});
});
@@ -3,7 +3,8 @@ import { describe, expect, it, vi } from 'vitest';
import { SetReviewAction } from '../../../../src/card-controller/actions/actions/set-review';
import { ViewMediaType } from '../../../../src/view/item';
import { QueryResults } from '../../../../src/view/query-results';
import { createCardAPI, createView, TestViewMedia } from '../../../test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView, TestViewMedia } from '../../../view/test-utils';
describe('SetReviewAction', () => {
it('should toggle item reviewed state', async () => {
@@ -27,13 +28,13 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).toBeCalledWith(item, true);
expect(api.getViewItemManager().reviewMedia).toHaveBeenCalledWith(item, true);
// toggleReviewed mutates the item in-place
expect(item.isReviewed()).toBe(true);
// Verify UI update is triggered to refresh menu icon
expect(api.getCardElementManager().update).toBeCalled();
expect(api.getCardElementManager().update).toHaveBeenCalled();
});
it('should set reviewed to true when requested and currently false', async () => {
@@ -58,7 +59,7 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).toBeCalledWith(item, true);
expect(api.getViewItemManager().reviewMedia).toHaveBeenCalledWith(item, true);
expect(item.isReviewed()).toBe(true);
});
@@ -84,7 +85,7 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).not.toBeCalled();
expect(api.getViewItemManager().reviewMedia).not.toHaveBeenCalled();
});
it('should not act on non-review media', async () => {
@@ -108,7 +109,7 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).not.toBeCalled();
expect(api.getViewItemManager().reviewMedia).not.toHaveBeenCalled();
});
it('should not act without a view', async () => {
@@ -125,7 +126,7 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).not.toBeCalled();
expect(api.getViewItemManager().reviewMedia).not.toHaveBeenCalled();
});
it('should not act without query results', async () => {
@@ -143,7 +144,7 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).not.toBeCalled();
expect(api.getViewItemManager().reviewMedia).not.toHaveBeenCalled();
});
it('should not update UI if review action fails', async () => {
@@ -170,7 +171,7 @@ describe('SetReviewAction', () => {
);
await action.execute(api);
expect(api.getViewItemManager().reviewMedia).toBeCalledWith(item, true);
expect(api.getCardElementManager().update).not.toBeCalled();
expect(api.getViewItemManager().reviewMedia).toHaveBeenCalledWith(item, true);
expect(api.getCardElementManager().update).not.toHaveBeenCalled();
});
});
@@ -25,7 +25,7 @@ describe('ActionSet', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await set.execute(api);
expect(consoleSpy).toBeCalled();
expect(consoleSpy).toHaveBeenCalled();
});
it('should not execute invalid action', async () => {
@@ -39,7 +39,7 @@ describe('ActionSet', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await set.execute(api);
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
});
it('should stop execution', async () => {
@@ -50,6 +50,6 @@ describe('ActionSet', () => {
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
await set.stop();
await set.execute(api);
expect(consoleSpy).not.toBeCalled();
expect(consoleSpy).not.toHaveBeenCalled();
});
});
@@ -22,5 +22,5 @@ it('should handle sleep action', async () => {
await action.execute(api);
expect(sleep).toBeCalledWith(5.2);
expect(sleep).toHaveBeenCalledWith(5.2);
});
@@ -17,7 +17,9 @@ describe('should handle status bar action', () => {
await action.execute(api);
expect(api.getStatusBarItemManager().removeAllDynamicStatusBarItems).toBeCalled();
expect(
api.getStatusBarItemManager().removeAllDynamicStatusBarItems,
).toHaveBeenCalled();
});
it('add', async () => {
@@ -39,7 +41,9 @@ describe('should handle status bar action', () => {
await action.execute(api);
expect(api.getStatusBarItemManager().addDynamicStatusBarItem).toBeCalledWith(item);
expect(api.getStatusBarItemManager().addDynamicStatusBarItem).toHaveBeenCalledWith(
item,
);
});
it('remove', async () => {
@@ -61,8 +65,8 @@ describe('should handle status bar action', () => {
await action.execute(api);
expect(api.getStatusBarItemManager().removeDynamicStatusBarItem).toBeCalledWith(
item,
);
expect(
api.getStatusBarItemManager().removeDynamicStatusBarItem,
).toHaveBeenCalledWith(item);
});
});
@@ -4,7 +4,8 @@ import { SubstreamOffAction } from '../../../../src/card-controller/actions/acti
import { applyViewModifiers } from '../../../../src/card-controller/view/modifiers';
import { createSubstreamOffAction } from '../../../../src/utils/action';
import type { View } from '../../../../src/view/view';
import { createCardAPI, createView } from '../../../test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView } from '../../../view/test-utils';
// Runs the off-action for `view` and applies the modifier it produces.
const applySubstreamOff = async (
@@ -7,13 +7,13 @@ import { createSubstreamOnAction } from '../../../../src/utils/action';
import { getStreamCameraID } from '../../../../src/view/substream';
import type { View } from '../../../../src/view/view';
import {
createCameraConfig,
createCameraManager,
createCapabilities,
createCardAPI,
createStore,
createView,
} from '../../../test-utils';
} from '../../../camera-manager/test-utils';
import { createCameraConfig } from '../../../config/test-utils';
import { createCardAPI } from '../../../test-utils';
import { createView } from '../../../view/test-utils';
// A store where `camera.office` has one substream dependency, `camera.kitchen`.
const createStoreWithSubstreams = (): CameraManagerStore =>
@@ -181,6 +181,6 @@ describe('SubstreamOnAction', () => {
await new SubstreamOnAction({}, createSubstreamOnAction()).execute(api);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
});
@@ -46,9 +46,13 @@ describe('ToggleAction', () => {
const action = new ToggleAction({}, { action: 'toggle' }, { entity: entityID });
await action.execute(api);
expect(hass.callService).toBeCalledWith(expectedServiceDomain, expectedService, {
entity_id: entityID,
});
expect(hass.callService).toHaveBeenCalledWith(
expectedServiceDomain,
expectedService,
{
entity_id: entityID,
},
);
},
);
});
@@ -62,7 +66,7 @@ describe('ToggleAction', () => {
await action.execute(api);
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
});
it('should do nothing without an entity state', async () => {
@@ -77,6 +81,6 @@ describe('ToggleAction', () => {
);
await action.execute(api);
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
});
});
@@ -23,5 +23,5 @@ it('should handle unmute action', async () => {
await action.execute(api);
expect(mediaPlayerController.unmute).toBeCalled();
expect(mediaPlayerController.unmute).toHaveBeenCalled();
});
@@ -28,7 +28,7 @@ describe('should handle view action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith(
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith(
expect.objectContaining({
params: {
view: viewName,
@@ -53,7 +53,7 @@ describe('should handle folder view action', () => {
await action.execute(api);
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith(
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith(
expect.objectContaining({
params: {
view: viewName,
@@ -40,7 +40,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toHaveBeenCalled();
});
it('should do nothing without being initialized', () => {
@@ -57,7 +57,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toHaveBeenCalled();
});
it('should do nothing when an issue is present', () => {
@@ -78,7 +78,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toHaveBeenCalled();
});
});
@@ -96,17 +96,17 @@ describe('AutomationsManager', () => {
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
// It does not re-trigger while its source stays in the same state.
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
stateManager.setState({ fullscreen: false });
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(2);
});
it('should subscribe automations registered before initialization', () => {
@@ -125,7 +125,7 @@ describe('AutomationsManager', () => {
// Registered before initialization: the triggers are not yet subscribed, so
// a matching change does nothing.
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toHaveBeenCalled();
// Initialization completes and subscribes the dormant automations.
isInitialized.mockReturnValue(true);
@@ -133,7 +133,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ fullscreen: false });
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
});
it('should run actions when the ongoing conditions hold', () => {
@@ -158,7 +158,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ expand: true });
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledWith({
expect(api.getActionsManager().executeActions).toHaveBeenCalledWith({
actions: actions,
triggerData: { platform: 'acc', type: 'fullscreen' },
});
@@ -186,7 +186,7 @@ describe('AutomationsManager', () => {
// nothing runs.
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toHaveBeenCalled();
});
it('should do nothing when the actions are empty', () => {
@@ -208,7 +208,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).not.toBeCalled();
expect(api.getActionsManager().executeActions).not.toHaveBeenCalled();
});
it('should prevent automation loops', () => {
@@ -245,7 +245,7 @@ describe('AutomationsManager', () => {
stateManager.setState({ camera: camera });
expect(api.getNotificationManager().setNotification).toBeCalledWith({
expect(api.getNotificationManager().setNotification).toHaveBeenCalledWith({
heading: {
text: 'Too many nested automation calls, please check your configuration for loops',
icon: 'mdi:alert',
@@ -253,7 +253,7 @@ describe('AutomationsManager', () => {
},
});
expect(api.getActionsManager().executeActions).toBeCalledTimes(10);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(10);
});
it('should reset the nested-execution counter after an overflow', async () => {
@@ -284,7 +284,7 @@ describe('AutomationsManager', () => {
);
stateManager.setState({ camera: camera });
expect(api.getActionsManager().executeActions).toBeCalledTimes(10);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(10);
// The counter is decremented on the microtasks that resume after each
// awaited execution, so let them drain before the next batch.
@@ -296,7 +296,7 @@ describe('AutomationsManager', () => {
// again -- only possible if the counter returned to zero. A leaked counter
// (overflow returning without decrementing) would cut this batch short.
stateManager.setState({ camera: 'three' });
expect(api.getActionsManager().executeActions).toBeCalledTimes(10);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(10);
});
it('should execute actions on a matching HA bus event trigger', () => {
@@ -321,13 +321,13 @@ describe('AutomationsManager', () => {
},
]);
expect(eventWatcher.subscribe).toBeCalledTimes(1);
expect(eventWatcher.subscribe).toHaveBeenCalledTimes(1);
// Simulate an event arrival.
const event = createHASSEvent('zha_event', { command: 'press' });
vi.mocked(eventWatcher.subscribe).mock.calls[0][0].callback(event);
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
expect(
vi.mocked(api.getActionsManager().executeActions).mock.calls[0][0].triggerData,
).toEqual({ platform: 'event', event });
@@ -356,23 +356,23 @@ describe('AutomationsManager', () => {
]);
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
// Delete the fullscreen automation.
automationsManager.deleteAutomations('fullscreen');
stateManager.setState({ fullscreen: false });
stateManager.setState({ fullscreen: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(1);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(1);
stateManager.setState({ expand: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(2);
// Delete all automations.
automationsManager.deleteAutomations();
stateManager.setState({ expand: false });
stateManager.setState({ expand: true });
expect(api.getActionsManager().executeActions).toBeCalledTimes(2);
expect(api.getActionsManager().executeActions).toHaveBeenCalledTimes(2);
});
});
+85 -80
View File
@@ -16,16 +16,15 @@ import type { TriggerOfType } from '../../../src/condition-trigger/triggers/trig
import type { RingtoneConfig } from '../../../src/config/schema/live';
import type { AdvancedCameraCardConfig } from '../../../src/config/schema/types';
import { View } from '../../../src/view/view';
import { createTriggerEvaluatorContext } from '../../condition-trigger/triggers/triggers/test-utils';
import {
createCameraConfig,
createCameraManager,
createCapabilities,
createCardAPI,
createConfig,
createStore,
createView,
} from '../../test-utils';
} from '../../camera-manager/test-utils';
import { createTriggerEvaluatorContext } from '../../condition-trigger/triggers/triggers/test-utils';
import { createCameraConfig, createConfig } from '../../config/test-utils';
import { createCardAPI } from '../../test-utils';
import { createView } from '../../view/test-utils';
// Replace Ringtone with a fresh `mock<Ringtone>()` per construction so each
// CallManager gets an isolated, type-safe ringtone we can assert on. The
@@ -130,7 +129,7 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
it('should do nothing when already active for the camera', async () => {
@@ -141,7 +140,7 @@ describe('start', () => {
vi.mocked(api.getViewManager().setViewByParameters).mockClear();
expect(await manager.start()).toBe(true);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
it('should start a call on the selected camera', async () => {
@@ -149,7 +148,7 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(true);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -162,7 +161,7 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(true);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
params: { view: 'live', camera: 'camera.office' },
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
@@ -176,11 +175,11 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(true);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
expect(api.getViewManager().setViewByParameters).not.toBeCalledWith(
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalledWith(
expect.objectContaining({ params: expect.anything() }),
);
});
@@ -251,7 +250,7 @@ describe('start', () => {
expect(call?.cameraID).toBe('camera.office');
expect(call?.previousView?.view).toBe('folder');
expect(call?.previousView?.camera).toBeNull();
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
params: { view: 'live', camera: 'camera.office' },
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
@@ -280,7 +279,7 @@ describe('start', () => {
expect(call?.cameraID).toBe('camera.garage');
expect(call?.previousView?.view).toBe('live');
expect(call?.previousView?.camera).toBe('camera.office');
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
params: { view: 'live', camera: 'camera.garage' },
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
@@ -321,8 +320,8 @@ describe('start', () => {
expect(await new CallManager(api).start({ cameraID: 'camera.unknown' })).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should abort when the requested stream is not 2-way audio of the parent camera', async () => {
@@ -347,8 +346,8 @@ describe('start', () => {
}),
).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should supersede an active call on a different camera', async () => {
@@ -439,8 +438,8 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should engage the active substream when it is call-capable', async () => {
@@ -464,7 +463,7 @@ describe('start', () => {
expect(await manager.start()).toBe(true);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -493,7 +492,7 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(true);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -507,8 +506,8 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should abort when the microphone is forbidden', async () => {
@@ -519,8 +518,8 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should connect the microphone when not already connected', async () => {
@@ -532,8 +531,8 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(true);
expect(api.getMicrophoneManager().connect).toBeCalled();
expect(api.getViewManager().setViewByParameters).toBeCalled();
expect(api.getMicrophoneManager().connect).toHaveBeenCalled();
expect(api.getViewManager().setViewByParameters).toHaveBeenCalled();
});
it('should abort when connecting the microphone fails', async () => {
@@ -545,8 +544,8 @@ describe('start', () => {
expect(await new CallManager(api).start()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
});
it('should ring an inbound call without connecting the microphone', async () => {
@@ -562,8 +561,8 @@ describe('start', () => {
// that would fail must not stop the call from ringing.
expect(await manager.start({ inbound: true })).toBe(true);
expect(api.getMicrophoneManager().connect).not.toBeCalled();
expect(getRingtone().start).toBeCalled();
expect(api.getMicrophoneManager().connect).not.toHaveBeenCalled();
expect(getRingtone().start).toHaveBeenCalled();
});
it('should ring an inbound call when the microphone is forbidden', async () => {
@@ -578,7 +577,7 @@ describe('start', () => {
// microphone, and answering retries the connect.
expect(await manager.start({ inbound: true })).toBe(true);
expect(getRingtone().start).toBeCalled();
expect(getRingtone().start).toHaveBeenCalled();
});
it('should abort an inbound call when the microphone is unsupported', async () => {
@@ -593,7 +592,7 @@ describe('start', () => {
// supporting it while the page is loaded, so the call can never be taken.
expect(await manager.start({ inbound: true })).toBe(false);
expect(getRingtone().start).not.toBeCalled();
expect(getRingtone().start).not.toHaveBeenCalled();
});
});
@@ -692,7 +691,7 @@ describe('end', () => {
expect(new CallManager(api).end()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
it('should end an active call', async () => {
@@ -704,7 +703,7 @@ describe('end', () => {
expect(manager.end()).toBe(true);
expect(manager.isActive()).toBe(false);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -734,7 +733,7 @@ describe('end', () => {
expect(manager.end()).toBe(true);
// The recorded pre-call substream (`camera.sub`) is reinstated.
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -749,7 +748,9 @@ describe('end', () => {
expect(manager.end()).toBe(true);
expect(api.getViewManager().setViewByParametersWithExistingQuery).toBeCalledWith({
expect(
api.getViewManager().setViewByParametersWithExistingQuery,
).toHaveBeenCalledWith({
baseView: expect.any(View),
force: true,
});
@@ -770,11 +771,13 @@ describe('end', () => {
expect(manager.end()).toBe(true);
// No navigation: only the substream is undone.
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
expect(api.getViewManager().setViewByParametersWithExistingQuery).not.toBeCalled();
expect(
api.getViewManager().setViewByParametersWithExistingQuery,
).not.toHaveBeenCalled();
});
it('should return to a camera-less pre-call view on an explicit end', async () => {
@@ -957,7 +960,7 @@ describe('condition state changes', () => {
});
expect(manager.isActive()).toBe(false);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -979,7 +982,9 @@ describe('condition state changes', () => {
});
expect(manager.isActive()).toBe(false);
expect(api.getViewManager().setViewByParametersWithExistingQuery).not.toBeCalled();
expect(
api.getViewManager().setViewByParametersWithExistingQuery,
).not.toHaveBeenCalled();
});
it('should end the call when the view leaves live', async () => {
@@ -996,7 +1001,7 @@ describe('condition state changes', () => {
});
expect(manager.isActive()).toBe(false);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
modifiers: [expect.any(SubstreamViewModifier)],
force: true,
});
@@ -1027,7 +1032,7 @@ describe('condition state changes', () => {
new: { camera: 'camera.other' },
});
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
it('should end the call when the substream changes away', async () => {
@@ -1103,7 +1108,7 @@ describe('initialize / uninitialize', () => {
const api = createAPI();
new CallManager(api);
expect(api.getConditionStateManager().addListener).not.toBeCalled();
expect(api.getConditionStateManager().addListener).not.toHaveBeenCalled();
});
it('should register the condition state listener on initialize', () => {
@@ -1112,7 +1117,7 @@ describe('initialize / uninitialize', () => {
manager.initialize();
expect(api.getConditionStateManager().addListener).toBeCalled();
expect(api.getConditionStateManager().addListener).toHaveBeenCalled();
});
it('should remove the condition state listener on uninitialize', () => {
@@ -1123,7 +1128,7 @@ describe('initialize / uninitialize', () => {
manager.uninitialize();
expect(api.getConditionStateManager().removeListener).toBeCalledWith(listener);
expect(api.getConditionStateManager().removeListener).toHaveBeenCalledWith(listener);
});
it('should tear down any active call session on uninitialize', async () => {
@@ -1156,7 +1161,7 @@ describe('initialize / uninitialize', () => {
new: { camera: 'camera.other', view: 'live' },
});
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
});
@@ -1179,7 +1184,7 @@ describe('inbound option', () => {
expect(await new CallManager(api).start({ inbound: true })).toBe(false);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
it('should suppress notification when the microphone is unsupported', async () => {
@@ -1190,7 +1195,7 @@ describe('inbound option', () => {
expect(await new CallManager(api).start({ inbound: true })).toBe(false);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
it('should suppress notification when an explicit stream is not 2-way audio', async () => {
@@ -1203,7 +1208,7 @@ describe('inbound option', () => {
}),
).toBe(false);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
it('should suppress notification when no stream supports 2-way audio', async () => {
@@ -1219,7 +1224,7 @@ describe('inbound option', () => {
expect(await new CallManager(api).start({ inbound: true })).toBe(false);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
it('should record the call as inbound on the session', async () => {
@@ -1274,7 +1279,7 @@ describe('answer', () => {
expect(await manager.start({ inbound: true })).toBe(true);
expect(manager.getCall()?.answered).toBe(false);
expect(getRingtone().start).toBeCalled();
expect(getRingtone().start).toHaveBeenCalled();
});
it('should no-op when no call is active', async () => {
@@ -1298,8 +1303,8 @@ describe('answer', () => {
expect(await manager.answer()).toBe(false);
expect(getRingtone().stop).not.toBeCalled();
expect(api.getCardElementManager().update).not.toBeCalled();
expect(getRingtone().stop).not.toHaveBeenCalled();
expect(api.getCardElementManager().update).not.toHaveBeenCalled();
});
it('should mark answered and replace the session immutably', async () => {
@@ -1345,7 +1350,7 @@ describe('answer', () => {
expect(await manager.answer()).toBe(true);
expect(getRingtone().stop).toBeCalled();
expect(getRingtone().stop).toHaveBeenCalled();
// Timer was armed and should now be cancelled: advancing past the
// timeout must not end the (now-answered) call.
@@ -1369,7 +1374,7 @@ describe('answer', () => {
expect(await manager.answer()).toBe(true);
expect(api.getMicrophoneManager().connect).toBeCalled();
expect(api.getMicrophoneManager().connect).toHaveBeenCalled();
});
it('should silence the ringtone before the microphone connect', async () => {
@@ -1393,7 +1398,7 @@ describe('answer', () => {
// The user has acknowledged the ring, so it must stop without waiting for a
// microphone permission prompt to be dealt with.
expect(getRingtone().stop).toBeCalled();
expect(getRingtone().stop).toHaveBeenCalled();
resolveConnect();
expect(await answerPromise).toBe(true);
@@ -1414,7 +1419,7 @@ describe('answer', () => {
// Answering is an explicit user gesture, so the failure is surfaced and the
// call remains answerable.
expect(api.getNotificationManager().setNotification).toBeCalled();
expect(api.getNotificationManager().setNotification).toHaveBeenCalled();
expect(manager.getCall()?.answered).toBe(false);
});
@@ -1433,7 +1438,7 @@ describe('answer', () => {
// The card subtree depends on `getCall().answered`, which the manager
// mutates outside the view-manager epoch -- so `update()` is what drives
// the re-render through to the call-controls overlay.
expect(api.getCardElementManager().update).toBeCalled();
expect(api.getCardElementManager().update).toHaveBeenCalled();
});
it('should not mark non-inbound (outbound) calls via answer (already answered)', async () => {
@@ -1483,12 +1488,12 @@ describe('microphone usage', () => {
expect(await manager.start()).toBe(true);
expect(api.getMicrophoneManager().startUsing).toBeCalledTimes(1);
expect(api.getMicrophoneManager().stopUsing).not.toBeCalled();
expect(api.getMicrophoneManager().startUsing).toHaveBeenCalledTimes(1);
expect(api.getMicrophoneManager().stopUsing).not.toHaveBeenCalled();
manager.end();
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
});
it('should not mark the microphone in use when the start is aborted', async () => {
@@ -1500,7 +1505,7 @@ describe('microphone usage', () => {
expect(await new CallManager(api).start()).toBe(false);
expect(api.getMicrophoneManager().startUsing).not.toBeCalled();
expect(api.getMicrophoneManager().startUsing).not.toHaveBeenCalled();
});
it('should keep the microphone in use when a call supersedes another', async () => {
@@ -1522,8 +1527,8 @@ describe('microphone usage', () => {
expect(await manager.start()).toBe(true);
expect(await manager.start({ cameraID: 'camera.garage' })).toBe(true);
expect(api.getMicrophoneManager().startUsing).toBeCalledTimes(2);
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
expect(api.getMicrophoneManager().startUsing).toHaveBeenCalledTimes(2);
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
});
it('should mark the microphone unused on uninitialization', async () => {
@@ -1533,7 +1538,7 @@ describe('microphone usage', () => {
expect(await manager.start()).toBe(true);
manager.uninitialize();
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
});
it('should not mark the microphone unused when uninitializing without a call', () => {
@@ -1541,7 +1546,7 @@ describe('microphone usage', () => {
new CallManager(api).uninitialize();
expect(api.getMicrophoneManager().stopUsing).not.toBeCalled();
expect(api.getMicrophoneManager().stopUsing).not.toHaveBeenCalled();
});
});
@@ -1559,7 +1564,7 @@ describe('ringtone', () => {
expect(await manager.start({ inbound: true })).toBe(true);
expect(getRingtone().start).toBeCalledWith(expect.objectContaining(ringtone));
expect(getRingtone().start).toHaveBeenCalledWith(expect.objectContaining(ringtone));
});
it('should not start the ringtone for a non-inbound call', async () => {
@@ -1572,7 +1577,7 @@ describe('ringtone', () => {
expect(await manager.start()).toBe(true);
expect(getRingtone().start).not.toBeCalled();
expect(getRingtone().start).not.toHaveBeenCalled();
});
it("should not start the ringtone when type is 'none'", async () => {
@@ -1585,7 +1590,7 @@ describe('ringtone', () => {
expect(await manager.start({ inbound: true })).toBe(true);
expect(getRingtone().start).not.toBeCalled();
expect(getRingtone().start).not.toHaveBeenCalled();
});
it('should stop the ringtone when the call ends', async () => {
@@ -1600,7 +1605,7 @@ describe('ringtone', () => {
expect(manager.end()).toBe(true);
expect(getRingtone().stop).toBeCalled();
expect(getRingtone().stop).toHaveBeenCalled();
});
it('should stop the ringtone on uninitialize', async () => {
@@ -1615,7 +1620,7 @@ describe('ringtone', () => {
manager.uninitialize();
expect(getRingtone().stop).toBeCalled();
expect(getRingtone().stop).toHaveBeenCalled();
});
});
@@ -1764,7 +1769,7 @@ describe('session end during setState', () => {
expect(await manager.start({ inbound: true })).toBe(false);
expect(getRingtone().start).not.toBeCalled();
expect(getRingtone().start).not.toHaveBeenCalled();
expect(manager.isActive()).toBe(false);
});
});
@@ -1790,7 +1795,7 @@ describe('state changes during in-flight start', () => {
expect(await startPromise).toBe(false);
expect(manager.isActive()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
it('should not install a session when uninitialized and re-initialized mid-await', async () => {
@@ -1814,7 +1819,7 @@ describe('state changes during in-flight start', () => {
expect(await startPromise).toBe(false);
expect(manager.isActive()).toBe(false);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
it('should supersede a session installed by another start mid-await', async () => {
@@ -1853,8 +1858,8 @@ describe('state changes during in-flight start', () => {
// overwrite it, leaving exactly one live session and no stranded microphone
// marking.
expect(manager.getCall()?.cameraID).toBe('camera.garage');
expect(api.getMicrophoneManager().startUsing).toBeCalledTimes(2);
expect(api.getMicrophoneManager().stopUsing).toBeCalledTimes(1);
expect(api.getMicrophoneManager().startUsing).toHaveBeenCalledTimes(2);
expect(api.getMicrophoneManager().stopUsing).toHaveBeenCalledTimes(1);
});
it('should suppress the microphone-failure notification when uninitialized mid-await', async () => {
@@ -1876,7 +1881,7 @@ describe('state changes during in-flight start', () => {
rejectConnect(new Error('denied'));
expect(await startPromise).toBe(false);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
});
@@ -1939,7 +1944,7 @@ describe('state changes during in-flight answer', () => {
rejectConnect(new Error('denied'));
expect(await answerPromise).toBe(false);
expect(api.getNotificationManager().setNotification).not.toBeCalled();
expect(api.getNotificationManager().setNotification).not.toHaveBeenCalled();
});
it('should not answer a session that ended mid-await', async () => {
+15 -15
View File
@@ -59,25 +59,25 @@ describe('factory dispatch', () => {
it('should construct a ChimeTone for type "chime"', () => {
new Ringtone(new Set()).start({ type: 'chime', repeat: 3 });
expect(ChimeTone).toBeCalledWith(3);
expect(ChimeTone).toHaveBeenCalledWith(3);
});
it('should construct a WestminsterTone for type "westminster"', () => {
new Ringtone(new Set()).start({ type: 'westminster', repeat: 2 });
expect(WestminsterTone).toBeCalledWith(2);
expect(WestminsterTone).toHaveBeenCalledWith(2);
});
it('should construct an ArpeggioTone for type "arpeggio"', () => {
new Ringtone(new Set()).start({ type: 'arpeggio', repeat: 1 });
expect(ArpeggioTone).toBeCalledWith(1);
expect(ArpeggioTone).toHaveBeenCalledWith(1);
});
it('should construct a MelodyTone for type "melody"', () => {
new Ringtone(new Set()).start({ type: 'melody', repeat: 5 });
expect(MelodyTone).toBeCalledWith(5);
expect(MelodyTone).toHaveBeenCalledWith(5);
});
it('should construct a CustomTone for type "custom" with a URL', () => {
@@ -87,7 +87,7 @@ describe('factory dispatch', () => {
repeat: 0,
});
expect(CustomTone).toBeCalledWith('http://localhost/ring.mp3', 0);
expect(CustomTone).toHaveBeenCalledWith('http://localhost/ring.mp3', 0);
});
it('should construct no tone for type "custom" without a URL', () => {
@@ -95,7 +95,7 @@ describe('factory dispatch', () => {
ringtone.start({ type: 'custom', repeat: 0 });
expect(CustomTone).not.toBeCalled();
expect(CustomTone).not.toHaveBeenCalled();
expect(ringtone.isPlaying()).toBe(false);
});
@@ -104,7 +104,7 @@ describe('factory dispatch', () => {
ringtone.start({ type: 'none', repeat: 0 });
expect(ChimeTone).not.toBeCalled();
expect(ChimeTone).not.toHaveBeenCalled();
expect(ringtone.isPlaying()).toBe(false);
});
});
@@ -115,7 +115,7 @@ describe('start', () => {
ringtone.start(chimeConfig);
expect(lastInstance(vi.mocked(ChimeTone)).start).toBeCalled();
expect(lastInstance(vi.mocked(ChimeTone)).start).toHaveBeenCalled();
expect(ringtone.isPlaying()).toBe(true);
});
@@ -125,8 +125,8 @@ describe('start', () => {
ringtone.start(chimeConfig);
ringtone.start(chimeConfig);
expect(ChimeTone).toBeCalledTimes(1);
expect(lastInstance(vi.mocked(ChimeTone)).start).toBeCalledTimes(1);
expect(ChimeTone).toHaveBeenCalledTimes(1);
expect(lastInstance(vi.mocked(ChimeTone)).start).toHaveBeenCalledTimes(1);
});
it('should claim the lock when a tone starts', () => {
@@ -158,7 +158,7 @@ describe('lock', () => {
vi.mocked(ChimeTone).mockClear();
second.start(chimeConfig);
expect(ChimeTone).not.toBeCalled();
expect(ChimeTone).not.toHaveBeenCalled();
expect(second.isPlaying()).toBe(false);
expect(first.isPlaying()).toBe(true);
});
@@ -171,11 +171,11 @@ describe('lock', () => {
first.start(chimeConfig);
const firstTone = lastInstance(vi.mocked(ChimeTone));
first.stop();
expect(firstTone.stop).toBeCalled();
expect(firstTone.stop).toHaveBeenCalled();
second.start(chimeConfig);
expect(lastInstance(vi.mocked(ChimeTone)).start).toBeCalled();
expect(lastInstance(vi.mocked(ChimeTone)).start).toHaveBeenCalled();
expect(second.isPlaying()).toBe(true);
});
@@ -227,7 +227,7 @@ describe('stop', () => {
ringtone.start(chimeConfig);
ringtone.stop();
expect(lastInstance(vi.mocked(ChimeTone)).stop).toBeCalled();
expect(lastInstance(vi.mocked(ChimeTone)).stop).toHaveBeenCalled();
expect(lock.has(ringtone)).toBe(false);
expect(ringtone.isPlaying()).toBe(false);
});
@@ -250,7 +250,7 @@ describe('default lock', () => {
vi.mocked(ChimeTone).mockClear();
second.start(chimeConfig);
expect(ChimeTone).not.toBeCalled();
expect(ChimeTone).not.toHaveBeenCalled();
first.stop();
});
});
@@ -19,15 +19,15 @@ describe('ArpeggioTone', () => {
// G5 (783.99) at t=0.
expect(audio.oscillators[1].frequency.value).toBe(783.99);
expect(audio.oscillators[1].start).toBeCalledWith(0);
expect(audio.oscillators[1].start).toHaveBeenCalledWith(0);
// E5 (659.25) at t=0.25.
expect(audio.oscillators[4].frequency.value).toBe(659.25);
expect(audio.oscillators[4].start).toBeCalledWith(0.25);
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0.25);
// C5 (523.25) at t=0.5.
expect(audio.oscillators[7].frequency.value).toBe(523.25);
expect(audio.oscillators[7].start).toBeCalledWith(0.5);
expect(audio.oscillators[7].start).toHaveBeenCalledWith(0.5);
});
it('should use the lighter PLUCK envelope for every strike', () => {
@@ -36,15 +36,15 @@ describe('ArpeggioTone', () => {
// Sparkle / fundamental / hum peaks for every strike.
for (let strike = 0; strike < 3; strike++) {
const i = strike * 3;
expect(audio.gainParams[i].linearRampToValueAtTime).toBeCalledWith(
expect(audio.gainParams[i].linearRampToValueAtTime).toHaveBeenCalledWith(
0.05,
expect.any(Number),
);
expect(audio.gainParams[i + 1].linearRampToValueAtTime).toBeCalledWith(
expect(audio.gainParams[i + 1].linearRampToValueAtTime).toHaveBeenCalledWith(
0.13,
expect.any(Number),
);
expect(audio.gainParams[i + 2].linearRampToValueAtTime).toBeCalledWith(
expect(audio.gainParams[i + 2].linearRampToValueAtTime).toHaveBeenCalledWith(
0.04,
expect.any(Number),
);
@@ -18,7 +18,7 @@ describe('start', () => {
it('should construct an AudioContext and play one iteration', () => {
new ChimeTone(0).start();
expect(audio.audioContextCtor).toBeCalledTimes(1);
expect(audio.audioContextCtor).toHaveBeenCalledTimes(1);
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS);
});
@@ -28,7 +28,7 @@ describe('start', () => {
tone.start();
tone.start();
expect(audio.audioContextCtor).toBeCalledTimes(1);
expect(audio.audioContextCtor).toHaveBeenCalledTimes(1);
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS);
});
@@ -40,7 +40,7 @@ describe('start', () => {
new ChimeTone(0).start(onFinished);
expect(onFinished).toBeCalled();
expect(onFinished).toHaveBeenCalled();
expect(audio.oscillators).toHaveLength(0);
});
});
@@ -52,7 +52,7 @@ describe('stop', () => {
tone.stop();
expect(audio.audioContext.close).toBeCalled();
expect(audio.audioContext.close).toHaveBeenCalled();
});
it('should not fire finishedHandler on external stop', () => {
@@ -62,7 +62,7 @@ describe('stop', () => {
tone.stop();
expect(onFinished).not.toBeCalled();
expect(onFinished).not.toHaveBeenCalled();
});
it('should swallow AudioContext.close rejections silently', () => {
@@ -91,7 +91,7 @@ describe('repeat counter', () => {
vi.advanceTimersByTime(ITERATION_INTERVAL_MS);
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS * i);
}
expect(audio.audioContext.close).not.toBeCalled();
expect(audio.audioContext.close).not.toHaveBeenCalled();
});
it('should play exactly `repeat` iterations and then finish', () => {
@@ -110,8 +110,8 @@ describe('repeat counter', () => {
// decay tail, then fires the finished handler and stops.
vi.advanceTimersByTime(ITERATION_INTERVAL_MS);
expect(audio.oscillators).toHaveLength(ITERATION_OSCILLATORS * 3);
expect(onFinished).toBeCalledTimes(1);
expect(audio.audioContext.close).toBeCalled();
expect(onFinished).toHaveBeenCalledTimes(1);
expect(audio.audioContext.close).toHaveBeenCalled();
});
it('should not fire finishedHandler when stopped mid-sequence', () => {
@@ -124,6 +124,6 @@ describe('repeat counter', () => {
// Even if any stale scheduled work fires, finishedHandler stays silent.
vi.advanceTimersByTime(ITERATION_INTERVAL_MS * 10);
expect(onFinished).not.toBeCalled();
expect(onFinished).not.toHaveBeenCalled();
});
});
+30 -18
View File
@@ -21,41 +21,53 @@ describe('ChimeTone', () => {
expect(audio.oscillators[0].frequency.value).toBe(622.25 * 2);
expect(audio.oscillators[1].frequency.value).toBe(622.25);
expect(audio.oscillators[2].frequency.value).toBe(622.25 / 2);
expect(audio.oscillators[0].start).toBeCalledWith(0);
expect(audio.oscillators[1].start).toBeCalledWith(0);
expect(audio.oscillators[2].start).toBeCalledWith(0);
expect(audio.oscillators[0].start).toHaveBeenCalledWith(0);
expect(audio.oscillators[1].start).toHaveBeenCalledWith(0);
expect(audio.oscillators[2].start).toHaveBeenCalledWith(0);
// DOOOOONG -- B4 (493.88Hz) at t=0.5.
expect(audio.oscillators[3].frequency.value).toBe(493.88 * 2);
expect(audio.oscillators[4].frequency.value).toBe(493.88);
expect(audio.oscillators[5].frequency.value).toBe(493.88 / 2);
expect(audio.oscillators[3].start).toBeCalledWith(0.5);
expect(audio.oscillators[4].start).toBeCalledWith(0.5);
expect(audio.oscillators[5].start).toBeCalledWith(0.5);
expect(audio.oscillators[3].start).toHaveBeenCalledWith(0.5);
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0.5);
expect(audio.oscillators[5].start).toHaveBeenCalledWith(0.5);
});
it('should give DING a brighter, shorter bell envelope', () => {
new ChimeTone(0).start();
// Sparkle / fundamental / hum peaks for DING.
expect(audio.gainParams[0].linearRampToValueAtTime).toBeCalledWith(0.1, 0.005);
expect(audio.gainParams[1].linearRampToValueAtTime).toBeCalledWith(0.22, 0.005);
expect(audio.gainParams[2].linearRampToValueAtTime).toBeCalledWith(0.08, 0.005);
expect(audio.gainParams[0].linearRampToValueAtTime).toHaveBeenCalledWith(0.1, 0.005);
expect(audio.gainParams[1].linearRampToValueAtTime).toHaveBeenCalledWith(
0.22,
0.005,
);
expect(audio.gainParams[2].linearRampToValueAtTime).toHaveBeenCalledWith(
0.08,
0.005,
);
// Decay constants (sparkle fades fastest, hum lingers).
expect(audio.gainParams[0].setTargetAtTime).toBeCalledWith(0, 0.005, 0.3);
expect(audio.gainParams[1].setTargetAtTime).toBeCalledWith(0, 0.005, 0.8);
expect(audio.gainParams[2].setTargetAtTime).toBeCalledWith(0, 0.005, 1.2);
expect(audio.gainParams[0].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.3);
expect(audio.gainParams[1].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.8);
expect(audio.gainParams[2].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 1.2);
});
it('should give DOOOOONG a fuller, longer bell envelope', () => {
new ChimeTone(0).start();
expect(audio.gainParams[3].linearRampToValueAtTime).toBeCalledWith(0.11, 0.505);
expect(audio.gainParams[4].linearRampToValueAtTime).toBeCalledWith(0.28, 0.505);
expect(audio.gainParams[5].linearRampToValueAtTime).toBeCalledWith(0.1, 0.505);
expect(audio.gainParams[3].setTargetAtTime).toBeCalledWith(0, 0.505, 0.5);
expect(audio.gainParams[4].setTargetAtTime).toBeCalledWith(0, 0.505, 1.3);
expect(audio.gainParams[5].setTargetAtTime).toBeCalledWith(0, 0.505, 1.8);
expect(audio.gainParams[3].linearRampToValueAtTime).toHaveBeenCalledWith(
0.11,
0.505,
);
expect(audio.gainParams[4].linearRampToValueAtTime).toHaveBeenCalledWith(
0.28,
0.505,
);
expect(audio.gainParams[5].linearRampToValueAtTime).toHaveBeenCalledWith(0.1, 0.505);
expect(audio.gainParams[3].setTargetAtTime).toHaveBeenCalledWith(0, 0.505, 0.5);
expect(audio.gainParams[4].setTargetAtTime).toHaveBeenCalledWith(0, 0.505, 1.3);
expect(audio.gainParams[5].setTargetAtTime).toHaveBeenCalledWith(0, 0.505, 1.8);
});
describe('with fake timers', () => {
+16 -16
View File
@@ -59,14 +59,14 @@ describe('start', () => {
it('should construct an Audio element with the configured URL', () => {
new CustomTone('http://example/ring.mp3', 0).start();
expect(audio.ctor).toBeCalledWith('http://example/ring.mp3');
expect(audio.ctor).toHaveBeenCalledWith('http://example/ring.mp3');
});
it('should loop indefinitely when repeat is 0', () => {
new CustomTone('http://example/ring.mp3', 0).start();
expect(audio.instances[0].loop).toBe(true);
expect(audio.instances[0].play).toBeCalled();
expect(audio.instances[0].play).toHaveBeenCalled();
});
it('should re-play (not loop natively) when repeat is finite', () => {
@@ -76,14 +76,14 @@ describe('start', () => {
// Observable proof the 'ended' listener was registered: dispatching the
// event triggers a second play().
audio.instances[0].dispatchEvent(new Event('ended'));
expect(audio.instances[0].play).toBeCalledTimes(2);
expect(audio.instances[0].play).toHaveBeenCalledTimes(2);
});
it('should reset currentTime before play', () => {
new CustomTone('http://example/ring.mp3', 0).start();
expect(audio.instances[0].currentTime).toBe(0);
expect(audio.instances[0].play).toBeCalled();
expect(audio.instances[0].play).toHaveBeenCalled();
});
it('should no-op when called twice without stop', () => {
@@ -92,7 +92,7 @@ describe('start', () => {
tone.start();
tone.start();
expect(audio.ctor).toBeCalledTimes(1);
expect(audio.ctor).toHaveBeenCalledTimes(1);
});
it('should fire finishedHandler when Audio construction throws', () => {
@@ -103,7 +103,7 @@ describe('start', () => {
new CustomTone('http://example/ring.mp3', 0).start(onFinished);
expect(onFinished).toBeCalled();
expect(onFinished).toHaveBeenCalled();
});
it('should fire finishedHandler when play() rejects (e.g. autoplay block)', async () => {
@@ -116,7 +116,7 @@ describe('start', () => {
await flushPromises();
expect(onFinished).toBeCalled();
expect(onFinished).toHaveBeenCalled();
});
});
@@ -128,15 +128,15 @@ describe('repeat counter', () => {
// Iteration 1 already started by `start()`. Two more 'ended' events
// should re-play, and the third 'ended' should finish.
expect(audio.instances[0].play).toBeCalledTimes(1);
expect(audio.instances[0].play).toHaveBeenCalledTimes(1);
audio.instances[0].dispatchEvent(new Event('ended'));
expect(audio.instances[0].play).toBeCalledTimes(2);
expect(audio.instances[0].play).toHaveBeenCalledTimes(2);
audio.instances[0].dispatchEvent(new Event('ended'));
expect(audio.instances[0].play).toBeCalledTimes(3);
expect(onFinished).not.toBeCalled();
expect(audio.instances[0].play).toHaveBeenCalledTimes(3);
expect(onFinished).not.toHaveBeenCalled();
audio.instances[0].dispatchEvent(new Event('ended'));
expect(onFinished).toBeCalledTimes(1);
expect(onFinished).toHaveBeenCalledTimes(1);
});
it('should ignore ended events after stop', () => {
@@ -150,7 +150,7 @@ describe('repeat counter', () => {
// no-op as far as the source is concerned.
element.dispatchEvent(new Event('ended'));
expect(onFinished).not.toBeCalled();
expect(onFinished).not.toHaveBeenCalled();
});
});
@@ -162,11 +162,11 @@ describe('stop', () => {
tone.stop();
expect(element.pause).toBeCalled();
expect(element.pause).toHaveBeenCalled();
// Confirm the 'ended' listener is gone: dispatching it must not re-play.
vi.mocked(HTMLMediaElement.prototype.play).mockClear();
element.dispatchEvent(new Event('ended'));
expect(element.play).not.toBeCalled();
expect(element.play).not.toHaveBeenCalled();
});
it('should not fire finishedHandler on external stop', () => {
@@ -176,7 +176,7 @@ describe('stop', () => {
tone.stop();
expect(onFinished).not.toBeCalled();
expect(onFinished).not.toHaveBeenCalled();
});
it('should be safe to call before start', () => {
+12 -12
View File
@@ -22,8 +22,8 @@ describe('MelodyTone', () => {
expect(audio.oscillators[2].frequency.value).toBe(659.25);
expect(audio.oscillators[3].frequency.value).toBe(783.99);
expect(audio.oscillators[4].frequency.value).toBe(261.63);
expect(audio.oscillators[0].start).toBeCalledWith(0);
expect(audio.oscillators[4].start).toBeCalledWith(0);
expect(audio.oscillators[0].start).toHaveBeenCalledWith(0);
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0);
// --- V chord (G major) at t=1: sparkle D6, G4 + B4 + D5, hum G3. ---
expect(audio.oscillators[5].frequency.value).toBe(1174.66);
@@ -31,8 +31,8 @@ describe('MelodyTone', () => {
expect(audio.oscillators[7].frequency.value).toBe(493.88);
expect(audio.oscillators[8].frequency.value).toBe(587.33);
expect(audio.oscillators[9].frequency.value).toBe(196.0);
expect(audio.oscillators[5].start).toBeCalledWith(1);
expect(audio.oscillators[9].start).toBeCalledWith(1);
expect(audio.oscillators[5].start).toHaveBeenCalledWith(1);
expect(audio.oscillators[9].start).toHaveBeenCalledWith(1);
// --- I chord (resolution, an octave higher) at t=2. ---
expect(audio.oscillators[10].frequency.value).toBe(2093.0);
@@ -40,21 +40,21 @@ describe('MelodyTone', () => {
expect(audio.oscillators[12].frequency.value).toBe(783.99);
expect(audio.oscillators[13].frequency.value).toBe(1046.5);
expect(audio.oscillators[14].frequency.value).toBe(329.63);
expect(audio.oscillators[10].start).toBeCalledWith(2);
expect(audio.oscillators[14].start).toBeCalledWith(2);
expect(audio.oscillators[10].start).toHaveBeenCalledWith(2);
expect(audio.oscillators[14].start).toHaveBeenCalledWith(2);
});
it('should give the resolving chord a longer tail than the I and V chords', () => {
new MelodyTone(0).start();
// I and V chords use default fundDecay=0.6, humDecay=1.1.
expect(audio.gainParams[1].setTargetAtTime).toBeCalledWith(0, 0.005, 0.6);
expect(audio.gainParams[4].setTargetAtTime).toBeCalledWith(0, 0.005, 1.1);
expect(audio.gainParams[6].setTargetAtTime).toBeCalledWith(0, 1.005, 0.6);
expect(audio.gainParams[9].setTargetAtTime).toBeCalledWith(0, 1.005, 1.1);
expect(audio.gainParams[1].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.6);
expect(audio.gainParams[4].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 1.1);
expect(audio.gainParams[6].setTargetAtTime).toHaveBeenCalledWith(0, 1.005, 0.6);
expect(audio.gainParams[9].setTargetAtTime).toHaveBeenCalledWith(0, 1.005, 1.1);
// Final I chord overrides to fundDecay=0.9, humDecay=1.4.
expect(audio.gainParams[11].setTargetAtTime).toBeCalledWith(0, 2.005, 0.9);
expect(audio.gainParams[14].setTargetAtTime).toBeCalledWith(0, 2.005, 1.4);
expect(audio.gainParams[11].setTargetAtTime).toHaveBeenCalledWith(0, 2.005, 0.9);
expect(audio.gainParams[14].setTargetAtTime).toHaveBeenCalledWith(0, 2.005, 1.4);
});
});
@@ -19,30 +19,38 @@ describe('WestminsterTone', () => {
// E5 (659.25) at t=0.
expect(audio.oscillators[1].frequency.value).toBe(659.25);
expect(audio.oscillators[1].start).toBeCalledWith(0);
expect(audio.oscillators[1].start).toHaveBeenCalledWith(0);
// D5 (587.33) at t=0.55.
expect(audio.oscillators[4].frequency.value).toBe(587.33);
expect(audio.oscillators[4].start).toBeCalledWith(0.55);
expect(audio.oscillators[4].start).toHaveBeenCalledWith(0.55);
// C5 (523.25) at t=1.1.
expect(audio.oscillators[7].frequency.value).toBe(523.25);
expect(audio.oscillators[7].start).toBeCalledWith(1.1);
expect(audio.oscillators[7].start).toHaveBeenCalledWith(1.1);
// G4 (392.0) at t=1.65 -- the resolution.
expect(audio.oscillators[10].frequency.value).toBe(392.0);
expect(audio.oscillators[10].start).toBeCalledWith(1.65);
expect(audio.oscillators[10].start).toHaveBeenCalledWith(1.65);
});
it('should give the resolving G4 a longer bell tail than the other strikes', () => {
new WestminsterTone(0).start();
// First three strikes use default decay (fundDecay=0.6, humDecay=1.0).
expect(audio.gainParams[1].setTargetAtTime).toBeCalledWith(0, 0.005, 0.6);
expect(audio.gainParams[2].setTargetAtTime).toBeCalledWith(0, 0.005, 1.0);
expect(audio.gainParams[1].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 0.6);
expect(audio.gainParams[2].setTargetAtTime).toHaveBeenCalledWith(0, 0.005, 1.0);
// G4 (final strike) overrides to fundDecay=0.9, humDecay=1.4.
expect(audio.gainParams[10].setTargetAtTime).toBeCalledWith(0, 1.65 + 0.005, 0.9);
expect(audio.gainParams[11].setTargetAtTime).toBeCalledWith(0, 1.65 + 0.005, 1.4);
expect(audio.gainParams[10].setTargetAtTime).toHaveBeenCalledWith(
0,
1.65 + 0.005,
0.9,
);
expect(audio.gainParams[11].setTargetAtTime).toHaveBeenCalledWith(
0,
1.65 + 0.005,
1.4,
);
});
});
@@ -7,17 +7,18 @@ import type { CardController } from '../../src/card-controller/controller';
import type { AdvancedCameraCardView } from '../../src/config/schema/common/const';
import { triggersSchema, type TriggersOptions } from '../../src/config/schema/view';
import {
createCameraConfig,
createCameraManager,
createCapabilities,
createStore,
} from '../camera-manager/test-utils';
import { createCameraConfig, createConfig } from '../config/test-utils';
import {
createCardAPI,
createConfig,
createHASS,
createStateEntity,
createStore,
createView,
flushPromises,
} from '../test-utils';
import { createView } from '../view/test-utils';
vi.mock('lodash-es', async () => ({
...(await vi.importActual('lodash-es')),
@@ -117,7 +118,7 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
});
it('should not trigger if there is no config', async () => {
@@ -156,7 +157,7 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({
queryExecutorOptions: { useCache: false },
});
});
@@ -181,7 +182,7 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledWith({
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledWith({
params: {
camera: 'camera_1',
},
@@ -208,7 +209,7 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({
params: {
view: 'live',
camera: 'camera_1',
@@ -257,10 +258,12 @@ describe('CameraTriggersManager', () => {
if (!viewName) {
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toBeCalled();
).not.toHaveBeenCalled();
} else {
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).toHaveBeenCalledWith({
params: {
camera: 'camera_1',
view: viewName,
@@ -291,8 +294,10 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toHaveBeenCalled();
});
it('should handle trigger action set to call', async () => {
@@ -313,11 +318,13 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeTruthy();
// `start()` is called with the triggered camera and the inbound flag --
// view navigation is delegated to CallManager itself.
expect(api.getCallManager().start).toBeCalledWith({
expect(api.getCallManager().start).toHaveBeenCalledWith({
cameraID: 'camera_1',
inbound: true,
});
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toHaveBeenCalled();
});
it('should start a call on a high-fidelity event with no media', async () => {
@@ -338,7 +345,7 @@ describe('CameraTriggersManager', () => {
fidelity: 'high',
});
expect(api.getCallManager().start).toBeCalledWith({
expect(api.getCallManager().start).toHaveBeenCalledWith({
cameraID: 'camera_1',
inbound: true,
});
@@ -376,8 +383,10 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toHaveBeenCalled();
});
it('should handle untrigger action set to default', async () => {
@@ -410,7 +419,7 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should handle untrigger action set to call', async () => {
@@ -437,12 +446,12 @@ describe('CameraTriggersManager', () => {
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getCallManager().endIf).toBeCalledWith({
expect(api.getCallManager().endIf).toHaveBeenCalledWith({
cameraID: 'camera_1',
inbound: true,
answered: false,
});
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
});
it('should handle untrigger call with no state', async () => {
@@ -455,7 +464,7 @@ describe('CameraTriggersManager', () => {
type: 'end',
});
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
});
it('should not untrigger if other sources are still active', async () => {
@@ -531,7 +540,7 @@ describe('CameraTriggersManager', () => {
// Should still be triggered because the second 'new' event should have cancelled the first timer.
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
});
it('should untrigger each camera independently', async () => {
@@ -580,7 +589,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
// Camera 1 untriggered: setViewDefaultWithNewQuery is called.
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledTimes(1);
// Camera 2 is still triggered.
expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_2']));
@@ -596,7 +605,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
// Camera 2 untriggered: setViewDefaultWithNewQuery is called again.
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(2);
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledTimes(2);
expect(manager.getTriggeredCameraIDs()).toEqual(new Set());
});
@@ -696,7 +705,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should force untrigger when untrigger_force_seconds expires', async () => {
@@ -718,7 +727,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledTimes(1);
});
it('should not reset force timer on source update', async () => {
@@ -750,7 +759,7 @@ describe('CameraTriggersManager', () => {
vi.advanceTimersByTime(100);
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledTimes(1);
});
it('should force untrigger all sources when one force timer expires', async () => {
@@ -777,7 +786,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledTimes(1);
});
it('should not extend force timer when a second source triggers later', async () => {
@@ -809,7 +818,7 @@ describe('CameraTriggersManager', () => {
vi.advanceTimersByTime(100);
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalledTimes(1);
});
it('should auto-untrigger after the delay on a momentary event', async () => {
@@ -827,14 +836,14 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
vi.setSystemTime(add(start, { seconds: 10 }));
vi.runOnlyPendingTimers();
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should auto-untrigger immediately on a momentary event when delay is 0', async () => {
@@ -854,7 +863,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should not auto-untrigger from a momentary event while a continuous source remains active', async () => {
@@ -885,7 +894,7 @@ describe('CameraTriggersManager', () => {
// Continuous source keeps the trigger alive.
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
});
it('should add event_hold_seconds on top of untrigger_delay_seconds for momentary events', async () => {
@@ -915,7 +924,7 @@ describe('CameraTriggersManager', () => {
vi.advanceTimersByTime(1_000);
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should not apply event_hold_seconds to non-momentary events', async () => {
@@ -1018,8 +1027,10 @@ describe('CameraTriggersManager', () => {
fidelity: 'high',
});
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toHaveBeenCalled();
});
it('should ignore high-fidelity events when default view is not live', async () => {
@@ -1043,8 +1054,10 @@ describe('CameraTriggersManager', () => {
fidelity: 'high',
});
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toHaveBeenCalled();
});
});
@@ -1063,8 +1076,8 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
await manager.handleCameraEvent({
cameraID: 'camera_1',
@@ -1077,8 +1090,8 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
});
it('should take no actions when actions are set to none', async () => {
@@ -1098,8 +1111,8 @@ describe('CameraTriggersManager', () => {
type: 'new',
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
await manager.handleCameraEvent({
cameraID: 'camera_1',
@@ -1111,8 +1124,8 @@ describe('CameraTriggersManager', () => {
vi.runOnlyPendingTimers();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toHaveBeenCalled();
});
it('should take actions with human interactions when interaction mode is active', async () => {
@@ -1136,7 +1149,7 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({
params: {
view: 'live' as const,
camera: 'camera_1' as const,
@@ -1155,7 +1168,7 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should report multiple triggered cameras', async () => {
@@ -1354,7 +1367,7 @@ describe('CameraTriggersManager', () => {
await flushPromises();
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
});
@@ -1475,7 +1488,9 @@ describe('CameraTriggersManager', () => {
expect(result).toBeTruthy();
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledTimes(
1,
);
});
it('should prioritize the first triggered camera action at startup', async () => {
@@ -1522,7 +1537,9 @@ describe('CameraTriggersManager', () => {
expect(result).toBeTruthy();
expect(manager.getTriggeredCameraIDs()).toEqual(new Set(['camera_1', 'camera_2']));
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledTimes(1);
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledTimes(
1,
);
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({
params: {
view: 'live',
@@ -1564,8 +1581,10 @@ describe('CameraTriggersManager', () => {
expect(result).toBeTruthy();
// ...but the camera was filtered out, so no trigger state/action was applied.
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toBeCalled();
expect(
api.getViewManager().setViewByParametersWithNewQuery,
).not.toHaveBeenCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
});
it('should not trigger a camera without the trigger capability', async () => {
@@ -1674,7 +1693,7 @@ describe('CameraTriggersManager', () => {
});
expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
expect(api.getViewManager().setViewByParametersWithNewQuery).toHaveBeenCalledWith({
params: {
view: 'live' as const,
camera: 'camera_1' as const,
@@ -1693,7 +1712,7 @@ describe('CameraTriggersManager', () => {
expect(manager.isTriggered()).toBeFalsy();
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
expect(api.getViewManager().setViewDefaultWithNewQuery).toHaveBeenCalled();
});
it('should ignore untrigger actions during non-allowable interaction but still untrigger camera', async () => {
@@ -3,7 +3,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { CameraURLManager } from '../../src/card-controller/camera-url-manager';
import type { CardCameraURLAPI } from '../../src/card-controller/types';
import type { Endpoint } from '../../src/types';
import { createCardAPI, createViewWithMedia } from '../test-utils';
import { createCardAPI } from '../test-utils';
import { createViewWithMedia } from '../view/test-utils';
const createAPIWithMedia = (): CardCameraURLAPI => {
const api = createCardAPI();
@@ -34,7 +35,7 @@ describe('CameraURLManager', () => {
const windowSpy = vi.spyOn(window, 'open').mockReturnValue(null);
manager.openURL();
expect(windowSpy).toBeCalledWith('http://frigate');
expect(windowSpy).toHaveBeenCalledWith('http://frigate');
});
it('should not get URL without view', () => {
@@ -43,7 +44,7 @@ describe('CameraURLManager', () => {
const windowSpy = vi.spyOn(window, 'open').mockReturnValue(null);
manager.openURL();
expect(windowSpy).not.toBeCalled();
expect(windowSpy).not.toHaveBeenCalled();
});
it('should not get URL without cameraManager endpoints', () => {
@@ -5,15 +5,14 @@ import { CardElementManager } from '../../src/card-controller/card-element-manag
import type { StateWatcher } from '../../src/card-controller/hass/state-watcher';
import { QueryResults } from '../../src/view/query-results';
import { View } from '../../src/view/view';
import { createConfig } from '../config/test-utils';
import {
callStateWatcherCallback,
createCardAPI,
createCardHTMLElement,
createConfig,
createStateEntity,
createView,
TestViewMedia,
} from '../test-utils';
import { createView, TestViewMedia } from '../view/test-utils';
// @vitest-environment jsdom
describe('CardElementManager', () => {
@@ -62,7 +61,7 @@ describe('CardElementManager', () => {
manager.scrollReset();
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalled();
});
it('should toggle menu', () => {
@@ -76,7 +75,7 @@ describe('CardElementManager', () => {
manager.toggleMenu();
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalled();
});
it('should update', () => {
@@ -89,7 +88,7 @@ describe('CardElementManager', () => {
);
manager.update();
expect(element.requestUpdate).toBeCalled();
expect(element.requestUpdate).toHaveBeenCalled();
});
it('should get hasUpdated', () => {
@@ -124,45 +123,48 @@ describe('CardElementManager', () => {
expect(element.getAttribute('panel')).toBeNull();
expect(element.getAttribute('casted')).toBeNull();
expect(api.getFullscreenManager().connect).toBeCalled();
expect(api.getFullscreenManager().connect).toHaveBeenCalled();
expect(addEventListener).toBeCalledWith(
expect(addEventListener).toHaveBeenCalledWith(
'mousemove',
api.getInteractionManager().reportInteraction,
);
expect(addEventListener).toBeCalledWith(
expect(addEventListener).toHaveBeenCalledWith(
'll-custom',
api.getActionsManager().handleCustomActionEvent,
);
expect(addEventListener).toBeCalledWith(
expect(addEventListener).toHaveBeenCalledWith(
'action',
api.getActionsManager().handleInteractionEvent,
);
expect(addEventListener).toBeCalledWith(
expect(addEventListener).toHaveBeenCalledWith(
'action',
api.getInteractionManager().reportInteraction,
);
expect(addEventListener).toBeCalledWith(
expect(addEventListener).toHaveBeenCalledWith(
'touchstart',
api.getInteractionManager().reportInteraction,
);
expect(addEventListener).toBeCalledWith(
expect(addEventListener).toHaveBeenCalledWith(
'touchmove',
api.getInteractionManager().reportInteraction,
);
expect(windowAddEventListener).toBeCalledWith('location-changed', expect.anything());
expect(windowAddEventListener).toBeCalledWith('popstate', expect.anything());
expect(windowAddEventListener).toBeCalledWith(
expect(windowAddEventListener).toHaveBeenCalledWith(
'location-changed',
expect.anything(),
);
expect(windowAddEventListener).toHaveBeenCalledWith('popstate', expect.anything());
expect(windowAddEventListener).toHaveBeenCalledWith(
'advanced-camera-card:editor:diagnostics',
expect.anything(),
);
expect(api.getInteractionManager().initialize).toBeCalled();
expect(api.getFullscreenManager().initialize).toBeCalled();
expect(api.getExpandManager().initialize).toBeCalled();
expect(api.getMediaLoadedInfoManager().initialize).toBeCalled();
expect(api.getMicrophoneManager().initialize).toBeCalled();
expect(api.getCallManager().initialize).toBeCalled();
expect(api.getInteractionManager().initialize).toHaveBeenCalled();
expect(api.getFullscreenManager().initialize).toHaveBeenCalled();
expect(api.getExpandManager().initialize).toHaveBeenCalled();
expect(api.getMediaLoadedInfoManager().initialize).toHaveBeenCalled();
expect(api.getMicrophoneManager().initialize).toHaveBeenCalled();
expect(api.getCallManager().initialize).toHaveBeenCalled();
});
it('should disconnect', () => {
@@ -188,49 +190,52 @@ describe('CardElementManager', () => {
expect(element.getAttribute('panel')).toBeNull();
expect(element.getAttribute('casted')).toBeNull();
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
expect(api.getFullscreenManager().disconnect).toBeCalled();
expect(api.getMediaLoadedInfoManager().clear).toHaveBeenCalled();
expect(api.getFullscreenManager().disconnect).toHaveBeenCalled();
expect(removeEventListener).toBeCalledWith(
expect(removeEventListener).toHaveBeenCalledWith(
'mousemove',
api.getInteractionManager().reportInteraction,
);
expect(removeEventListener).toBeCalledWith(
expect(removeEventListener).toHaveBeenCalledWith(
'll-custom',
api.getActionsManager().handleCustomActionEvent,
);
expect(removeEventListener).toBeCalledWith(
expect(removeEventListener).toHaveBeenCalledWith(
'action',
api.getActionsManager().handleInteractionEvent,
);
expect(removeEventListener).toBeCalledWith(
expect(removeEventListener).toHaveBeenCalledWith(
'action',
api.getInteractionManager().reportInteraction,
);
expect(removeEventListener).toBeCalledWith(
expect(removeEventListener).toHaveBeenCalledWith(
'touchstart',
api.getInteractionManager().reportInteraction,
);
expect(removeEventListener).toBeCalledWith(
expect(removeEventListener).toHaveBeenCalledWith(
'touchmove',
api.getInteractionManager().reportInteraction,
);
expect(windowRemoveEventListener).toBeCalledWith(
expect(windowRemoveEventListener).toHaveBeenCalledWith(
'location-changed',
expect.anything(),
);
expect(windowRemoveEventListener).toBeCalledWith('popstate', expect.anything());
expect(windowRemoveEventListener).toBeCalledWith(
expect(windowRemoveEventListener).toHaveBeenCalledWith(
'popstate',
expect.anything(),
);
expect(windowRemoveEventListener).toHaveBeenCalledWith(
'advanced-camera-card:editor:diagnostics',
expect.anything(),
);
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
expect(api.getFullscreenManager().disconnect).toBeCalled();
expect(api.getKeyboardStateManager().uninitialize).toBeCalled();
expect(api.getActionsManager().uninitialize).toBeCalled();
expect(api.getCallManager().uninitialize).toBeCalled();
expect(api.getInitializationManager().uninitialize).toBeCalledWith('cameras');
expect(api.getMediaLoadedInfoManager().clear).toHaveBeenCalled();
expect(api.getFullscreenManager().disconnect).toHaveBeenCalled();
expect(api.getKeyboardStateManager().uninitialize).toHaveBeenCalled();
expect(api.getActionsManager().uninitialize).toHaveBeenCalled();
expect(api.getCallManager().uninitialize).toHaveBeenCalled();
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith('cameras');
});
describe('should update card when', () => {
@@ -263,7 +268,7 @@ describe('CardElementManager', () => {
};
callStateWatcherCallback(stateWatcher, diff);
expect(element.requestUpdate).toBeCalled();
expect(element.requestUpdate).toHaveBeenCalled();
});
it('media player entity changes', () => {
@@ -291,7 +296,7 @@ describe('CardElementManager', () => {
};
callStateWatcherCallback(stateWatcher, diff);
expect(element.requestUpdate).toBeCalled();
expect(element.requestUpdate).toHaveBeenCalled();
});
it('selected media review status changes', () => {
@@ -325,7 +330,7 @@ describe('CardElementManager', () => {
}),
);
expect(element.requestUpdate).toBeCalled();
expect(element.requestUpdate).toHaveBeenCalled();
});
it('non-selected media review status changes does not update', () => {
@@ -361,7 +366,7 @@ describe('CardElementManager', () => {
);
// Should NOT update because the reviewed item is not the selected item.
expect(element.requestUpdate).not.toBeCalled();
expect(element.requestUpdate).not.toHaveBeenCalled();
});
});
@@ -406,7 +411,7 @@ describe('CardElementManager', () => {
fireFromDialog(dialog);
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
expect(api.getViewManager().setViewByParameters).toHaveBeenCalledWith({
params: { view: 'diagnostics' },
});
});
@@ -431,7 +436,7 @@ describe('CardElementManager', () => {
fireFromDialog(dialog);
expect(api.getViewManager().setViewDefault).toBeCalled();
expect(api.getViewManager().setViewDefault).toHaveBeenCalled();
});
it('does not set view to diagnostics if card is not in editor', () => {
@@ -452,7 +457,7 @@ describe('CardElementManager', () => {
document.body.append(otherDialog);
fireFromDialog(otherDialog);
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
expect(api.getViewManager().setViewByParameters).not.toHaveBeenCalled();
});
});
});
@@ -11,9 +11,9 @@ import type { Automation } from '../../../src/config/schema/automations';
import type { Trigger } from '../../../src/config/schema/condition-trigger/triggers/types';
import { advancedCameraCardConfigSchema } from '../../../src/config/schema/types';
import { createGeneralAction } from '../../../src/utils/action';
import { createConfig } from '../../config/test-utils';
import {
createCardAPI,
createConfig,
createHASS,
createStateEntity,
flushPromises,
@@ -122,7 +122,7 @@ describe('ConfigManager', () => {
describe('should handle error when', () => {
it('should handle no input', () => {
const manager = new ConfigManager(createCardAPI());
expect(() => manager.setConfig()).toThrowError(/Invalid configuration/);
expect(() => manager.setConfig()).toThrow(/Invalid configuration/);
});
it('should handle invalid configuration', () => {
@@ -132,7 +132,7 @@ describe('ConfigManager', () => {
.mockReturnValue({ success: false, error: new ZodError([]) });
const manager = new ConfigManager(createCardAPI());
expect(() => manager.setConfig({})).toThrowError(
expect(() => manager.setConfig({})).toThrow(
'Invalid configuration: No location hint available (bad or missing type?)',
);
@@ -141,7 +141,7 @@ describe('ConfigManager', () => {
it('should handle invalid configuration with hint', () => {
const manager = new ConfigManager(createCardAPI());
expect(() => manager.setConfig({})).toThrowError(
expect(() => manager.setConfig({})).toThrow(
'Invalid configuration: [\n "type"\n]',
);
});
@@ -154,7 +154,7 @@ describe('ConfigManager', () => {
type: 'custom:frigate-card',
cameras: 'WILL_NOT_PARSE',
}),
).toThrowError(
).toThrow(
'An automated card configuration upgrade is ' +
'available, please visit the visual card editor. ' +
'Invalid configuration: [\n "cameras"\n]',
@@ -190,17 +190,17 @@ describe('ConfigManager', () => {
expect(manager.getConfig()?.menu.alignment).toBe('left');
// Verify appropriate API calls are made.
expect(api.getConditionStateManager().setState).toBeCalledWith({
expect(api.getConditionStateManager().setState).toHaveBeenCalledWith({
view: undefined,
displayMode: undefined,
camera: undefined,
});
expect(api.getIssueManager().reset).toBeCalledWith('config_error');
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
expect(api.getViewManager().reset).toBeCalled();
expect(api.getAutomationsManager().addAutomations).toBeCalled();
expect(api.getStyleManager().updateFromConfig).toBeCalled();
expect(api.getCardElementManager().update).toBeCalled();
expect(api.getIssueManager().reset).toHaveBeenCalledWith('config_error');
expect(api.getMediaLoadedInfoManager().clear).toHaveBeenCalled();
expect(api.getViewManager().reset).toHaveBeenCalled();
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalled();
expect(api.getStyleManager().updateFromConfig).toHaveBeenCalled();
expect(api.getCardElementManager().update).toHaveBeenCalled();
});
it('should apply profiles', () => {
@@ -243,12 +243,12 @@ describe('ConfigManager', () => {
};
manager.setConfig(config);
expect(api.getViewManager().reset).toBeCalled();
expect(api.getViewManager().reset).toHaveBeenCalled();
vi.mocked(api.getViewManager().reset).mockClear();
manager.setConfig(config);
expect(api.getViewManager().reset).not.toBeCalled();
expect(api.getViewManager().reset).not.toHaveBeenCalled();
});
it('should get card wide config', () => {
@@ -321,7 +321,7 @@ describe('ConfigManager', () => {
const configAfter = manager.getConfig();
expect(configAfter).toEqual(configBefore);
expect(api.getStyleManager().updateFromConfig).toBeCalledTimes(1);
expect(api.getStyleManager().updateFromConfig).toHaveBeenCalledTimes(1);
});
it('should honor override', () => {
@@ -372,7 +372,7 @@ describe('ConfigManager', () => {
stateManager.setState({ fullscreen: true });
expect(manager.getConfig()).not.toBeNull();
expect(api.getIssueManager().trigger).toBeCalledWith(
expect(api.getIssueManager().trigger).toHaveBeenCalledWith(
'config_error',
expect.objectContaining({ error: expect.any(Error) }),
);
@@ -502,9 +502,11 @@ describe('ConfigManager', () => {
await flushPromises();
expect(api.getDefaultManager().initializeIfNecessary).toBeCalledTimes(1);
expect(api.getMediaPlayerManager().initializeIfNecessary).toBeCalledTimes(1);
expect(listener).not.toBeCalledWith(
expect(api.getDefaultManager().initializeIfNecessary).toHaveBeenCalledTimes(1);
expect(api.getMediaPlayerManager().initializeIfNecessary).toHaveBeenCalledTimes(
1,
);
expect(listener).not.toHaveBeenCalledWith(
expect.objectContaining({ change: { config: expect.anything() } }),
);
@@ -515,11 +517,13 @@ describe('ConfigManager', () => {
await flushPromises();
expect(api.getDefaultManager().initializeIfNecessary).toBeCalledTimes(2);
expect(api.getMediaPlayerManager().initializeIfNecessary).toBeCalledTimes(2);
expect(api.getDefaultManager().initializeIfNecessary).toHaveBeenCalledTimes(2);
expect(api.getMediaPlayerManager().initializeIfNecessary).toHaveBeenCalledTimes(
2,
);
// Should set the config condition state.
expect(listener).toBeCalledWith(
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({ change: { config: expect.anything() } }),
);
});
@@ -616,7 +620,7 @@ describe('ConfigManager', () => {
await flushPromises();
// Verify delete was called when override triggered
expect(api.getFoldersManager().deleteFolders).toBeCalled();
expect(api.getFoldersManager().deleteFolders).toHaveBeenCalled();
// Verify folder 'f' is no longer present after override
// Since the override removes folders, the folders passed should no
@@ -1,15 +1,16 @@
import { describe, expect, it, vi } from 'vitest';
import { setAutomationsFromConfig } from '../../../src/card-controller/config/load-automations';
import { createCardAPI, createConfig } from '../../test-utils';
import { createConfig } from '../../config/test-utils';
import { createCardAPI } from '../../test-utils';
describe('setAutomationsFromConfig', () => {
it('without config', () => {
const api = createCardAPI();
setAutomationsFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([]);
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalled();
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith([]);
});
it('with config', () => {
@@ -33,7 +34,7 @@ describe('setAutomationsFromConfig', () => {
setAutomationsFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
expect(api.getAutomationsManager().addAutomations).toBeCalledWith(automations);
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalled();
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith(automations);
});
});
@@ -19,14 +19,10 @@ import {
isAdvancedCameraCardCustomAction,
} from '../../../src/utils/action';
import { arrayify } from '../../../src/utils/basic';
import {
createCardAPI,
createConfig,
createHASS,
createStateEntity,
createStore,
createView,
} from '../../test-utils';
import { createStore } from '../../camera-manager/test-utils';
import { createConfig } from '../../config/test-utils';
import { createCardAPI, createHASS, createStateEntity } from '../../test-utils';
import { createView } from '../../view/test-utils';
const isGeneratedAction = (action: ActionConfig): action is GeneratedActionConfig =>
'advanced_camera_card_action' in action &&
@@ -37,8 +33,8 @@ describe('setRemoteControlEntityFromConfig', () => {
const api = createCardAPI();
setRemoteControlEntityFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalled();
expect(api.getAutomationsManager().addAutomations).not.toHaveBeenCalled();
});
it('with control entity and card priority', () => {
@@ -56,8 +52,8 @@ describe('setRemoteControlEntityFromConfig', () => {
setRemoteControlEntityFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalled();
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith([
{
actions: [
{
@@ -138,8 +134,8 @@ describe('setRemoteControlEntityFromConfig', () => {
setRemoteControlEntityFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalled();
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalled();
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith([
{
actions: [
{
@@ -240,7 +236,7 @@ describe('setRemoteControlEntityFromConfig', () => {
);
addOptionsAction.callback(api);
expect(hass.callService).toBeCalledWith(
expect(hass.callService).toHaveBeenCalledWith(
'input_select',
'set_options',
{
@@ -290,7 +286,7 @@ describe('setRemoteControlEntityFromConfig', () => {
);
addOptionsAction.callback(api);
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
});
it('should not throw when hass is undefined setting options', () => {
@@ -371,7 +367,7 @@ describe('setRemoteControlEntityFromConfig', () => {
);
await cameraSyncAction.callback(api);
expect(hass.callService).toBeCalledWith(
expect(hass.callService).toHaveBeenCalledWith(
'input_select',
'select_option',
{
@@ -425,7 +421,7 @@ describe('setRemoteControlEntityFromConfig', () => {
cameraSyncAction.callback(api);
// Should NOT call select_option since entity already shows camera.one
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
});
it('should not select option when camera is undefined', () => {
@@ -462,7 +458,7 @@ describe('setRemoteControlEntityFromConfig', () => {
cameraSyncAction.callback(api);
// Should NOT call select_option since camera is undefined
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
});
it('should not select option when view exists but has no camera', () => {
@@ -496,13 +492,13 @@ describe('setRemoteControlEntityFromConfig', () => {
const cameraSyncAction = vi.mocked(api.getAutomationsManager().addAutomations).mock
.calls[0][0][1].actions?.[0] as InternalCallbackActionConfig;
cameraSyncAction.callback(api);
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
// Also test the 'initialized' condition callback (automation index 2)
const initializedSyncAction = vi.mocked(api.getAutomationsManager().addAutomations)
.mock.calls[0][0][2].actions?.[0] as InternalCallbackActionConfig;
initializedSyncAction.callback(api);
expect(hass.callService).not.toBeCalled();
expect(hass.callService).not.toHaveBeenCalled();
});
it('should not throw when hass is undefined', async () => {
@@ -574,7 +570,7 @@ describe('setRemoteControlEntityFromConfig', () => {
);
await cameraSyncAction.callback(api);
expect(hass.callService).toBeCalledWith(
expect(hass.callService).toHaveBeenCalledWith(
'input_select',
'select_option',
{
@@ -624,7 +620,7 @@ describe('setRemoteControlEntityFromConfig', () => {
expect(initAction.advanced_camera_card_action).toBe(INTERNAL_CALLBACK_ACTION);
await initAction.callback(api);
expect(hass.callService).toBeCalledWith(
expect(hass.callService).toHaveBeenCalledWith(
'input_select',
'select_option',
{
@@ -3,7 +3,8 @@ import { mock } from 'vitest-mock-extended';
import { setFoldersFromConfig } from '../../../src/card-controller/config/load-folders';
import type { FoldersManager } from '../../../src/card-controller/folders/manager';
import { createCardAPI, createConfig, createFolder } from '../../test-utils';
import { createConfig } from '../../config/test-utils';
import { createCardAPI, createFolder } from '../../test-utils';
describe('setFoldersFromConfig', () => {
it('should replace folders', () => {
@@ -19,8 +20,8 @@ describe('setFoldersFromConfig', () => {
setFoldersFromConfig(api);
expect(foldersManager.deleteFolders).toBeCalled();
expect(foldersManager.addFolders).toBeCalledWith(folders);
expect(foldersManager.deleteFolders).toHaveBeenCalled();
expect(foldersManager.addFolders).toHaveBeenCalledWith(folders);
});
it('should handle exceptions', () => {
@@ -40,7 +41,7 @@ describe('setFoldersFromConfig', () => {
setFoldersFromConfig(api);
expect(api.getIssueManager().trigger).toBeCalledWith('config_error', {
expect(api.getIssueManager().trigger).toHaveBeenCalledWith('config_error', {
error,
});
});
@@ -3,17 +3,18 @@ import { describe, expect, it, vi } from 'vitest';
import { setKeyboardShortcutsFromConfig } from '../../../src/card-controller/config/load-keyboard-shortcuts';
import type { PTZAction } from '../../../src/config/schema/actions/custom/ptz';
import type { PTZKeyboardShortcutName } from '../../../src/config/schema/view';
import { createCardAPI, createConfig } from '../../test-utils';
import { createConfig } from '../../config/test-utils';
import { createCardAPI } from '../../test-utils';
describe('setKeyboardShortcutsFromConfig', () => {
it('without shortcuts', () => {
const api = createCardAPI();
setKeyboardShortcutsFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalledWith(
setKeyboardShortcutsFromConfig,
);
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
expect(api.getAutomationsManager().addAutomations).not.toHaveBeenCalled();
});
it('with shortcuts disabled', () => {
@@ -29,10 +30,10 @@ describe('setKeyboardShortcutsFromConfig', () => {
);
setKeyboardShortcutsFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalledWith(
setKeyboardShortcutsFromConfig,
);
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
expect(api.getAutomationsManager().addAutomations).not.toHaveBeenCalled();
});
describe('PTZ shortcuts', () => {
@@ -66,10 +67,10 @@ describe('setKeyboardShortcutsFromConfig', () => {
setKeyboardShortcutsFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalledWith(
setKeyboardShortcutsFromConfig,
);
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith([
{
actions: [
{
@@ -119,10 +120,10 @@ describe('setKeyboardShortcutsFromConfig', () => {
setKeyboardShortcutsFromConfig(api);
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalledWith(
setKeyboardShortcutsFromConfig,
);
expect(api.getAutomationsManager().addAutomations).toBeCalledWith(
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith(
expect.arrayContaining([
{
actions: [
@@ -4,7 +4,8 @@ import { OverridesManager } from '../../../src/card-controller/config/overrides-
import { ConditionStateManager } from '../../../src/condition-trigger/conditions/state-manager';
import type { AdvancedCameraCardConfig } from '../../../src/config/schema/types';
import { AdvancedCameraCardError } from '../../../src/types';
import { createConfig, createMockTemplateRenderer } from '../../test-utils';
import { createConfig } from '../../config/test-utils';
import { createMockTemplateRenderer } from '../../test-utils';
describe('OverridesManager', () => {
const templateManager = createMockTemplateRenderer();
@@ -110,11 +111,11 @@ describe('OverridesManager', () => {
expect(manager.getConfig(config).menu?.style).toBe('hidden');
expect(callback).not.toBeCalled();
expect(callback).not.toHaveBeenCalled();
stateManager.setState({ fullscreen: true });
expect(callback).toBeCalledTimes(1);
expect(callback).toHaveBeenCalledTimes(1);
});
describe('should handle override merge', () => {
+6 -4
View File
@@ -113,7 +113,7 @@ describe('CardController', () => {
const controller = new CardController(element, scrollCallback, menuToggleCallback);
expect(CardElementManager).toBeCalledWith(
expect(CardElementManager).toHaveBeenCalledWith(
controller,
element,
scrollCallback,
@@ -137,7 +137,9 @@ describe('CardController', () => {
const csmListener = calls[0][0];
const hass = {} as Parameters<typeof csmListener>[0];
csmListener(hass, null);
expect(vi.mocked(ConditionStateManager).mock.instances[0].setState).toBeCalledWith({
expect(
vi.mocked(ConditionStateManager).mock.instances[0].setState,
).toHaveBeenCalledWith({
hass,
});
});
@@ -372,14 +374,14 @@ describe('CardController', () => {
createController().hostConnected();
expect(
vi.mocked(CardElementManager).mock.instances[0].elementConnected,
).toBeCalled();
).toHaveBeenCalled();
});
it('should handle hostDisconnected', () => {
createController().hostDisconnected();
expect(
vi.mocked(CardElementManager).mock.instances[0].elementDisconnected,
).toBeCalled();
).toHaveBeenCalled();
});
});
});
+19 -17
View File
@@ -4,10 +4,10 @@ import { mock } from 'vitest-mock-extended';
import type { CardController } from '../../src/card-controller/controller';
import { DefaultManager } from '../../src/card-controller/default-manager';
import type { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher';
import { createConfig } from '../config/test-utils';
import {
callStateWatcherCallback,
createCardAPI,
createConfig,
createHASS,
createStateEntity,
} from '../test-utils';
@@ -46,21 +46,21 @@ describe('DefaultManager', () => {
const manager = new DefaultManager(api);
await manager.initialize();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefault).toHaveBeenCalledTimes(1);
manager.uninitialize();
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefault).toHaveBeenCalledTimes(1);
});
it('should not set default view when not configured', () => {
@@ -82,11 +82,11 @@ describe('DefaultManager', () => {
const manager = new DefaultManager(api);
manager.initialize();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
});
it('should restart timer when reconfigured', async () => {
@@ -108,14 +108,14 @@ describe('DefaultManager', () => {
const manager = new DefaultManager(api);
await manager.initialize();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
await manager.initialize();
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).toBeCalled();
expect(api.getViewManager().setViewDefault).toHaveBeenCalled();
});
});
@@ -146,7 +146,7 @@ describe('DefaultManager', () => {
newState: createStateEntity({ state: 'on' }),
});
expect(api.getViewManager().setViewDefault).toBeCalled();
expect(api.getViewManager().setViewDefault).toHaveBeenCalled();
});
describe('interaction based', () => {
@@ -166,7 +166,7 @@ describe('DefaultManager', () => {
const manager = new DefaultManager(api);
await manager.initialize();
expect(api.getAutomationsManager().addAutomations).not.toBeCalled();
expect(api.getAutomationsManager().addAutomations).not.toHaveBeenCalled();
});
it('should register automation on initialization', async () => {
@@ -185,7 +185,7 @@ describe('DefaultManager', () => {
const manager = new DefaultManager(api);
await manager.initialize();
expect(api.getAutomationsManager().addAutomations).toBeCalledWith([
expect(api.getAutomationsManager().addAutomations).toHaveBeenCalledWith([
{
actions: [
{
@@ -209,7 +209,9 @@ describe('DefaultManager', () => {
const manager = new DefaultManager(api);
manager.uninitialize();
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(manager);
expect(api.getAutomationsManager().deleteAutomations).toHaveBeenCalledWith(
manager,
);
});
});
@@ -241,18 +243,18 @@ describe('DefaultManager', () => {
await manager.initializeIfNecessary(null);
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefault).toHaveBeenCalledTimes(1);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(configOff);
await manager.initializeIfNecessary(configOn);
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefault).toHaveBeenCalledTimes(1);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(configOff);
await manager.initializeIfNecessary(configOff);
vi.runOnlyPendingTimers();
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
expect(api.getViewManager().setViewDefault).toHaveBeenCalledTimes(1);
});
});
+9 -5
View File
@@ -16,7 +16,9 @@ describe('ExpandManager', () => {
const manager = new ExpandManager(api);
manager.initialize();
expect(api.getConditionStateManager().setState).toBeCalledWith({ expand: false });
expect(api.getConditionStateManager().setState).toHaveBeenCalledWith({
expand: false,
});
});
it('should set expanded', () => {
@@ -29,9 +31,11 @@ describe('ExpandManager', () => {
manager.setExpanded(true);
expect(manager.isExpanded()).toBeTruthy();
expect(api.getFullscreenManager().setFullscreen).toBeCalledWith(false);
expect(api.getConditionStateManager().setState).toBeCalledWith({ expand: true });
expect(api.getCardElementManager().update).toBeCalled();
expect(api.getFullscreenManager().setFullscreen).toHaveBeenCalledWith(false);
expect(api.getConditionStateManager().setState).toHaveBeenCalledWith({
expand: true,
});
expect(api.getCardElementManager().update).toHaveBeenCalled();
expect(element.hasAttribute('expanded')).toBeTruthy();
});
@@ -44,7 +48,7 @@ describe('ExpandManager', () => {
manager.setExpanded(true);
expect(api.getFullscreenManager().setFullscreen).not.toBeCalled();
expect(api.getFullscreenManager().setFullscreen).not.toHaveBeenCalled();
});
it('should toggle expanded', () => {
@@ -9,7 +9,8 @@ import type { FolderConfig } from '../../../src/config/schema/folders';
import { QuerySource } from '../../../src/query-source';
import type { Endpoint } from '../../../src/types';
import { ViewFolder } from '../../../src/view/item';
import { createFolder, createHASS, TestViewMedia } from '../../test-utils';
import { createFolder, createHASS } from '../../test-utils';
import { TestViewMedia } from '../../view/test-utils';
vi.mock('../../../src/card-controller/folders/ha/engine');
vi.mock('../../../../src/utils/ha/download');
@@ -134,7 +135,7 @@ describe('FoldersExecutor', () => {
await executor.favorite(hass, item, true);
expect(haFolderEngine.favorite).toBeCalledWith(hass, item, true);
expect(haFolderEngine.favorite).toHaveBeenCalledWith(hass, item, true);
});
});
@@ -153,7 +154,10 @@ describe('FoldersExecutor', () => {
const executor = new FoldersExecutor(templateManager, { ha: haFolderEngine });
expect(executor.generateChildFolderQuery(query, viewFolder)).toEqual(query);
expect(haFolderEngine.generateChildFolderQuery).toBeCalledWith(query, viewFolder);
expect(haFolderEngine.generateChildFolderQuery).toHaveBeenCalledWith(
query,
viewFolder,
);
});
it('should return null for non-existent folder engine', () => {
@@ -11,12 +11,8 @@ import { homeAssistantWSRequest } from '../../../../src/ha/ws-request';
import { QuerySource } from '../../../../src/query-source';
import type { Endpoint } from '../../../../src/types';
import { ViewFolder, ViewMedia } from '../../../../src/view/item';
import {
createBrowseMedia,
createFolder,
createHASS,
TestViewMedia,
} from '../../../test-utils';
import { createBrowseMedia, createFolder, createHASS } from '../../../test-utils';
import { TestViewMedia } from '../../../view/test-utils';
vi.mock('../../../../src/ha/download');
vi.mock('../../../../src/ha/ws-request');
@@ -143,11 +139,11 @@ describe('HAFoldersEngine', () => {
expect(results?.[0]).toBeInstanceOf(ViewMedia);
expect(results?.[1]).toBeInstanceOf(ViewFolder);
expect(homeAssistantWSRequest).toBeCalledTimes(1);
expect(homeAssistantWSRequest).toHaveBeenCalledTimes(1);
// Expanding the folder again should use the cache.
await engine.expandFolder(createHASS(), query);
expect(homeAssistantWSRequest).toBeCalledTimes(1);
expect(homeAssistantWSRequest).toHaveBeenCalledTimes(1);
});
it('should expand folder without cache when requested', async () => {
@@ -190,11 +186,11 @@ describe('HAFoldersEngine', () => {
expect(results?.[0]).toBeInstanceOf(ViewMedia);
expect(results?.[1]).toBeInstanceOf(ViewFolder);
expect(homeAssistantWSRequest).toBeCalledTimes(1);
expect(homeAssistantWSRequest).toHaveBeenCalledTimes(1);
// Expanding the folder again should use the cache.
await engine.expandFolder(createHASS(), query);
expect(homeAssistantWSRequest).toBeCalledTimes(2);
expect(homeAssistantWSRequest).toHaveBeenCalledTimes(2);
});
it('should use id from browsemedia in folder in query', async () => {
@@ -225,7 +221,7 @@ describe('HAFoldersEngine', () => {
const engine = new HAFoldersEngine(templateManager);
await engine.expandFolder(hass, query);
expect(homeAssistantWSRequest).toBeCalledWith(hass, browseMediaSchema, {
expect(homeAssistantWSRequest).toHaveBeenCalledWith(hass, browseMediaSchema, {
type: 'media_source/browse_media',
media_content_id: 'media-source://id',
});
+14 -16
View File
@@ -13,12 +13,8 @@ import { QuerySource } from '../../../src/query-source';
import type { Endpoint } from '../../../src/types';
import { ViewFolder } from '../../../src/view/item';
import type { ViewItemCapabilities } from '../../../src/view/types';
import {
createCardAPI,
createFolder,
createHASS,
TestViewMedia,
} from '../../test-utils';
import { createCardAPI, createFolder, createHASS } from '../../test-utils';
import { TestViewMedia } from '../../view/test-utils';
describe('FoldersManager', () => {
it('should initialize with no folders', () => {
@@ -74,7 +70,7 @@ describe('FoldersManager', () => {
const folder_1 = createFolder({ id: 'DUP' });
const folder_2 = createFolder({ id: 'DUP' });
expect(() => manager.addFolders([folder_1, folder_2])).toThrowError(
expect(() => manager.addFolders([folder_1, folder_2])).toThrow(
/Duplicate folder id/,
);
});
@@ -119,7 +115,7 @@ describe('FoldersManager', () => {
executor.getDefaultQueryParameters.mockReturnValue(query);
expect(manager.getDefaultQueryParameters(folder)).toEqual(query);
expect(executor.getDefaultQueryParameters).toBeCalledWith(folder);
expect(executor.getDefaultQueryParameters).toHaveBeenCalledWith(folder);
});
it('should fallback to default folder if none provided', () => {
@@ -133,7 +129,9 @@ describe('FoldersManager', () => {
executor.getDefaultQueryParameters.mockReturnValue(query);
expect(manager.getDefaultQueryParameters()).toEqual(query);
expect(executor.getDefaultQueryParameters).toBeCalledWith(manager.getFolder());
expect(executor.getDefaultQueryParameters).toHaveBeenCalledWith(
manager.getFolder(),
);
});
});
@@ -186,7 +184,7 @@ describe('FoldersManager', () => {
const manager = new FoldersManager(createCardAPI(), executor);
expect(manager.generateChildFolderQuery(query, viewFolder)).toEqual(query);
expect(executor.generateChildFolderQuery).toBeCalledWith(query, viewFolder);
expect(executor.generateChildFolderQuery).toHaveBeenCalledWith(query, viewFolder);
});
});
@@ -215,7 +213,7 @@ describe('FoldersManager', () => {
media,
]);
expect(executor.expandFolder).toBeCalledWith(
expect(executor.expandFolder).toHaveBeenCalledWith(
hass,
query,
conditionState,
@@ -237,7 +235,7 @@ describe('FoldersManager', () => {
}),
).toBeNull();
expect(executor.expandFolder).not.toBeCalled();
expect(executor.expandFolder).not.toHaveBeenCalled();
});
it('should return null for query with unsupported filters', async () => {
@@ -257,7 +255,7 @@ describe('FoldersManager', () => {
};
expect(await manager.expandFolder(query)).toBeNull();
expect(executor.expandFolder).not.toBeCalled();
expect(executor.expandFolder).not.toHaveBeenCalled();
});
});
@@ -275,7 +273,7 @@ describe('FoldersManager', () => {
const manager = new FoldersManager(api, executor);
expect(manager.getItemCapabilities(item)).toEqual(capabilities);
expect(executor.getItemCapabilities).toBeCalledWith(item);
expect(executor.getItemCapabilities).toHaveBeenCalledWith(item);
});
});
@@ -294,7 +292,7 @@ describe('FoldersManager', () => {
const manager = new FoldersManager(api, executor);
expect(await manager.getDownloadPath(item)).toEqual(endpoint);
expect(executor.getDownloadPath).toBeCalledWith(hass, item, {
expect(executor.getDownloadPath).toHaveBeenCalledWith(hass, item, {
resolvedMediaCache: cache,
});
});
@@ -311,7 +309,7 @@ describe('FoldersManager', () => {
const manager = new FoldersManager(api, executor);
await manager.favorite(item, true);
expect(executor.favorite).toBeCalledWith(hass, item, true);
expect(executor.favorite).toHaveBeenCalledWith(hass, item, true);
});
});
@@ -19,7 +19,7 @@ describe('FullscreenManager', () => {
const manager = new FullscreenManager(api, mock<FullscreenProvider>());
manager.initialize();
expect(api.getConditionStateManager().setState).toBeCalledWith({
expect(api.getConditionStateManager().setState).toHaveBeenCalledWith({
fullscreen: false,
});
});
@@ -49,7 +49,7 @@ describe('FullscreenManager', () => {
manager.toggleFullscreen();
expect(provider.setFullscreen).toBeCalledWith(expected);
expect(provider.setFullscreen).toHaveBeenCalledWith(expected);
});
});
@@ -60,7 +60,7 @@ describe('FullscreenManager', () => {
manager.setFullscreen(fullscreen);
expect(provider.setFullscreen).toBeCalledWith(fullscreen);
expect(provider.setFullscreen).toHaveBeenCalledWith(fullscreen);
});
});
@@ -70,7 +70,7 @@ describe('FullscreenManager', () => {
manager.connect();
expect(provider.connect).toBeCalled();
expect(provider.connect).toHaveBeenCalled();
});
it('should disconnect', () => {
@@ -79,7 +79,7 @@ describe('FullscreenManager', () => {
manager.disconnect();
expect(provider.disconnect).toBeCalled();
expect(provider.disconnect).toHaveBeenCalled();
});
describe('should confirm whether fullscreen is supported', () => {
@@ -137,7 +137,7 @@ describe('FullscreenManager', () => {
handler();
expect(api.getConditionStateManager().setState).toBeCalledWith({
expect(api.getConditionStateManager().setState).toHaveBeenCalledWith({
fullscreen: fullscreen,
});
});
@@ -148,6 +148,9 @@ describe('FullscreenManager', () => {
new FullscreenManager(api);
expect(FullscreenProviderFactory.create).toBeCalledWith(api, expect.anything());
expect(FullscreenProviderFactory.create).toHaveBeenCalledWith(
api,
expect.anything(),
);
});
});
@@ -41,7 +41,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.connect();
expect(on).toBeCalledWith('change', expect.anything());
expect(on).toHaveBeenCalledWith('change', expect.anything());
});
it('should not connect if not enabled', () => {
@@ -52,7 +52,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.connect();
expect(on).not.toBeCalled();
expect(on).not.toHaveBeenCalled();
});
});
@@ -65,7 +65,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.disconnect();
expect(off).toBeCalledWith('change', expect.anything());
expect(off).toHaveBeenCalledWith('change', expect.anything());
});
it('should not disconnect if not enabled', () => {
@@ -76,7 +76,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.disconnect();
expect(off).not.toBeCalled();
expect(off).not.toHaveBeenCalled();
});
});
@@ -138,7 +138,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.setFullscreen(true);
expect(screenfull.request).toBeCalledWith(element);
expect(screenfull.request).toHaveBeenCalledWith(element);
});
it('should exit fullscreen if fullscreen is false', () => {
@@ -148,7 +148,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.setFullscreen(false);
expect(screenfull.exit).toBeCalled();
expect(screenfull.exit).toHaveBeenCalled();
});
it('should take no action if not supported', () => {
@@ -162,8 +162,8 @@ describe('ScreenfullFullScreenProvider', () => {
provider.setFullscreen(true);
provider.setFullscreen(false);
expect(screenfull.request).not.toBeCalled();
expect(screenfull.exit).not.toBeCalled();
expect(screenfull.request).not.toHaveBeenCalled();
expect(screenfull.exit).not.toHaveBeenCalled();
});
it('should swallow a rejected fullscreen request', async () => {
@@ -177,7 +177,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.setFullscreen(true);
await flushPromises();
expect(screenfull.request).toBeCalledWith(element);
expect(screenfull.request).toHaveBeenCalledWith(element);
});
it('should swallow a rejected fullscreen exit', async () => {
@@ -188,7 +188,7 @@ describe('ScreenfullFullScreenProvider', () => {
provider.setFullscreen(false);
await flushPromises();
expect(screenfull.exit).toBeCalled();
expect(screenfull.exit).toHaveBeenCalled();
});
});
});
@@ -40,7 +40,9 @@ describe('WebkitFullScreenProvider', () => {
provider.connect();
expect(api.getConditionStateManager().addListener).toBeCalledWith(expect.anything());
expect(api.getConditionStateManager().addListener).toHaveBeenCalledWith(
expect.anything(),
);
});
it('should disconnect', () => {
@@ -49,7 +51,7 @@ describe('WebkitFullScreenProvider', () => {
provider.disconnect();
expect(api.getConditionStateManager().removeListener).toBeCalledWith(
expect(api.getConditionStateManager().removeListener).toHaveBeenCalledWith(
expect.anything(),
);
});
@@ -110,7 +112,7 @@ describe('WebkitFullScreenProvider', () => {
provider.setFullscreen(true);
expect(element.webkitEnterFullscreen).toBeCalled();
expect(element.webkitEnterFullscreen).toHaveBeenCalled();
});
it('should exit fullscreen if fullscreen is true', () => {
@@ -130,7 +132,7 @@ describe('WebkitFullScreenProvider', () => {
provider.setFullscreen(false);
expect(element.webkitExitFullscreen).toBeCalled();
expect(element.webkitExitFullscreen).toHaveBeenCalled();
});
it('should take no action if not supported', () => {
@@ -152,8 +154,8 @@ describe('WebkitFullScreenProvider', () => {
provider.setFullscreen(true);
provider.setFullscreen(false);
expect(element.webkitEnterFullscreen).not.toBeCalled();
expect(element.webkitExitFullscreen).not.toBeCalled();
expect(element.webkitEnterFullscreen).not.toHaveBeenCalled();
expect(element.webkitExitFullscreen).not.toHaveBeenCalled();
});
it('should take no action if element is not a video', () => {
@@ -175,8 +177,8 @@ describe('WebkitFullScreenProvider', () => {
provider.setFullscreen(true);
provider.setFullscreen(false);
expect(element.webkitEnterFullscreen).not.toBeCalled();
expect(element.webkitExitFullscreen).not.toBeCalled();
expect(element.webkitEnterFullscreen).not.toHaveBeenCalled();
expect(element.webkitExitFullscreen).not.toHaveBeenCalled();
});
});
@@ -205,7 +207,7 @@ describe('WebkitFullScreenProvider', () => {
element_1.dispatchEvent(new Event(event));
expect(handler).toBeCalledTimes(1);
expect(handler).toHaveBeenCalledTimes(1);
const element_2 = createWebkitVideoElement();
const mediaPlayerController_2 = mock<MediaPlayerController>();
@@ -218,12 +220,12 @@ describe('WebkitFullScreenProvider', () => {
element_2.dispatchEvent(new Event(event));
expect(handler).toBeCalledTimes(2);
expect(handler).toHaveBeenCalledTimes(2);
// Events on the old element should be ignored.
element_1.dispatchEvent(new Event(event));
expect(handler).toBeCalledTimes(2);
expect(handler).toHaveBeenCalledTimes(2);
// Test the media loaded info changing, but the player not changing.
stateManager.setState({
@@ -233,7 +235,7 @@ describe('WebkitFullScreenProvider', () => {
// Events on the new element should still be handled.
element_2.dispatchEvent(new Event(event));
expect(handler).toBeCalledTimes(3);
expect(handler).toHaveBeenCalledTimes(3);
},
);
});
@@ -265,11 +267,11 @@ describe('WebkitFullScreenProvider', () => {
element.dispatchEvent(new Event('webkitendfullscreen'));
expect(element.play).not.toBeCalled();
expect(element.play).not.toHaveBeenCalled();
vi.runOnlyPendingTimers();
expect(element.play).toBeCalled();
expect(element.play).toHaveBeenCalled();
});
it('should swallow a rejected video replay after fullscreen ends', async () => {
@@ -295,6 +297,6 @@ describe('WebkitFullScreenProvider', () => {
vi.runOnlyPendingTimers();
await flushPromises();
expect(element.play).toBeCalled();
expect(element.play).toHaveBeenCalled();
});
});
@@ -35,7 +35,7 @@ describe('EventWatcher', () => {
watcher.subscribe({ event_type: 'zha_event', callback: vi.fn() });
await flushPromises();
expect(hass.connection.subscribeEvents).toBeCalledTimes(1);
expect(hass.connection.subscribeEvents).toHaveBeenCalledTimes(1);
expect(vi.mocked(hass.connection.subscribeEvents).mock.calls[0][1]).toBe(
'zha_event',
);
@@ -50,7 +50,7 @@ describe('EventWatcher', () => {
watcher.subscribe({ event_type: 'zha_event', callback: vi.fn() });
await flushPromises();
expect(hass.connection.subscribeEvents).toBeCalledTimes(1);
expect(hass.connection.subscribeEvents).toHaveBeenCalledTimes(1);
});
it('should open separate WS subscriptions for distinct event_types', async () => {
@@ -62,7 +62,7 @@ describe('EventWatcher', () => {
watcher.subscribe({ event_type: 'deconz_event', callback: vi.fn() });
await flushPromises();
expect(hass.connection.subscribeEvents).toBeCalledTimes(2);
expect(hass.connection.subscribeEvents).toHaveBeenCalledTimes(2);
});
it('should dispatch to every subscriber whose event_type matches', async () => {
@@ -79,8 +79,8 @@ describe('EventWatcher', () => {
fireEvent(hass, event);
expect(cb1).toBeCalledWith(event);
expect(cb2).toBeCalledWith(event);
expect(cb1).toHaveBeenCalledWith(event);
expect(cb2).toHaveBeenCalledWith(event);
});
it('should gate dispatch on the request matcher when provided', async () => {
@@ -98,9 +98,9 @@ describe('EventWatcher', () => {
fireEvent(hass, matching);
fireEvent(hass, nonMatching);
expect(matcher).toBeCalledTimes(2);
expect(cb).toBeCalledTimes(1);
expect(cb).toBeCalledWith(matching);
expect(matcher).toHaveBeenCalledTimes(2);
expect(cb).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledWith(matching);
});
it('should tear down the WS subscription only when the last subscriber unsubscribes', async () => {
@@ -118,11 +118,11 @@ describe('EventWatcher', () => {
watcher.unsubscribe(req1);
await flushPromises();
expect(unsub).not.toBeCalled();
expect(unsub).not.toHaveBeenCalled();
watcher.unsubscribe(req2);
await flushPromises();
expect(unsub).toBeCalledTimes(1);
expect(unsub).toHaveBeenCalledTimes(1);
});
it('should drop events from an old-connection subscription after a swap', async () => {
@@ -147,7 +147,7 @@ describe('EventWatcher', () => {
// Old dispatcher fires: guard.isConnected() is now false, callback must NOT
// receive the event.
oldDispatcher?.(createHASSEvent('zha_event', { command: 'press' }));
expect(cb).not.toBeCalled();
expect(cb).not.toHaveBeenCalled();
});
it('should not dispatch to a subscriber that registers mid-dispatch', async () => {
@@ -164,8 +164,8 @@ describe('EventWatcher', () => {
fireEvent(hass, createHASSEvent('zha_event', { command: 'press' }));
expect(reentrantCallback).toBeCalledTimes(1);
expect(lateCallback).not.toBeCalled();
expect(reentrantCallback).toHaveBeenCalledTimes(1);
expect(lateCallback).not.toHaveBeenCalled();
});
describe('subscription health monitoring', () => {
+30 -32
View File
@@ -4,16 +4,10 @@ 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 {
createCameraConfig,
createCameraManager,
createCardAPI,
createConfig,
createHASS,
createStateEntity,
createStore,
createView,
} from '../../test-utils';
import { createCameraManager, createStore } from '../../camera-manager/test-utils';
import { createCameraConfig, createConfig } from '../../config/test-utils';
import { createCardAPI, createHASS, createStateEntity } from '../../test-utils';
import { createView } from '../../view/test-utils';
describe('HASSManager', () => {
beforeEach(() => {
@@ -53,11 +47,11 @@ describe('HASSManager', () => {
const hass1 = createHASS();
manager.setHASS(hass1);
expect(listener).toBeCalledWith(hass1, null);
expect(listener).toHaveBeenCalledWith(hass1, null);
const hass2 = createHASS();
manager.setHASS(hass2);
expect(listener).toBeCalledWith(hass2, hass1);
expect(listener).toHaveBeenCalledWith(hass2, hass1);
});
it('should call listeners in insertion order on every fan-out', () => {
@@ -79,7 +73,7 @@ describe('HASSManager', () => {
unlisten();
manager.setHASS(createHASS());
expect(listener).not.toBeCalled();
expect(listener).not.toHaveBeenCalled();
});
it('should not fan out on null/undefined hass', () => {
@@ -90,7 +84,7 @@ describe('HASSManager', () => {
manager.setHASS(null);
manager.setHASS();
expect(listener).not.toBeCalled();
expect(listener).not.toHaveBeenCalled();
});
it('should expose current hass via getHASS for source consumers', () => {
@@ -128,10 +122,12 @@ describe('HASSManager', () => {
// Cameras and view should be uninitialized so they get re-subscribed
// to event sources (e.g. Frigate WebSocket events) on the next
// render cycle.
expect(api.getInitializationManager().uninitialize).toBeCalledWith('cameras');
expect(api.getCameraManager().destroy).toBeCalled();
expect(api.getInitializationManager().uninitialize).toBeCalledWith('view');
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
'cameras',
);
expect(api.getCameraManager().destroy).toHaveBeenCalled();
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith('view');
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
'initial-trigger',
);
});
@@ -147,8 +143,8 @@ describe('HASSManager', () => {
manager.setHASS(startingHASS);
// No reinit yet -- HA isn't fully ready.
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
expect(api.getCameraManager().destroy).not.toBeCalled();
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalled();
expect(api.getCameraManager().destroy).not.toHaveBeenCalled();
// HA finishes booting.
const readyHASS = createHASS();
@@ -156,10 +152,12 @@ describe('HASSManager', () => {
readyHASS.config.state = STATE_RUNNING;
manager.setHASS(readyHASS);
expect(api.getInitializationManager().uninitialize).toBeCalledWith('cameras');
expect(api.getCameraManager().destroy).toBeCalled();
expect(api.getInitializationManager().uninitialize).toBeCalledWith('view');
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
'cameras',
);
expect(api.getCameraManager().destroy).toHaveBeenCalled();
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith('view');
expect(api.getInitializationManager().uninitialize).toHaveBeenCalledWith(
'initial-trigger',
);
});
@@ -178,8 +176,8 @@ describe('HASSManager', () => {
manager.setHASS(startingHASS);
// WS came back but integrations still loading -- wait for RUNNING.
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
expect(api.getCameraManager().destroy).not.toBeCalled();
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalled();
expect(api.getCameraManager().destroy).not.toHaveBeenCalled();
});
it('should not reinitialize on first hass set (no previous hass)', () => {
@@ -194,8 +192,8 @@ describe('HASSManager', () => {
// First-ever hass set -- there's no "previous not-ready state" to
// transition from, so the normal first-load init flow applies and we must
// not blow away cameras.
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
expect(api.getCameraManager().destroy).not.toBeCalled();
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalled();
expect(api.getCameraManager().destroy).not.toHaveBeenCalled();
});
it('should not reinitialize on ready → ready (no transition)', () => {
@@ -212,8 +210,8 @@ describe('HASSManager', () => {
anotherReadyHASS.config.state = STATE_RUNNING;
manager.setHASS(anotherReadyHASS);
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
expect(api.getCameraManager().destroy).not.toBeCalled();
expect(api.getInitializationManager().uninitialize).not.toHaveBeenCalled();
expect(api.getCameraManager().destroy).not.toHaveBeenCalled();
});
it('should not crash when hass is null', () => {
@@ -257,7 +255,7 @@ describe('HASSManager', () => {
manager.setHASS(hass);
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
});
it('should not set default view when there is card interaction', () => {
@@ -280,7 +278,7 @@ describe('HASSManager', () => {
manager.setHASS(hass);
expect(api.getViewManager().setViewDefault).not.toBeCalled();
expect(api.getViewManager().setViewDefault).not.toHaveBeenCalled();
});
});
});

Some files were not shown because too many files have changed in this diff Show More