fix: Fix initialization race condition that occurs in certain circumstances (#1545)
* fix: Fix initialization race condition in certain circumstances * Minor improvements and camera iris logo
This commit is contained in:
@@ -1289,4 +1289,19 @@ describe('CameraManager', async () => {
|
||||
expect(await manager.getMediaSeekTime(media, middleTime)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset', async () => {
|
||||
const api = createCardAPI();
|
||||
const engine = mock<CameraManagerEngine>();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const manager = createCameraManager(api, engine);
|
||||
|
||||
expect(await manager.initializeCamerasFromConfig()).toBeTruthy();
|
||||
|
||||
expect(manager.getStore().getCameraCount()).toBe(1);
|
||||
|
||||
await manager.reset();
|
||||
|
||||
expect(manager.getStore().getCameraCount()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -308,4 +308,39 @@ describe('CameraManagerStore', async () => {
|
||||
);
|
||||
expect(store.getCameraIDsWithCapability('clips')).toEqual(new Set(['one']));
|
||||
});
|
||||
|
||||
it('setCameras', async () => {
|
||||
const store = new CameraManagerStore();
|
||||
const camera_1 = new Camera(createCameraConfig({ id: 'camera-1' }), engineGeneric);
|
||||
camera_1.destroy = vi.fn();
|
||||
|
||||
const camera_2 = new Camera(createCameraConfig({ id: 'camera-2' }), engineGeneric);
|
||||
camera_2.destroy = vi.fn();
|
||||
|
||||
const camera_3 = new Camera(createCameraConfig({ id: 'camera-3' }), engineGeneric);
|
||||
camera_3.destroy = vi.fn();
|
||||
|
||||
const camera_3_new = new Camera(
|
||||
createCameraConfig({ id: 'camera-3' }),
|
||||
engineGeneric,
|
||||
);
|
||||
camera_3_new.destroy = vi.fn();
|
||||
|
||||
const camera_4 = new Camera(createCameraConfig({ id: 'camera-4' }), engineGeneric);
|
||||
camera_4.destroy = vi.fn();
|
||||
|
||||
await store.setCameras([camera_1, camera_2, camera_3]);
|
||||
await store.setCameras([camera_2, camera_3_new, camera_4]);
|
||||
|
||||
expect(store.getCamera('camera-1')).toBeNull();
|
||||
expect(store.getCamera('camera-2')).toBe(camera_2);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ZodError } from 'zod';
|
||||
import { frigateCardConfigSchema } from '../../../src/config/types';
|
||||
import { getOverriddenConfig } from '../../../src/card-controller/conditions-manager';
|
||||
import { ConfigManager } from '../../../src/card-controller/config/config-manager';
|
||||
import { InitializationAspect } from '../../../src/card-controller/initialization-manager';
|
||||
import { createCardAPI, createConfig } from '../../test-utils';
|
||||
import { frigateCardConfigSchema } from '../../../src/config/types';
|
||||
import { createCardAPI, createConfig, flushPromises } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/card-controller/conditions-manager.js');
|
||||
|
||||
@@ -229,7 +229,9 @@ describe('ConfigManager', () => {
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.VIEW,
|
||||
);
|
||||
|
||||
const config_2 = {
|
||||
type: 'custom:frigate-card',
|
||||
@@ -238,7 +240,7 @@ describe('ConfigManager', () => {
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
});
|
||||
@@ -253,7 +255,9 @@ describe('ConfigManager', () => {
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.VIEW,
|
||||
);
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
@@ -264,7 +268,7 @@ describe('ConfigManager', () => {
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
});
|
||||
@@ -284,7 +288,9 @@ describe('ConfigManager', () => {
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.VIEW,
|
||||
);
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
@@ -297,42 +303,26 @@ describe('ConfigManager', () => {
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
expect(api.getInitializationManager().uninitialize).toHaveBeenLastCalledWith(
|
||||
InitializationAspect.MICROPHONE_CONNECT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('view.default_reset', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
view: {
|
||||
default_reset: {
|
||||
every_seconds: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
it('should initialize background items', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
manager.setConfig(config);
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
view: {
|
||||
default_reset: {
|
||||
every_seconds: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
await flushPromises();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
InitializationAspect.DEFAULT_RESET,
|
||||
);
|
||||
});
|
||||
expect(api.getDefaultManager().initializeIfNecessary).toBeCalledWith(null);
|
||||
expect(api.getMediaPlayerManager().initializeIfNecessary).toBeCalledWith(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ describe('DefaultManager', () => {
|
||||
describe('time based', () => {
|
||||
it('should set default view when allowed', async () => {
|
||||
const api = createCardAPIWithStateWatcher();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
@@ -62,7 +63,8 @@ describe('DefaultManager', () => {
|
||||
});
|
||||
|
||||
it('should not set default view when not configured', () => {
|
||||
const api = createCardAPI();
|
||||
const api = createCardAPIWithStateWatcher();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
@@ -88,6 +90,7 @@ describe('DefaultManager', () => {
|
||||
|
||||
it('should restart timer when reconfigured', async () => {
|
||||
const api = createCardAPIWithStateWatcher();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
@@ -99,9 +102,6 @@ describe('DefaultManager', () => {
|
||||
);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
|
||||
|
||||
const hass = createHASS();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
const manager = new DefaultManager(api);
|
||||
@@ -120,6 +120,7 @@ describe('DefaultManager', () => {
|
||||
|
||||
it('should set default view when state changed', async () => {
|
||||
const api = createCardAPIWithStateWatcher();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
@@ -210,4 +211,47 @@ describe('DefaultManager', () => {
|
||||
expect(api.getAutomationsManager().deleteAutomations).toBeCalledWith(manager);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reinitialize when there is a config change', async () => {
|
||||
const configOn = createConfig({
|
||||
view: {
|
||||
default_reset: {
|
||||
every_seconds: 10,
|
||||
},
|
||||
},
|
||||
});
|
||||
const configOff = createConfig({
|
||||
view: {
|
||||
default_reset: {
|
||||
every_seconds: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const api = createCardAPIWithStateWatcher();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
const manager = new DefaultManager(api);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(configOn);
|
||||
await manager.initializeIfNecessary(null);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(configOff);
|
||||
await manager.initializeIfNecessary(configOn);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(configOff);
|
||||
await manager.initializeIfNecessary(configOff);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(api.getViewManager().setViewDefault).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,50 +21,23 @@ describe('InitializationManager', () => {
|
||||
describe('should correctly determine when mandatory initialization is required', () => {
|
||||
it('without config', () => {
|
||||
const api = createCardAPI();
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(manager.isInitializedMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('without aspects', () => {
|
||||
const api = createCardAPI();
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
initializer.isInitializedMultiple.mockReturnValue(false);
|
||||
|
||||
expect(manager.isInitializedMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('without view', () => {
|
||||
const api = createCardAPI();
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
initializer.isInitializedMultiple.mockReturnValue(true);
|
||||
|
||||
expect(manager.isInitializedMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with aspects and view', () => {
|
||||
const api = createCardAPI();
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
initializer.isInitializedMultiple.mockReturnValue(true);
|
||||
vi.mocked(api.getViewManager().hasView).mockReturnValue(true);
|
||||
|
||||
expect(manager.isInitializedMandatory()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('with microphone if configured', () => {
|
||||
const api = createCardAPI();
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
@@ -75,24 +48,25 @@ describe('InitializationManager', () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
initializer.isInitializedMultiple.mockReturnValue(true);
|
||||
vi.mocked(api.getViewManager().hasView).mockReturnValue(true);
|
||||
|
||||
expect(manager.isInitializedMandatory()).toBeTruthy();
|
||||
expect(manager.isInitializedMandatory()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should initialize mandatory', () => {
|
||||
it('without hass', async () => {
|
||||
const manager = new InitializationManager(createCardAPI());
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
await manager.initializeMandatory();
|
||||
});
|
||||
|
||||
it('without config', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new InitializationManager(api);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
vi.mocked(loadLanguages).mockResolvedValue(true);
|
||||
vi.mocked(sideLoadHomeAssistantElements).mockResolvedValue(true);
|
||||
|
||||
await manager.initializeMandatory();
|
||||
});
|
||||
|
||||
it('successfully', async () => {
|
||||
@@ -103,15 +77,23 @@ describe('InitializationManager', () => {
|
||||
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(
|
||||
false,
|
||||
);
|
||||
vi.mocked(loadLanguages).mockResolvedValue(true);
|
||||
vi.mocked(sideLoadHomeAssistantElements).mockResolvedValue(true);
|
||||
vi.mocked(api.getCameraManager().initializeCamerasFromConfig).mockResolvedValue(
|
||||
true,
|
||||
);
|
||||
vi.mocked(api.getViewManager().initialize).mockResolvedValue(true);
|
||||
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
await manager.initializeMandatory();
|
||||
|
||||
expect(loadLanguages).toBeCalled();
|
||||
expect(sideLoadHomeAssistantElements).toBeCalled();
|
||||
expect(api.getCameraManager().initializeCamerasFromConfig).toBeCalled();
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
|
||||
expect(api.getViewManager().initialize).toBeCalled();
|
||||
expect(api.getMicrophoneManager().connect).not.toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('successfully with microphone if configured', async () => {
|
||||
@@ -126,25 +108,19 @@ describe('InitializationManager', () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(loadLanguages).mockResolvedValue(true);
|
||||
vi.mocked(sideLoadHomeAssistantElements).mockResolvedValue(true);
|
||||
vi.mocked(api.getCameraManager().initializeCamerasFromConfig).mockResolvedValue(
|
||||
true,
|
||||
);
|
||||
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
await manager.initializeMandatory();
|
||||
|
||||
expect(api.getMicrophoneManager().connect).toBeCalled();
|
||||
});
|
||||
|
||||
it('successfully with querystring view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(false);
|
||||
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(true);
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
|
||||
expect(api.getQueryStringManager().executeViewRelated).toBeCalled();
|
||||
});
|
||||
|
||||
it('with message set during initialization', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
@@ -153,12 +129,17 @@ describe('InitializationManager', () => {
|
||||
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(
|
||||
false,
|
||||
);
|
||||
vi.mocked(loadLanguages).mockResolvedValue(true);
|
||||
vi.mocked(sideLoadHomeAssistantElements).mockResolvedValue(true);
|
||||
vi.mocked(api.getCameraManager().initializeCamerasFromConfig).mockResolvedValue(
|
||||
true,
|
||||
);
|
||||
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
await manager.initializeMandatory();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
expect(api.getViewManager().initialize).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('with languages and side load elements in progress', async () => {
|
||||
@@ -168,7 +149,7 @@ describe('InitializationManager', () => {
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
initializer.initializeMultipleIfNecessary.mockResolvedValue(false);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
await manager.initializeMandatory();
|
||||
});
|
||||
|
||||
it('with cameras in progress', async () => {
|
||||
@@ -182,100 +163,7 @@ describe('InitializationManager', () => {
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockResolvedValueOnce(false);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with existing view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getViewManager().hasView).mockReturnValue(true);
|
||||
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
initializer.initializeMultipleIfNecessary
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockResolvedValueOnce(true);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should initialize background', () => {
|
||||
it('without hass and config', async () => {
|
||||
const manager = new InitializationManager(createCardAPI());
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('successfully when already initialized', async () => {
|
||||
const api = createCardAPI();
|
||||
|
||||
const initializer = mock<Initializer>();
|
||||
initializer.isInitializedMultiple.mockReturnValue(true);
|
||||
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
menu: {
|
||||
buttons: {
|
||||
media_player: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
|
||||
expect(api.getMediaPlayerManager().initialize).not.toBeCalled();
|
||||
expect(api.getDefaultManager().initialize).not.toBeCalled();
|
||||
expect(api.getCardElementManager().update).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('successfully with all inititalizers', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new InitializationManager(api);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
menu: {
|
||||
buttons: {
|
||||
media_player: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
|
||||
expect(api.getMediaPlayerManager().initialize).toBeCalled();
|
||||
expect(api.getDefaultManager().initialize).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('with initializers in progress', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
menu: {
|
||||
buttons: {
|
||||
media_player: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const initializer = mock<Initializer>();
|
||||
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
initializer.initializeMultipleIfNecessary.mockResolvedValue(false);
|
||||
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeFalsy();
|
||||
await manager.initializeMandatory();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createHASS,
|
||||
createRegistryEntity,
|
||||
createStateEntity,
|
||||
@@ -75,6 +76,17 @@ describe('MediaPlayerManager', () => {
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||
createHASSWithMediaPlayers(),
|
||||
);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
menu: {
|
||||
buttons: {
|
||||
media_player: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getEntityRegistryManager).mockReturnValue(entityRegistryManager);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
@@ -108,6 +120,17 @@ describe('MediaPlayerManager', () => {
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||
createHASSWithMediaPlayers(),
|
||||
);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
menu: {
|
||||
buttons: {
|
||||
media_player: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getEntityRegistryManager).mockReturnValue(entityRegistryManager);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
@@ -121,6 +144,39 @@ describe('MediaPlayerManager', () => {
|
||||
expect(manager.hasMediaPlayers()).toBeTruthy();
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
|
||||
it('should reinitialize when there is a config change', async () => {
|
||||
const entityRegistryManager = mock<EntityRegistryManager>();
|
||||
entityRegistryManager.getEntities.mockResolvedValue(
|
||||
new Map([
|
||||
['media_player.ok1', createRegistryEntity({ hidden_by: '' })],
|
||||
['media_player.ok2', createRegistryEntity({ hidden_by: 'user' })],
|
||||
]),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||
createHASSWithMediaPlayers(),
|
||||
);
|
||||
const config = createConfig({
|
||||
menu: {
|
||||
buttons: {
|
||||
media_player: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(config);
|
||||
vi.mocked(api.getEntityRegistryManager).mockReturnValue(entityRegistryManager);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.initializeIfNecessary(null);
|
||||
expect(manager.hasMediaPlayers()).toBeTruthy();
|
||||
|
||||
await manager.initializeIfNecessary(config);
|
||||
expect(manager.hasMediaPlayers()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should stop', async () => {
|
||||
|
||||
@@ -16,13 +16,13 @@ describe('QueryStringManager', () => {
|
||||
global.window.location = mock<Location>();
|
||||
});
|
||||
|
||||
it('should reject malformed query string', () => {
|
||||
it('should reject malformed query string', async () => {
|
||||
setQueryString('BOGUS_KEY=BOGUS_VALUE');
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
@@ -41,7 +41,7 @@ describe('QueryStringManager', () => {
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (viewName: string) => {
|
||||
])('%s', async (viewName: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${viewName}=`);
|
||||
const api = createCardAPI();
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('QueryStringManager', () => {
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(false);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
@@ -66,13 +66,13 @@ describe('QueryStringManager', () => {
|
||||
['download' as const],
|
||||
['expand' as const],
|
||||
['menu_toggle' as const],
|
||||
])('%s', (action: string) => {
|
||||
])('%s', async (action: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${action}=`);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeActions).toBeCalledWith([
|
||||
@@ -85,7 +85,7 @@ describe('QueryStringManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute view default action', () => {
|
||||
it('should execute view default action', async () => {
|
||||
setQueryString('?frigate-card-action.id.default=');
|
||||
const api = createCardAPI();
|
||||
// View actions do not need the card to have been updated.
|
||||
@@ -93,7 +93,7 @@ describe('QueryStringManager', () => {
|
||||
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalled();
|
||||
|
||||
@@ -102,13 +102,13 @@ describe('QueryStringManager', () => {
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute camera_select action', () => {
|
||||
it('should execute camera_select action', async () => {
|
||||
setQueryString('?frigate-card-action.id.camera_select=camera.office');
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
params: {
|
||||
@@ -121,13 +121,13 @@ describe('QueryStringManager', () => {
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute live_substream_select action', () => {
|
||||
it('should execute live_substream_select action', async () => {
|
||||
setQueryString('?frigate-card-action.id.live_substream_select=camera.office_hd');
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
modifiers: [expect.any(SubstreamSelectViewModifier)],
|
||||
@@ -142,13 +142,13 @@ describe('QueryStringManager', () => {
|
||||
describe('should ignore action without value', () => {
|
||||
it.each([['camera_select' as const], ['live_substream_select' as const]])(
|
||||
'%s',
|
||||
(action: string) => {
|
||||
async (action: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${action}=`);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
@@ -158,7 +158,7 @@ describe('QueryStringManager', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unknown action', () => {
|
||||
it('should handle unknown action', async () => {
|
||||
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
setQueryString('?frigate-card-action.id.not_an_action=value');
|
||||
@@ -166,7 +166,7 @@ describe('QueryStringManager', () => {
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
@@ -187,13 +187,13 @@ describe('QueryStringManager', () => {
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (viewName: string) => {
|
||||
])('%s', async (viewName: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${viewName}=`);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
@@ -210,13 +210,13 @@ describe('QueryStringManager', () => {
|
||||
['download' as const],
|
||||
['expand' as const],
|
||||
['menu_toggle' as const],
|
||||
])('%s', (action: string) => {
|
||||
])('%s', async (action: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${action}=value`);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(false);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
@@ -225,7 +225,7 @@ describe('QueryStringManager', () => {
|
||||
});
|
||||
|
||||
describe('should handle conflicting but valid actions', () => {
|
||||
it('view and default with camera and substream specified', () => {
|
||||
it('view and default with camera and substream specified', async () => {
|
||||
setQueryString(
|
||||
'?frigate-card-action.id.clips=' +
|
||||
'&frigate-card-action.id.live_substream_select=camera.kitchen_hd' +
|
||||
@@ -236,7 +236,7 @@ describe('QueryStringManager', () => {
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewDefaultWithNewQuery).toBeCalledWith({
|
||||
params: {
|
||||
@@ -247,7 +247,7 @@ describe('QueryStringManager', () => {
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('multiple cameras specified', () => {
|
||||
it('multiple cameras specified', async () => {
|
||||
setQueryString(
|
||||
'?frigate-card-action.id.camera_select=camera.kitchen' +
|
||||
'&frigate-card-action.id.camera_select=camera.office',
|
||||
@@ -256,7 +256,7 @@ describe('QueryStringManager', () => {
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
await manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({
|
||||
params: {
|
||||
@@ -279,13 +279,13 @@ describe('QueryStringManager', () => {
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (viewName: string) => {
|
||||
])('%s', async (viewName: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${viewName}=`);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeNonViewRelated();
|
||||
await manager.executeNonViewRelated();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
@@ -298,13 +298,13 @@ describe('QueryStringManager', () => {
|
||||
['download' as const],
|
||||
['expand' as const],
|
||||
['menu_toggle' as const],
|
||||
])('%s', (viewName: string) => {
|
||||
])('%s', async (viewName: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${viewName}=`);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeViewRelated();
|
||||
await manager.executeViewRelated();
|
||||
|
||||
expect(api.getActionsManager().executeActions).not.toBeCalled();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@ import { mock } from 'vitest-mock-extended';
|
||||
import { ViewFactory } from '../../../src/card-controller/view/factory';
|
||||
import { ViewManager } from '../../../src/card-controller/view/view-manager';
|
||||
import { FrigateCardView } from '../../../src/config/types';
|
||||
import { ViewMedia } from '../../../src/view/media';
|
||||
import { MediaQueriesResults } from '../../../src/view/media-queries-results';
|
||||
import {
|
||||
createCameraManager,
|
||||
createCapabilities,
|
||||
@@ -11,8 +13,6 @@ import {
|
||||
createStore,
|
||||
createView,
|
||||
} from '../../test-utils';
|
||||
import { ViewMedia } from '../../../src/view/media';
|
||||
import { MediaQueriesResults } from '../../../src/view/media-queries-results';
|
||||
|
||||
describe('should act correctly when view is set', () => {
|
||||
it('basic view', () => {
|
||||
@@ -360,4 +360,33 @@ describe('hasMajorMediaChange', () => {
|
||||
manager.hasMajorMediaChange(createView({ queryResults: queryResults_2 })),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('should initialize', () => {
|
||||
it('without querystring', async () => {
|
||||
const api = createCardAPI();
|
||||
const factory = mock<ViewFactory>();
|
||||
const manager = new ViewManager(api, factory);
|
||||
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
});
|
||||
factory.getViewDefaultWithNewQuery.mockResolvedValue(view);
|
||||
|
||||
expect(await manager.initialize()).toBeTruthy();
|
||||
|
||||
expect(manager.getView()).toBe(view);
|
||||
});
|
||||
|
||||
it('with querystring', async () => {
|
||||
const api = createCardAPI();
|
||||
const factory = mock<ViewFactory>();
|
||||
const manager = new ViewManager(api, factory);
|
||||
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(true);
|
||||
|
||||
expect(await manager.initialize()).toBeTruthy();
|
||||
|
||||
expect(api.getQueryStringManager().executeViewRelated).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { Initializer } from '../../src/utils/initializer/initializer';
|
||||
|
||||
describe('Initializer', () => {
|
||||
it('should initialize with initializer', async () => {
|
||||
const initializer = new Initializer();
|
||||
|
||||
expect(initializer.isInitialized('foo')).toBeFalsy();
|
||||
expect(
|
||||
await initializer.initializeIfNecessary('foo', async () => true),
|
||||
).toBeTruthy();
|
||||
expect(initializer.isInitialized('foo')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialize without initializer', async () => {
|
||||
const initializer = new Initializer();
|
||||
|
||||
expect(initializer.isInitialized('foo')).toBeFalsy();
|
||||
expect(
|
||||
await initializer.initializeIfNecessary('foo', async () => true),
|
||||
).toBeTruthy();
|
||||
expect(initializer.isInitialized('foo')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialize when already initialized', async () => {
|
||||
const initializer = new Initializer();
|
||||
|
||||
expect(initializer.isInitialized('foo')).toBeFalsy();
|
||||
expect(
|
||||
await initializer.initializeIfNecessary('foo', async () => true),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
await initializer.initializeIfNecessary('foo', async () => true),
|
||||
).toBeTruthy();
|
||||
expect(initializer.isInitialized('foo')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not initialize with failed initializer', async () => {
|
||||
const initializer = new Initializer();
|
||||
|
||||
expect(initializer.isInitialized('foo')).toBeFalsy();
|
||||
expect(
|
||||
await initializer.initializeIfNecessary('foo', async () => false),
|
||||
).toBeFalsy();
|
||||
expect(initializer.isInitialized('foo')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should initialize multiple', async () => {
|
||||
const initializer = new Initializer();
|
||||
|
||||
expect(initializer.isInitializedMultiple(['foo', 'bar'])).toBeFalsy();
|
||||
expect(
|
||||
await initializer.initializeMultipleIfNecessary({
|
||||
foo: async () => true,
|
||||
bar: async () => false,
|
||||
}),
|
||||
).toBeFalsy();
|
||||
|
||||
expect(initializer.isInitializedMultiple(['foo', 'bar'])).toBeFalsy();
|
||||
|
||||
expect(
|
||||
await initializer.initializeMultipleIfNecessary({
|
||||
bar: async () => true,
|
||||
}),
|
||||
).toBeTruthy();
|
||||
|
||||
expect(initializer.isInitializedMultiple(['foo', 'bar'])).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should uninitialize', async () => {
|
||||
const initializer = new Initializer();
|
||||
await initializer.initializeIfNecessary('foo');
|
||||
expect(initializer.isInitialized('foo')).toBeTruthy();
|
||||
|
||||
initializer.uninitialize('foo');
|
||||
|
||||
expect(initializer.isInitialized('foo')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user