feat: Implement basic general folder support (#2051)

- Related: #1748
This commit is contained in:
Dermot Duffy
2025-05-21 19:59:21 -07:00
committed by GitHub
parent 2eb0d9e35e
commit c6a4c8aea2
350 changed files with 12837 additions and 4509 deletions
+14 -2
View File
@@ -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',
+4 -14
View File
@@ -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();
+5 -5
View File
@@ -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', () => {
+2 -132
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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';
+2 -2
View File
@@ -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 () => {
-78
View File
@@ -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();
});
});
-19
View File
@@ -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' },
);
});
});
-49
View File
@@ -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]);
});
});
-47
View File
@@ -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> {}
}
-86
View File
@@ -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,
},
);
});
});
+3 -3
View File
@@ -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() }),
+3 -3
View File
@@ -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');
+15
View File
@@ -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);
});
});