@@ -5,6 +5,7 @@ import { ActionConfig } from '../../src/config/schema/actions/types.js';
|
||||
import {
|
||||
createCameraAction,
|
||||
createDisplayModeAction,
|
||||
createFolderAction,
|
||||
createGeneralAction,
|
||||
createInternalCallbackAction,
|
||||
createLogAction,
|
||||
@@ -50,7 +51,7 @@ describe('createViewAction', () => {
|
||||
});
|
||||
|
||||
describe('createCameraAction', () => {
|
||||
it('should create camera_select', () => {
|
||||
it('should create camera_select action', () => {
|
||||
expect(createCameraAction('camera_select', 'camera', { cardID: 'card_id' })).toEqual(
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
@@ -62,8 +63,19 @@ describe('createCameraAction', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFolderAction', () => {
|
||||
it('should create folder action', () => {
|
||||
expect(createFolderAction({ folderID: 'folderID', cardID: 'card_id' })).toEqual({
|
||||
action: 'fire-dom-event',
|
||||
advanced_camera_card_action: 'folder',
|
||||
card_id: 'card_id',
|
||||
folder: 'folderID',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createMediaPlayerAction', () => {
|
||||
it('should create media_player', () => {
|
||||
it('should create media_player action', () => {
|
||||
expect(
|
||||
createMediaPlayerAction('device', 'play', {
|
||||
cardID: 'card_id',
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
runWhenIdleIfSupported,
|
||||
setify,
|
||||
setOrRemoveAttribute,
|
||||
sleep,
|
||||
} from '../../src/utils/basic.js';
|
||||
import { createSlot, createSlotHost } from '../test-utils.js';
|
||||
|
||||
@@ -185,6 +184,10 @@ describe('getDurationString', () => {
|
||||
const end = new Date(2023, 3, 14, 13, 35, 12);
|
||||
expect(getDurationString(start, end)).toBe('2s');
|
||||
});
|
||||
it('should return 0s for no delta', () => {
|
||||
const start = new Date(2023, 3, 14, 13, 35, 10);
|
||||
expect(getDurationString(start, start)).toBe('0s');
|
||||
});
|
||||
});
|
||||
|
||||
describe('allPromises', () => {
|
||||
@@ -209,19 +212,6 @@ describe('isSuperset', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sleep', () => {
|
||||
it('should sleep', async () => {
|
||||
const spy = vi
|
||||
.spyOn(global, 'setTimeout')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
||||
.mockImplementation((func: () => unknown, _time?: number): any => {
|
||||
func();
|
||||
});
|
||||
sleep(10);
|
||||
expect(spy).toHaveBeenCalledWith(expect.anything(), 10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidDate', () => {
|
||||
it('should be valid date', () => {
|
||||
expect(isValidDate(new Date(2023, 3, 28))).toBeTruthy();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { HassConfig } from 'home-assistant-js-websocket';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { DeviceRegistryManager } from '../../src/ha/registry/device';
|
||||
import { homeAssistantWSRequest } from '../../src/ha/ws-request';
|
||||
import { getLanguage } from '../../src/localize/localize';
|
||||
import { getDiagnostics, getReleaseVersion } from '../../src/utils/diagnostics.js';
|
||||
import { DeviceRegistryManager } from '../../src/utils/ha/registry/device';
|
||||
import { homeAssistantWSRequest } from '../../src/utils/ha/ws-request';
|
||||
import { createHASS, createRegistryDevice } from '../test-utils';
|
||||
|
||||
vi.mock('../../package.json', () => ({
|
||||
@@ -14,10 +14,10 @@ vi.mock('../../package.json', () => ({
|
||||
gitDate: 'Wed, 6 Sep 2023 21:27:28 -0700',
|
||||
},
|
||||
}));
|
||||
vi.mock('../../src/utils/ha');
|
||||
vi.mock('../../src/ha');
|
||||
vi.mock('../../src/localize/localize.js');
|
||||
vi.mock('../../src/utils/ha/registry/device/index.js');
|
||||
vi.mock('../../src/utils/ha/ws-request.js');
|
||||
vi.mock('../../src/ha/registry/device/index.js');
|
||||
vi.mock('../../src/ha/ws-request.js');
|
||||
|
||||
describe('getReleaseVersion', () => {
|
||||
it('should get release version', () => {
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { downloadMedia, downloadURL } from '../../src/utils/download';
|
||||
import { homeAssistantSignPath } from '../../src/utils/ha';
|
||||
import { ViewMedia } from '../../src/view/media';
|
||||
import {
|
||||
createCameraManager,
|
||||
createHASS,
|
||||
createStore,
|
||||
TestViewMedia,
|
||||
} from '../test-utils';
|
||||
|
||||
vi.mock('../../src/utils/ha');
|
||||
|
||||
const media = new ViewMedia('clip', 'camera.office');
|
||||
import { downloadURL } from '../../src/utils/download';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('downloadURL', () => {
|
||||
@@ -64,121 +52,3 @@ describe('downloadURL', () => {
|
||||
expect(windowSpy).toBeCalledWith('http://bar/url.mp4', '_blank');
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadMedia', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(window, 'location', 'get').mockReturnValue(
|
||||
mock<Location>({ origin: 'https://foo' }),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should throw error when no media', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getMediaDownloadPath).mockResolvedValue(null);
|
||||
|
||||
expect(downloadMedia(createHASS(), cameraManager, media)).rejects.toThrow(
|
||||
/No media to download/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when signing fails', () => {
|
||||
vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager).getMediaDownloadPath.mockResolvedValue({
|
||||
sign: true,
|
||||
endpoint: 'foo',
|
||||
});
|
||||
const signError = new Error('sign-error');
|
||||
vi.mocked(homeAssistantSignPath).mockRejectedValue(signError);
|
||||
|
||||
expect(downloadMedia(createHASS(), cameraManager, media)).rejects.toThrow(
|
||||
/Could not sign media URL for download/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should download media', async () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager).getMediaDownloadPath.mockResolvedValue({
|
||||
sign: true,
|
||||
endpoint: 'foo',
|
||||
});
|
||||
vi.mocked(homeAssistantSignPath).mockResolvedValue('http://foo/signed-url');
|
||||
const windowSpy = vi.spyOn(window, 'open').mockReturnValue(null);
|
||||
|
||||
await downloadMedia(createHASS(), cameraManager, media);
|
||||
expect(windowSpy).toBeCalledWith('http://foo/signed-url', '_blank');
|
||||
});
|
||||
|
||||
it('should download media without signing', async () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager).getMediaDownloadPath.mockResolvedValue({
|
||||
sign: false,
|
||||
endpoint: 'https://another/',
|
||||
});
|
||||
const windowSpy = vi.spyOn(window, 'open').mockReturnValue(null);
|
||||
|
||||
await downloadMedia(createHASS(), cameraManager, media);
|
||||
expect(windowSpy).toBeCalledWith('https://another/', '_blank');
|
||||
});
|
||||
|
||||
describe('should generate useful download filenames', () => {
|
||||
it('should generate filename with just camera ID', async () => {
|
||||
const cameraManager = createCameraManager(
|
||||
createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(cameraManager).getMediaDownloadPath.mockResolvedValue({
|
||||
sign: false,
|
||||
endpoint: 'https://foo/',
|
||||
});
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.click = vi.fn();
|
||||
link.setAttribute = vi.fn();
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(link);
|
||||
|
||||
await downloadMedia(createHASS(), cameraManager, media);
|
||||
|
||||
expect(link.setAttribute).toBeCalledWith('download', 'camera-office');
|
||||
});
|
||||
|
||||
it('should generate filename with full details ID', async () => {
|
||||
const cameraManager = createCameraManager(
|
||||
createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(cameraManager).getMediaDownloadPath.mockResolvedValue({
|
||||
sign: false,
|
||||
endpoint: 'https://foo/',
|
||||
});
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.click = vi.fn();
|
||||
link.setAttribute = vi.fn();
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(link);
|
||||
|
||||
const media = new TestViewMedia({
|
||||
cameraID: 'camera.office',
|
||||
id: 'clip-id',
|
||||
startTime: new Date('2025-03-06T21:31:29Z'),
|
||||
});
|
||||
await downloadMedia(createHASS(), cameraManager, media);
|
||||
|
||||
expect(link.setAttribute).toBeCalledWith(
|
||||
'download',
|
||||
'camera-office_clip-id_2025-03-06-21-31-29',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
createTestSlideNodes,
|
||||
} from '../../test-utils';
|
||||
|
||||
vi.mock('lodash-es/debounce', () => ({
|
||||
default: vi.fn((fn) => fn),
|
||||
vi.mock('lodash-es', () => ({
|
||||
debounce: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
// @vitest-environment jsdom
|
||||
|
||||
@@ -3,8 +3,8 @@ import { EmblaReInitController } from '../../../src/utils/embla/reinit-controlle
|
||||
import { requestAnimationFrameMock } from '../../test-utils';
|
||||
import { callEmblaHandler, createEmblaApiInstance } from './test-utils';
|
||||
|
||||
vi.mock('lodash-es/debounce', () => ({
|
||||
default: vi.fn((fn) => fn),
|
||||
vi.mock('lodash-es', () => ({
|
||||
debounce: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
// @vitest-environment jsdom
|
||||
|
||||
@@ -2,7 +2,7 @@ import { EmblaCarouselType, EmblaEventType } from 'embla-carousel';
|
||||
import { EngineType } from 'embla-carousel/components/Engine';
|
||||
import { LooseOptionsType } from 'embla-carousel/components/Options';
|
||||
import { OptionsHandlerType } from 'embla-carousel/components/OptionsHandler';
|
||||
import merge from 'lodash-es/merge';
|
||||
import { merge } from 'lodash-es';
|
||||
import { vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { homeAssistantSignPath } from '../../src/ha/sign-path.js';
|
||||
import { convertEndpointAddressToSignedWebsocket } from '../../src/utils/endpoint';
|
||||
import { homeAssistantSignPath } from '../../src/utils/ha';
|
||||
import { createHASS } from '../test-utils';
|
||||
|
||||
vi.mock('../../src/utils/ha');
|
||||
vi.mock('../../src/ha/sign-path.js');
|
||||
|
||||
describe('convertEndpointAddressToSignedWebsocket', () => {
|
||||
it('without signing', async () => {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { HomeAssistant } from '../../../src/ha/types.js';
|
||||
import {
|
||||
canonicalizeHAURL,
|
||||
hasHAConnectionStateChanged,
|
||||
isHARelativeURL,
|
||||
} from '../../../src/utils/ha/index.js';
|
||||
import { createHASS } from '../../test-utils.js';
|
||||
|
||||
const createConnected = (connected: boolean): HomeAssistant => {
|
||||
const hass = createHASS();
|
||||
hass.connected = connected;
|
||||
return hass;
|
||||
};
|
||||
|
||||
describe('hasHAConnectionStateChanged', () => {
|
||||
it('initially connected', () => {
|
||||
expect(hasHAConnectionStateChanged(null, createConnected(true))).toBeTruthy();
|
||||
});
|
||||
it('initially disconnected', () => {
|
||||
expect(hasHAConnectionStateChanged(null, createConnected(false))).toBeTruthy();
|
||||
});
|
||||
it('disconnected', () => {
|
||||
expect(
|
||||
hasHAConnectionStateChanged(createConnected(true), createConnected(false)),
|
||||
).toBeTruthy();
|
||||
});
|
||||
it('disconnected via absence', () => {
|
||||
expect(hasHAConnectionStateChanged(createConnected(true), null)).toBeTruthy();
|
||||
});
|
||||
it('connected', () => {
|
||||
expect(
|
||||
hasHAConnectionStateChanged(createConnected(false), createConnected(true)),
|
||||
).toBeTruthy();
|
||||
});
|
||||
it('still disconnected', () => {
|
||||
expect(
|
||||
hasHAConnectionStateChanged(createConnected(false), createConnected(false)),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('still connected', () => {
|
||||
expect(
|
||||
hasHAConnectionStateChanged(createConnected(true), createConnected(true)),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('still absent', () => {
|
||||
expect(hasHAConnectionStateChanged(null, null)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isHARelativeURL', () => {
|
||||
it('should return true when URL is HA relative', () => {
|
||||
expect(isHARelativeURL('/api/foo')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when URL is not HA relative', () => {
|
||||
expect(isHARelativeURL('http://localhost/api/foo')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('canonicalizeHAURL', () => {
|
||||
it('should return canonicalized HA url', () => {
|
||||
const hass = createHASS();
|
||||
vi.mocked(hass.hassUrl).mockReturnValue('http://localhost:8123/foo/bar');
|
||||
|
||||
expect(canonicalizeHAURL(hass, '/api/foo')).toBe('http://localhost:8123/foo/bar');
|
||||
});
|
||||
|
||||
it('should return untouched URL when not HA relative', () => {
|
||||
expect(canonicalizeHAURL(createHASS(), 'http://localhost:8123/foo/bar')).toBe(
|
||||
'http://localhost:8123/foo/bar',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null without a URL', () => {
|
||||
expect(canonicalizeHAURL(createHASS())).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { getIntegrationManifest } from '../../../../src/utils/ha/integration';
|
||||
import { integrationManifestSchema } from '../../../../src/utils/ha/integration/types';
|
||||
import { homeAssistantWSRequest } from '../../../../src/utils/ha/ws-request';
|
||||
import { createHASS } from '../../../test-utils';
|
||||
|
||||
vi.mock('../../../../src/utils/ha/ws-request');
|
||||
|
||||
describe('getIntegrationManifest', () => {
|
||||
it('should get integration manifest', async () => {
|
||||
const hass = createHASS();
|
||||
await getIntegrationManifest(hass, 'INTEGRATION');
|
||||
expect(homeAssistantWSRequest).toHaveBeenCalledWith(
|
||||
hass,
|
||||
integrationManifestSchema,
|
||||
{ type: 'manifest/get', integration: 'INTEGRATION' },
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { RegistryCache } from '../../../../src/utils/ha/registry/cache';
|
||||
|
||||
interface TestCacheValue {
|
||||
id: string;
|
||||
val?: number;
|
||||
}
|
||||
|
||||
describe('RegistryCache', () => {
|
||||
describe('has', () => {
|
||||
it('positive', () => {
|
||||
const cache = new RegistryCache<TestCacheValue>((arg) => arg.id);
|
||||
cache.add({ id: 'test' });
|
||||
expect(cache.has('test')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('negative', () => {
|
||||
const cache = new RegistryCache<TestCacheValue>((arg) => arg.id);
|
||||
cache.add({ id: 'test' });
|
||||
expect(cache.has('absent')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('getMatches', () => {
|
||||
const cache = new RegistryCache<TestCacheValue>((arg) => arg.id);
|
||||
cache.add([
|
||||
{ id: 'test-1', val: 1 },
|
||||
{ id: 'test-5', val: 5 },
|
||||
{ id: 'test-8', val: 8 },
|
||||
]);
|
||||
expect(cache.getMatches((obj) => !!obj.val && obj.val >= 5)).toEqual([
|
||||
{ id: 'test-5', val: 5 },
|
||||
{ id: 'test-8', val: 8 },
|
||||
]);
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('positive', () => {
|
||||
const cache = new RegistryCache<TestCacheValue>((arg) => arg.id);
|
||||
cache.add({ id: 'test', val: 42 });
|
||||
expect(cache.get('test')).toEqual({ id: 'test', val: 42 });
|
||||
});
|
||||
|
||||
it('negative', () => {
|
||||
const cache = new RegistryCache<TestCacheValue>((arg) => arg.id);
|
||||
expect(cache.get('test')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
createDeviceRegistryCache,
|
||||
DeviceRegistryManager,
|
||||
} from '../../../../../src/utils/ha/registry/device';
|
||||
import { homeAssistantWSRequest } from '../../../../../src/utils/ha/ws-request';
|
||||
import { createHASS, createRegistryDevice } from '../../../../test-utils.js';
|
||||
|
||||
vi.mock('../../../../../src/utils/ha/ws-request');
|
||||
vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||
|
||||
describe('DeviceRegistryManager', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getDevice', () => {
|
||||
it('should not fetch when cached', async () => {
|
||||
const cache = createDeviceRegistryCache();
|
||||
const testDevice = createRegistryDevice({ id: 'test' });
|
||||
|
||||
cache.add(testDevice);
|
||||
|
||||
const manager = new DeviceRegistryManager(cache);
|
||||
expect(await manager.getDevice(createHASS(), 'test')).toEqual(testDevice);
|
||||
|
||||
expect(homeAssistantWSRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fetch and cache when not cached', async () => {
|
||||
const testDevice = createRegistryDevice({ id: 'test' });
|
||||
|
||||
const manager = new DeviceRegistryManager(createDeviceRegistryCache());
|
||||
vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce([testDevice]);
|
||||
|
||||
expect(await manager.getDevice(createHASS(), 'test')).toEqual(testDevice);
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
|
||||
expect(await manager.getDevice(createHASS(), 'test')).toEqual(testDevice);
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
|
||||
expect(await manager.getDevice(createHASS(), 'missing')).toBeNull();
|
||||
|
||||
// The fetch call is called exactly once.
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return null when fetch fails', async () => {
|
||||
vi.mocked(homeAssistantWSRequest).mockRejectedValueOnce(new Error('Fetch error'));
|
||||
|
||||
const manager = new DeviceRegistryManager(createDeviceRegistryCache());
|
||||
expect(await manager.getDevice(createHASS(), 'test')).toBeNull();
|
||||
|
||||
vi.mocked(expect(console.warn)).toBeCalledWith('Fetch error');
|
||||
});
|
||||
});
|
||||
|
||||
it('getMatchingDevices', async () => {
|
||||
const matchingDevice = createRegistryDevice({ id: 'matching' });
|
||||
const notMatchingDevice = createRegistryDevice({ id: 'not-matching' });
|
||||
const hass = createHASS();
|
||||
|
||||
vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce([
|
||||
matchingDevice,
|
||||
notMatchingDevice,
|
||||
]);
|
||||
|
||||
const manager = new DeviceRegistryManager(createDeviceRegistryCache());
|
||||
expect(
|
||||
await manager.getMatchingDevices(hass, (entity) => entity.id == 'matching'),
|
||||
).toEqual([matchingDevice]);
|
||||
});
|
||||
});
|
||||
@@ -1,132 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
createEntityRegistryCache,
|
||||
EntityRegistryManagerLive,
|
||||
} from '../../../../../src/utils/ha/registry/entity';
|
||||
import { homeAssistantWSRequest } from '../../../../../src/utils/ha/ws-request';
|
||||
import { createHASS, createRegistryEntity } from '../../../../test-utils.js';
|
||||
|
||||
vi.mock('../../../../../src/utils/ha/ws-request');
|
||||
vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||
|
||||
describe('EntityRegistryManager', () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getEntity', () => {
|
||||
it('should not fetch when cached', async () => {
|
||||
const cache = createEntityRegistryCache();
|
||||
const testEntity = createRegistryEntity({ entity_id: 'test' });
|
||||
|
||||
cache.add(testEntity);
|
||||
|
||||
const manager = new EntityRegistryManagerLive(cache);
|
||||
expect(await manager.getEntity(createHASS(), 'test')).toEqual(testEntity);
|
||||
|
||||
expect(homeAssistantWSRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fetch and cache when not cached', async () => {
|
||||
const testEntity = createRegistryEntity({ entity_id: 'test' });
|
||||
|
||||
const manager = new EntityRegistryManagerLive(createEntityRegistryCache());
|
||||
vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce(testEntity);
|
||||
|
||||
expect(await manager.getEntity(createHASS(), 'test')).toEqual(testEntity);
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
|
||||
expect(await manager.getEntity(createHASS(), 'test')).toEqual(testEntity);
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should return null when entity does not exist', async () => {
|
||||
vi.mocked(homeAssistantWSRequest).mockRejectedValueOnce(new Error('Not found'));
|
||||
|
||||
const manager = new EntityRegistryManagerLive(createEntityRegistryCache());
|
||||
expect(await manager.getEntity(createHASS(), 'missing')).toBeNull();
|
||||
|
||||
vi.mocked(expect(console.warn)).toBeCalledWith('Not found');
|
||||
});
|
||||
});
|
||||
|
||||
it('getEntities', async () => {
|
||||
const cachedEntity = createRegistryEntity({ entity_id: 'cached' });
|
||||
const notCachedEntity = createRegistryEntity({ entity_id: 'not-cached' });
|
||||
|
||||
const cache = createEntityRegistryCache();
|
||||
cache.add(cachedEntity);
|
||||
|
||||
const manager = new EntityRegistryManagerLive(cache);
|
||||
vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce(notCachedEntity);
|
||||
vi.mocked(homeAssistantWSRequest).mockRejectedValueOnce(new Error('Not found'));
|
||||
|
||||
expect(
|
||||
await manager.getEntities(createHASS(), ['cached', 'not-cached', 'missing']),
|
||||
).toEqual(
|
||||
new Map([
|
||||
['cached', cachedEntity],
|
||||
['not-cached', notCachedEntity],
|
||||
]),
|
||||
);
|
||||
|
||||
vi.mocked(expect(console.warn)).toBeCalledWith('Not found');
|
||||
});
|
||||
|
||||
describe('fetchEntityList', async () => {
|
||||
it('should fetch entire entity list once', async () => {
|
||||
const hass = createHASS();
|
||||
const entity = createRegistryEntity({ entity_id: 'cached' });
|
||||
vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce([entity]);
|
||||
|
||||
const manager = new EntityRegistryManagerLive(createEntityRegistryCache());
|
||||
|
||||
await manager.fetchEntityList(hass);
|
||||
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
expect(homeAssistantWSRequest).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
{
|
||||
type: 'config/entity_registry/list',
|
||||
},
|
||||
);
|
||||
|
||||
expect(await manager.getEntity(hass, 'cached')).toEqual(entity);
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
|
||||
await manager.fetchEntityList(hass);
|
||||
expect(homeAssistantWSRequest).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should log to console on error', async () => {
|
||||
const hass = createHASS();
|
||||
vi.mocked(homeAssistantWSRequest).mockRejectedValueOnce(new Error('Fetch error'));
|
||||
|
||||
const manager = new EntityRegistryManagerLive(createEntityRegistryCache());
|
||||
|
||||
await manager.fetchEntityList(hass);
|
||||
|
||||
vi.mocked(expect(console.warn)).toBeCalledWith('Fetch error');
|
||||
});
|
||||
});
|
||||
|
||||
it('getMatchingEntities', async () => {
|
||||
const matchingEntity = createRegistryEntity({ entity_id: 'matching' });
|
||||
const notMatchingEntity = createRegistryEntity({ entity_id: 'not-matching' });
|
||||
const hass = createHASS();
|
||||
|
||||
vi.mocked(homeAssistantWSRequest).mockResolvedValueOnce([
|
||||
matchingEntity,
|
||||
notMatchingEntity,
|
||||
]);
|
||||
|
||||
const manager = new EntityRegistryManagerLive(createEntityRegistryCache());
|
||||
expect(
|
||||
await manager.getMatchingEntities(
|
||||
hass,
|
||||
(entity) => entity.entity_id == 'matching',
|
||||
),
|
||||
).toEqual([matchingEntity]);
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
import { HomeAssistant } from '../../../../../src/ha/types';
|
||||
import { RegistryCache } from '../../../../../src/utils/ha/registry/cache';
|
||||
import {
|
||||
Entity,
|
||||
EntityRegistryManager,
|
||||
} from '../../../../../src/utils/ha/registry/entity/types';
|
||||
|
||||
export class EntityRegistryManagerMock implements EntityRegistryManager {
|
||||
protected _cache: RegistryCache<Entity>;
|
||||
protected _fetchedEntityList = false;
|
||||
|
||||
constructor(data?: Entity[]) {
|
||||
this._cache = new RegistryCache<Entity>((ent) => ent.entity_id);
|
||||
this._cache.add(data ?? []);
|
||||
}
|
||||
|
||||
public async getEntity(
|
||||
_hass: HomeAssistant,
|
||||
entityID: string,
|
||||
): Promise<Entity | null> {
|
||||
return this._cache.get(entityID);
|
||||
}
|
||||
|
||||
public async getMatchingEntities(
|
||||
_hass: HomeAssistant,
|
||||
func: (arg: Entity) => boolean,
|
||||
): Promise<Entity[]> {
|
||||
return this._cache.getMatches(func);
|
||||
}
|
||||
|
||||
public async getEntities(
|
||||
hass: HomeAssistant,
|
||||
entityIDs: string[],
|
||||
): Promise<Map<string, Entity>> {
|
||||
const output: Map<string, Entity> = new Map();
|
||||
for (const entityID of entityIDs) {
|
||||
const entityData = await this.getEntity(hass, entityID);
|
||||
if (entityData) {
|
||||
output.set(entityID, entityData);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public async fetchEntityList(_hass: HomeAssistant): Promise<void> {}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { CameraProxyConfig } from '../../../src/camera-manager/types.js';
|
||||
import {
|
||||
addDynamicProxyURL,
|
||||
getWebProxiedURL,
|
||||
shouldUseWebProxy,
|
||||
} from '../../../src/utils/ha/web-proxy.js';
|
||||
import { createHASS } from '../../test-utils.js';
|
||||
|
||||
describe('getWebProxiedURL', () => {
|
||||
it('should return proxied URL with v != 0', () => {
|
||||
expect(getWebProxiedURL('http://example.com', 2)).toBe(
|
||||
'/api/hass_web_proxy/v2/?url=http%3A%2F%2Fexample.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return proxied URL with default v', () => {
|
||||
expect(getWebProxiedURL('http://example.com')).toBe(
|
||||
'/api/hass_web_proxy/v0/?url=http%3A%2F%2Fexample.com',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldUseWebProxy', () => {
|
||||
const createProxyConfig = (
|
||||
config: Partial<CameraProxyConfig> = {},
|
||||
): CameraProxyConfig => ({
|
||||
media: true,
|
||||
ssl_verification: true,
|
||||
ssl_ciphers: 'default',
|
||||
dynamic: true,
|
||||
...config,
|
||||
});
|
||||
|
||||
it('should return false without a the proxy installed', () => {
|
||||
const hass = createHASS();
|
||||
hass.config.components = [];
|
||||
|
||||
expect(shouldUseWebProxy(hass, createProxyConfig())).toBe(false);
|
||||
});
|
||||
|
||||
it('should return when proxy config does not want proxying', () => {
|
||||
const hass = createHASS();
|
||||
hass.config.components = ['hass_web_proxy'];
|
||||
|
||||
const proxyConfig = createProxyConfig({ media: false });
|
||||
expect(shouldUseWebProxy(hass, proxyConfig, 'media')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return when proxy config does want proxying', () => {
|
||||
const hass = createHASS();
|
||||
hass.config.components = ['hass_web_proxy'];
|
||||
|
||||
const proxyConfig = createProxyConfig({ media: true });
|
||||
expect(shouldUseWebProxy(hass, proxyConfig, 'media')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDynamicProxyURL', () => {
|
||||
it('should add dynamic proxy URL', async () => {
|
||||
const hass = createHASS();
|
||||
|
||||
await addDynamicProxyURL(hass, 'http://example.com', {
|
||||
urlID: 'id',
|
||||
sslVerification: true,
|
||||
sslCiphers: 'modern',
|
||||
openLimit: 5,
|
||||
ttl: 60,
|
||||
allowUnauthenticated: false,
|
||||
});
|
||||
|
||||
expect(hass.callService).toHaveBeenCalledWith(
|
||||
'hass_web_proxy',
|
||||
'create_proxied_url',
|
||||
{
|
||||
url_pattern: 'http://example.com',
|
||||
url_id: 'id',
|
||||
ssl_verification: true,
|
||||
ssl_ciphers: 'modern',
|
||||
open_limit: 5,
|
||||
ttl: 60,
|
||||
allow_unauthenticated: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
hasCameraTruePTZ,
|
||||
ptzActionToCapabilityKey,
|
||||
} from '../../src/utils/ptz';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { QueryResults } from '../../src/view/query-results';
|
||||
import {
|
||||
TestViewMedia,
|
||||
createCameraManager,
|
||||
@@ -21,7 +21,7 @@ describe('getPTZTarget', () => {
|
||||
const media = [new TestViewMedia({ id: 'media-id' })];
|
||||
const view = createView({
|
||||
view: 'media',
|
||||
queryResults: new MediaQueriesResults({ results: media, selectedIndex: 0 }),
|
||||
queryResults: new QueryResults({ results: media, selectedIndex: 0 }),
|
||||
});
|
||||
expect(getPTZTarget(view, { cameraManager: createCameraManager() })).toEqual({
|
||||
targetID: 'media-id',
|
||||
@@ -40,7 +40,7 @@ describe('getPTZTarget', () => {
|
||||
const media = [new TestViewMedia({ id: 'media-id' })];
|
||||
const view = createView({
|
||||
view: 'media',
|
||||
queryResults: new MediaQueriesResults({ results: media, selectedIndex: 0 }),
|
||||
queryResults: new QueryResults({ results: media, selectedIndex: 0 }),
|
||||
});
|
||||
expect(
|
||||
getPTZTarget(view, { type: 'ptz', cameraManager: createCameraManager() }),
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
screenshotImage,
|
||||
screenshotVideo,
|
||||
} from '../../src/utils/screenshot';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { QueryResults } from '../../src/view/query-results';
|
||||
import { View } from '../../src/view/view';
|
||||
import { TestViewMedia, createView } from '../test-utils';
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('generateScreenshotTitle', () => {
|
||||
const view = createView({
|
||||
view: 'media',
|
||||
camera: 'camera-1',
|
||||
queryResults: new MediaQueriesResults({ results: [media], selectedIndex: 0 }),
|
||||
queryResults: new QueryResults({ results: [media], selectedIndex: 0 }),
|
||||
});
|
||||
|
||||
expect(generateScreenshotFilename(view)).toBe('media_camera-1_id1.jpg');
|
||||
@@ -118,7 +118,7 @@ describe('generateScreenshotTitle', () => {
|
||||
const view = createView({
|
||||
view: 'media',
|
||||
camera: 'camera-1',
|
||||
queryResults: new MediaQueriesResults({ results: [media], selectedIndex: 0 }),
|
||||
queryResults: new QueryResults({ results: [media], selectedIndex: 0 }),
|
||||
});
|
||||
|
||||
expect(generateScreenshotFilename(view)).toBe('media_camera-1.jpg');
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { sleep } from '../../src/utils/sleep';
|
||||
|
||||
describe('sleep', () => {
|
||||
it('should sleep', async () => {
|
||||
const spy = vi
|
||||
.spyOn(global, 'setTimeout')
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
||||
.mockImplementation((func: () => unknown, _time?: number): any => {
|
||||
func();
|
||||
});
|
||||
sleep(10);
|
||||
expect(spy).toHaveBeenCalledWith(expect.anything(), 10000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user