fix: Use HA style and coloring for all icons (#1790)
This commit is contained in:
@@ -206,7 +206,11 @@ describe('GenericCameraManagerEngine', () => {
|
||||
expect(
|
||||
createEngine().getCameraMetadata(createHASS(), createGenericCameraConfig()),
|
||||
).toEqual({
|
||||
icon: 'mdi:video',
|
||||
icon: {
|
||||
entity: undefined,
|
||||
icon: undefined,
|
||||
fallback: 'mdi:video',
|
||||
},
|
||||
title: '',
|
||||
});
|
||||
});
|
||||
@@ -217,10 +221,11 @@ describe('GenericCameraManagerEngine', () => {
|
||||
createHASS(),
|
||||
createGenericCameraConfig({ id: 'https://go2rtc#stream' }),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:video',
|
||||
title: 'https://go2rtc#stream',
|
||||
});
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'https://go2rtc#stream',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with configured title', async () => {
|
||||
@@ -231,10 +236,11 @@ describe('GenericCameraManagerEngine', () => {
|
||||
title: 'My Camera',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:video',
|
||||
title: 'My Camera',
|
||||
});
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'My Camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('with entity title', () => {
|
||||
@@ -250,10 +256,11 @@ describe('GenericCameraManagerEngine', () => {
|
||||
camera_entity: 'camera.test',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:video',
|
||||
title: 'My Entity Camera',
|
||||
});
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'My Entity Camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('webrtc_card.entity', async () => {
|
||||
@@ -270,10 +277,11 @@ describe('GenericCameraManagerEngine', () => {
|
||||
},
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
icon: 'mdi:video',
|
||||
title: 'My Entity Camera',
|
||||
});
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
title: 'My Entity Camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -259,8 +259,12 @@ describe('ReolinkCameraManagerEngine', () => {
|
||||
});
|
||||
const engine = createEngine();
|
||||
expect(engine.getCameraMetadata(createHASS(), cameraConfig)).toEqual({
|
||||
engineLogo: '/src/camera-manager/reolink/assets/reolink.svg',
|
||||
icon: 'mdi:camera',
|
||||
engineIcon: 'reolink',
|
||||
icon: {
|
||||
icon: 'mdi:camera',
|
||||
entity: 'camera.office',
|
||||
fallback: 'mdi:video',
|
||||
},
|
||||
title: 'Office',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('StatusBarItemManager', () => {
|
||||
const cameraManager = createCameraManager(store);
|
||||
vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({
|
||||
title: 'Camera Title',
|
||||
icon: 'mdi:camera',
|
||||
icon: { icon: 'mdi:camera' },
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -225,8 +225,10 @@ describe('StatusBarItemManager', () => {
|
||||
const cameraManager = createCameraManager(store);
|
||||
vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({
|
||||
title: 'Camera Title',
|
||||
icon: 'mdi:camera',
|
||||
engineLogo: 'IMAGE_LOGO',
|
||||
icon: {
|
||||
icon: 'mdi:camera',
|
||||
},
|
||||
engineIcon: 'ENGINE_ICON',
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -235,8 +237,8 @@ describe('StatusBarItemManager', () => {
|
||||
view: createView({ view: 'live', camera: 'camera-1' }),
|
||||
}),
|
||||
).toContainEqual({
|
||||
type: 'custom:frigate-card-status-bar-image' as const,
|
||||
image: 'IMAGE_LOGO',
|
||||
type: 'custom:frigate-card-status-bar-icon' as const,
|
||||
icon: 'ENGINE_ICON',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { IconController } from '../../src/components-lib/icon-controller';
|
||||
import { createHASS, createStateEntity } from '../test-utils';
|
||||
|
||||
describe('IconController', () => {
|
||||
describe('should get custom icon', () => {
|
||||
it('should return frigate SVG for frigate icon', () => {
|
||||
expect(new IconController().getCustomIcon({ icon: 'frigate' })).toMatch(
|
||||
/frigate.svg$/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return motioneye SVG for motioneye icon', () => {
|
||||
expect(new IconController().getCustomIcon({ icon: 'motioneye' })).toMatch(
|
||||
/motioneye.svg$/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return reolink SVG for reolink icon', () => {
|
||||
expect(new IconController().getCustomIcon({ icon: 'reolink' })).toMatch(
|
||||
/reolink.svg$/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null for mdi icon', () => {
|
||||
expect(new IconController().getCustomIcon({ icon: 'mdi:car' })).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for undefined icon', () => {
|
||||
expect(new IconController().getCustomIcon()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should create state object for state badge', () => {
|
||||
it('should return null for non-existent entity', () => {
|
||||
expect(
|
||||
new IconController().createStateObjectForStateBadge(
|
||||
createHASS(),
|
||||
'sensor.DOES_NOT_EXIST',
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should return modified state object for existing entity', () => {
|
||||
expect(
|
||||
new IconController().createStateObjectForStateBadge(
|
||||
createHASS({
|
||||
'sensor.existing': createStateEntity({
|
||||
entity_id: 'sensor.existing',
|
||||
attributes: {
|
||||
friendly_name: 'Existing',
|
||||
icon: 'mdi:car',
|
||||
entity_picture: 'http://example.com/image.jpg',
|
||||
entity_picture_local: 'local.jpg',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
'sensor.existing',
|
||||
),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
entity_id: 'sensor.existing',
|
||||
attributes: expect.objectContaining({
|
||||
friendly_name: 'Existing',
|
||||
icon: 'mdi:car',
|
||||
entity_picture: undefined,
|
||||
entity_picture_local: undefined,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -127,7 +127,9 @@ describe('MenuButtonController', () => {
|
||||
);
|
||||
vi.mocked(cameraManager).getCameraMetadata.mockReturnValue({
|
||||
title: 'title',
|
||||
icon: 'icon',
|
||||
icon: {
|
||||
icon: 'icon',
|
||||
},
|
||||
});
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
@@ -178,7 +180,9 @@ describe('MenuButtonController', () => {
|
||||
);
|
||||
vi.mocked(cameraManager).getCameraMetadata.mockReturnValue({
|
||||
title: 'title',
|
||||
icon: 'icon',
|
||||
icon: {
|
||||
icon: 'icon',
|
||||
},
|
||||
});
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
@@ -332,7 +336,10 @@ describe('MenuButtonController', () => {
|
||||
return cameraID === 'camera-1'
|
||||
? {
|
||||
title: 'title',
|
||||
icon: 'icon',
|
||||
icon: {
|
||||
icon: 'icon',
|
||||
entity: 'entity',
|
||||
},
|
||||
}
|
||||
: null;
|
||||
},
|
||||
@@ -351,7 +358,7 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
icon: 'icon',
|
||||
entity: 'camera.1',
|
||||
entity: 'entity',
|
||||
state_color: true,
|
||||
title: 'title',
|
||||
selected: true,
|
||||
@@ -364,7 +371,7 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
icon: undefined,
|
||||
entity: 'camera.2',
|
||||
entity: undefined,
|
||||
state_color: true,
|
||||
title: undefined,
|
||||
selected: false,
|
||||
@@ -377,7 +384,7 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
icon: undefined,
|
||||
entity: 'camera.3',
|
||||
entity: undefined,
|
||||
state_color: true,
|
||||
title: undefined,
|
||||
selected: false,
|
||||
@@ -443,7 +450,7 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
icon: undefined,
|
||||
entity: 'camera.1',
|
||||
entity: undefined,
|
||||
state_color: true,
|
||||
title: undefined,
|
||||
selected: false,
|
||||
@@ -456,7 +463,7 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
icon: undefined,
|
||||
entity: 'camera.2',
|
||||
entity: undefined,
|
||||
state_color: true,
|
||||
title: undefined,
|
||||
// camera-2 is selected in this test scenario because of the view
|
||||
@@ -471,7 +478,7 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
icon: undefined,
|
||||
entity: 'camera.3',
|
||||
entity: undefined,
|
||||
state_color: true,
|
||||
title: undefined,
|
||||
selected: false,
|
||||
@@ -1212,7 +1219,6 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
selected: false,
|
||||
icon: 'mdi:cast',
|
||||
entity: 'media_player.tv',
|
||||
state_color: false,
|
||||
title: 'media_player.tv',
|
||||
@@ -1266,7 +1272,6 @@ describe('MenuButtonController', () => {
|
||||
{
|
||||
enabled: true,
|
||||
selected: false,
|
||||
icon: 'mdi:bookmark',
|
||||
entity: 'not_a_real_player',
|
||||
state_color: false,
|
||||
title: 'not_a_real_player',
|
||||
|
||||
@@ -2,9 +2,7 @@ import { handleActionConfig } from '@dermotduffy/custom-card-helpers';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { MenuController } from '../../src/components-lib/menu-controller';
|
||||
import { MenuConfig, menuConfigSchema } from '../../src/config/types';
|
||||
import { StateParameters } from '../../src/types';
|
||||
import { refreshDynamicStateParameters } from '../../src/utils/ha';
|
||||
import { createInteractionEvent, createHASS, createLitElement } from '../test-utils';
|
||||
import { createInteractionEvent, createLitElement } from '../test-utils';
|
||||
|
||||
vi.mock('@dermotduffy/custom-card-helpers');
|
||||
vi.mock('../../src/utils/ha');
|
||||
@@ -362,38 +360,6 @@ describe('MenuController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get fresh button state', () => {
|
||||
it('on state icon', () => {
|
||||
const controller = new MenuController(createLitElement());
|
||||
const stateButton = {
|
||||
type: 'custom:frigate-card-menu-state-icon' as const,
|
||||
icon: 'mdi:sheep',
|
||||
entity: 'switch.foo',
|
||||
state_color: true,
|
||||
};
|
||||
|
||||
const stateParameters: StateParameters = {};
|
||||
vi.mocked(refreshDynamicStateParameters).mockReturnValue(stateParameters);
|
||||
|
||||
expect(controller.getFreshButtonState(createHASS(), stateButton)).toBe(
|
||||
stateParameters,
|
||||
);
|
||||
|
||||
expect(vi.mocked(refreshDynamicStateParameters)).toBeCalled();
|
||||
});
|
||||
|
||||
it('on non state icon', () => {
|
||||
const controller = new MenuController(createLitElement());
|
||||
const button = {
|
||||
type: 'custom:frigate-card-menu-icon' as const,
|
||||
icon: 'mdi:sheep',
|
||||
};
|
||||
|
||||
expect(controller.getFreshButtonState(createHASS(), button)).toEqual(button);
|
||||
expect(vi.mocked(refreshDynamicStateParameters)).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle actions', () => {
|
||||
it('should bail without config', () => {
|
||||
const controller = new MenuController(createLitElement());
|
||||
|
||||
@@ -285,7 +285,7 @@ describe('config defaults', () => {
|
||||
},
|
||||
},
|
||||
status_bar: {
|
||||
height: 46,
|
||||
height: 30,
|
||||
items: {
|
||||
engine: {
|
||||
enabled: true,
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import frigateSVG from '../../src/camera-manager/frigate/assets/frigate.svg';
|
||||
import motioneyeSVG from '../../src/camera-manager/motioneye/assets/motioneye.svg';
|
||||
import reolinkSVG from '../../src/camera-manager/reolink/assets/reolink.svg';
|
||||
import { getCustomIconURL } from '../../src/utils/custom-icons';
|
||||
|
||||
describe('getCustomIconURL', () => {
|
||||
it('should return frigate SVG for frigate icon', () => {
|
||||
expect(getCustomIconURL('frigate')).toBe(frigateSVG);
|
||||
});
|
||||
|
||||
it('should return motioneye SVG for motioneye icon', () => {
|
||||
expect(getCustomIconURL('motioneye')).toBe(motioneyeSVG);
|
||||
});
|
||||
|
||||
it('should return reolink SVG for reolink icon', () => {
|
||||
expect(getCustomIconURL('reolink')).toBe(reolinkSVG);
|
||||
});
|
||||
|
||||
it('should return null for mdi icon', () => {
|
||||
expect(getCustomIconURL('mdi:car')).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for undefined icon', () => {
|
||||
expect(getCustomIconURL()).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -2,11 +2,10 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
canonicalizeHAURL,
|
||||
getEntityIcon,
|
||||
hasHAConnectionStateChanged,
|
||||
isHARelativeURL,
|
||||
} from '../../../src/utils/ha/index.js';
|
||||
import { createHASS, createStateEntity } from '../../test-utils.js';
|
||||
import { createHASS } from '../../test-utils.js';
|
||||
|
||||
const createConnected = (connected: boolean): HomeAssistant => {
|
||||
const hass = createHASS();
|
||||
@@ -49,27 +48,6 @@ describe('hasHAConnectionStateChanged', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEntityIcon', () => {
|
||||
it('should get icon from attributes', () => {
|
||||
expect(
|
||||
getEntityIcon(
|
||||
createHASS({
|
||||
'camera.test': createStateEntity({
|
||||
attributes: {
|
||||
icon: 'mdi:cow',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
'camera.test',
|
||||
),
|
||||
).toBe('mdi:cow');
|
||||
});
|
||||
|
||||
it('should get icon from domain', () => {
|
||||
expect(getEntityIcon(createHASS(), 'camera.test')).toBe('mdi:video');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isHARelativeURL', () => {
|
||||
it('should return true when URL is HA relative', () => {
|
||||
expect(isHARelativeURL('/api/foo')).toBeTruthy();
|
||||
|
||||
Reference in New Issue
Block a user