@@ -0,0 +1,106 @@
|
|||||||
|
import { userEvent } from 'vitest/browser';
|
||||||
|
|
||||||
|
// `querySelectorAll` does not look inside a shadow root, so a full search has
|
||||||
|
// to step through them a level at a time. The node's own root counts because a
|
||||||
|
// Lit element renders into that, not into its children.
|
||||||
|
const getImmediateShadowRoots = (root: ParentNode): ShadowRoot[] => {
|
||||||
|
const roots = root instanceof Element && root.shadowRoot ? [root.shadowRoot] : [];
|
||||||
|
for (const child of root.querySelectorAll('*')) {
|
||||||
|
if (child.shadowRoot) {
|
||||||
|
roots.push(child.shadowRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roots;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get every shadow root at or below an element.
|
||||||
|
*/
|
||||||
|
export const getAllShadowRoots = (root: ParentNode): ShadowRoot[] =>
|
||||||
|
getImmediateShadowRoots(root).flatMap((child) => [child, ...getAllShadowRoots(child)]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search an element and every shadow root beneath it. The card nests its own
|
||||||
|
* components several roots deep, and neither the source tree nor the node
|
||||||
|
* suite has a helper for this.
|
||||||
|
*/
|
||||||
|
export const deepQuery = <T extends Element = Element>(
|
||||||
|
root: ParentNode,
|
||||||
|
selector: string,
|
||||||
|
): T | null => {
|
||||||
|
const direct = root.querySelector<T>(selector);
|
||||||
|
if (direct) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
for (const child of getImmediateShadowRoots(root)) {
|
||||||
|
const found = deepQuery<T>(child, selector);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every match for a selector across an element and the shadow roots beneath it,
|
||||||
|
* for asking how many of something the card rendered rather than whether it
|
||||||
|
* rendered any.
|
||||||
|
*/
|
||||||
|
export const deepQueryAll = <T extends Element = Element>(
|
||||||
|
root: ParentNode,
|
||||||
|
selector: string,
|
||||||
|
): T[] => [
|
||||||
|
...root.querySelectorAll<T>(selector),
|
||||||
|
...getImmediateShadowRoots(root).flatMap((child) => deepQueryAll<T>(child, selector)),
|
||||||
|
];
|
||||||
|
|
||||||
|
// `userEvent.keyboard` is given one string naming every key to press, in which
|
||||||
|
// a name of more than one character is wrapped in braces (`{Escape}`) and a
|
||||||
|
// single character stands for itself.
|
||||||
|
// See: https://vitest.dev/guide/browser/interactivity-api.html#userevent-keyboard
|
||||||
|
const asKeyboardInput = (key: string): string => (key.length === 1 ? key : `{${key}}`);
|
||||||
|
|
||||||
|
export const pressKey = async (key: string): Promise<void> =>
|
||||||
|
await userEvent.keyboard(asKeyboardInput(key));
|
||||||
|
|
||||||
|
export const holdKey = async (key: string): Promise<void> =>
|
||||||
|
await userEvent.keyboard(`{${key}>}`);
|
||||||
|
|
||||||
|
export const releaseKey = async (key: string): Promise<void> =>
|
||||||
|
await userEvent.keyboard(`{/${key}}`);
|
||||||
|
|
||||||
|
export const pressTab = async (): Promise<void> => await userEvent.tab();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click an element with a real pointer, which is the only kind that carries the
|
||||||
|
* browser's own behaviour: the press moves focus, and an element that stops the
|
||||||
|
* press doing so leaves it where it was.
|
||||||
|
*/
|
||||||
|
export const clickElement = async (element: Element): Promise<void> =>
|
||||||
|
await userEvent.click(element);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a `pointerdown` to an element without moving a real pointer, so the page
|
||||||
|
* stays scrolled where the test left it: `clickElement` scrolls its target into
|
||||||
|
* view before pressing it.
|
||||||
|
*
|
||||||
|
* The browser does nothing of its own with a press it did not itself deliver,
|
||||||
|
* so what follows is only what the card's own listener does.
|
||||||
|
*/
|
||||||
|
export const dispatchPointerDown = (element: Element): void => {
|
||||||
|
element.dispatchEvent(
|
||||||
|
new PointerEvent('pointerdown', { bubbles: true, composed: true }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element that actually has focus. `document.activeElement` names the
|
||||||
|
* outermost shadow host in the way, since focus is reported per tree.
|
||||||
|
*/
|
||||||
|
export const getFocusedElement = (): Element | null => {
|
||||||
|
let focused = document.activeElement;
|
||||||
|
while (focused?.shadowRoot?.activeElement) {
|
||||||
|
focused = focused.shadowRoot.activeElement;
|
||||||
|
}
|
||||||
|
return focused;
|
||||||
|
};
|
||||||
@@ -3,9 +3,9 @@ import { expect, onTestFinished, vi } from 'vitest';
|
|||||||
import type { AdvancedCameraCard } from '../../src/card';
|
import type { AdvancedCameraCard } from '../../src/card';
|
||||||
import type { RawAdvancedCameraCardConfig } from '../../src/config/types';
|
import type { RawAdvancedCameraCardConfig } from '../../src/config/types';
|
||||||
import { ACTION_HANDLER_HOLD_SECONDS } from '../../src/const';
|
import { ACTION_HANDLER_HOLD_SECONDS } from '../../src/const';
|
||||||
|
import { clickElement, deepQuery, deepQueryAll, getAllShadowRoots } from './dom';
|
||||||
import type { FakeEntityOptions, FakeHASS } from './fake-hass';
|
import type { FakeEntityOptions, FakeHASS } from './fake-hass';
|
||||||
import { defineHAElementStubs } from './ha-element-stubs';
|
import { defineHAElementStubs } from './ha-element-stubs';
|
||||||
import { clickElement, deepQuery, deepQueryAll, getAllShadowRoots } from './test-utils';
|
|
||||||
|
|
||||||
// Home Assistant's masonry columns are `max-width: 500px`, so this is the width
|
// Home Assistant's masonry columns are `max-width: 500px`, so this is the width
|
||||||
// a card usually gets. The card derives height from the media it is showing.
|
// a card usually gets. The card derives height from the media it is showing.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { beforeAll } from 'vitest';
|
|||||||
|
|
||||||
import { createFixtureURL, SNAPSHOT_FIXTURE_FILENAME } from './fixtures';
|
import { createFixtureURL, SNAPSHOT_FIXTURE_FILENAME } from './fixtures';
|
||||||
|
|
||||||
|
const HTTP_NOT_FOUND = 404;
|
||||||
const HTTP_OK = 200;
|
const HTTP_OK = 200;
|
||||||
|
|
||||||
// Where this worker answers.
|
// Where this worker answers.
|
||||||
@@ -20,7 +21,7 @@ let inUse = false;
|
|||||||
/**
|
/**
|
||||||
* See the worker below for what `responses` and `repeat` ask it to do.
|
* See the worker below for what `responses` and `repeat` ask it to do.
|
||||||
*/
|
*/
|
||||||
export const createTestMediaURL = (
|
const createTestMediaURL = (
|
||||||
responses: number[],
|
responses: number[],
|
||||||
repeat = false,
|
repeat = false,
|
||||||
filename: string = SNAPSHOT_FIXTURE_FILENAME,
|
filename: string = SNAPSHOT_FIXTURE_FILENAME,
|
||||||
@@ -44,6 +45,36 @@ export const createTestMediaURL = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A media URL that fails the given number of times and then works from there
|
||||||
|
* on, so a test can make a camera recover rather than only fail.
|
||||||
|
*/
|
||||||
|
export const createTemporarilyFailingMediaURL = (
|
||||||
|
failures: number,
|
||||||
|
filename?: string,
|
||||||
|
): string =>
|
||||||
|
createTestMediaURL([...Array(failures).fill(HTTP_NOT_FOUND), HTTP_OK], true, filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A media URL that never works, for a camera that is simply broken.
|
||||||
|
*/
|
||||||
|
export const createFailingMediaURL = (): string =>
|
||||||
|
createTestMediaURL([HTTP_NOT_FOUND], true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A media URL that is never answered, for a camera that accepts the request and
|
||||||
|
* then says nothing. Silence is a different failure from a refusal, and the
|
||||||
|
* only one that can run a loading timeout out.
|
||||||
|
*/
|
||||||
|
export const createUnansweredMediaURL = (): string => createTestMediaURL([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A media URL that answers once and is then never answered again, for a camera
|
||||||
|
* that delivers a picture and goes quiet behind it.
|
||||||
|
*/
|
||||||
|
export const createStallingMediaURL = (filename?: string): string =>
|
||||||
|
createTestMediaURL([HTTP_OK], false, filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serves a fixture at `/test-media/<file>`, behaving as the query asks:
|
* Serves a fixture at `/test-media/<file>`, behaving as the query asks:
|
||||||
*
|
*
|
||||||
@@ -71,7 +102,7 @@ const worker = setupWorker(
|
|||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const token = url.searchParams.get('token');
|
const token = url.searchParams.get('token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return new HttpResponse(null, { status: 404 });
|
return new HttpResponse(null, { status: HTTP_NOT_FOUND });
|
||||||
}
|
}
|
||||||
|
|
||||||
const responses = (url.searchParams.get('responses') ?? '')
|
const responses = (url.searchParams.get('responses') ?? '')
|
||||||
|
|||||||
+1
-141
@@ -1,5 +1,3 @@
|
|||||||
import { userEvent } from 'vitest/browser';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
PartialAdvancedCameraCardConfig,
|
PartialAdvancedCameraCardConfig,
|
||||||
RawAdvancedCameraCardConfig,
|
RawAdvancedCameraCardConfig,
|
||||||
@@ -8,10 +6,10 @@ import type { Entity } from '../../src/ha/registry/entity/types';
|
|||||||
import type { MediaLoadedInfoEventDetail } from '../../src/types';
|
import type { MediaLoadedInfoEventDetail } from '../../src/types';
|
||||||
import { createLogAction } from '../../src/utils/action';
|
import { createLogAction } from '../../src/utils/action';
|
||||||
import { isTruthy } from '../../src/utils/basic';
|
import { isTruthy } from '../../src/utils/basic';
|
||||||
|
import { clickElement, deepQuery, deepQueryAll } from './dom';
|
||||||
import { FakeHASS, type FakeEntityOptions } from './fake-hass';
|
import { FakeHASS, type FakeEntityOptions } from './fake-hass';
|
||||||
import { createFixtureURL, SNAPSHOT_FIXTURE_FILENAME } from './fixtures';
|
import { createFixtureURL, SNAPSHOT_FIXTURE_FILENAME } from './fixtures';
|
||||||
import type { MountedCard } from './mounted-card';
|
import type { MountedCard } from './mounted-card';
|
||||||
import { createTestMediaURL } from './test-media';
|
|
||||||
|
|
||||||
export const CAMERA_ENTITY = 'camera.office';
|
export const CAMERA_ENTITY = 'camera.office';
|
||||||
|
|
||||||
@@ -41,39 +39,6 @@ export const createStillImageCameraConfig = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const HTTP_NOT_FOUND = 404;
|
|
||||||
const HTTP_OK = 200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A media URL that fails the given number of times and then works from there
|
|
||||||
* on, so a test can make a camera recover rather than only fail.
|
|
||||||
*/
|
|
||||||
export const createTemporarilyFailingMediaURL = (
|
|
||||||
failures: number,
|
|
||||||
filename?: string,
|
|
||||||
): string =>
|
|
||||||
createTestMediaURL([...Array(failures).fill(HTTP_NOT_FOUND), HTTP_OK], true, filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A media URL that never works, for a camera that is simply broken.
|
|
||||||
*/
|
|
||||||
export const createFailingMediaURL = (): string =>
|
|
||||||
createTestMediaURL([HTTP_NOT_FOUND], true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A media URL that is never answered, for a camera that accepts the request and
|
|
||||||
* then says nothing. Silence is a different failure from a refusal, and the
|
|
||||||
* only one that can run a loading timeout out.
|
|
||||||
*/
|
|
||||||
export const createUnansweredMediaURL = (): string => createTestMediaURL([]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A media URL that answers once and is then never answered again, for a camera
|
|
||||||
* that delivers a picture and goes quiet behind it.
|
|
||||||
*/
|
|
||||||
export const createStallingMediaURL = (filename?: string): string =>
|
|
||||||
createTestMediaURL([HTTP_OK], false, filename);
|
|
||||||
|
|
||||||
export interface FakeCameraDescription {
|
export interface FakeCameraDescription {
|
||||||
entityID: string;
|
entityID: string;
|
||||||
entity: FakeEntityOptions;
|
entity: FakeEntityOptions;
|
||||||
@@ -167,60 +132,6 @@ export const createInitializedAutomation = (): RawAdvancedCameraCardConfig => ({
|
|||||||
actions: [createLogAction(CARD_INITIALIZED_MESSAGE.source)],
|
actions: [createLogAction(CARD_INITIALIZED_MESSAGE.source)],
|
||||||
});
|
});
|
||||||
|
|
||||||
// `querySelectorAll` does not look inside a shadow root, so a full search has
|
|
||||||
// to step through them a level at a time. The node's own root counts because a
|
|
||||||
// Lit element renders into that, not into its children.
|
|
||||||
const getImmediateShadowRoots = (root: ParentNode): ShadowRoot[] => {
|
|
||||||
const roots = root instanceof Element && root.shadowRoot ? [root.shadowRoot] : [];
|
|
||||||
for (const child of root.querySelectorAll('*')) {
|
|
||||||
if (child.shadowRoot) {
|
|
||||||
roots.push(child.shadowRoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return roots;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get every shadow root at or below an element.
|
|
||||||
*/
|
|
||||||
export const getAllShadowRoots = (root: ParentNode): ShadowRoot[] =>
|
|
||||||
getImmediateShadowRoots(root).flatMap((child) => [child, ...getAllShadowRoots(child)]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search an element and every shadow root beneath it. The card nests its own
|
|
||||||
* components several roots deep, and neither the source tree nor the node
|
|
||||||
* suite has a helper for this.
|
|
||||||
*/
|
|
||||||
export const deepQuery = <T extends Element = Element>(
|
|
||||||
root: ParentNode,
|
|
||||||
selector: string,
|
|
||||||
): T | null => {
|
|
||||||
const direct = root.querySelector<T>(selector);
|
|
||||||
if (direct) {
|
|
||||||
return direct;
|
|
||||||
}
|
|
||||||
for (const child of getImmediateShadowRoots(root)) {
|
|
||||||
const found = deepQuery<T>(child, selector);
|
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Every match for a selector across an element and the shadow roots beneath it,
|
|
||||||
* for asking how many of something the card rendered rather than whether it
|
|
||||||
* rendered any.
|
|
||||||
*/
|
|
||||||
export const deepQueryAll = <T extends Element = Element>(
|
|
||||||
root: ParentNode,
|
|
||||||
selector: string,
|
|
||||||
): T[] => [
|
|
||||||
...root.querySelectorAll<T>(selector),
|
|
||||||
...getImmediateShadowRoots(root).flatMap((child) => deepQueryAll<T>(child, selector)),
|
|
||||||
];
|
|
||||||
|
|
||||||
export const isMediaLoadedInfoEventDetail = (
|
export const isMediaLoadedInfoEventDetail = (
|
||||||
detail: unknown,
|
detail: unknown,
|
||||||
): detail is MediaLoadedInfoEventDetail =>
|
): detail is MediaLoadedInfoEventDetail =>
|
||||||
@@ -388,54 +299,3 @@ export const getStatusBarStrings = (root: ParentNode): string[] =>
|
|||||||
*/
|
*/
|
||||||
export const getStatusBarItem = (root: ParentNode, title: string): Element | null =>
|
export const getStatusBarItem = (root: ParentNode, title: string): Element | null =>
|
||||||
getStatusBarItems(root).find((item) => item.getAttribute('title') === title) ?? null;
|
getStatusBarItems(root).find((item) => item.getAttribute('title') === title) ?? null;
|
||||||
|
|
||||||
// `userEvent.keyboard` is given one string naming every key to press, in which
|
|
||||||
// a name of more than one character is wrapped in braces (`{Escape}`) and a
|
|
||||||
// single character stands for itself.
|
|
||||||
// See: https://vitest.dev/guide/browser/interactivity-api.html#userevent-keyboard
|
|
||||||
const asKeyboardInput = (key: string): string => (key.length === 1 ? key : `{${key}}`);
|
|
||||||
|
|
||||||
export const pressKey = async (key: string): Promise<void> =>
|
|
||||||
await userEvent.keyboard(asKeyboardInput(key));
|
|
||||||
|
|
||||||
export const holdKey = async (key: string): Promise<void> =>
|
|
||||||
await userEvent.keyboard(`{${key}>}`);
|
|
||||||
|
|
||||||
export const releaseKey = async (key: string): Promise<void> =>
|
|
||||||
await userEvent.keyboard(`{/${key}}`);
|
|
||||||
|
|
||||||
export const pressTab = async (): Promise<void> => await userEvent.tab();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Click an element with a real pointer, which is the only kind that carries the
|
|
||||||
* browser's own behaviour: the press moves focus, and an element that stops the
|
|
||||||
* press doing so leaves it where it was.
|
|
||||||
*/
|
|
||||||
export const clickElement = async (element: Element): Promise<void> =>
|
|
||||||
await userEvent.click(element);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a `pointerdown` to an element without moving a real pointer, so the page
|
|
||||||
* stays scrolled where the test left it: `clickElement` scrolls its target into
|
|
||||||
* view before pressing it.
|
|
||||||
*
|
|
||||||
* The browser does nothing of its own with a press it did not itself deliver,
|
|
||||||
* so what follows is only what the card's own listener does.
|
|
||||||
*/
|
|
||||||
export const dispatchPointerDown = (element: Element): void => {
|
|
||||||
element.dispatchEvent(
|
|
||||||
new PointerEvent('pointerdown', { bubbles: true, composed: true }),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The element that actually has focus. `document.activeElement` names the
|
|
||||||
* outermost shadow host in the way, since focus is reported per tree.
|
|
||||||
*/
|
|
||||||
export const getFocusedElement = (): Element | null => {
|
|
||||||
let focused = document.activeElement;
|
|
||||||
while (focused?.shadowRoot?.activeElement) {
|
|
||||||
focused = focused.shadowRoot.activeElement;
|
|
||||||
}
|
|
||||||
return focused;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { describe, expect, it, onTestFinished } from 'vitest';
|
import { describe, expect, it, onTestFinished } from 'vitest';
|
||||||
|
|
||||||
import { createLogAction } from '../../src/utils/action';
|
import { createLogAction } from '../../src/utils/action';
|
||||||
|
import { deepQueryAll, getFocusedElement, pressKey, pressTab } from '../browser/dom';
|
||||||
import { MountedCardFactory, type MountedCard } from '../browser/mounted-card';
|
import { MountedCardFactory, type MountedCard } from '../browser/mounted-card';
|
||||||
import {
|
import {
|
||||||
CARD_INITIALIZED_MESSAGE,
|
CARD_INITIALIZED_MESSAGE,
|
||||||
createGenericCameraHASS,
|
createGenericCameraHASS,
|
||||||
createInitializedAutomation,
|
createInitializedAutomation,
|
||||||
createStillImageCardConfig,
|
createStillImageCardConfig,
|
||||||
deepQueryAll,
|
|
||||||
getFocusedElement,
|
|
||||||
pressKey,
|
|
||||||
pressTab,
|
|
||||||
} from '../browser/test-utils';
|
} from '../browser/test-utils';
|
||||||
|
|
||||||
// What the automation writes when it runs, written as the pattern the console
|
// What the automation writes when it runs, written as the pattern the console
|
||||||
|
|||||||
@@ -5,23 +5,24 @@ import { LIVENESS_ENTITY_UNAVAILABLE_GRACE_SECONDS } from '../../../../src/compo
|
|||||||
import { MEDIA_LOADING_TIMEOUT_SECONDS } from '../../../../src/components-lib/media-load-watchdog-controller';
|
import { MEDIA_LOADING_TIMEOUT_SECONDS } from '../../../../src/components-lib/media-load-watchdog-controller';
|
||||||
import { FRAME_STALL_SECONDS } from '../../../../src/components-lib/media-player/frame-stall-watchdog';
|
import { FRAME_STALL_SECONDS } from '../../../../src/components-lib/media-player/frame-stall-watchdog';
|
||||||
import type { RawAdvancedCameraCardConfig } from '../../../../src/config/types';
|
import type { RawAdvancedCameraCardConfig } from '../../../../src/config/types';
|
||||||
|
import { deepQuery, deepQueryAll } from '../../../browser/dom';
|
||||||
import {
|
import {
|
||||||
MountedCardFactory,
|
MountedCardFactory,
|
||||||
type MountedCard,
|
type MountedCard,
|
||||||
type MountOptions,
|
type MountOptions,
|
||||||
} from '../../../browser/mounted-card';
|
} from '../../../browser/mounted-card';
|
||||||
import { useTestMedia } from '../../../browser/test-media';
|
|
||||||
import {
|
import {
|
||||||
CAMERA_ENTITY,
|
|
||||||
createFailingMediaURL,
|
createFailingMediaURL,
|
||||||
createGenericCameraHASS,
|
|
||||||
createStallingMediaURL,
|
createStallingMediaURL,
|
||||||
createStillImageCameraConfig,
|
|
||||||
createStillImageCardConfig,
|
|
||||||
createTemporarilyFailingMediaURL,
|
createTemporarilyFailingMediaURL,
|
||||||
createUnansweredMediaURL,
|
createUnansweredMediaURL,
|
||||||
deepQuery,
|
useTestMedia,
|
||||||
deepQueryAll,
|
} from '../../../browser/test-media';
|
||||||
|
import {
|
||||||
|
CAMERA_ENTITY,
|
||||||
|
createGenericCameraHASS,
|
||||||
|
createStillImageCameraConfig,
|
||||||
|
createStillImageCardConfig,
|
||||||
getBlockNotificationText,
|
getBlockNotificationText,
|
||||||
getStatusBarItem,
|
getStatusBarItem,
|
||||||
isLiveMediaShowing,
|
isLiveMediaShowing,
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ import { assert, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
import type { LogActionConfig } from '../../src/config/schema/actions/custom/log';
|
import type { LogActionConfig } from '../../src/config/schema/actions/custom/log';
|
||||||
import { createLogAction } from '../../src/utils/action';
|
import { createLogAction } from '../../src/utils/action';
|
||||||
|
import {
|
||||||
|
clickElement,
|
||||||
|
dispatchPointerDown,
|
||||||
|
getFocusedElement,
|
||||||
|
holdKey,
|
||||||
|
pressKey,
|
||||||
|
releaseKey,
|
||||||
|
} from '../browser/dom';
|
||||||
import {
|
import {
|
||||||
MountedCardFactory,
|
MountedCardFactory,
|
||||||
type MountedCard,
|
type MountedCard,
|
||||||
@@ -9,15 +17,9 @@ import {
|
|||||||
} from '../browser/mounted-card';
|
} from '../browser/mounted-card';
|
||||||
import {
|
import {
|
||||||
CARD_INITIALIZED_MESSAGE,
|
CARD_INITIALIZED_MESSAGE,
|
||||||
clickElement,
|
|
||||||
createGenericCameraHASS,
|
createGenericCameraHASS,
|
||||||
createInitializedAutomation,
|
createInitializedAutomation,
|
||||||
createStillImageCardConfig,
|
createStillImageCardConfig,
|
||||||
dispatchPointerDown,
|
|
||||||
getFocusedElement,
|
|
||||||
holdKey,
|
|
||||||
pressKey,
|
|
||||||
releaseKey,
|
|
||||||
} from '../browser/test-utils';
|
} from '../browser/test-utils';
|
||||||
|
|
||||||
const ZOOM_ENTITY = 'input_boolean.zoom';
|
const ZOOM_ENTITY = 'input_boolean.zoom';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
import type { FrigateEvent } from '../../../src/camera-manager/frigate/types';
|
import type { FrigateEvent } from '../../../src/camera-manager/frigate/types';
|
||||||
import type { PartialAdvancedCameraCardConfig } from '../../../src/config/types';
|
import type { PartialAdvancedCameraCardConfig } from '../../../src/config/types';
|
||||||
|
import { deepQuery } from '../../browser/dom';
|
||||||
import {
|
import {
|
||||||
createTestFrigateEvent,
|
createTestFrigateEvent,
|
||||||
EVENT_TIME_NEWER,
|
EVENT_TIME_NEWER,
|
||||||
@@ -11,7 +12,6 @@ import {
|
|||||||
import type { MountedCard } from '../../browser/mounted-card';
|
import type { MountedCard } from '../../browser/mounted-card';
|
||||||
import {
|
import {
|
||||||
clickThumbnail,
|
clickThumbnail,
|
||||||
deepQuery,
|
|
||||||
getBlockNotificationText,
|
getBlockNotificationText,
|
||||||
getMediaViewerMediaURLs,
|
getMediaViewerMediaURLs,
|
||||||
getThumbnails,
|
getThumbnails,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { assert, describe, expect, it, onTestFinished, vi } from 'vitest';
|
|||||||
|
|
||||||
import { MEDIA_LOADING_TIMEOUT_SECONDS } from '../../../src/components-lib/media-load-watchdog-controller';
|
import { MEDIA_LOADING_TIMEOUT_SECONDS } from '../../../src/components-lib/media-load-watchdog-controller';
|
||||||
import type { PartialAdvancedCameraCardConfig } from '../../../src/config/types';
|
import type { PartialAdvancedCameraCardConfig } from '../../../src/config/types';
|
||||||
|
import { clickElement, deepQuery } from '../../browser/dom';
|
||||||
import {
|
import {
|
||||||
createTestFrigateEvent,
|
createTestFrigateEvent,
|
||||||
EVENT_TIME_NEWER,
|
EVENT_TIME_NEWER,
|
||||||
@@ -9,13 +10,10 @@ import {
|
|||||||
mountCardWithFrigate,
|
mountCardWithFrigate,
|
||||||
} from '../../browser/fake-frigate';
|
} from '../../browser/fake-frigate';
|
||||||
import type { MountedCard } from '../../browser/mounted-card';
|
import type { MountedCard } from '../../browser/mounted-card';
|
||||||
import { useTestMedia } from '../../browser/test-media';
|
import { createFailingMediaURL, useTestMedia } from '../../browser/test-media';
|
||||||
import {
|
import {
|
||||||
clickElement,
|
|
||||||
clickNextPreviousMedia,
|
clickNextPreviousMedia,
|
||||||
clickThumbnail,
|
clickThumbnail,
|
||||||
createFailingMediaURL,
|
|
||||||
deepQuery,
|
|
||||||
getMediaViewerMediaURLs,
|
getMediaViewerMediaURLs,
|
||||||
getSelectedThumbnail,
|
getSelectedThumbnail,
|
||||||
getStatusBarItem,
|
getStatusBarItem,
|
||||||
|
|||||||
Reference in New Issue
Block a user