test: Add a browser based test harness (#2641)
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { css, html, LitElement, type TemplateResult } from 'lit';
|
||||
|
||||
import { SIDE_LOADED_ELEMENTS } from '../../src/ha/side-load-ha-elements';
|
||||
|
||||
/**
|
||||
* The base the three `src/patches` classes subclass at runtime. Home
|
||||
* Assistant's real players own a `<video>` and a transport; this carries only
|
||||
* the members the patches touch, so the subclasses can be defined and
|
||||
* constructed. It plays nothing.
|
||||
*/
|
||||
class HAPlayerStandIn extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
public entityid?: string;
|
||||
public posterUrl?: string;
|
||||
public autoPlay = false;
|
||||
public muted = true;
|
||||
public playsInline = true;
|
||||
public controls = false;
|
||||
|
||||
protected _error?: string;
|
||||
protected _errorIsFatal = false;
|
||||
|
||||
protected _loadedData(): void {
|
||||
// The patches call through to this on the <video>'s `loadeddata`.
|
||||
}
|
||||
|
||||
protected async _startWebRtc(): Promise<void> {
|
||||
// WebRTC negotiation, which this stand-in does not perform.
|
||||
}
|
||||
|
||||
protected _cleanUp(): void {
|
||||
// Transport teardown, which this stand-in has none of.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A stand-in that renders its children and nothing else. Behaviour is added
|
||||
* only for elements the card is observed to depend on.
|
||||
*/
|
||||
class HAElementStub extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The surface a card is drawn on. Home Assistant's own `:host` styles to ensure
|
||||
* the card has background, border and corners for screenshots.
|
||||
*/
|
||||
class HACardStub extends HAElementStub {
|
||||
static styles = css`
|
||||
:host {
|
||||
background: var(--ha-card-background, var(--card-background-color, white));
|
||||
box-shadow: var(--ha-card-box-shadow, none);
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
border-width: var(--ha-card-border-width, 1px);
|
||||
border-style: solid;
|
||||
border-color: var(--ha-card-border-color, var(--divider-color, #e0e0e0));
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* A round tap target sized by `--ha-icon-button-size`, which is how Home
|
||||
* Assistant's own icon button sizes itself and what the card sets to lay its
|
||||
* menu out. Without it a button is only as big as whatever it contains.
|
||||
*/
|
||||
class HAIconButtonStub extends HAElementStub {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--ha-icon-button-size, 48px);
|
||||
height: var(--ha-icon-button-size, 48px);
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Home Assistant fetches `mdi:` icons from Home Assistant, so there is nothing
|
||||
* to draw here. It still occupies an icon's space, so that the layout around it
|
||||
* is the layout a user would see.
|
||||
*/
|
||||
class HAIconStub extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
width: var(--mdc-icon-size, 24px);
|
||||
height: var(--mdc-icon-size, 24px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Home Assistant's conditional picture element, which the card builds one of on
|
||||
* every mount to host whatever picture elements are configured.
|
||||
*/
|
||||
class HuiConditionalElementStub extends HTMLElement {
|
||||
public hass?: unknown;
|
||||
|
||||
public setConfig(): void {
|
||||
// The card only needs the call to succeed; nothing renders from it here.
|
||||
}
|
||||
}
|
||||
|
||||
// A fresh anonymous subclass every call: `customElements.define` rejects a
|
||||
// constructor that is already registered under another name.
|
||||
const createStub = (element: string): CustomElementConstructor => {
|
||||
switch (element) {
|
||||
case 'ha-camera-stream':
|
||||
case 'ha-hls-player':
|
||||
case 'ha-web-rtc-player':
|
||||
return class extends HAPlayerStandIn {};
|
||||
|
||||
case 'ha-card':
|
||||
return class extends HACardStub {};
|
||||
|
||||
case 'ha-icon-button':
|
||||
case 'ha-icon-button-prev':
|
||||
return class extends HAIconButtonStub {};
|
||||
|
||||
case 'ha-icon':
|
||||
case 'ha-state-icon':
|
||||
case 'state-badge':
|
||||
return class extends HAIconStub {};
|
||||
|
||||
case 'hui-conditional-element':
|
||||
return class extends HuiConditionalElementStub {};
|
||||
|
||||
default:
|
||||
return class extends HAElementStub {};
|
||||
}
|
||||
};
|
||||
|
||||
const defineElement = (name: string, constructor: CustomElementConstructor): void => {
|
||||
if (!customElements.get(name)) {
|
||||
customElements.define(name, constructor);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Define every Home Assistant element the card expects to already exist, so
|
||||
* that sideLoadHomeAssistantElements() returns at its first check and never
|
||||
* reaches its `picture-glance` side-load trick.
|
||||
*/
|
||||
export const defineHAElementStubs = (): void => {
|
||||
for (const element of SIDE_LOADED_ELEMENTS) {
|
||||
defineElement(element, createStub(element));
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user