Add mixxing part of prior commit.
This commit is contained in:
@@ -1,728 +0,0 @@
|
||||
import { afterAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import {
|
||||
ActionType,
|
||||
FrigateCardCustomAction,
|
||||
FrigateCardView,
|
||||
frigateCardCustomActionSchema,
|
||||
} from '../../../src/config/types';
|
||||
import { FrigateCardMediaPlayer } from '../../../src/types';
|
||||
import {
|
||||
convertActionToFrigateCardCustomAction,
|
||||
frigateCardHandleActionConfig,
|
||||
getActionConfigGivenAction,
|
||||
} from '../../../src/utils/action.js';
|
||||
import { ActionsManager } from '../../../src/utils/card-controller/actions-manager';
|
||||
import {
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createHASS,
|
||||
createMediaLoadedInfo,
|
||||
createView,
|
||||
createViewWithMedia,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/utils/action.js');
|
||||
vi.mock('../../../src/camera-manager/manager.js');
|
||||
|
||||
const createAction = (
|
||||
action: Record<string, unknown>,
|
||||
): FrigateCardCustomAction | null => {
|
||||
const result = frigateCardCustomActionSchema.safeParse({
|
||||
action: 'custom:frigate-card-action',
|
||||
...action,
|
||||
});
|
||||
return result.success ? result.data : null;
|
||||
};
|
||||
|
||||
describe('ActionsManager.getMergedActions', () => {
|
||||
const config = {
|
||||
view: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
live: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '2',
|
||||
},
|
||||
},
|
||||
},
|
||||
media_gallery: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '3',
|
||||
},
|
||||
},
|
||||
},
|
||||
media_viewer: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '4',
|
||||
},
|
||||
},
|
||||
},
|
||||
image: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '5',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should get no merged actions with a message', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ view: 'live' }),
|
||||
);
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true);
|
||||
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
expect(manager.getMergedActions()).toEqual({});
|
||||
});
|
||||
|
||||
describe('should get merged actions with live view', () => {
|
||||
it.each([
|
||||
[
|
||||
'live' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '2',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'clips' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '3',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'clip' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '4',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'image' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '5',
|
||||
},
|
||||
},
|
||||
],
|
||||
['timeline' as const, {}],
|
||||
])('%s', (viewName: FrigateCardView, result: Record<string, unknown>) => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ view: viewName }),
|
||||
);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig(config));
|
||||
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
expect(manager.getMergedActions()).toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('ActionsManager.handleInteraction', () => {
|
||||
it('should handle interaction', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const hass = createHASS();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
|
||||
const actionForThisInteraction: ActionType = {
|
||||
action: 'none',
|
||||
};
|
||||
vi.mocked(getActionConfigGivenAction).mockReturnValue(actionForThisInteraction);
|
||||
|
||||
manager.handleInteraction('tap');
|
||||
|
||||
expect(frigateCardHandleActionConfig).toBeCalledWith(
|
||||
element,
|
||||
hass,
|
||||
manager.getMergedActions(),
|
||||
'tap',
|
||||
actionForThisInteraction,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not handle interaction', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
|
||||
|
||||
// No values of hass.
|
||||
manager.handleInteraction('tap');
|
||||
expect(frigateCardHandleActionConfig).not.toBeCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ActionsManager.handleActionEvent', () => {
|
||||
it('should handle event', () => {
|
||||
const action = createAction({ frigate_card_action: 'default' })!;
|
||||
const event: CustomEvent<FrigateCardCustomAction> = new CustomEvent('ll-custom', {
|
||||
detail: action,
|
||||
});
|
||||
|
||||
// The file containing convertActionToFrigateCardCustomAction (action.ts) is
|
||||
// mocked, so need to provide a value here.
|
||||
vi.mocked(convertActionToFrigateCardCustomAction).mockReturnValue(action);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
manager.handleActionEvent(event);
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not handle event without detail', () => {
|
||||
const action = createAction({ frigate_card_action: 'default' })!;
|
||||
const event = new Event('ll-custom');
|
||||
|
||||
// Mock this out just so that if the sentinel in handleActionEvent failed,
|
||||
// it would still trigger a test failure below.
|
||||
vi.mocked(convertActionToFrigateCardCustomAction).mockReturnValue(action);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
manager.handleActionEvent(event);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not handle malformed action', () => {
|
||||
const action = createAction({ frigate_card_action: 'default' })!;
|
||||
const event: CustomEvent<FrigateCardCustomAction> = new CustomEvent('ll-custom', {
|
||||
detail: action,
|
||||
});
|
||||
|
||||
vi.mocked(convertActionToFrigateCardCustomAction).mockReturnValue(null);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
manager.handleActionEvent(event);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ActionsManager.executeAction', () => {
|
||||
it('should not handle actions with different card_id', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
card_id: 'foo',
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
card_id: 'NOT_foo',
|
||||
frigate_card_action: 'default',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle default action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'default',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should handle view action', async () => {
|
||||
it.each([
|
||||
['clip' as const],
|
||||
['clips' as const],
|
||||
['image' as const],
|
||||
['live' as const],
|
||||
['recording' as const],
|
||||
['recordings' as const],
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', async (viewName: FrigateCardView) => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: viewName,
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: viewName,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle download action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'download',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getDownloadManager().downloadViewerMedia).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle camera ui action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_ui',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getCameraURLManager().openURL).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle expand action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'expand',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getExpandManager().toggleExpanded).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle fullscreen action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'fullscreen',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getFullscreenManager().toggleFullscreen).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle menu toggle action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'menu_toggle',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getCardElementManager().toggleMenu).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should handle camera_select action', () => {
|
||||
it('with valid camera and view', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('without config', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'timeline',
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'timeline',
|
||||
cameraID: 'camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with target view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
// Change to clips view when the camera changes.
|
||||
camera_select: 'clips',
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'live',
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'clips',
|
||||
cameraID: 'camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('without a current view', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('with an unsupported view', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'timeline',
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
// Should have fallen back to the default view.
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle live_substream_select action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'live_substream_select',
|
||||
camera: 'substream',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewWithSubstream).toBeCalledWith('substream');
|
||||
});
|
||||
|
||||
it('should handle live_substream_off action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'live_substream_off',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewWithoutSubstream).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle live_substream_on action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'live_substream_on',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewWithSubstream).toBeCalledWith();
|
||||
});
|
||||
|
||||
describe('should handle media_player action', () => {
|
||||
it('to stop', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'stop',
|
||||
media_player: 'this_is_a_media_player',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getMediaPlayerManager().stop).toBeCalledWith('this_is_a_media_player');
|
||||
});
|
||||
|
||||
it('to play live', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera',
|
||||
view: 'live',
|
||||
}),
|
||||
);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'play',
|
||||
media_player: 'this_is_a_media_player',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getMediaPlayerManager().playLive).toBeCalledWith(
|
||||
'this_is_a_media_player',
|
||||
'camera',
|
||||
);
|
||||
});
|
||||
|
||||
it('to play media', async () => {
|
||||
const api = createCardAPI();
|
||||
const view = createViewWithMedia({
|
||||
camera: 'camera',
|
||||
view: 'media',
|
||||
});
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'play',
|
||||
media_player: 'this_is_a_media_player',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getMediaPlayerManager().playMedia).toBeCalledWith(
|
||||
'this_is_a_media_player',
|
||||
view.queryResults?.getSelectedResult(),
|
||||
);
|
||||
});
|
||||
|
||||
it('to play media without selected media', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'media',
|
||||
}),
|
||||
);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'play',
|
||||
media_player: 'this_is_a_media_player',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getMediaPlayerManager().playMedia).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle diagnostics action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'diagnostics',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'diagnostics',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle microphone_mute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'microphone_mute',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getMicrophoneManager().mute).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle microphone_unmute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'microphone_unmute',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getMicrophoneManager().unmute).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should handle media player action', () => {
|
||||
it.each([
|
||||
['mute' as const],
|
||||
['unmute' as const],
|
||||
['play' as const],
|
||||
['pause' as const],
|
||||
])('%s', async (action: 'mute' | 'unmute' | 'play' | 'pause') => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: action,
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(player[action]).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle screenshot action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'screenshot',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getDownloadManager().downloadScreenshot).toBeCalled();
|
||||
});
|
||||
|
||||
it('should handle display_mode_select action', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
await manager.executeAction(
|
||||
createAction({
|
||||
frigate_card_action: 'display_mode_select',
|
||||
display_mode: 'grid',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewWithNewDisplayMode).toBeCalledWith('grid');
|
||||
});
|
||||
|
||||
it('should handle unknown action', async () => {
|
||||
const manager = new ActionsManager(createCardAPI());
|
||||
|
||||
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||
|
||||
await manager.executeAction(
|
||||
// Have to manually create the action (vs using `createAction()`) since
|
||||
// it's malformed.
|
||||
{
|
||||
frigate_card_action: 'not_a_real_action',
|
||||
} as unknown as FrigateCardCustomAction,
|
||||
);
|
||||
|
||||
expect(spy).toBeCalledWith(
|
||||
'Frigate card received unknown card action: not_a_real_action',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
import add from 'date-fns/add';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AutoUpdateManager } from '../../../src/utils/card-controller/auto-update-manager';
|
||||
import { createCardAPI, createConfig } from '../../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('AutoUpdateManager', () => {
|
||||
const start = new Date('2023-09-23T19:12:00');
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should set default view when allowed', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
update_seconds: 10,
|
||||
},
|
||||
}),
|
||||
);
|
||||
// Card is triggered.
|
||||
vi.mocked(api.getTriggersManager().isTriggered).mockReturnValue(true);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
|
||||
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(start);
|
||||
|
||||
const manager = new AutoUpdateManager(api);
|
||||
manager.startDefaultViewTimer();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
|
||||
vi.mocked(api.getTriggersManager().isTriggered).mockReturnValue(false);
|
||||
|
||||
vi.setSystemTime(add(start, { seconds: 20 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not set default view when not configured', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
update_seconds: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getTriggersManager().isTriggered).mockReturnValue(false);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
|
||||
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(start);
|
||||
|
||||
const manager = new AutoUpdateManager(api);
|
||||
manager.startDefaultViewTimer();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { frigateCardHandleAction } from '../../../src/utils/action.js';
|
||||
import {
|
||||
AutomationsManager,
|
||||
} from '../../../src/utils/card-controller/automations-manager';
|
||||
import { createCardAPI, createConfig, createHASS } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/utils/action.js');
|
||||
|
||||
describe('AutomationsManager', () => {
|
||||
const actions = [
|
||||
{
|
||||
action: 'custom:frigate-card-action',
|
||||
frigate_card_action: 'clips',
|
||||
},
|
||||
];
|
||||
const conditions = { fullscreen: true };
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should do nothing without hass', () => {
|
||||
const api = createCardAPI();
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should do nothing without automations', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.setAutomationsFromConfig();
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute actions', () => {
|
||||
const config = createConfig({
|
||||
automations: [
|
||||
{
|
||||
conditions: conditions,
|
||||
actions: actions,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getNonOverriddenConfig).mockReturnValue(config);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.setAutomationsFromConfig();
|
||||
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).not.toBeCalled();
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateCondition).mockReturnValue(true);
|
||||
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).toBeCalledTimes(1);
|
||||
|
||||
// Automation will not re-fire when condition continues to evaluate the
|
||||
// same.
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).toBeCalledTimes(1);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateCondition).mockReturnValue(false);
|
||||
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).toBeCalledTimes(1);
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateCondition).mockReturnValue(true);
|
||||
|
||||
automationsManager.execute();
|
||||
expect(frigateCardHandleAction).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should execute actions_not', () => {
|
||||
const config = createConfig({
|
||||
automations: [
|
||||
{
|
||||
conditions: conditions,
|
||||
actions_not: actions,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getNonOverriddenConfig).mockReturnValue(config);
|
||||
vi.mocked(api.getConditionsManager().evaluateCondition).mockReturnValue(false);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.setAutomationsFromConfig();
|
||||
|
||||
automationsManager.execute();
|
||||
|
||||
expect(frigateCardHandleAction).toBeCalled();
|
||||
});
|
||||
|
||||
it('should prevent automation loops', () => {
|
||||
const config = createConfig({
|
||||
automations: [
|
||||
{
|
||||
conditions: { fullscreen: true },
|
||||
actions: actions,
|
||||
},
|
||||
{
|
||||
conditions: { fullscreen: true },
|
||||
actions_not: actions,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().getNonOverriddenConfig).mockReturnValue(config);
|
||||
|
||||
const automationsManager = new AutomationsManager(api);
|
||||
automationsManager.setAutomationsFromConfig();
|
||||
|
||||
// Create a setup where one automation action causes another...
|
||||
let evaluation = true;
|
||||
vi.mocked(frigateCardHandleAction).mockImplementation(() => {
|
||||
evaluation = !evaluation;
|
||||
vi.mocked(api.getConditionsManager().evaluateCondition).mockReturnValue(
|
||||
evaluation,
|
||||
);
|
||||
automationsManager.execute();
|
||||
});
|
||||
|
||||
vi.mocked(api.getConditionsManager().evaluateCondition).mockReturnValue(evaluation);
|
||||
|
||||
automationsManager.execute();
|
||||
|
||||
expect(api.getMessageManager().setMessageIfHigherPriority).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'error',
|
||||
message:
|
||||
'Too many nested automation calls, please check your configuration for loops',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(frigateCardHandleAction).toBeCalledTimes(10);
|
||||
});
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CameraEndpoint } from '../../../src/camera-manager/types';
|
||||
import { CameraURLManager } from '../../../src/utils/card-controller/camera-url-manager';
|
||||
import {
|
||||
CardCameraURLAPI
|
||||
} from '../../../src/utils/card-controller/types';
|
||||
import { createCardAPI, createViewWithMedia } from '../../test-utils';
|
||||
|
||||
const createAPIWithMedia = (): CardCameraURLAPI => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createViewWithMedia()
|
||||
)
|
||||
return api;
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('CameraURLManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should get URL', () => {
|
||||
const api = createAPIWithMedia();
|
||||
const manager = new CameraURLManager(api);
|
||||
|
||||
const endpoint: CameraEndpoint = {
|
||||
endpoint: 'http://frigate',
|
||||
};
|
||||
|
||||
vi.mocked(api.getCameraManager().getCameraEndpoints)?.mockReturnValue({
|
||||
ui: endpoint,
|
||||
});
|
||||
|
||||
expect(manager.getCameraURL()).toBe('http://frigate');
|
||||
expect(manager.hasCameraURL()).toBeTruthy();
|
||||
|
||||
const windowSpy = vi.spyOn(window, 'open').mockReturnValue(null);
|
||||
manager.openURL();
|
||||
expect(windowSpy).toBeCalledWith('http://frigate');
|
||||
});
|
||||
|
||||
it('should not get URL without view', () => {
|
||||
const manager = new CameraURLManager(createCardAPI());
|
||||
expect(manager.getCameraURL()).toBeNull();
|
||||
|
||||
const windowSpy = vi.spyOn(window, 'open').mockReturnValue(null);
|
||||
manager.openURL();
|
||||
expect(windowSpy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not get URL without cameraManager endpoints', () => {
|
||||
const api = createAPIWithMedia();
|
||||
vi.mocked(api.getCameraManager().getCameraEndpoints)?.mockReturnValue(null);
|
||||
const manager = new CameraURLManager(api);
|
||||
expect(manager.getCameraURL()).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,182 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
CardElementManager,
|
||||
CardHTMLElement,
|
||||
} from '../../../src/utils/card-controller/card-element-manager';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
const createElement = (): CardHTMLElement => {
|
||||
const element = document.createElement('div') as unknown as CardHTMLElement;
|
||||
element.requestUpdate = vi.fn();
|
||||
return element as CardHTMLElement;
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('CardElementManager', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
global.window.location = mock<Location>();
|
||||
});
|
||||
|
||||
it('should get element', () => {
|
||||
const element = createElement();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
expect(manager.getElement()).toBe(element);
|
||||
});
|
||||
|
||||
it('should reset scroll', () => {
|
||||
const callback = vi.fn();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
createElement(),
|
||||
callback,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
manager.scrollReset();
|
||||
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
it('should toggle menu', () => {
|
||||
const callback = vi.fn();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
createElement(),
|
||||
() => undefined,
|
||||
callback,
|
||||
);
|
||||
|
||||
manager.toggleMenu();
|
||||
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
it('should update', () => {
|
||||
const element = createElement();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
manager.update();
|
||||
expect(element.requestUpdate).toBeCalled();
|
||||
});
|
||||
|
||||
it('should get hasUpdated', () => {
|
||||
const element = createElement();
|
||||
element.hasUpdated = true;
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
expect(manager.hasUpdated()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get height', () => {
|
||||
const element = createElement();
|
||||
element.getBoundingClientRect = vi.fn().mockReturnValue({
|
||||
width: 200,
|
||||
height: 800,
|
||||
});
|
||||
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
expect(manager.getCardHeight()).toBe(800);
|
||||
});
|
||||
|
||||
it('should connect', () => {
|
||||
const windowAddEventListener = vi.spyOn(global.window, 'addEventListener');
|
||||
|
||||
const addEventListener = vi.fn();
|
||||
const element = createElement();
|
||||
element.addEventListener = addEventListener;
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
manager.elementConnected();
|
||||
|
||||
expect(element.getAttribute('panel')).toBeNull();
|
||||
expect(api.getFullscreenManager().connect).toBeCalled();
|
||||
|
||||
expect(addEventListener).toBeCalledWith(
|
||||
'mousemove',
|
||||
api.getInteractionManager().reportInteraction,
|
||||
);
|
||||
expect(addEventListener).toBeCalledWith(
|
||||
'll-custom',
|
||||
api.getActionsManager().handleActionEvent,
|
||||
);
|
||||
expect(addEventListener).toBeCalledWith(
|
||||
'@action',
|
||||
api.getInteractionManager().reportInteraction,
|
||||
);
|
||||
expect(windowAddEventListener).toBeCalledWith('location-changed', expect.anything());
|
||||
expect(windowAddEventListener).toBeCalledWith('popstate', expect.anything());
|
||||
});
|
||||
|
||||
it('should disconnect', () => {
|
||||
const windowRemoveEventListener = vi.spyOn(global.window, 'removeEventListener');
|
||||
|
||||
const element = createElement();
|
||||
element.setAttribute('panel', '');
|
||||
|
||||
const removeEventListener = vi.fn();
|
||||
element.removeEventListener = removeEventListener;
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
|
||||
manager.elementDisconnected();
|
||||
|
||||
expect(element.getAttribute('panel')).toBeNull();
|
||||
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
||||
expect(api.getFullscreenManager().disconnect).toBeCalled();
|
||||
|
||||
expect(removeEventListener).toBeCalledWith(
|
||||
'mousemove',
|
||||
api.getInteractionManager().reportInteraction,
|
||||
);
|
||||
expect(removeEventListener).toBeCalledWith(
|
||||
'll-custom',
|
||||
api.getActionsManager().handleActionEvent,
|
||||
);
|
||||
expect(removeEventListener).toBeCalledWith(
|
||||
'@action',
|
||||
api.getInteractionManager().reportInteraction,
|
||||
);
|
||||
expect(windowRemoveEventListener).toBeCalledWith(
|
||||
'location-changed',
|
||||
expect.anything(),
|
||||
);
|
||||
expect(windowRemoveEventListener).toBeCalledWith('popstate', expect.anything());
|
||||
});
|
||||
});
|
||||
@@ -1,363 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FrigateCardCondition } from '../../../src/config/types';
|
||||
import {
|
||||
ConditionEvaluateRequestEvent,
|
||||
ConditionsManager,
|
||||
evaluateConditionViaEvent,
|
||||
getOverriddenConfig,
|
||||
getOverridesByKey,
|
||||
} from '../../../src/utils/card-controller/conditions-manager';
|
||||
import {
|
||||
createCardAPI,
|
||||
createCondition,
|
||||
createConfig,
|
||||
createStateEntity,
|
||||
} from '../../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('ConditionEvaluateRequestEvent', () => {
|
||||
it('should construct', () => {
|
||||
const condition = createCondition({ fullscreen: true });
|
||||
const event = new ConditionEvaluateRequestEvent(condition, {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
|
||||
expect(event.type).toBe('frigate-card:condition:evaluate');
|
||||
expect(event.condition).toBe(condition);
|
||||
expect(event.bubbles).toBeTruthy();
|
||||
expect(event.composed).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('evaluateConditionViaEvent', () => {
|
||||
it('should evaluate true without condition', () => {
|
||||
const element = document.createElement('div');
|
||||
expect(evaluateConditionViaEvent(element)).toBeTruthy();
|
||||
});
|
||||
it('should dispatch event with condition and evaluate true', () => {
|
||||
const element = document.createElement('div');
|
||||
const condition = createCondition({ fullscreen: true });
|
||||
const handler = vi.fn().mockImplementation((ev: ConditionEvaluateRequestEvent) => {
|
||||
expect(ev.condition).toBe(condition);
|
||||
ev.evaluation = true;
|
||||
});
|
||||
element.addEventListener('frigate-card:condition:evaluate', handler);
|
||||
|
||||
expect(evaluateConditionViaEvent(element, condition)).toBeTruthy();
|
||||
expect(handler).toBeCalled();
|
||||
});
|
||||
it('should dispatch event with condition and evaluate false', () => {
|
||||
const element = document.createElement('div');
|
||||
const condition = createCondition({ fullscreen: true });
|
||||
const handler = vi.fn().mockImplementation((ev: ConditionEvaluateRequestEvent) => {
|
||||
expect(ev.condition).toBe(condition);
|
||||
ev.evaluation = false;
|
||||
});
|
||||
element.addEventListener('frigate-card:condition:evaluate', handler);
|
||||
|
||||
expect(evaluateConditionViaEvent(element, condition)).toBeFalsy();
|
||||
expect(handler).toBeCalled();
|
||||
});
|
||||
it('should dispatch event evaluate false if no evaluation', () => {
|
||||
const element = document.createElement('div');
|
||||
const condition = createCondition({ fullscreen: true });
|
||||
const handler = vi.fn();
|
||||
element.addEventListener('frigate-card:condition:evaluate', handler);
|
||||
|
||||
expect(evaluateConditionViaEvent(element, condition)).toBeFalsy();
|
||||
expect(handler).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOverriddenConfig', () => {
|
||||
const config = {
|
||||
menu: {
|
||||
style: 'none',
|
||||
},
|
||||
};
|
||||
const overrides = [
|
||||
{
|
||||
overrides: {
|
||||
menu: {
|
||||
style: 'above',
|
||||
},
|
||||
},
|
||||
conditions: {
|
||||
fullscreen: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it('should not override config', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
expect(getOverriddenConfig(manager, config, overrides)).toBe(config);
|
||||
});
|
||||
|
||||
it('should override config', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
manager.setState({ fullscreen: true });
|
||||
|
||||
expect(getOverriddenConfig(manager, config, overrides)).toEqual({
|
||||
menu: {
|
||||
style: 'above',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing without overrides', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
manager.setState({ fullscreen: true });
|
||||
|
||||
expect(getOverriddenConfig(manager, config)).toBe(config);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOverridesByKey', () => {
|
||||
const condition = {
|
||||
fullscreen: true,
|
||||
};
|
||||
const override = {
|
||||
menu: {
|
||||
style: 'above',
|
||||
},
|
||||
};
|
||||
const overrides = [
|
||||
{
|
||||
overrides: override,
|
||||
conditions: condition,
|
||||
},
|
||||
];
|
||||
|
||||
it('should get overrides', () => {
|
||||
expect(getOverridesByKey('menu', overrides)).toEqual([
|
||||
{ conditions: condition, overrides: { style: 'above' } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should get no overrides', () => {
|
||||
expect(getOverridesByKey('live', overrides)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should get no overrides when undefined', () => {
|
||||
expect(getOverridesByKey('live')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ConditionsManager', () => {
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [],
|
||||
elements: [
|
||||
{
|
||||
type: 'custom:frigate-card-conditional',
|
||||
conditions: {
|
||||
fullscreen: true,
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
type: 'custom:nested-unknown-object',
|
||||
unknown_key: {
|
||||
type: 'custom:frigate-card-conditional',
|
||||
conditions: {
|
||||
media_query: 'media query goes here',
|
||||
},
|
||||
elements: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
overrides: {
|
||||
menu: {
|
||||
style: 'overlay',
|
||||
},
|
||||
},
|
||||
conditions: {
|
||||
fullscreen: true,
|
||||
state: [
|
||||
{
|
||||
entity: 'binary_sensor.foo',
|
||||
state: 'on',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should get epoch', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const epoch_1 = manager.getEpoch();
|
||||
expect(epoch_1).toEqual({ manager: manager });
|
||||
|
||||
manager.setState({ fullscreen: true });
|
||||
|
||||
const epoch_2 = manager.getEpoch();
|
||||
expect(epoch_2).toEqual({ manager: manager });
|
||||
|
||||
// Since the state was set the wrappers should be different.
|
||||
expect(epoch_1).not.toBe(epoch_2);
|
||||
});
|
||||
|
||||
it('should not return hasHAStateConditions without HA state conditions', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
expect(manager.hasHAStateConditions()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return hasHAStateConditions with HA state conditions', () => {
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValueOnce({
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
} as unknown as MediaQueryList);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig(config));
|
||||
const manager = new ConditionsManager(api);
|
||||
|
||||
manager.setConditionsFromConfig();
|
||||
|
||||
expect(manager.hasHAStateConditions()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with a view', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = { view: ['foo'] };
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ view: 'foo' });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with fullscreen', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = { fullscreen: true };
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ fullscreen: true });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
manager.setState({ fullscreen: false });
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with expand', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = { expand: true };
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ expand: true });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
manager.setState({ expand: false });
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with camera', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = { camera: ['bar'] };
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ camera: 'bar' });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
manager.setState({ camera: 'will-not-match' });
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with ha state positive check', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = {
|
||||
state: [
|
||||
{
|
||||
entity: 'binary_sensor.foo',
|
||||
state: 'on',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ state: { 'binary_sensor.foo': createStateEntity() } });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
manager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with ha state negative check', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = {
|
||||
state: [
|
||||
{
|
||||
entity: 'binary_sensor.foo',
|
||||
state_not: 'on',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ state: { 'binary_sensor.foo': createStateEntity() } });
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({
|
||||
state: { 'binary_sensor.foo': createStateEntity({ state: 'off' }) },
|
||||
});
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with media_loaded', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = { media_loaded: true };
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ media_loaded: true });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
manager.setState({ media_loaded: false });
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with media query', () => {
|
||||
vi.spyOn(window, 'matchMedia')
|
||||
.mockReturnValueOnce(<MediaQueryList>{ matches: true })
|
||||
.mockReturnValueOnce(<MediaQueryList>{ matches: false });
|
||||
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition = { media_query: 'whatever' };
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should trigger on changes to media query conditions', () => {
|
||||
const addEventListener = vi.fn();
|
||||
const removeEventListener = vi.fn();
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValueOnce({
|
||||
matches: true,
|
||||
addEventListener: addEventListener,
|
||||
removeEventListener: removeEventListener,
|
||||
} as unknown as MediaQueryList);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig(config));
|
||||
const callback = vi.fn();
|
||||
const manager = new ConditionsManager(api, callback);
|
||||
|
||||
manager.setConditionsFromConfig();
|
||||
|
||||
expect(addEventListener).toHaveBeenCalledWith('change', expect.anything());
|
||||
|
||||
// Call the media query callback and use it to pretend a match happened. The
|
||||
// callback is the 0th mock innvocation and the 1st argument.
|
||||
addEventListener.mock.calls[0][1]();
|
||||
|
||||
// This should result in a callback to our state listener.
|
||||
expect(callback).toBeCalled();
|
||||
|
||||
// Remove the conditions, which should remove the media query listener.
|
||||
manager.removeConditions();
|
||||
expect(removeEventListener).toBeCalled();
|
||||
});
|
||||
|
||||
it('should evaluate conditions with display mode', () => {
|
||||
const manager = new ConditionsManager(createCardAPI());
|
||||
const condition: FrigateCardCondition = { display_mode: 'grid' };
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
manager.setState({ displayMode: 'grid' });
|
||||
expect(manager.evaluateCondition(condition)).toBeTruthy();
|
||||
manager.setState({ displayMode: 'single' });
|
||||
expect(manager.evaluateCondition(condition)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,304 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ZodError } from 'zod';
|
||||
import { frigateCardConfigSchema } from '../../../src/config/types';
|
||||
import { getOverriddenConfig } from '../../../src/utils/card-controller/conditions-manager';
|
||||
import { ConfigManager } from '../../../src/utils/card-controller/config-manager';
|
||||
import { InitializationAspect } from '../../../src/utils/card-controller/initialization-manager';
|
||||
import { createCardAPI, createConfig } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/utils/card-controller/conditions-manager.js');
|
||||
|
||||
describe('ConfigManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('should handle error when', () => {
|
||||
it('no input', () => {
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
expect(() => manager.setConfig()).toThrowError(/Invalid configuration/);
|
||||
});
|
||||
|
||||
it('invalid configuration', () => {
|
||||
const spy = vi.spyOn(frigateCardConfigSchema, 'safeParse').mockReturnValue({
|
||||
success: false,
|
||||
error: new ZodError([]),
|
||||
});
|
||||
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
expect(() => manager.setConfig({})).toThrowError(
|
||||
'Invalid configuration: No location hint available (bad or missing type?)',
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('invalid configuration with hint', () => {
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
expect(() => manager.setConfig({})).toThrowError(
|
||||
'Invalid configuration: [\n "cameras",\n "type"\n]',
|
||||
);
|
||||
});
|
||||
|
||||
it('upgradeable', () => {
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
expect(() =>
|
||||
manager.setConfig({
|
||||
cameras: [
|
||||
{
|
||||
frigate: {
|
||||
label: 'foo',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toThrowError(
|
||||
'An automated card configuration upgrade is ' +
|
||||
'available, please visit the visual card editor. ' +
|
||||
'Invalid configuration: [\n "type"\n]',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have initial state', () => {
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
|
||||
expect(manager.getConfig()).toBeNull();
|
||||
expect(manager.getNonOverriddenConfig()).toBeNull();
|
||||
expect(manager.getRawConfig()).toBeNull();
|
||||
});
|
||||
|
||||
it('should successfully parse basic config', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
|
||||
manager.setConfig(config);
|
||||
|
||||
expect(manager.hasConfig()).toBeTruthy()
|
||||
expect(manager.getRawConfig()).toBe(config);
|
||||
|
||||
// Verify at least the camera is set.
|
||||
expect(manager.getConfig()?.cameras[0].camera_entity).toBe('camera.office');
|
||||
|
||||
// Verify at least one default was set.
|
||||
expect(manager.getConfig()?.menu.alignment).toBe('left');
|
||||
|
||||
// Verify appropriate API calls are made.
|
||||
expect(api.getConditionsManager().setConditionsFromConfig).toBeCalled();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
view: undefined,
|
||||
displayMode: undefined,
|
||||
camera: undefined,
|
||||
});
|
||||
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
||||
expect(api.getViewManager().reset).toBeCalled();
|
||||
expect(api.getMessageManager().reset).toBeCalled();
|
||||
expect(api.getAutomationsManager().setAutomationsFromConfig).toBeCalled();
|
||||
expect(api.getStyleManager().setPerformance).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should apply low performance defaults', () => {
|
||||
const manager = new ConfigManager(createCardAPI());
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
performance: { profile: 'low' },
|
||||
};
|
||||
|
||||
manager.setConfig(config);
|
||||
|
||||
// Verify at least one low performance default.
|
||||
expect(manager.getConfig()?.live.draggable).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should skip identical configs', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
|
||||
manager.setConfig(config);
|
||||
expect(api.getViewManager().reset).toBeCalled();
|
||||
|
||||
vi.mocked(api.getViewManager().reset).mockClear();
|
||||
|
||||
manager.setConfig(config);
|
||||
expect(api.getViewManager().reset).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should get card wide config', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
debug: {
|
||||
logging: true,
|
||||
},
|
||||
performance: {
|
||||
profile: 'low',
|
||||
},
|
||||
};
|
||||
|
||||
manager.setConfig(config);
|
||||
|
||||
expect(manager.getCardWideConfig()).toEqual({
|
||||
debug: {
|
||||
logging: true,
|
||||
},
|
||||
performance: {
|
||||
features: {
|
||||
animated_progress_indicator: false,
|
||||
media_chunk_size: 10,
|
||||
},
|
||||
profile: 'low',
|
||||
style: {
|
||||
border_radius: false,
|
||||
box_shadow: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore overrides without a config', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(manager.getConfig()).toBeNull();
|
||||
expect(api.getStyleManager().setMinMaxHeight).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should ignore overrides with same config', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(config);
|
||||
|
||||
manager.setConfig(config);
|
||||
expect(api.getStyleManager().setMinMaxHeight).toBeCalled();
|
||||
|
||||
vi.mocked(api.getStyleManager().setMinMaxHeight).mockClear();
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getStyleManager().setMinMaxHeight).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should override', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
manager.setConfig(config_1);
|
||||
vi.mocked(api.getStyleManager().setMinMaxHeight).mockClear();
|
||||
|
||||
const config_2 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.kitchen' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(config_2);
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getStyleManager().setMinMaxHeight).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
expect(manager.getConfig()).not.toEqual(manager.getNonOverriddenConfig());
|
||||
});
|
||||
|
||||
describe('should uninitialize on override', () => {
|
||||
it('cameras', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
|
||||
const config_2 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.kitchen' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
});
|
||||
|
||||
it('cameras_global', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
cameras_global: {
|
||||
live_provider: 'jsmpeg'
|
||||
}
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
InitializationAspect.CAMERAS,
|
||||
);
|
||||
});
|
||||
|
||||
it('live.microphone.always_connected', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ConfigManager(api);
|
||||
const config_1 = {
|
||||
type: 'custom:frigate-card',
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: false
|
||||
}
|
||||
}
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1));
|
||||
|
||||
manager.setConfig(config_1);
|
||||
expect(api.getInitializationManager().uninitialize).not.toBeCalled();
|
||||
|
||||
const config_2 = {
|
||||
...config_1,
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: true
|
||||
}
|
||||
}
|
||||
};
|
||||
vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2));
|
||||
manager.computeOverrideConfig();
|
||||
|
||||
expect(api.getInitializationManager().uninitialize).toBeCalledWith(
|
||||
InitializationAspect.MICROPHONE_CONNECT,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,276 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CameraManager } from '../../../src/camera-manager/manager';
|
||||
import { FrigateCardEditor } from '../../../src/editor';
|
||||
import { ActionsManager } from '../../../src/utils/card-controller/actions-manager';
|
||||
import { AutoUpdateManager } from '../../../src/utils/card-controller/auto-update-manager';
|
||||
import { AutomationsManager } from '../../../src/utils/card-controller/automations-manager';
|
||||
import { CameraURLManager } from '../../../src/utils/card-controller/camera-url-manager';
|
||||
import {
|
||||
CardElementManager,
|
||||
CardHTMLElement,
|
||||
} from '../../../src/utils/card-controller/card-element-manager';
|
||||
import { ConditionsManager } from '../../../src/utils/card-controller/conditions-manager';
|
||||
import { ConfigManager } from '../../../src/utils/card-controller/config-manager';
|
||||
import { CardController } from '../../../src/utils/card-controller/controller';
|
||||
import { DownloadManager } from '../../../src/utils/card-controller/download-manager';
|
||||
import { ExpandManager } from '../../../src/utils/card-controller/expand-manager';
|
||||
import { FullscreenManager } from '../../../src/utils/card-controller/fullscreen-manager';
|
||||
import { HASSManager } from '../../../src/utils/card-controller/hass-manager';
|
||||
import { InitializationManager } from '../../../src/utils/card-controller/initialization-manager';
|
||||
import { InteractionManager } from '../../../src/utils/card-controller/interaction-manager';
|
||||
import { MediaLoadedInfoManager } from '../../../src/utils/card-controller/media-info-manager';
|
||||
import { MediaPlayerManager } from '../../../src/utils/card-controller/media-player-manager';
|
||||
import { MessageManager } from '../../../src/utils/card-controller/message-manager';
|
||||
import { MicrophoneManager } from '../../../src/utils/card-controller/microphone-manager';
|
||||
import { QueryStringManager } from '../../../src/utils/card-controller/query-string-manager';
|
||||
import { StyleManager } from '../../../src/utils/card-controller/style-manager';
|
||||
import { TriggersManager } from '../../../src/utils/card-controller/triggers-manager';
|
||||
import { ViewManager } from '../../../src/utils/card-controller/view-manager';
|
||||
import { EntityRegistryManager } from '../../../src/utils/ha/entity-registry';
|
||||
import { ResolvedMediaCache } from '../../../src/utils/ha/resolved-media';
|
||||
|
||||
vi.mock('../../../src/camera-manager/manager');
|
||||
vi.mock('../../../src/utils/card-controller/actions-manager');
|
||||
vi.mock('../../../src/utils/card-controller/auto-update-manager');
|
||||
vi.mock('../../../src/utils/card-controller/automations-manager');
|
||||
vi.mock('../../../src/utils/card-controller/camera-url-manager');
|
||||
vi.mock('../../../src/utils/card-controller/card-element-manager');
|
||||
vi.mock('../../../src/utils/card-controller/conditions-manager');
|
||||
vi.mock('../../../src/utils/card-controller/config-manager');
|
||||
vi.mock('../../../src/utils/card-controller/download-manager');
|
||||
vi.mock('../../../src/utils/card-controller/expand-manager');
|
||||
vi.mock('../../../src/utils/card-controller/fullscreen-manager');
|
||||
vi.mock('../../../src/utils/card-controller/hass-manager');
|
||||
vi.mock('../../../src/utils/card-controller/initialization-manager');
|
||||
vi.mock('../../../src/utils/card-controller/interaction-manager');
|
||||
vi.mock('../../../src/utils/card-controller/media-info-manager');
|
||||
vi.mock('../../../src/utils/card-controller/media-player-manager');
|
||||
vi.mock('../../../src/utils/card-controller/message-manager');
|
||||
vi.mock('../../../src/utils/card-controller/microphone-manager');
|
||||
vi.mock('../../../src/utils/card-controller/query-string-manager');
|
||||
vi.mock('../../../src/utils/card-controller/style-manager');
|
||||
vi.mock('../../../src/utils/card-controller/triggers-manager');
|
||||
vi.mock('../../../src/utils/card-controller/view-manager');
|
||||
vi.mock('../../../src/utils/ha/entity-registry');
|
||||
vi.mock('../../../src/utils/ha/resolved-media');
|
||||
|
||||
const createCardElement = (): CardHTMLElement => {
|
||||
const element = document.createElement('div') as unknown as CardHTMLElement;
|
||||
element.addController = vi.fn();
|
||||
return element;
|
||||
};
|
||||
|
||||
const createController = (): CardController => {
|
||||
return new CardController(createCardElement(), vi.fn(), vi.fn(), vi.fn());
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('CardController', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should construct correctly', () => {
|
||||
const element = createCardElement();
|
||||
const scrollCallback = vi.fn();
|
||||
const menuToggleCallback = vi.fn();
|
||||
const conditionListener = vi.fn();
|
||||
|
||||
const manager = new CardController(
|
||||
element,
|
||||
scrollCallback,
|
||||
menuToggleCallback,
|
||||
conditionListener,
|
||||
);
|
||||
|
||||
expect(ConditionsManager).toBeCalledWith(manager, conditionListener);
|
||||
expect(CardElementManager).toBeCalledWith(
|
||||
manager,
|
||||
element,
|
||||
scrollCallback,
|
||||
menuToggleCallback,
|
||||
);
|
||||
});
|
||||
|
||||
describe('accessors', () => {
|
||||
it('getActionsManager', () => {
|
||||
expect(createController().getActionsManager()).toBe(
|
||||
vi.mocked(ActionsManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getAutomationsManager', () => {
|
||||
expect(createController().getAutomationsManager()).toBe(
|
||||
vi.mocked(AutomationsManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getAutoUpdateManager', () => {
|
||||
expect(createController().getAutoUpdateManager()).toBe(
|
||||
vi.mocked(AutoUpdateManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getCameraManager', () => {
|
||||
expect(createController().getCameraManager()).toBe(
|
||||
vi.mocked(CameraManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getCameraURLManager', () => {
|
||||
expect(createController().getCameraURLManager()).toBe(
|
||||
vi.mocked(CameraURLManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getCardElementManager', () => {
|
||||
expect(createController().getCardElementManager()).toBe(
|
||||
vi.mocked(CardElementManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getConditionsManager', () => {
|
||||
expect(createController().getConditionsManager()).toBe(
|
||||
vi.mocked(ConditionsManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getConfigElement', async () => {
|
||||
expect((await CardController.getConfigElement()) instanceof FrigateCardEditor);
|
||||
});
|
||||
|
||||
it('getConfigManager', () => {
|
||||
expect(createController().getConfigManager()).toBe(
|
||||
vi.mocked(ConfigManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getDownloadManager', () => {
|
||||
expect(createController().getDownloadManager()).toBe(
|
||||
vi.mocked(DownloadManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getEntityRegistryManager', () => {
|
||||
expect(createController().getEntityRegistryManager()).toBe(
|
||||
vi.mocked(EntityRegistryManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getExpandManager', () => {
|
||||
expect(createController().getExpandManager()).toBe(
|
||||
vi.mocked(ExpandManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getFullscreenManager', () => {
|
||||
expect(createController().getFullscreenManager()).toBe(
|
||||
vi.mocked(FullscreenManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getHASSManager', () => {
|
||||
expect(createController().getHASSManager()).toBe(
|
||||
vi.mocked(HASSManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getInitializationManager', () => {
|
||||
expect(createController().getInitializationManager()).toBe(
|
||||
vi.mocked(InitializationManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getInteractionManager', () => {
|
||||
expect(createController().getInteractionManager()).toBe(
|
||||
vi.mocked(InteractionManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getMediaLoadedInfoManager', () => {
|
||||
expect(createController().getMediaLoadedInfoManager()).toBe(
|
||||
vi.mocked(MediaLoadedInfoManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getMediaPlayerManager', () => {
|
||||
expect(createController().getMediaPlayerManager()).toBe(
|
||||
vi.mocked(MediaPlayerManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getMessageManager', () => {
|
||||
expect(createController().getMessageManager()).toBe(
|
||||
vi.mocked(MessageManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getMicrophoneManager', () => {
|
||||
expect(createController().getMicrophoneManager()).toBe(
|
||||
vi.mocked(MicrophoneManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getResolvedMediaCache', () => {
|
||||
expect(createController().getResolvedMediaCache()).toBe(
|
||||
vi.mocked(ResolvedMediaCache).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
describe('getStubConfig', () => {
|
||||
it('with camera entities', () => {
|
||||
expect(
|
||||
CardController.getStubConfig(['camera.office', 'binary_sensor.motion']),
|
||||
).toEqual({
|
||||
cameras: [{ camera_entity: 'camera.office' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('without camera entities', () => {
|
||||
expect(CardController.getStubConfig(['binary_sensor.motion'])).toEqual({
|
||||
cameras: [{ camera_entity: 'camera.demo' }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('getQueryStringManager', () => {
|
||||
expect(createController().getQueryStringManager()).toBe(
|
||||
vi.mocked(QueryStringManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getStyleManager', () => {
|
||||
expect(createController().getStyleManager()).toBe(
|
||||
vi.mocked(StyleManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getTriggersManager', () => {
|
||||
expect(createController().getTriggersManager()).toBe(
|
||||
vi.mocked(TriggersManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('getViewManager', () => {
|
||||
expect(createController().getViewManager()).toBe(
|
||||
vi.mocked(ViewManager).mock.instances[0],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlers', () => {
|
||||
it('hostConnected', () => {
|
||||
createController().hostConnected();
|
||||
expect(
|
||||
vi.mocked(CardElementManager).mock.instances[0].elementConnected,
|
||||
).toBeCalled();
|
||||
});
|
||||
|
||||
it('hostDisconnected', () => {
|
||||
createController().hostDisconnected();
|
||||
expect(
|
||||
vi.mocked(CardElementManager).mock.instances[0].elementDisconnected,
|
||||
).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { FrigateCardMediaPlayer } from '../../../src/types';
|
||||
import { DownloadManager } from '../../../src/utils/card-controller/download-manager';
|
||||
import { downloadMedia, downloadURL } from '../../../src/utils/download.js';
|
||||
import {
|
||||
createCardAPI,
|
||||
createHASS,
|
||||
createMediaLoadedInfo,
|
||||
createViewWithMedia,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/utils/download.js');
|
||||
|
||||
describe('DownloadManager.downloadViewerMedia', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should download', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createViewWithMedia());
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const manager = new DownloadManager(api);
|
||||
|
||||
expect(await manager.downloadViewerMedia()).toBeTruthy();
|
||||
expect(downloadMedia).toBeCalledWith(
|
||||
api.getHASSManager().getHASS(),
|
||||
api.getCameraManager(),
|
||||
api.getViewManager().getView()?.queryResults?.getResult(0),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not download due to exception thrown', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createViewWithMedia());
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const manager = new DownloadManager(api);
|
||||
|
||||
const error = new Error();
|
||||
vi.mocked(downloadMedia).mockRejectedValue(error);
|
||||
|
||||
expect(await manager.downloadViewerMedia()).toBeFalsy();
|
||||
expect(api.getMessageManager().setErrorIfHigherPriority).toBeCalledWith(error);
|
||||
});
|
||||
|
||||
it('should not download without hass', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createViewWithMedia());
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
|
||||
const manager = new DownloadManager(api);
|
||||
|
||||
expect(await manager.downloadViewerMedia()).toBeFalsy();
|
||||
expect(downloadMedia).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DownloadManager.downloadScreenshot', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('with url', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
player.getScreenshotURL.mockResolvedValue('http://screenshot');
|
||||
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const manager = new DownloadManager(api);
|
||||
await manager.downloadScreenshot();
|
||||
|
||||
expect(downloadURL).toBeCalledWith('http://screenshot', 'screenshot.jpg');
|
||||
});
|
||||
|
||||
it('without url', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
player.getScreenshotURL.mockResolvedValue(null);
|
||||
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const manager = new DownloadManager(api);
|
||||
await manager.downloadScreenshot();
|
||||
|
||||
expect(downloadURL).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ExpandManager } from '../../../src/utils/card-controller/expand-manager';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
|
||||
describe('ExpandManager', () => {
|
||||
it('should construct', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ExpandManager(api);
|
||||
expect(manager.isExpanded()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set expanded', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getFullscreenManager().isInFullscreen).mockReturnValue(true);
|
||||
const manager = new ExpandManager(api);
|
||||
|
||||
manager.setExpanded(true);
|
||||
|
||||
expect(manager.isExpanded()).toBeTruthy();
|
||||
expect(api.getFullscreenManager().stopFullscreen).toBeCalled();
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({ expand: true });
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not exit fullscreen when not in fullscreen', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getFullscreenManager().isInFullscreen).mockReturnValue(false);
|
||||
const manager = new ExpandManager(api);
|
||||
|
||||
manager.setExpanded(true);
|
||||
|
||||
expect(api.getFullscreenManager().stopFullscreen).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should toggle expanded', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getFullscreenManager().isInFullscreen).mockReturnValue(false);
|
||||
const manager = new ExpandManager(api);
|
||||
|
||||
manager.toggleExpanded();
|
||||
expect(manager.isExpanded()).toBeTruthy();
|
||||
|
||||
manager.toggleExpanded();
|
||||
expect(manager.isExpanded()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,129 +0,0 @@
|
||||
import screenfull from 'screenfull';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FullscreenManager } from '../../../src/utils/card-controller/fullscreen-manager';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
|
||||
vi.mock('screenfull', () => ({
|
||||
default: {
|
||||
exit: vi.fn(),
|
||||
toggle: vi.fn(),
|
||||
off: vi.fn(),
|
||||
on: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const setScreenfulEnabled = (enabled: boolean): void => {
|
||||
Object.defineProperty(screenfull, 'isEnabled', { value: enabled, writable: true });
|
||||
};
|
||||
|
||||
const setScreenfulFullscreen = (fullscreen: boolean): void => {
|
||||
Object.defineProperty(screenfull, 'isFullscreen', {
|
||||
value: fullscreen,
|
||||
writable: true,
|
||||
});
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('FullscreenManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should correctly determine whether in fullscreen', () => {
|
||||
const manager = new FullscreenManager(createCardAPI());
|
||||
|
||||
setScreenfulEnabled(true);
|
||||
setScreenfulFullscreen(true);
|
||||
expect(manager.isInFullscreen()).toBeTruthy();
|
||||
|
||||
setScreenfulFullscreen(false);
|
||||
expect(manager.isInFullscreen()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should toggle fullscreen', () => {
|
||||
const toggle = vi.mocked(screenfull.toggle);
|
||||
const element = document.createElement('div')
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(
|
||||
element,
|
||||
);
|
||||
const manager = new FullscreenManager(api);
|
||||
|
||||
manager.toggleFullscreen();
|
||||
|
||||
expect(toggle).toBeCalledWith(element);
|
||||
});
|
||||
|
||||
it('should stop fullscreen', () => {
|
||||
const manager = new FullscreenManager(createCardAPI());
|
||||
const exit = vi.mocked(screenfull.exit);
|
||||
|
||||
manager.stopFullscreen();
|
||||
|
||||
expect(exit).toBeCalled();
|
||||
});
|
||||
|
||||
it('should disconnect', () => {
|
||||
const manager = new FullscreenManager(createCardAPI());
|
||||
const off = vi.mocked(screenfull.off);
|
||||
|
||||
setScreenfulEnabled(true);
|
||||
|
||||
manager.disconnect();
|
||||
|
||||
expect(off).toBeCalledWith('change', expect.anything());
|
||||
});
|
||||
|
||||
it('should not disconnect when screenfull disabled', () => {
|
||||
const manager = new FullscreenManager(createCardAPI());
|
||||
const off = vi.mocked(screenfull.off);
|
||||
|
||||
setScreenfulEnabled(false);
|
||||
|
||||
manager.disconnect();
|
||||
|
||||
expect(off).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should connect', () => {
|
||||
const manager = new FullscreenManager(createCardAPI());
|
||||
const on = vi.mocked(screenfull.on);
|
||||
|
||||
setScreenfulEnabled(true);
|
||||
|
||||
manager.connect();
|
||||
|
||||
expect(on).toBeCalledWith('change', expect.anything());
|
||||
});
|
||||
|
||||
it('should not connect when screenfull disabled', () => {
|
||||
const manager = new FullscreenManager(createCardAPI());
|
||||
const on = vi.mocked(screenfull.on);
|
||||
|
||||
setScreenfulEnabled(false);
|
||||
|
||||
manager.connect();
|
||||
|
||||
expect(on).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should make correct api calls on fullscreen change', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new FullscreenManager(api);
|
||||
const on = vi.mocked(screenfull.on);
|
||||
|
||||
setScreenfulEnabled(true);
|
||||
setScreenfulFullscreen(true);
|
||||
|
||||
manager.connect();
|
||||
|
||||
expect(on).toBeCalled();
|
||||
on.mock.calls[0][1](new Event('fullscreen'));
|
||||
|
||||
expect(api.getExpandManager().setExpanded).toBeCalledWith(false);
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith({
|
||||
fullscreen: true,
|
||||
});
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,279 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { HASSManager } from '../../../src/utils/card-controller/hass-manager';
|
||||
import { CardHASSAPI } from '../../../src/utils/card-controller/types';
|
||||
import {
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createHASS,
|
||||
createStateEntity,
|
||||
createView,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/camera-manager/manager.js');
|
||||
|
||||
const createAPIWithoutMediaPlayers = (): CardHASSAPI => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getMediaPlayerManager().getMediaPlayers).mockReturnValue([]);
|
||||
return api;
|
||||
};
|
||||
|
||||
describe('HASSManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should have null hass on construction', () => {
|
||||
const manager = new HASSManager(createCardAPI());
|
||||
expect(manager.getHASS()).toBeNull();
|
||||
});
|
||||
|
||||
it('should set light or dark mode upon setting hass', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
|
||||
manager.setHASS(createHASS());
|
||||
|
||||
expect(api.getStyleManager().setLightOrDarkMode).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should set condition manager state', () => {
|
||||
it('positively', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
vi.mocked(api.getConditionsManager().hasHAStateConditions).mockReturnValue(true);
|
||||
|
||||
const states = { 'switch.foo': createStateEntity() };
|
||||
const hass = createHASS(states);
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
state: states,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('negatively', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
vi.mocked(api.getConditionsManager().hasHAStateConditions).mockReturnValue(false);
|
||||
|
||||
manager.setHASS(createHASS());
|
||||
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should update triggered cameras', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
|
||||
const originalHASS = createHASS();
|
||||
manager.setHASS(originalHASS);
|
||||
expect(api.getTriggersManager().updateTriggeredCameras).toBeCalledWith(null);
|
||||
|
||||
manager.setHASS(createHASS());
|
||||
expect(api.getTriggersManager().updateTriggeredCameras).toBeCalledWith(originalHASS);
|
||||
});
|
||||
|
||||
describe('should handle connection state change when', () => {
|
||||
it('initially disconnected', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
|
||||
const disconnectedHASS = createHASS();
|
||||
disconnectedHASS.connected = false;
|
||||
|
||||
manager.setHASS(disconnectedHASS);
|
||||
|
||||
expect(api.getMessageManager().setMessageIfHigherPriority).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Reconnecting',
|
||||
icon: 'mdi:lan-disconnect',
|
||||
type: 'connection',
|
||||
dotdotdot: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('disconnected', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
|
||||
manager.setHASS(createHASS());
|
||||
|
||||
const disconnectedHASS = createHASS();
|
||||
disconnectedHASS.connected = false;
|
||||
manager.setHASS(disconnectedHASS);
|
||||
|
||||
expect(api.getMessageManager().setMessageIfHigherPriority).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Reconnecting',
|
||||
icon: 'mdi:lan-disconnect',
|
||||
type: 'connection',
|
||||
dotdotdot: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('reconnected', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
const manager = new HASSManager(api);
|
||||
|
||||
const disconnectedHASS = createHASS();
|
||||
disconnectedHASS.connected = false;
|
||||
manager.setHASS(disconnectedHASS);
|
||||
|
||||
const reconnectedHASS = createHASS();
|
||||
manager.setHASS(reconnectedHASS);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should set default view when', () => {
|
||||
it('selected camera trigger entity changes', () => {
|
||||
const cameraManager = createCameraManager({
|
||||
configs: new Map([
|
||||
[
|
||||
'camera.foo',
|
||||
createCameraConfig({
|
||||
triggers: {
|
||||
entities: ['binary_sensor.motion'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
]),
|
||||
});
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera.foo',
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new HASSManager(api);
|
||||
const hass = createHASS({
|
||||
'binary_sensor.motion': createStateEntity(),
|
||||
});
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
it('selected camera is unknown', () => {
|
||||
const cameraManager = createCameraManager({
|
||||
configs: new Map([
|
||||
[
|
||||
'camera.foo',
|
||||
createCameraConfig({
|
||||
triggers: {
|
||||
entities: ['binary_sensor.motion'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
]),
|
||||
});
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera.UNKNOWN',
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new HASSManager(api);
|
||||
const hass = createHASS({
|
||||
'binary_sensor.motion': createStateEntity(),
|
||||
});
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('view.update_entities changes', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
update_entities: ['sensor.force_default_view'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new HASSManager(api);
|
||||
const hass = createHASS({
|
||||
'sensor.force_default_view': createStateEntity(),
|
||||
});
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should update card when', () => {
|
||||
it('render entity changes', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
render_entities: ['sensor.force_update'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new HASSManager(api);
|
||||
const hass = createHASS({
|
||||
'sensor.force_update': createStateEntity(),
|
||||
});
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('media player entity changes', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getMediaPlayerManager().getMediaPlayers).mockReturnValue([
|
||||
'media_player.foo',
|
||||
]);
|
||||
|
||||
const manager = new HASSManager(api);
|
||||
const hass = createHASS({
|
||||
'media_player.foo': createStateEntity(),
|
||||
});
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('set view default is not called when there is card interaction', () => {
|
||||
const api = createAPIWithoutMediaPlayers();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
update_entities: ['sensor.force_default_view'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(true);
|
||||
|
||||
const manager = new HASSManager(api);
|
||||
const hass = createHASS({
|
||||
'sensor.force_default_view': createStateEntity(),
|
||||
});
|
||||
|
||||
manager.setHASS(hass);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,203 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { loadLanguages } from '../../../src/localize/localize';
|
||||
import {
|
||||
InitializationAspect,
|
||||
InitializationManager,
|
||||
} from '../../../src/utils/card-controller/initialization-manager';
|
||||
import { sideLoadHomeAssistantElements } from '../../../src/utils/ha';
|
||||
import { Initializer } from '../../../src/utils/initializer/initializer';
|
||||
import { createCardAPI, createConfig, createHASS } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/localize/localize.js');
|
||||
vi.mock('../../../src/utils/ha/index.js');
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('InitializationManager', () => {
|
||||
beforeEach(async () => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should not be initialized', () => {
|
||||
const manager = new InitializationManager(createCardAPI());
|
||||
expect(manager.isInitializedMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('should initialize mandatory', () => {
|
||||
it('without hass', async () => {
|
||||
const manager = new InitializationManager(createCardAPI());
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('without config', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new InitializationManager(api);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('successfully', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true);
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(false);
|
||||
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(false);
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
|
||||
expect(loadLanguages).toBeCalled();
|
||||
expect(sideLoadHomeAssistantElements).toBeCalled();
|
||||
expect(api.getCameraManager().initializeCamerasFromConfig).toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('successfully with querystring view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true);
|
||||
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());
|
||||
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true);
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true);
|
||||
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(false);
|
||||
const manager = new InitializationManager(api);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeTruthy();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('with languages and side load elements in progress', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
initializer.initializeMultipleIfNecessary.mockResolvedValue(false);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with cameras in progress', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true);
|
||||
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
initializer.initializeMultipleIfNecessary.mockResolvedValue(true);
|
||||
initializer.initializeIfNecessary.mockResolvedValue(false);
|
||||
|
||||
expect(await manager.initializeMandatory()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should initialize background', () => {
|
||||
it('without hass and config', async () => {
|
||||
const manager = new InitializationManager(createCardAPI());
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('successfully with minimal initializers', 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: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
|
||||
expect(api.getMediaPlayerManager().initialize).not.toBeCalled();
|
||||
expect(api.getMicrophoneManager().connect).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,
|
||||
},
|
||||
},
|
||||
},
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
|
||||
expect(api.getMediaPlayerManager().initialize).toBeCalled();
|
||||
expect(api.getMicrophoneManager().connect).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('with media player and microphone connect 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const initializer = mock<Initializer>();
|
||||
|
||||
const manager = new InitializationManager(api, initializer);
|
||||
initializer.initializeMultipleIfNecessary.mockResolvedValue(false);
|
||||
|
||||
expect(await manager.initializeBackgroundIfNecessary()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should uninitialize', () => {
|
||||
const initializer = mock<Initializer>();
|
||||
const manager = new InitializationManager(createCardAPI(), initializer);
|
||||
|
||||
manager.uninitialize(InitializationAspect.CAMERAS);
|
||||
|
||||
expect(initializer.uninitialize).toBeCalledWith(InitializationAspect.CAMERAS);
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
import add from 'date-fns/add';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { InteractionManager } from '../../../src/utils/card-controller/interaction-manager';
|
||||
import { createCardAPI, createConfig } from '../../test-utils';
|
||||
|
||||
vi.mock('lodash-es/throttle', () => ({
|
||||
default: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('InteractionManager', () => {
|
||||
const start = new Date('2023-09-24T20:20:00');
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should take action when interaction is reported', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
timeout_seconds: 10,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new InteractionManager(api);
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(start);
|
||||
|
||||
manager.reportInteraction();
|
||||
|
||||
expect(api.getTriggersManager().untrigger).toBeCalled();
|
||||
expect(manager.hasInteraction()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
|
||||
vi.mocked(api.getTriggersManager().isTriggered).mockReturnValue(false);
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not take action when triggered', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
timeout_seconds: 10,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new InteractionManager(api);
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(start);
|
||||
|
||||
manager.reportInteraction();
|
||||
|
||||
vi.mocked(api.getTriggersManager().isTriggered).mockReturnValue(true);
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
// First call is blocked by triggers (above), so interaction will report
|
||||
// true but the default view will not have been set.
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not take action when not configured', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
timeout_seconds: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new InteractionManager(api);
|
||||
|
||||
manager.reportInteraction();
|
||||
|
||||
// First call is blocked by triggers (above), so interaction will report
|
||||
// true but the default view will not have been set.
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MediaLoadedInfoManager } from '../../../src/utils/card-controller/media-info-manager';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../test-utils.js';
|
||||
|
||||
describe('MediaLoadedInfoManager', () => {
|
||||
it('should set', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MediaLoadedInfoManager(api);
|
||||
const mediaInfo = createMediaLoadedInfo();
|
||||
|
||||
manager.set(mediaInfo);
|
||||
|
||||
expect(manager.has()).toBeTruthy();
|
||||
expect(manager.get()).toBe(mediaInfo);
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({ media_loaded: true }),
|
||||
);
|
||||
expect(api.getStyleManager().setExpandedMode).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not set invalid media info', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MediaLoadedInfoManager(api);
|
||||
const mediaInfo = createMediaLoadedInfo({ width: 0, height: 0 });
|
||||
|
||||
manager.set(mediaInfo);
|
||||
|
||||
expect(manager.has()).toBeFalsy();
|
||||
expect(manager.get()).toBeNull();
|
||||
expect(api.getConditionsManager().setState).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should get last known', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MediaLoadedInfoManager(api);
|
||||
const mediaInfo = createMediaLoadedInfo();
|
||||
|
||||
manager.set(mediaInfo);
|
||||
|
||||
expect(manager.has()).toBeTruthy();
|
||||
|
||||
manager.clear();
|
||||
|
||||
expect(manager.has()).toBeFalsy();
|
||||
expect(manager.getLastKnown()).toBe(mediaInfo);
|
||||
expect(api.getConditionsManager().setState).toBeCalledWith(
|
||||
expect.objectContaining({ media_loaded: false }),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,278 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
TestViewMedia,
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createHASS,
|
||||
createRegistryEntity,
|
||||
createStateEntity,
|
||||
} from '../../test-utils';
|
||||
import { MediaPlayerManager } from '../../../src/utils/card-controller/media-player-manager';
|
||||
import { ExtendedHomeAssistant } from '../../../src/types';
|
||||
import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA } from '../../../src/const';
|
||||
import { EntityRegistryManager } from '../../../src/utils/ha/entity-registry';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
vi.mock('../../../src/camera-manager/manager.js');
|
||||
|
||||
const createHASSWithMediaPlayers = (): ExtendedHomeAssistant => {
|
||||
const attributesSupported = {
|
||||
supported_features: MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA,
|
||||
};
|
||||
const attributesUnsupported = {
|
||||
supported_features: 0,
|
||||
};
|
||||
|
||||
return createHASS({
|
||||
'media_player.ok1': createStateEntity({
|
||||
entity_id: 'media_player.ok1',
|
||||
state: 'on',
|
||||
attributes: attributesSupported,
|
||||
}),
|
||||
'media_player.ok2': createStateEntity({
|
||||
entity_id: 'media_player.ok2',
|
||||
state: 'on',
|
||||
attributes: attributesSupported,
|
||||
}),
|
||||
'media_player.ok3': createStateEntity({
|
||||
entity_id: 'media_player.ok3',
|
||||
state: 'on',
|
||||
attributes: attributesSupported,
|
||||
}),
|
||||
'media_player.unavailable': createStateEntity({
|
||||
entity_id: 'media_player.sitting_room',
|
||||
state: 'unavailable',
|
||||
attributes: attributesSupported,
|
||||
}),
|
||||
'media_player.unsupported': createStateEntity({
|
||||
entity_id: 'media_player.sitting_room',
|
||||
state: 'on',
|
||||
attributes: attributesUnsupported,
|
||||
}),
|
||||
'switch.unrelated': createStateEntity({
|
||||
entity_id: 'switch.unrelated',
|
||||
state: 'on',
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
describe('MediaPlayerManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('should initialize', () => {
|
||||
it('correctly', 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(),
|
||||
);
|
||||
vi.mocked(api.getEntityRegistryManager).mockReturnValue(entityRegistryManager);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
expect(manager.getMediaPlayers()).toEqual([
|
||||
'media_player.ok1',
|
||||
'media_player.ok3',
|
||||
]);
|
||||
expect(manager.hasMediaPlayers()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('without hass', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
expect(manager.getMediaPlayers()).toEqual([]);
|
||||
expect(manager.hasMediaPlayers()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('even if entity registry call fails', async () => {
|
||||
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||
|
||||
const entityRegistryManager = mock<EntityRegistryManager>();
|
||||
entityRegistryManager.getEntities.mockRejectedValue(new Error('message'));
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||
createHASSWithMediaPlayers(),
|
||||
);
|
||||
vi.mocked(api.getEntityRegistryManager).mockReturnValue(entityRegistryManager);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.initialize();
|
||||
|
||||
expect(manager.getMediaPlayers()).toEqual([
|
||||
'media_player.ok1',
|
||||
'media_player.ok2',
|
||||
'media_player.ok3',
|
||||
]);
|
||||
expect(manager.hasMediaPlayers()).toBeTruthy();
|
||||
expect(spy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should stop', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.stop('media_player.foo');
|
||||
|
||||
expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
|
||||
'media_player',
|
||||
'media_stop',
|
||||
{
|
||||
entity_id: 'media_player.foo',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('should play', () => {
|
||||
describe('live', () => {
|
||||
it('successfully', async () => {
|
||||
const api = createCardAPI();
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getCameraConfig).mockReturnValue(
|
||||
createCameraConfig({
|
||||
camera_entity: 'camera.foo',
|
||||
}),
|
||||
);
|
||||
vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({
|
||||
title: 'camera title',
|
||||
icon: 'icon',
|
||||
});
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
const hass = createHASS({
|
||||
'camera.foo': createStateEntity({
|
||||
attributes: {
|
||||
entity_picture: 'http://thumbnail',
|
||||
},
|
||||
}),
|
||||
});
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.playLive('media_player.foo', 'camera');
|
||||
|
||||
expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
|
||||
'media_player',
|
||||
'play_media',
|
||||
{
|
||||
entity_id: 'media_player.foo',
|
||||
media_content_id: 'media-source://camera/camera.foo',
|
||||
media_content_type: 'application/vnd.apple.mpegurl',
|
||||
extra: {
|
||||
title: 'camera title',
|
||||
thumb: 'http://thumbnail',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('without camera_entity', async () => {
|
||||
const api = createCardAPI();
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getCameraConfig).mockReturnValue(
|
||||
createCameraConfig({}),
|
||||
);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.playLive('media_player.foo', 'camera');
|
||||
|
||||
expect(api.getHASSManager().getHASS()?.callService).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('without title and thumbnail', async () => {
|
||||
const api = createCardAPI();
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getCameraConfig).mockReturnValue(
|
||||
createCameraConfig({
|
||||
camera_entity: 'camera.foo',
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.playLive('media_player.foo', 'camera');
|
||||
|
||||
expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
|
||||
'media_player',
|
||||
'play_media',
|
||||
{
|
||||
entity_id: 'media_player.foo',
|
||||
media_content_id: 'media-source://camera/camera.foo',
|
||||
media_content_type: 'application/vnd.apple.mpegurl',
|
||||
extra: {},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('media', () => {
|
||||
describe('successfully with', () => {
|
||||
it.each([
|
||||
['clip' as const, 'video' as const],
|
||||
['snapshot' as const, 'image' as const],
|
||||
])(
|
||||
'%s',
|
||||
async (mediaType: 'clip' | 'snapshot', contentType: 'video' | 'image') => {
|
||||
const media = new TestViewMedia({
|
||||
title: 'media title',
|
||||
thumbnail: 'http://thumbnail',
|
||||
contentID: 'media-source://contentid',
|
||||
mediaType: mediaType,
|
||||
});
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
const manager = new MediaPlayerManager(api);
|
||||
|
||||
await manager.playMedia('media_player.foo', media);
|
||||
|
||||
expect(api.getHASSManager().getHASS()?.callService).toBeCalledWith(
|
||||
'media_player',
|
||||
'play_media',
|
||||
{
|
||||
entity_id: 'media_player.foo',
|
||||
media_content_id: 'media-source://contentid',
|
||||
media_content_type: contentType,
|
||||
extra: {
|
||||
title: 'media title',
|
||||
thumb: 'http://thumbnail',
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('without hass', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(null);
|
||||
const manager = new MediaPlayerManager(api);
|
||||
const media = new TestViewMedia();
|
||||
|
||||
await manager.playMedia('media_player.foo', media);
|
||||
|
||||
// No actual test can be performed here as nothing observable happens.
|
||||
// This test serves only as code-coverage long-tail.
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,141 +0,0 @@
|
||||
import { afterAll, describe, expect, it, vi } from 'vitest';
|
||||
import { FrigateCardError, Message } from '../../../src/types';
|
||||
import { MessageManager } from '../../../src/utils/card-controller/message-manager';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
|
||||
const createMessage = (options?: Partial<Message>): Message => {
|
||||
return {
|
||||
message: options?.message ?? 'message',
|
||||
type: options?.type ?? 'info',
|
||||
...(!!options?.icon && { icon: options.icon }),
|
||||
...(!!options?.context && { context: options.context }),
|
||||
...(!!options?.dotdotdot && { dotdotdot: options.dotdotdot }),
|
||||
};
|
||||
};
|
||||
|
||||
describe('MessageManager', () => {
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should construct', () => {
|
||||
const manager = new MessageManager(createCardAPI());
|
||||
expect(manager.hasMessage()).toBeFalsy();
|
||||
expect(manager.getMessage()).toBeNull();
|
||||
expect(manager.hasErrorMessage()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set info message', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
const message = createMessage();
|
||||
manager.setMessageIfHigherPriority(message);
|
||||
expect(manager.hasMessage()).toBeTruthy();
|
||||
expect(manager.getMessage()).toBe(message);
|
||||
expect(manager.hasErrorMessage()).toBeFalsy();
|
||||
|
||||
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
||||
expect(api.getCardElementManager().scrollReset).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should set error message', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
const message = createMessage({ type: 'error' });
|
||||
manager.setMessageIfHigherPriority(message);
|
||||
expect(manager.hasMessage()).toBeTruthy();
|
||||
expect(manager.getMessage()).toBe(message);
|
||||
expect(manager.hasErrorMessage()).toBeTruthy();
|
||||
|
||||
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
||||
expect(api.getCardElementManager().scrollReset).toBeCalled();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should reset message', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
|
||||
manager.reset();
|
||||
expect(manager.hasMessage()).toBeFalsy();
|
||||
|
||||
const message = createMessage({ type: 'error' });
|
||||
manager.setMessageIfHigherPriority(message);
|
||||
expect(manager.hasMessage()).toBeTruthy();
|
||||
|
||||
vi.mocked(api.getCardElementManager().update).mockClear();
|
||||
manager.reset();
|
||||
|
||||
expect(manager.hasMessage()).toBeFalsy();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should respect priority', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
|
||||
manager.reset();
|
||||
expect(manager.hasMessage()).toBeFalsy();
|
||||
|
||||
const errorMessage = createMessage({ type: 'error' });
|
||||
manager.setMessageIfHigherPriority(errorMessage);
|
||||
|
||||
const infoMessage = createMessage({ type: 'info' });
|
||||
manager.setMessageIfHigherPriority(infoMessage);
|
||||
|
||||
expect(manager.getMessage()).toBe(errorMessage);
|
||||
|
||||
const connectionMessage = createMessage({ type: 'connection' });
|
||||
manager.setMessageIfHigherPriority(connectionMessage);
|
||||
|
||||
expect(manager.getMessage()).toBe(connectionMessage);
|
||||
});
|
||||
|
||||
it('should set FrigateCardError object', () => {
|
||||
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
const context = { foo: 'bar' };
|
||||
|
||||
manager.setErrorIfHigherPriority(
|
||||
new FrigateCardError('frigate card message', context),
|
||||
);
|
||||
expect(manager.hasMessage()).toBeTruthy();
|
||||
expect(manager.getMessage()).toEqual({
|
||||
message: 'frigate card message',
|
||||
type: 'error',
|
||||
context: context,
|
||||
});
|
||||
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it('should set Error object', () => {
|
||||
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
|
||||
manager.setErrorIfHigherPriority(new Error('generic error message'));
|
||||
expect(manager.hasMessage()).toBeTruthy();
|
||||
expect(manager.getMessage()).toEqual({
|
||||
message: 'generic error message',
|
||||
type: 'error',
|
||||
});
|
||||
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not set unknown error type', () => {
|
||||
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new MessageManager(api);
|
||||
|
||||
manager.setErrorIfHigherPriority('not_an_error_object');
|
||||
expect(manager.hasMessage()).toBeFalsy();
|
||||
expect(consoleSpy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,193 +0,0 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { MicrophoneManager } from '../../../src/utils/card-controller/microphone-manager';
|
||||
import { createCardAPI, createConfig } from '../../test-utils';
|
||||
|
||||
const navigatorMock = {
|
||||
mediaDevices: {
|
||||
getUserMedia: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('MicrophoneManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('navigator', navigatorMock);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.unstubAllGlobals;
|
||||
});
|
||||
|
||||
const createMockStream = (mute?: boolean): MediaStream => {
|
||||
const stream = mock<MediaStream>();
|
||||
const track = mock<MediaStreamTrack>();
|
||||
track.enabled = !mute;
|
||||
stream.getTracks.mockImplementation(() => [track]);
|
||||
return stream;
|
||||
};
|
||||
|
||||
it('should be muted on creation', () => {
|
||||
const manager = new MicrophoneManager(createCardAPI());
|
||||
expect(manager).toBeTruthy();
|
||||
expect(manager.isMuted()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be undefined without creation', () => {
|
||||
const manager = new MicrophoneManager(createCardAPI());
|
||||
expect(manager.getStream()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should connect', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
|
||||
const stream = createMockStream();
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(stream);
|
||||
|
||||
await manager.connect();
|
||||
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(manager.getStream()).toBe(stream);
|
||||
expect(manager.isMuted()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should be forbidden when permission denied', async () => {
|
||||
// Don't actually log messages to the console during the test.
|
||||
vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
navigatorMock.mediaDevices.getUserMedia.mockRejectedValue(new Error());
|
||||
|
||||
await manager.connect();
|
||||
|
||||
expect(manager.isConnected()).toBeFalsy();
|
||||
expect(manager.isForbidden()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should mute and unmute', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(createMockStream());
|
||||
|
||||
await manager.connect();
|
||||
expect(manager.isMuted()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
|
||||
manager.mute();
|
||||
expect(manager.isMuted()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(2);
|
||||
|
||||
await manager.unmute();
|
||||
expect(manager.isMuted()).toBeFalsy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should not unmute when microphone forbidden', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(null);
|
||||
|
||||
await manager.connect();
|
||||
|
||||
expect(manager.isMuted()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
|
||||
await manager.unmute();
|
||||
expect(manager.isMuted()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should connect on unmute', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(createMockStream());
|
||||
|
||||
expect(manager.isConnected()).toBeFalsy();
|
||||
|
||||
await manager.unmute();
|
||||
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(manager.isMuted()).toBeFalsy();
|
||||
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should disconnect', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(createMockStream());
|
||||
|
||||
await manager.connect();
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
|
||||
await manager.disconnect();
|
||||
expect(manager.isConnected()).toBeFalsy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should automatically disconnect', async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const disconnectSeconds = 10;
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(createMockStream());
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: false,
|
||||
disconnect_seconds: disconnectSeconds,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.connect();
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
|
||||
vi.advanceTimersByTime(disconnectSeconds * 1000);
|
||||
|
||||
expect(manager.isConnected()).toBeFalsy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should not automatically disconnect when always connected', async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const disconnectSeconds = 10;
|
||||
const api = createCardAPI();
|
||||
const manager = new MicrophoneManager(api);
|
||||
navigatorMock.mediaDevices.getUserMedia.mockReturnValue(createMockStream());
|
||||
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
live: {
|
||||
microphone: {
|
||||
always_connected: true,
|
||||
disconnect_seconds: disconnectSeconds,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.connect();
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
|
||||
vi.advanceTimersByTime(disconnectSeconds * 1000);
|
||||
|
||||
expect(manager.isConnected()).toBeTruthy();
|
||||
expect(api.getCardElementManager().update).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,298 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createCardAPI } from '../../test-utils';
|
||||
import { QueryStringManager } from '../../../src/utils/card-controller/query-string-manager';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
const setQueryString = (qs: string): void => {
|
||||
const location: Location = mock<Location>();
|
||||
location.search = qs;
|
||||
global.window.location = location;
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('QueryStringManager', () => {
|
||||
beforeEach(() => {
|
||||
global.window.location = mock<Location>();
|
||||
});
|
||||
|
||||
it('should reject malformed query string', () => {
|
||||
setQueryString('BOGUS_KEY=BOGUS_VALUE');
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('should execute view name action from query string', () => {
|
||||
it.each([
|
||||
['clip' as const],
|
||||
['clips' as const],
|
||||
['diagnostics' as const],
|
||||
['image' as const],
|
||||
['live' as const],
|
||||
['recording' as const],
|
||||
['recordings' as const],
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (viewName: string) => {
|
||||
setQueryString(`?frigate-card-action.id.${viewName}=`);
|
||||
const api = createCardAPI();
|
||||
|
||||
// View actions do not need the card to have been updated.
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(false);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
viewName: viewName,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should execute non-view action from query string', () => {
|
||||
it.each([
|
||||
['camera_ui' as const],
|
||||
['download' as const],
|
||||
['expand' as const],
|
||||
['menu_toggle' as const],
|
||||
])('%s', (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();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeAction).toBeCalledWith({
|
||||
action: 'fire-dom-event',
|
||||
card_id: 'id',
|
||||
frigate_card_action: action,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute view default action', () => {
|
||||
setQueryString('?frigate-card-action.id.default=');
|
||||
const api = createCardAPI();
|
||||
// View actions do not need the card to have been updated.
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(false);
|
||||
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute camera_select action', () => {
|
||||
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();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
cameraID: 'camera.office',
|
||||
});
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute live_substream_select action', () => {
|
||||
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();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
substream: 'camera.office_hd',
|
||||
});
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('should ignore action without value', () => {
|
||||
it.each([['camera_select' as const], ['live_substream_select' as const]])(
|
||||
'%s',
|
||||
(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();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unknown action', () => {
|
||||
const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
setQueryString('?frigate-card-action.id.not_an_action=value');
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeFalsy();
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should execute view name action from query string', () => {
|
||||
it.each([
|
||||
['clip' as const],
|
||||
['clips' as const],
|
||||
['diagnostics' as const],
|
||||
['image' as const],
|
||||
['live' as const],
|
||||
['recording' as const],
|
||||
['recordings' as const],
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (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();
|
||||
|
||||
expect(manager.hasViewRelatedActions()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
viewName: viewName,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not execute non-view actions without an initial update', () => {
|
||||
it.each([
|
||||
['camera_ui' as const],
|
||||
['download' as const],
|
||||
['expand' as const],
|
||||
['menu_toggle' as const],
|
||||
])('%s', (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();
|
||||
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle conflicting but valid actions', () => {
|
||||
it('view and default with camera and substream specified', () => {
|
||||
setQueryString(
|
||||
'?frigate-card-action.id.clips=' +
|
||||
'&frigate-card-action.id.live_substream_select=camera.kitchen_hd' +
|
||||
'&frigate-card-action.id.default=' +
|
||||
'&frigate-card-action.id.camera_select=camera.kitchen',
|
||||
);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true);
|
||||
const manager = new QueryStringManager(api);
|
||||
|
||||
manager.executeAll();
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalledWith({
|
||||
cameraID: 'camera.kitchen',
|
||||
substream: 'camera.kitchen_hd',
|
||||
});
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('multiple cameras specified', () => {
|
||||
setQueryString(
|
||||
'?frigate-card-action.id.camera_select=camera.kitchen' +
|
||||
'&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();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
cameraID: 'camera.office',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not execute view related actions', () => {
|
||||
it.each([
|
||||
['clip' as const],
|
||||
['clips' as const],
|
||||
['default' as const],
|
||||
['diagnostics' as const],
|
||||
['image' as const],
|
||||
['live' as const],
|
||||
['recording' as const],
|
||||
['recordings' as const],
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (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();
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not execute non-view related actions', () => {
|
||||
it.each([
|
||||
['camera_ui' as const],
|
||||
['download' as const],
|
||||
['expand' as const],
|
||||
['menu_toggle' as const],
|
||||
])('%s', (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();
|
||||
|
||||
expect(api.getActionsManager().executeAction).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,387 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FrigateCardView } from '../../../src/config/types';
|
||||
import { setPerformanceCSSStyles } from '../../../src/performance';
|
||||
import { StyleManager } from '../../../src/utils/card-controller/style-manager';
|
||||
import { createCardAPI, createConfig, createHASS, createView } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/performance');
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('StyleManager', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('setLightOrDarkMode', () => {
|
||||
it('dark mode unspecified', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setLightOrDarkMode();
|
||||
|
||||
expect(element.getAttribute('dark')).toBeNull();
|
||||
});
|
||||
|
||||
it('dark mode explicitly off', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
dark_mode: 'off',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setLightOrDarkMode();
|
||||
|
||||
expect(element.getAttribute('dark')).toBeNull();
|
||||
});
|
||||
|
||||
it('dark mode explicitly set', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
dark_mode: 'on',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setLightOrDarkMode();
|
||||
|
||||
expect(element.getAttribute('dark')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('dark mode auto without interaction', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
dark_mode: 'auto',
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(false);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setLightOrDarkMode();
|
||||
|
||||
expect(element.getAttribute('dark')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('dark mode auto with HA dark mode', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
dark_mode: 'auto',
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(true);
|
||||
const hass = createHASS();
|
||||
hass.themes.darkMode = true;
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setLightOrDarkMode();
|
||||
|
||||
expect(element.getAttribute('dark')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setExpandedMode', () => {
|
||||
it('with no view or known media', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue(null);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setExpandedMode();
|
||||
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
||||
'unset',
|
||||
);
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe(
|
||||
'var(--frigate-card-expand-max-width)',
|
||||
);
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
||||
'var(--frigate-card-expand-max-height)',
|
||||
);
|
||||
});
|
||||
|
||||
it('with view but without media', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const view = createView({ view: 'media', displayMode: 'single' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue(null);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setExpandedMode();
|
||||
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
||||
'unset',
|
||||
);
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe('none');
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
||||
'none',
|
||||
);
|
||||
});
|
||||
|
||||
it('with view and media', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const view = createView({ view: 'media', displayMode: 'single' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue({
|
||||
width: 800,
|
||||
height: 600,
|
||||
});
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setExpandedMode();
|
||||
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
||||
'800 / 600',
|
||||
);
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe('none');
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
||||
'none',
|
||||
);
|
||||
});
|
||||
|
||||
it('with view and grid display mode', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const view = createView({ view: 'media', displayMode: 'grid' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue({
|
||||
width: 800,
|
||||
height: 600,
|
||||
});
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setExpandedMode();
|
||||
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe(
|
||||
'800 / 600',
|
||||
);
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe(
|
||||
'var(--frigate-card-expand-max-width)',
|
||||
);
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe(
|
||||
'var(--frigate-card-expand-max-height)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMinMaxHeight', () => {
|
||||
it('without a config', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setMinMaxHeight();
|
||||
|
||||
expect(element.style.getPropertyValue('--frigate-card-max-height')).toBeFalsy();
|
||||
expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('with a config', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
dimensions: {
|
||||
max_height: '800px',
|
||||
min_height: '400px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setMinMaxHeight();
|
||||
|
||||
expect(element.style.getPropertyValue('--frigate-card-min-height')).toBe('400px');
|
||||
expect(element.style.getPropertyValue('--frigate-card-max-height')).toBe('800px');
|
||||
});
|
||||
});
|
||||
|
||||
it('setPerformance', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
const config = createConfig();
|
||||
vi.mocked(api.getConfigManager().getCardWideConfig).mockReturnValue({
|
||||
performance: config.performance,
|
||||
});
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
manager.setPerformance();
|
||||
|
||||
expect(setPerformanceCSSStyles).toBeCalledWith(element, config.performance);
|
||||
});
|
||||
|
||||
describe('getAspectRatioStyle', () => {
|
||||
it('without config or view', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new StyleManager(api);
|
||||
expect(manager.getAspectRatioStyle()).toBe('16 / 9');
|
||||
});
|
||||
|
||||
it('should be auto with unconstrained aspect ratio', () => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: 'media' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
dimensions: {
|
||||
aspect_ratio_mode: 'unconstrained',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
});
|
||||
|
||||
it('should be auto in fullscreen', () => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: 'media' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getFullscreenManager().isInFullscreen).mockReturnValue(true);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
});
|
||||
|
||||
it('should be auto when expanded', () => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: 'media' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getExpandManager().isExpanded).mockReturnValue(true);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
});
|
||||
|
||||
it('should be auto when there is yet to be a view', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(null);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
});
|
||||
|
||||
describe('should be auto when dynamic in certain views', () => {
|
||||
it.each([
|
||||
['clip' as const],
|
||||
['diagnostics' as const],
|
||||
['image' as const],
|
||||
['media' as const],
|
||||
['live' as const],
|
||||
['recording' as const],
|
||||
['snapshot' as const],
|
||||
['timeline' as const],
|
||||
])('%s', (viewName: FrigateCardView) => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: viewName });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
dimensions: {
|
||||
aspect_ratio_mode: 'dynamic',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('auto');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be enforced when dynamic in certain views', () => {
|
||||
it.each([['clips' as const], ['recordings' as const], ['snapshots' as const]])(
|
||||
'%s',
|
||||
(viewName: FrigateCardView) => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: viewName });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
dimensions: {
|
||||
aspect_ratio_mode: 'dynamic',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('16 / 9');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('should use media dimensions in dynamic', () => {
|
||||
it.each([['clips' as const], ['recordings' as const], ['snapshots' as const]])(
|
||||
'%s',
|
||||
(viewName: FrigateCardView) => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: viewName });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
dimensions: {
|
||||
aspect_ratio_mode: 'dynamic',
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().getLastKnown).mockReturnValue({
|
||||
width: 800,
|
||||
height: 600,
|
||||
});
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('800 / 600');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should respect default aspect ratio', () => {
|
||||
const api = createCardAPI();
|
||||
const view = createView({ view: 'clips' });
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
dimensions: {
|
||||
aspect_ratio_mode: 'dynamic',
|
||||
aspect_ratio: '4:3',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new StyleManager(api);
|
||||
|
||||
expect(manager.getAspectRatioStyle()).toBe('4 / 3');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,190 +0,0 @@
|
||||
import add from 'date-fns/add';
|
||||
import { HassEntities } from 'home-assistant-js-websocket';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ScanOptions } from '../../../src/config/types';
|
||||
import { TriggersManager } from '../../../src/utils/card-controller/triggers-manager';
|
||||
import {
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createHASS,
|
||||
createStateEntity,
|
||||
createView,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/camera-manager/manager.js');
|
||||
|
||||
// Creating and mocking a trigger API is a lot of boilerplate, this convenience
|
||||
// function reduces it.
|
||||
const createTriggerAPI = (options?: {
|
||||
config?: Partial<ScanOptions>;
|
||||
hassStates?: HassEntities;
|
||||
interaction?: boolean;
|
||||
}) => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
scan: options?.config ?? {
|
||||
enabled: true,
|
||||
untrigger_reset: true,
|
||||
untrigger_seconds: 10,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||
createHASS(options?.hassStates),
|
||||
);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(
|
||||
createCameraManager({
|
||||
configs: new Map([
|
||||
[
|
||||
'camera_1',
|
||||
createCameraConfig({
|
||||
triggers: {
|
||||
entities: ['binary_sensor.motion'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
]),
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getInteractionManager().hasInteraction).mockReturnValue(
|
||||
options?.interaction ?? false,
|
||||
);
|
||||
|
||||
return api;
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('TriggersManager', () => {
|
||||
const hassActiveState = {
|
||||
'binary_sensor.motion': createStateEntity({ state: 'on' }),
|
||||
};
|
||||
const hassInactiveState = {
|
||||
'binary_sensor.motion': createStateEntity({ state: 'off' }),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
it('should not be triggered by default', () => {
|
||||
const manager = new TriggersManager(createCardAPI());
|
||||
expect(manager.isTriggered()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not trigger when scan mode disabled default', () => {
|
||||
const api = createTriggerAPI({
|
||||
config: { enabled: false },
|
||||
hassStates: hassActiveState,
|
||||
});
|
||||
const manager = new TriggersManager(api);
|
||||
|
||||
manager.updateTriggeredCameras(null);
|
||||
|
||||
expect(manager.isTriggered()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should trigger and untrigger based on entity state', () => {
|
||||
const start = new Date('2023-10-01T17:14');
|
||||
vi.setSystemTime(start);
|
||||
const api = createTriggerAPI({
|
||||
hassStates: hassActiveState,
|
||||
});
|
||||
const manager = new TriggersManager(api);
|
||||
|
||||
manager.updateTriggeredCameras(createHASS(hassInactiveState));
|
||||
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera_1',
|
||||
});
|
||||
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(
|
||||
createHASS(hassInactiveState),
|
||||
);
|
||||
|
||||
manager.updateTriggeredCameras(createHASS(hassActiveState));
|
||||
|
||||
// Intentional state update with no change.
|
||||
manager.updateTriggeredCameras(createHASS(hassActiveState));
|
||||
|
||||
// Will still be triggered, but untrigger timer will be running.
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
|
||||
vi.setSystemTime(add(start, { seconds: 10 }));
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(manager.isTriggered()).toBeFalsy();
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
it('should trigger and set view if current view is wrong', () => {
|
||||
const api = createTriggerAPI({
|
||||
hassStates: hassActiveState,
|
||||
});
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
// Correct camera, but wrong view.
|
||||
view: 'clips',
|
||||
camera: 'camera_1',
|
||||
}),
|
||||
);
|
||||
const manager = new TriggersManager(api);
|
||||
manager.updateTriggeredCameras(null);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera_1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should trigger when entity state is active on startup', () => {
|
||||
const api = createTriggerAPI({
|
||||
hassStates: hassActiveState,
|
||||
});
|
||||
const manager = new TriggersManager(api);
|
||||
expect(manager.isTriggered()).toBeFalsy();
|
||||
|
||||
manager.updateTriggeredCameras(null);
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should untrigger manually', () => {
|
||||
const api = createTriggerAPI({
|
||||
hassStates: hassActiveState,
|
||||
});
|
||||
const manager = new TriggersManager(api);
|
||||
|
||||
// Untriggering when not triggered.
|
||||
manager.untrigger();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
|
||||
manager.updateTriggeredCameras(null);
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
|
||||
manager.untrigger();
|
||||
expect(manager.isTriggered()).toBeFalsy();
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
|
||||
it('should take no actions when automated actions are not allowed', () => {
|
||||
const api = createTriggerAPI({
|
||||
hassStates: hassActiveState,
|
||||
// Interaction present.
|
||||
interaction: true,
|
||||
});
|
||||
const manager = new TriggersManager(api);
|
||||
manager.updateTriggeredCameras(null);
|
||||
expect(manager.isTriggered()).toBeTruthy();
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
|
||||
manager.untrigger();
|
||||
expect(api.getViewManager().setViewDefault).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,698 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { QueryType } from '../../../src/camera-manager/types';
|
||||
import { FrigateCardView } from '../../../src/config/types';
|
||||
import { getAllDependentCameras } from '../../../src/utils/camera';
|
||||
import { ViewManager } from '../../../src/utils/card-controller/view-manager';
|
||||
import { EventMediaQueries } from '../../../src/view/media-queries';
|
||||
import {
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createHASS,
|
||||
createView,
|
||||
generateViewMediaArray,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/camera-manager/manager.js');
|
||||
vi.mock('../../../src/utils/camera');
|
||||
|
||||
describe('ViewManager.setView', () => {
|
||||
it('should set view', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
displayMode: 'grid',
|
||||
});
|
||||
manager.setView(view);
|
||||
|
||||
expect(manager.getView()).toBe(view);
|
||||
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
||||
expect(api.getCardElementManager().scrollReset).toBeCalled();
|
||||
expect(api.getMessageManager().reset).toBeCalled();
|
||||
expect(api.getStyleManager().setExpandedMode).toBeCalled();
|
||||
expect(api.getConditionsManager()?.setState).toBeCalledWith({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
displayMode: 'grid',
|
||||
});
|
||||
expect(api.getCardElementManager().update).toBeCalled();
|
||||
});
|
||||
|
||||
it('should set view with minor changes without media clearing or scroll', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
const view_1 = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
});
|
||||
manager.setView(view_1);
|
||||
|
||||
vi.mocked(api.getMediaLoadedInfoManager().clear).mockClear();
|
||||
vi.mocked(api.getCardElementManager().scrollReset).mockClear();
|
||||
|
||||
const view_2 = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
displayMode: 'single',
|
||||
});
|
||||
|
||||
manager.setView(view_2);
|
||||
|
||||
expect(manager.getView()).toBe(view_2);
|
||||
|
||||
// The new view is neither a major media change, nor a different view name,
|
||||
// so media clearing and scrolling should not happen.
|
||||
expect(api.getMediaLoadedInfoManager().clear).not.toBeCalled();
|
||||
expect(api.getCardElementManager().scrollReset).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should set view with new context', () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ViewManager(api);
|
||||
const context = { thumbnails: { fetch: false } };
|
||||
|
||||
// Setting context with no existing view does nothing.
|
||||
manager.setViewWithNewContext(context);
|
||||
expect(manager.getView()).toBeNull();
|
||||
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
});
|
||||
manager.setView(view);
|
||||
manager.setViewWithNewContext(context);
|
||||
|
||||
expect(manager.getView()?.camera).toBe('camera');
|
||||
expect(manager.getView()?.view).toBe('live');
|
||||
expect(manager.getView()?.context).toEqual(context);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ViewManager.reset', () => {
|
||||
it('should reset', () => {
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
|
||||
const view = createView();
|
||||
manager.setView(view);
|
||||
manager.reset();
|
||||
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ViewManager.setViewDefault', () => {
|
||||
it('should set default view', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewDefault();
|
||||
|
||||
expect(manager.getView()?.view).toBe('live');
|
||||
expect(manager.getView()?.camera).toBe('camera');
|
||||
expect(api.getAutoUpdateManager().startDefaultViewTimer).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not set default view without config', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewDefault();
|
||||
|
||||
expect(manager.getView()).toBeNull();
|
||||
expect(api.getAutoUpdateManager().startDefaultViewTimer).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should cycle camera when configured', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
update_cycle_camera: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
manager.setViewDefault();
|
||||
expect(manager.getView()?.camera).toBe('camera_1');
|
||||
|
||||
manager.setViewDefault();
|
||||
expect(manager.getView()?.camera).toBe('camera_2');
|
||||
|
||||
manager.setViewDefault();
|
||||
expect(manager.getView()?.camera).toBe('camera_1');
|
||||
|
||||
// When a parameter is specified, it will not cycle.
|
||||
manager.setViewDefault({ cameraID: 'camera_1' });
|
||||
expect(manager.getView()?.camera).toBe('camera_1');
|
||||
});
|
||||
|
||||
it('should respect parameters', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera.kitchen', 'camera.office']),
|
||||
);
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
manager.setViewDefault({
|
||||
cameraID: 'camera.office',
|
||||
substream: 'camera.office_hd',
|
||||
});
|
||||
expect(manager.getView()?.view).toBe('live');
|
||||
expect(manager.getView()?.camera).toBe('camera.office');
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(
|
||||
new Map([['camera.office', 'camera.office_hd']]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ViewManager.setViewByParameters', () => {
|
||||
it('should set view by parameters specifying camera and view', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera',
|
||||
viewName: 'clips',
|
||||
});
|
||||
|
||||
expect(manager.getView()?.view).toBe('clips');
|
||||
expect(manager.getView()?.camera).toBe('camera');
|
||||
});
|
||||
|
||||
it('should set view by parameters using existing view if unspecified', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera_1',
|
||||
viewName: 'clips',
|
||||
});
|
||||
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera_2',
|
||||
});
|
||||
|
||||
expect(manager.getView()?.view).toBe('clips');
|
||||
expect(manager.getView()?.camera).toBe('camera_2');
|
||||
});
|
||||
|
||||
it('should set view by parameters using config as fallback', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera_1',
|
||||
// No prior view, and no specified view. This could happen during query
|
||||
// string based initialization.
|
||||
});
|
||||
|
||||
expect(manager.getView()?.view).toBe('live');
|
||||
expect(manager.getView()?.camera).toBe('camera_1');
|
||||
});
|
||||
|
||||
it('should not set view by parameters without config', () => {
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
|
||||
manager.setViewByParameters({
|
||||
viewName: 'live',
|
||||
});
|
||||
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
it('should not set view by parameters without visible cameras', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(new Set());
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
viewName: 'live',
|
||||
});
|
||||
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
describe('should set view by parameters and respect display mode in config for view', () => {
|
||||
it.each([
|
||||
['media' as const],
|
||||
['clip' as const],
|
||||
['recording' as const],
|
||||
['snapshot' as const],
|
||||
['live' as const],
|
||||
])('%s', (viewName: FrigateCardView) => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
vi.mocked(api.getConfigManager()).getConfig.mockReturnValue(
|
||||
createConfig({
|
||||
media_viewer: {
|
||||
display: {
|
||||
mode: 'grid',
|
||||
},
|
||||
},
|
||||
live: {
|
||||
display: {
|
||||
mode: 'grid',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera',
|
||||
viewName: viewName,
|
||||
});
|
||||
|
||||
expect(manager.getView()?.displayMode).toBe('grid');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should set view by parameters and leave display mode unset for view', () => {
|
||||
it.each([
|
||||
['media' as const],
|
||||
['clip' as const],
|
||||
['recording' as const],
|
||||
['snapshot' as const],
|
||||
['live' as const],
|
||||
])('%s', (viewName: FrigateCardView) => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera',
|
||||
viewName: viewName,
|
||||
});
|
||||
|
||||
expect(manager.getView()?.displayMode).toBe('single');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set view by parameters using config as fallback', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(getAllDependentCameras).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_1_hd']),
|
||||
);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
cameraID: 'camera_1',
|
||||
viewName: 'live',
|
||||
substream: 'camera_1_hd',
|
||||
});
|
||||
|
||||
expect(manager.getView()?.view).toBe('live');
|
||||
expect(manager.getView()?.camera).toBe('camera_1');
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(
|
||||
new Map([['camera_1', 'camera_1_hd']]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('ViewManager.setViewWithNewDisplayMode', () => {
|
||||
it('should set display mode', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
const manager = new ViewManager(api);
|
||||
manager.setView(createView());
|
||||
|
||||
await manager.setViewWithNewDisplayMode('grid');
|
||||
|
||||
expect(manager.getView()?.displayMode).toBe('grid');
|
||||
});
|
||||
|
||||
it('should not set display mode without view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
manager.setViewWithNewDisplayMode('grid');
|
||||
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
it('should set display mode to grid and create new query', async () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraCount).mockReturnValue(2);
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
|
||||
const hass = createHASS();
|
||||
vi.mocked(api.getHASSManager()).getHASS.mockReturnValue(hass);
|
||||
|
||||
const media = generateViewMediaArray({ count: 5 });
|
||||
vi.mocked(cameraManager.executeMediaQueries).mockResolvedValue(media);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
const query = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera_1']), hasClip: true },
|
||||
]);
|
||||
|
||||
manager.setView(
|
||||
createView({
|
||||
camera: 'camera_1',
|
||||
view: 'clip',
|
||||
query: query,
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.setViewWithNewDisplayMode('grid');
|
||||
|
||||
expect(manager.getView()?.queryResults?.getResults()).toBe(media);
|
||||
expect(cameraManager.executeMediaQueries).toBeCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'event-query',
|
||||
cameraIDs: new Set(['camera_1', 'camera_2']),
|
||||
hasClip: true,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set display mode to single and create new query', async () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraCount).mockReturnValue(2);
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
|
||||
const hass = createHASS();
|
||||
vi.mocked(api.getHASSManager()).getHASS.mockReturnValue(hass);
|
||||
|
||||
const media = generateViewMediaArray({ count: 5 });
|
||||
vi.mocked(cameraManager.executeMediaQueries).mockResolvedValue(media);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
const query = new EventMediaQueries([
|
||||
{
|
||||
type: QueryType.Event,
|
||||
cameraIDs: new Set(['camera_1', 'camera_2']),
|
||||
hasClip: true,
|
||||
},
|
||||
]);
|
||||
|
||||
manager.setView(
|
||||
createView({
|
||||
view: 'clip',
|
||||
camera: 'camera_2',
|
||||
query: query,
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.setViewWithNewDisplayMode('single');
|
||||
|
||||
expect(manager.getView()?.queryResults?.getResults()).toBe(media);
|
||||
expect(cameraManager.executeMediaQueries).toBeCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: 'event-query',
|
||||
cameraIDs: new Set(['camera_2']),
|
||||
hasClip: true,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set display mode to single and handle failed new query', async () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraCount).mockReturnValue(2);
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
const query = new EventMediaQueries([
|
||||
{
|
||||
type: QueryType.Event,
|
||||
cameraIDs: new Set(['camera_1', 'camera_2']),
|
||||
hasClip: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const originalView = createView({
|
||||
view: 'clip',
|
||||
camera: 'camera_2',
|
||||
query: query,
|
||||
});
|
||||
manager.setView(originalView);
|
||||
|
||||
// Query execution fails / returns null.
|
||||
vi.mocked(cameraManager.executeMediaQueries).mockRejectedValue(null);
|
||||
|
||||
await manager.setViewWithNewDisplayMode('single');
|
||||
|
||||
expect(manager.getView()).toBe(originalView);
|
||||
});
|
||||
|
||||
it('should set display mode and handle empty new query results', async () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraCount).mockReturnValue(2);
|
||||
vi.mocked(cameraManager.getStore().getVisibleCameraIDs).mockReturnValue(
|
||||
new Set(['camera_1', 'camera_2']),
|
||||
);
|
||||
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
const query = new EventMediaQueries([
|
||||
{
|
||||
type: QueryType.Event,
|
||||
cameraIDs: new Set(['camera_1']),
|
||||
hasClip: true,
|
||||
},
|
||||
]);
|
||||
const originalView = createView({
|
||||
view: 'clip',
|
||||
camera: 'camera_2',
|
||||
query: query,
|
||||
});
|
||||
manager.setView(originalView);
|
||||
|
||||
await manager.setViewWithNewDisplayMode('grid');
|
||||
|
||||
vi.mocked(cameraManager.executeMediaQueries).mockResolvedValue(null);
|
||||
|
||||
// Empty queries will not be executed, so view will not be changed.
|
||||
expect(manager.getView()?.displayMode).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ViewManager.setViewWithSubstream', () => {
|
||||
it('should set new equal view with no dependencies', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
});
|
||||
vi.mocked(getAllDependentCameras).mockReturnValue(new Set(['camera']));
|
||||
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setView(view);
|
||||
manager.setViewWithSubstream();
|
||||
|
||||
expect(manager.getView()?.camera).toBe(view.camera);
|
||||
expect(manager.getView()?.view).toBe(view.view);
|
||||
expect(manager.getView()?.context).toEqual(view.context);
|
||||
});
|
||||
|
||||
it('should set new view with next substream', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
});
|
||||
vi.mocked(getAllDependentCameras).mockReturnValue(new Set(['camera', 'camera2']));
|
||||
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setView(view);
|
||||
manager.setViewWithSubstream();
|
||||
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(
|
||||
new Map([['camera', 'camera2']]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set new view with next substream when view has invalid substream', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera', 'camera-that-does-not-exist']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
vi.mocked(getAllDependentCameras).mockReturnValue(new Set(['camera', 'camera2']));
|
||||
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setView(view);
|
||||
manager.setViewWithSubstream();
|
||||
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(
|
||||
new Map([['camera', 'camera']]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set new view with selected substream', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
});
|
||||
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setView(view);
|
||||
manager.setViewWithSubstream('substream');
|
||||
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(
|
||||
new Map([['camera', 'substream']]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set view with next substream without an existing view', () => {
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setViewWithSubstream();
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
it('should not set view with selected substream without an existing view', () => {
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setViewWithSubstream('substream');
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
it('should not set view without substream without an existing view', () => {
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setViewWithoutSubstream();
|
||||
expect(manager.getView()).toBeNull();
|
||||
});
|
||||
|
||||
it('should set new view without substream', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera', 'camera']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setView(view);
|
||||
manager.setViewWithoutSubstream();
|
||||
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(new Map());
|
||||
});
|
||||
|
||||
it('should set new view without substream', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera-2', 'camera-3']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const manager = new ViewManager(createCardAPI());
|
||||
manager.setView(view);
|
||||
manager.setViewWithoutSubstream();
|
||||
|
||||
expect(manager.getView()?.context?.live?.overrides).toEqual(
|
||||
view.context?.live?.overrides,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ViewManager.isViewSupportedByCamera', () => {
|
||||
it.each([
|
||||
['live' as const, true],
|
||||
['image' as const, true],
|
||||
['diagnostics' as const, true],
|
||||
['clip' as const, false],
|
||||
['clips' as const, false],
|
||||
['snapshot' as const, false],
|
||||
['snapshots' as const, false],
|
||||
['recording' as const, false],
|
||||
['recordings' as const, false],
|
||||
['timeline' as const, false],
|
||||
['media' as const, false],
|
||||
])('%s', (viewName: FrigateCardView, expected: boolean) => {
|
||||
const api = createCardAPI();
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockReturnValue({
|
||||
canFavoriteEvents: false,
|
||||
canFavoriteRecordings: false,
|
||||
canSeek: false,
|
||||
supportsClips: false,
|
||||
supportsRecordings: false,
|
||||
supportsSnapshots: false,
|
||||
supportsTimeline: false,
|
||||
});
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(cameraManager);
|
||||
const manager = new ViewManager(api);
|
||||
|
||||
expect(manager.isViewSupportedByCamera('camera', viewName)).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -7,8 +7,8 @@ import { CameraManagerCameraMetadata } from '../../src/camera-manager/types';
|
||||
import { FrigateCardConfig, MenuItem, ViewDisplayMode } from '../../src/config/types';
|
||||
import { FrigateCardMediaPlayer } from '../../src/types';
|
||||
import { createFrigateCardCustomAction } from '../../src/utils/action';
|
||||
import { MediaPlayerManager } from '../../src/utils/card-controller/media-player-manager';
|
||||
import { MicrophoneManager } from '../../src/utils/card-controller/microphone-manager';
|
||||
import { MediaPlayerManager } from '../../src/card-controller/media-player-manager';
|
||||
import { MicrophoneManager } from '../../src/card-controller/microphone-manager';
|
||||
import {
|
||||
MenuButtonController,
|
||||
MenuButtonControllerOptions,
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
|
||||
vi.mock('../../src/camera-manager/manager.js');
|
||||
vi.mock('../../src/utils/media-player-controller.js');
|
||||
vi.mock('../../src/utils/card-controller/microphone-manager.js');
|
||||
vi.mock('../../src/card-controller/microphone-manager.js');
|
||||
|
||||
const calculateButtons = (
|
||||
controller: MenuButtonController,
|
||||
|
||||
Reference in New Issue
Block a user