fix: Various editor / diagnostic improvements (#2363)
- Closes: #2360 - Closes: #2328
This commit is contained in:
@@ -20,7 +20,11 @@ assignees: ''
|
|||||||
**[REQUIRED] Card diagnostic information:**
|
**[REQUIRED] Card diagnostic information:**
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
On the card, hold down the Iris (default) menu button for a few seconds then paste the diagnostics *between* the pair of triple backticks below. No help can be provided without this.
|
No help can be provided without this information. Fetch the diagnostics and paste them *between* the pair of triple backticks below.
|
||||||
|
|
||||||
|
Use either of two ways to fetch the diagnostics:
|
||||||
|
- Hold down the Iris (default) menu button for a few seconds.
|
||||||
|
- Open the card editor and click the 'Toggle Diagnostics' button.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
+4
-2
@@ -19,8 +19,10 @@ incrementally improve, and I think time is better spent working on reproducible
|
|||||||
issues or new features, than guessing what versions/options you are using. The
|
issues or new features, than guessing what versions/options you are using. The
|
||||||
new issue template asks for the minimum possible information to support you:
|
new issue template asks for the minimum possible information to support you:
|
||||||
|
|
||||||
- **Diagnostics**: On the card, hold down the Iris (default) menu button for a
|
- **Diagnostics**: On the card, fetch the diagnostics and paste into the issue
|
||||||
few seconds then paste the diagnostics into the issue between the backticks.
|
between the backticks. You can fetch the diagnostics in either of two ways:
|
||||||
|
- Hold down the Iris (default) menu button for a few seconds.
|
||||||
|
- Open the card editor and click the 'Toggle Diagnostics' button.
|
||||||
- **Description of the problem**: I need to understand the issue in sufficient
|
- **Description of the problem**: I need to understand the issue in sufficient
|
||||||
detail.
|
detail.
|
||||||
|
|
||||||
|
|||||||
@@ -32,15 +32,7 @@ class ActionHandler extends HTMLElement implements ActionHandlerInterface {
|
|||||||
private started = false;
|
private started = false;
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
[
|
['mouseup', 'mousewheel', 'scroll', 'touchcancel', 'wheel'].forEach((ev) => {
|
||||||
'touchcancel',
|
|
||||||
'mouseout',
|
|
||||||
'mouseup',
|
|
||||||
'touchmove',
|
|
||||||
'mousewheel',
|
|
||||||
'wheel',
|
|
||||||
'scroll',
|
|
||||||
].forEach((ev) => {
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
ev,
|
ev,
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { LitElement, ReactiveControllerHost } from 'lit';
|
import { LitElement, ReactiveControllerHost } from 'lit';
|
||||||
import { ActionEventTarget } from '../action-handler-directive';
|
import { ActionEventTarget } from '../action-handler-directive';
|
||||||
import { isCardInPanel } from '../ha/panel';
|
import { isCardInPanel } from '../ha/panel';
|
||||||
|
import { LovelaceCard } from '../ha/types';
|
||||||
import { setOrRemoveAttribute } from '../utils/basic';
|
import { setOrRemoveAttribute } from '../utils/basic';
|
||||||
import { isBeingCasted } from '../utils/casting';
|
import { isBeingCasted } from '../utils/casting';
|
||||||
|
import { isAncestorInEventPath } from '../utils/event-ancestor';
|
||||||
import { CardMediaReviewEventTarget } from '../utils/review';
|
import { CardMediaReviewEventTarget } from '../utils/review';
|
||||||
import { ViewItem } from '../view/item';
|
import { ViewItem } from '../view/item';
|
||||||
import { ActionExecutionRequestEventTarget } from './actions/utils/execution-request';
|
import { ActionExecutionRequestEventTarget } from './actions/utils/execution-request';
|
||||||
@@ -12,7 +14,8 @@ import { CardElementAPI } from './types';
|
|||||||
export type ScrollCallback = () => void;
|
export type ScrollCallback = () => void;
|
||||||
export type MenuToggleCallback = () => void;
|
export type MenuToggleCallback = () => void;
|
||||||
|
|
||||||
export type CardHTMLElement = LitElement &
|
export type CardHTMLElement = LovelaceCard &
|
||||||
|
LitElement &
|
||||||
ReactiveControllerHost &
|
ReactiveControllerHost &
|
||||||
ActionEventTarget &
|
ActionEventTarget &
|
||||||
ActionExecutionRequestEventTarget &
|
ActionExecutionRequestEventTarget &
|
||||||
@@ -141,6 +144,10 @@ export class CardElementManager {
|
|||||||
'popstate',
|
'popstate',
|
||||||
this._api.getQueryStringManager().requestExecution,
|
this._api.getQueryStringManager().requestExecution,
|
||||||
);
|
);
|
||||||
|
window.addEventListener(
|
||||||
|
'advanced-camera-card:editor:diagnostics',
|
||||||
|
this._editorDiagnosticsHandler,
|
||||||
|
);
|
||||||
|
|
||||||
this._api.getConditionStateManager()?.setState({
|
this._api.getConditionStateManager()?.setState({
|
||||||
userAgent: navigator.userAgent,
|
userAgent: navigator.userAgent,
|
||||||
@@ -218,6 +225,10 @@ export class CardElementManager {
|
|||||||
'popstate',
|
'popstate',
|
||||||
this._api.getQueryStringManager().requestExecution,
|
this._api.getQueryStringManager().requestExecution,
|
||||||
);
|
);
|
||||||
|
window.removeEventListener(
|
||||||
|
'advanced-camera-card:editor:diagnostics',
|
||||||
|
this._editorDiagnosticsHandler,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleMediaReviewed = (ev: CustomEvent<ViewItem>): void => {
|
private _handleMediaReviewed = (ev: CustomEvent<ViewItem>): void => {
|
||||||
@@ -230,4 +241,21 @@ export class CardElementManager {
|
|||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected _editorDiagnosticsHandler = (ev: Event): void => {
|
||||||
|
const toggleDiagnostics = (): void => {
|
||||||
|
const viewManager = this._api.getViewManager();
|
||||||
|
if (viewManager.getView()?.view === 'diagnostics') {
|
||||||
|
viewManager.setViewDefault();
|
||||||
|
} else {
|
||||||
|
viewManager.setViewByParameters({
|
||||||
|
params: { view: 'diagnostics' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAncestorInEventPath(this._element, ev, 'hui-dialog-edit-card')) {
|
||||||
|
toggleDiagnostics();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-20
@@ -271,6 +271,7 @@ import { localize } from './localize/localize.js';
|
|||||||
import editorStyle from './scss/editor.scss';
|
import editorStyle from './scss/editor.scss';
|
||||||
import { arrayMove, prettifyTitle } from './utils/basic.js';
|
import { arrayMove, prettifyTitle } from './utils/basic.js';
|
||||||
import { getCameraID } from './utils/camera.js';
|
import { getCameraID } from './utils/camera.js';
|
||||||
|
import { fireAdvancedCameraCardEvent } from './utils/fire-advanced-camera-card-event.js';
|
||||||
import { getFolderID } from './utils/folder.js';
|
import { getFolderID } from './utils/folder.js';
|
||||||
|
|
||||||
const MENU_CAMERAS = 'cameras';
|
const MENU_CAMERAS = 'cameras';
|
||||||
@@ -2965,26 +2966,6 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
|||||||
[]) as RawAdvancedCameraCardConfigArray;
|
[]) as RawAdvancedCameraCardConfigArray;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this._configUpgradeable
|
|
||||||
? html` <div class="upgrade">
|
|
||||||
<span>${localize('editor.upgrade_available')}</span>
|
|
||||||
<span>
|
|
||||||
<mwc-button
|
|
||||||
raised
|
|
||||||
label="${localize('editor.upgrade')}"
|
|
||||||
@click=${() => {
|
|
||||||
if (this._config) {
|
|
||||||
const upgradedConfig = copyConfig(this._config);
|
|
||||||
upgradeConfig(upgradedConfig);
|
|
||||||
this._updateConfig(upgradedConfig);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
</mwc-button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<br />`
|
|
||||||
: html``}
|
|
||||||
<div class="card-config">
|
<div class="card-config">
|
||||||
${this._renderOptionSetHeader('cameras')}
|
${this._renderOptionSetHeader('cameras')}
|
||||||
${this._expandedMenus[MENU_OPTIONS] === 'cameras'
|
${this._expandedMenus[MENU_OPTIONS] === 'cameras'
|
||||||
@@ -3597,6 +3578,34 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
|||||||
</div>`
|
</div>`
|
||||||
: ''}`
|
: ''}`
|
||||||
: html``}
|
: html``}
|
||||||
|
<div class="action-buttons">
|
||||||
|
${this._configUpgradeable
|
||||||
|
? html`<ha-button
|
||||||
|
appearance="filled"
|
||||||
|
variant="warning"
|
||||||
|
title=${localize('editor.upgrade_available')}
|
||||||
|
aria-label=${localize('editor.upgrade_available')}
|
||||||
|
@click=${() => {
|
||||||
|
if (this._config) {
|
||||||
|
const upgradedConfig = copyConfig(this._config);
|
||||||
|
upgradeConfig(upgradedConfig);
|
||||||
|
this._updateConfig(upgradedConfig);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${localize('editor.upgrade')}
|
||||||
|
</ha-button>`
|
||||||
|
: ''}
|
||||||
|
<ha-button
|
||||||
|
title=${localize('editor.toggle_diagnostics')}
|
||||||
|
aria-label=${localize('editor.toggle_diagnostics')}
|
||||||
|
@click=${() => {
|
||||||
|
fireAdvancedCameraCardEvent(this, 'editor:diagnostics');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${localize('editor.toggle_diagnostics')}
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export const sideLoadHomeAssistantElements = async (): Promise<boolean> => {
|
|||||||
'ha-spinner',
|
'ha-spinner',
|
||||||
'ha-state-icon',
|
'ha-state-icon',
|
||||||
'ha-web-rtc-player',
|
'ha-web-rtc-player',
|
||||||
'mwc-button',
|
|
||||||
'mwc-list-item',
|
'mwc-list-item',
|
||||||
'state-badge',
|
'state-badge',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export interface LovelaceCard extends HTMLElement {
|
|||||||
hass?: HomeAssistant;
|
hass?: HomeAssistant;
|
||||||
isPanel?: boolean;
|
isPanel?: boolean;
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
|
preview?: boolean;
|
||||||
getCardSize(): number | Promise<number>;
|
getCardSize(): number | Promise<number>;
|
||||||
setConfig(config: LovelaceCardConfig): void;
|
setConfig(config: LovelaceCardConfig): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -688,11 +688,12 @@
|
|||||||
"profiles_secondary": "Choose pre-configured sets of defaults",
|
"profiles_secondary": "Choose pre-configured sets of defaults",
|
||||||
"remote_control": "Remote Control",
|
"remote_control": "Remote Control",
|
||||||
"remote_control_secondary": "Options for remote controlling the card",
|
"remote_control_secondary": "Options for remote controlling the card",
|
||||||
|
"toggle_diagnostics": "Toggle diagnostics",
|
||||||
"status_bar": "Status bar",
|
"status_bar": "Status bar",
|
||||||
"status_bar_secondary": "Status bar look & feel options",
|
"status_bar_secondary": "Status bar look & feel options",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
"timeline_secondary": "Event timeline options",
|
"timeline_secondary": "Event timeline options",
|
||||||
"upgrade": "Upgrade",
|
"upgrade": "Automatic Upgrade",
|
||||||
"upgrade_available": "An automatic card configuration upgrade is available",
|
"upgrade_available": "An automatic card configuration upgrade is available",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"view_secondary": "What the card should show and how to show it"
|
"view_secondary": "What the card should show and how to show it"
|
||||||
|
|||||||
@@ -36,16 +36,9 @@
|
|||||||
.submenu + .option {
|
.submenu + .option {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
div.upgrade {
|
.action-buttons {
|
||||||
width: auto;
|
|
||||||
border: 1px dotted var(--primary-color);
|
|
||||||
margin: 10px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
div.upgrade span {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submenu-header {
|
.submenu-header {
|
||||||
@@ -150,3 +143,7 @@ advanced-camera-card-message::part(icon) {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-button {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Walk up `element`'s ancestor chain (through shadow boundaries) looking for
|
||||||
|
* an ancestor with the given tag name. Returns true if one is found and that
|
||||||
|
* same element also appears in the event's composedPath — indicating the event
|
||||||
|
* originated from within the same subtree as the element.
|
||||||
|
*/
|
||||||
|
export const isAncestorInEventPath = (
|
||||||
|
element: Element,
|
||||||
|
ev: Event,
|
||||||
|
tagName: string,
|
||||||
|
): boolean => {
|
||||||
|
const composedPath = ev.composedPath();
|
||||||
|
let node: Node | null = element;
|
||||||
|
while (node) {
|
||||||
|
if (node instanceof Element && node.tagName.toLowerCase() === tagName) {
|
||||||
|
return composedPath.includes(node);
|
||||||
|
}
|
||||||
|
node = node instanceof ShadowRoot ? node.host : node.parentNode;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, assert, describe, expect, it, vi } from 'vitest';
|
||||||
import { mock } from 'vitest-mock-extended';
|
import { mock } from 'vitest-mock-extended';
|
||||||
import { CardElementManager } from '../../src/card-controller/card-element-manager';
|
import { CardElementManager } from '../../src/card-controller/card-element-manager';
|
||||||
import { StateWatcher } from '../../src/card-controller/hass/state-watcher';
|
import { StateWatcher } from '../../src/card-controller/hass/state-watcher';
|
||||||
import { QueryResults } from '../../src/view/query-results';
|
import { QueryResults } from '../../src/view/query-results';
|
||||||
|
import { View } from '../../src/view/view';
|
||||||
import {
|
import {
|
||||||
callStateWatcherCallback,
|
callStateWatcherCallback,
|
||||||
createCardAPI,
|
createCardAPI,
|
||||||
|
createCardHTMLElement,
|
||||||
createConfig,
|
createConfig,
|
||||||
createLitElement,
|
|
||||||
createStateEntity,
|
createStateEntity,
|
||||||
createView,
|
createView,
|
||||||
TestViewMedia,
|
TestViewMedia,
|
||||||
@@ -20,7 +21,7 @@ describe('CardElementManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should get element', () => {
|
it('should get element', () => {
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
createCardAPI(),
|
createCardAPI(),
|
||||||
element,
|
element,
|
||||||
@@ -35,7 +36,7 @@ describe('CardElementManager', () => {
|
|||||||
const callback = vi.fn();
|
const callback = vi.fn();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
createCardAPI(),
|
createCardAPI(),
|
||||||
createLitElement(),
|
createCardHTMLElement(),
|
||||||
callback,
|
callback,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
);
|
);
|
||||||
@@ -49,7 +50,7 @@ describe('CardElementManager', () => {
|
|||||||
const callback = vi.fn();
|
const callback = vi.fn();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
createCardAPI(),
|
createCardAPI(),
|
||||||
createLitElement(),
|
createCardHTMLElement(),
|
||||||
() => undefined,
|
() => undefined,
|
||||||
callback,
|
callback,
|
||||||
);
|
);
|
||||||
@@ -60,7 +61,7 @@ describe('CardElementManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update', () => {
|
it('should update', () => {
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
createCardAPI(),
|
createCardAPI(),
|
||||||
element,
|
element,
|
||||||
@@ -73,7 +74,7 @@ describe('CardElementManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should get hasUpdated', () => {
|
it('should get hasUpdated', () => {
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
element.hasUpdated = true;
|
element.hasUpdated = true;
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
createCardAPI(),
|
createCardAPI(),
|
||||||
@@ -89,7 +90,7 @@ describe('CardElementManager', () => {
|
|||||||
const windowAddEventListener = vi.spyOn(global.window, 'addEventListener');
|
const windowAddEventListener = vi.spyOn(global.window, 'addEventListener');
|
||||||
|
|
||||||
const addEventListener = vi.fn();
|
const addEventListener = vi.fn();
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
element.addEventListener = addEventListener;
|
element.addEventListener = addEventListener;
|
||||||
|
|
||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
@@ -132,6 +133,10 @@ describe('CardElementManager', () => {
|
|||||||
);
|
);
|
||||||
expect(windowAddEventListener).toBeCalledWith('location-changed', expect.anything());
|
expect(windowAddEventListener).toBeCalledWith('location-changed', expect.anything());
|
||||||
expect(windowAddEventListener).toBeCalledWith('popstate', expect.anything());
|
expect(windowAddEventListener).toBeCalledWith('popstate', expect.anything());
|
||||||
|
expect(windowAddEventListener).toBeCalledWith(
|
||||||
|
'advanced-camera-card:editor:diagnostics',
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
|
||||||
expect(api.getInteractionManager().initialize).toBeCalled();
|
expect(api.getInteractionManager().initialize).toBeCalled();
|
||||||
expect(api.getFullscreenManager().initialize).toBeCalled();
|
expect(api.getFullscreenManager().initialize).toBeCalled();
|
||||||
@@ -143,7 +148,7 @@ describe('CardElementManager', () => {
|
|||||||
it('should disconnect', () => {
|
it('should disconnect', () => {
|
||||||
const windowRemoveEventListener = vi.spyOn(global.window, 'removeEventListener');
|
const windowRemoveEventListener = vi.spyOn(global.window, 'removeEventListener');
|
||||||
|
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
element.setAttribute('panel', '');
|
element.setAttribute('panel', '');
|
||||||
element.setAttribute('casted', '');
|
element.setAttribute('casted', '');
|
||||||
|
|
||||||
@@ -195,6 +200,10 @@ describe('CardElementManager', () => {
|
|||||||
expect.anything(),
|
expect.anything(),
|
||||||
);
|
);
|
||||||
expect(windowRemoveEventListener).toBeCalledWith('popstate', expect.anything());
|
expect(windowRemoveEventListener).toBeCalledWith('popstate', expect.anything());
|
||||||
|
expect(windowRemoveEventListener).toBeCalledWith(
|
||||||
|
'advanced-camera-card:editor:diagnostics',
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
|
||||||
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
expect(api.getMediaLoadedInfoManager().clear).toBeCalled();
|
||||||
expect(api.getFullscreenManager().disconnect).toBeCalled();
|
expect(api.getFullscreenManager().disconnect).toBeCalled();
|
||||||
@@ -217,7 +226,7 @@ describe('CardElementManager', () => {
|
|||||||
const stateWatcher = mock<StateWatcher>();
|
const stateWatcher = mock<StateWatcher>();
|
||||||
vi.mocked(api.getHASSManager().getStateWatcher).mockReturnValue(stateWatcher);
|
vi.mocked(api.getHASSManager().getStateWatcher).mockReturnValue(stateWatcher);
|
||||||
|
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
api,
|
api,
|
||||||
element,
|
element,
|
||||||
@@ -245,7 +254,7 @@ describe('CardElementManager', () => {
|
|||||||
const stateWatcher = mock<StateWatcher>();
|
const stateWatcher = mock<StateWatcher>();
|
||||||
vi.mocked(api.getHASSManager().getStateWatcher).mockReturnValue(stateWatcher);
|
vi.mocked(api.getHASSManager().getStateWatcher).mockReturnValue(stateWatcher);
|
||||||
|
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
api,
|
api,
|
||||||
element,
|
element,
|
||||||
@@ -275,7 +284,7 @@ describe('CardElementManager', () => {
|
|||||||
|
|
||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||||
|
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
api,
|
api,
|
||||||
element,
|
element,
|
||||||
@@ -310,7 +319,7 @@ describe('CardElementManager', () => {
|
|||||||
|
|
||||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||||
|
|
||||||
const element = createLitElement();
|
const element = createCardHTMLElement();
|
||||||
const manager = new CardElementManager(
|
const manager = new CardElementManager(
|
||||||
api,
|
api,
|
||||||
element,
|
element,
|
||||||
@@ -334,4 +343,95 @@ describe('CardElementManager', () => {
|
|||||||
expect(element.requestUpdate).not.toBeCalled();
|
expect(element.requestUpdate).not.toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('should handle diagnostics', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const createDialogWithCard = (element: HTMLElement) => {
|
||||||
|
const dialog = document.createElement('hui-dialog-edit-card');
|
||||||
|
dialog.attachShadow({ mode: 'open' });
|
||||||
|
assert(dialog.shadowRoot);
|
||||||
|
dialog.shadowRoot.append(element);
|
||||||
|
return dialog;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fireFromDialog = (dialog: HTMLElement) => {
|
||||||
|
const editorDiv = document.createElement('div');
|
||||||
|
assert(dialog.shadowRoot);
|
||||||
|
dialog.shadowRoot.append(editorDiv);
|
||||||
|
editorDiv.dispatchEvent(
|
||||||
|
new CustomEvent('advanced-camera-card:editor:diagnostics', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('sets view to diagnostics if card is in editor', () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const element = createCardHTMLElement();
|
||||||
|
const manager = new CardElementManager(
|
||||||
|
api,
|
||||||
|
element,
|
||||||
|
() => undefined,
|
||||||
|
() => undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dialog = createDialogWithCard(element);
|
||||||
|
document.body.append(dialog);
|
||||||
|
manager.elementConnected();
|
||||||
|
|
||||||
|
fireFromDialog(dialog);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewByParameters).toBeCalledWith({
|
||||||
|
params: { view: 'diagnostics' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets to default view if already in diagnostics view', () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const element = createCardHTMLElement();
|
||||||
|
const manager = new CardElementManager(
|
||||||
|
api,
|
||||||
|
element,
|
||||||
|
() => undefined,
|
||||||
|
() => undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||||
|
new View({ view: 'diagnostics' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const dialog = createDialogWithCard(element);
|
||||||
|
document.body.append(dialog);
|
||||||
|
manager.elementConnected();
|
||||||
|
|
||||||
|
fireFromDialog(dialog);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not set view to diagnostics if card is not in editor', () => {
|
||||||
|
const api = createCardAPI();
|
||||||
|
const element = createCardHTMLElement();
|
||||||
|
const manager = new CardElementManager(
|
||||||
|
api,
|
||||||
|
element,
|
||||||
|
() => undefined,
|
||||||
|
() => undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.elementConnected();
|
||||||
|
|
||||||
|
// Event fired from a different dialog that does not contain the card
|
||||||
|
const otherDialog = document.createElement('hui-dialog-edit-card');
|
||||||
|
otherDialog.attachShadow({ mode: 'open' });
|
||||||
|
document.body.append(otherDialog);
|
||||||
|
fireFromDialog(otherDialog);
|
||||||
|
|
||||||
|
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+11
-1
@@ -24,7 +24,10 @@ import {
|
|||||||
import { ActionsManager } from '../src/card-controller/actions/actions-manager';
|
import { ActionsManager } from '../src/card-controller/actions/actions-manager';
|
||||||
import { AutomationsManager } from '../src/card-controller/automations-manager';
|
import { AutomationsManager } from '../src/card-controller/automations-manager';
|
||||||
import { CameraURLManager } from '../src/card-controller/camera-url-manager';
|
import { CameraURLManager } from '../src/card-controller/camera-url-manager';
|
||||||
import { CardElementManager } from '../src/card-controller/card-element-manager';
|
import {
|
||||||
|
CardElementManager,
|
||||||
|
CardHTMLElement,
|
||||||
|
} from '../src/card-controller/card-element-manager';
|
||||||
import { ConfigManager } from '../src/card-controller/config/config-manager';
|
import { ConfigManager } from '../src/card-controller/config/config-manager';
|
||||||
import { CardController } from '../src/card-controller/controller';
|
import { CardController } from '../src/card-controller/controller';
|
||||||
import { DefaultManager } from '../src/card-controller/default-manager';
|
import { DefaultManager } from '../src/card-controller/default-manager';
|
||||||
@@ -626,6 +629,13 @@ export const createParent = (options?: { children?: HTMLElement[] }): HTMLElemen
|
|||||||
return parent;
|
return parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createCardHTMLElement = (): CardHTMLElement => {
|
||||||
|
const element = createLitElement() as CardHTMLElement;
|
||||||
|
element.getCardSize = vi.fn();
|
||||||
|
element.setConfig = vi.fn();
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
export const createLitElement = (): LitElement => {
|
export const createLitElement = (): LitElement => {
|
||||||
const element = document.createElement('div') as unknown as LitElement;
|
const element = document.createElement('div') as unknown as LitElement;
|
||||||
element.addController = vi.fn();
|
element.addController = vi.fn();
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { assert, describe, expect, it } from 'vitest';
|
||||||
|
import { isAncestorInEventPath } from '../../src/utils/event-ancestor';
|
||||||
|
|
||||||
|
// @vitest-environment jsdom
|
||||||
|
describe('isAncestorInEventPath', () => {
|
||||||
|
const dispatch = (source: EventTarget, listener: (ev: Event) => void) => {
|
||||||
|
window.addEventListener('test', listener, { once: true });
|
||||||
|
source.dispatchEvent(new CustomEvent('test', { bubbles: true, composed: true }));
|
||||||
|
};
|
||||||
|
|
||||||
|
it('returns true when element shares the ancestor with the event source', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
const ancestor = document.createElement('hui-dialog-edit-card');
|
||||||
|
ancestor.attachShadow({ mode: 'open' });
|
||||||
|
assert(ancestor.shadowRoot);
|
||||||
|
ancestor.shadowRoot.append(element);
|
||||||
|
document.body.append(ancestor);
|
||||||
|
|
||||||
|
const eventSource = document.createElement('div');
|
||||||
|
ancestor.shadowRoot.append(eventSource);
|
||||||
|
|
||||||
|
let result: boolean | undefined;
|
||||||
|
dispatch(eventSource, (ev) => {
|
||||||
|
result = isAncestorInEventPath(element, ev, 'hui-dialog-edit-card');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when element and event source are in different ancestors with the same tag', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
const ancestor1 = document.createElement('hui-dialog-edit-card');
|
||||||
|
ancestor1.attachShadow({ mode: 'open' });
|
||||||
|
assert(ancestor1.shadowRoot);
|
||||||
|
ancestor1.shadowRoot.append(element);
|
||||||
|
document.body.append(ancestor1);
|
||||||
|
|
||||||
|
const ancestor2 = document.createElement('hui-dialog-edit-card');
|
||||||
|
ancestor2.attachShadow({ mode: 'open' });
|
||||||
|
assert(ancestor2.shadowRoot);
|
||||||
|
const eventSource = document.createElement('div');
|
||||||
|
ancestor2.shadowRoot.append(eventSource);
|
||||||
|
document.body.append(ancestor2);
|
||||||
|
|
||||||
|
let result: boolean | undefined;
|
||||||
|
dispatch(eventSource, (ev) => {
|
||||||
|
result = isAncestorInEventPath(element, ev, 'hui-dialog-edit-card');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when element has no ancestor with the given tag', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
document.body.append(element);
|
||||||
|
|
||||||
|
const eventSource = document.createElement('div');
|
||||||
|
document.body.append(eventSource);
|
||||||
|
|
||||||
|
let result: boolean | undefined;
|
||||||
|
dispatch(eventSource, (ev) => {
|
||||||
|
result = isAncestorInEventPath(element, ev, 'hui-dialog-edit-card');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('traverses shadow boundaries in the element ancestor chain', () => {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
const inner = document.createElement('hui-card');
|
||||||
|
inner.attachShadow({ mode: 'open' });
|
||||||
|
assert(inner.shadowRoot);
|
||||||
|
inner.shadowRoot.append(element);
|
||||||
|
|
||||||
|
const ancestor = document.createElement('hui-dialog-edit-card');
|
||||||
|
ancestor.attachShadow({ mode: 'open' });
|
||||||
|
assert(ancestor.shadowRoot);
|
||||||
|
ancestor.shadowRoot.append(inner);
|
||||||
|
document.body.append(ancestor);
|
||||||
|
|
||||||
|
const eventSource = document.createElement('div');
|
||||||
|
ancestor.shadowRoot.append(eventSource);
|
||||||
|
|
||||||
|
let result: boolean | undefined;
|
||||||
|
dispatch(eventSource, (ev) => {
|
||||||
|
result = isAncestorInEventPath(element, ev, 'hui-dialog-edit-card');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user