fix: Various editor / diagnostic improvements (#2363)
- Closes: #2360 - Closes: #2328
This commit is contained in:
@@ -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 { CardElementManager } from '../../src/card-controller/card-element-manager';
|
||||
import { StateWatcher } from '../../src/card-controller/hass/state-watcher';
|
||||
import { QueryResults } from '../../src/view/query-results';
|
||||
import { View } from '../../src/view/view';
|
||||
import {
|
||||
callStateWatcherCallback,
|
||||
createCardAPI,
|
||||
createCardHTMLElement,
|
||||
createConfig,
|
||||
createLitElement,
|
||||
createStateEntity,
|
||||
createView,
|
||||
TestViewMedia,
|
||||
@@ -20,7 +21,7 @@ describe('CardElementManager', () => {
|
||||
});
|
||||
|
||||
it('should get element', () => {
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
element,
|
||||
@@ -35,7 +36,7 @@ describe('CardElementManager', () => {
|
||||
const callback = vi.fn();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
createLitElement(),
|
||||
createCardHTMLElement(),
|
||||
callback,
|
||||
() => undefined,
|
||||
);
|
||||
@@ -49,7 +50,7 @@ describe('CardElementManager', () => {
|
||||
const callback = vi.fn();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
createLitElement(),
|
||||
createCardHTMLElement(),
|
||||
() => undefined,
|
||||
callback,
|
||||
);
|
||||
@@ -60,7 +61,7 @@ describe('CardElementManager', () => {
|
||||
});
|
||||
|
||||
it('should update', () => {
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
element,
|
||||
@@ -73,7 +74,7 @@ describe('CardElementManager', () => {
|
||||
});
|
||||
|
||||
it('should get hasUpdated', () => {
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
element.hasUpdated = true;
|
||||
const manager = new CardElementManager(
|
||||
createCardAPI(),
|
||||
@@ -89,7 +90,7 @@ describe('CardElementManager', () => {
|
||||
const windowAddEventListener = vi.spyOn(global.window, 'addEventListener');
|
||||
|
||||
const addEventListener = vi.fn();
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
element.addEventListener = addEventListener;
|
||||
|
||||
const api = createCardAPI();
|
||||
@@ -132,6 +133,10 @@ describe('CardElementManager', () => {
|
||||
);
|
||||
expect(windowAddEventListener).toBeCalledWith('location-changed', 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.getFullscreenManager().initialize).toBeCalled();
|
||||
@@ -143,7 +148,7 @@ describe('CardElementManager', () => {
|
||||
it('should disconnect', () => {
|
||||
const windowRemoveEventListener = vi.spyOn(global.window, 'removeEventListener');
|
||||
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
element.setAttribute('panel', '');
|
||||
element.setAttribute('casted', '');
|
||||
|
||||
@@ -195,6 +200,10 @@ describe('CardElementManager', () => {
|
||||
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.getFullscreenManager().disconnect).toBeCalled();
|
||||
@@ -217,7 +226,7 @@ describe('CardElementManager', () => {
|
||||
const stateWatcher = mock<StateWatcher>();
|
||||
vi.mocked(api.getHASSManager().getStateWatcher).mockReturnValue(stateWatcher);
|
||||
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
@@ -245,7 +254,7 @@ describe('CardElementManager', () => {
|
||||
const stateWatcher = mock<StateWatcher>();
|
||||
vi.mocked(api.getHASSManager().getStateWatcher).mockReturnValue(stateWatcher);
|
||||
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
@@ -275,7 +284,7 @@ describe('CardElementManager', () => {
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
@@ -310,7 +319,7 @@ describe('CardElementManager', () => {
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
|
||||
const element = createLitElement();
|
||||
const element = createCardHTMLElement();
|
||||
const manager = new CardElementManager(
|
||||
api,
|
||||
element,
|
||||
@@ -334,4 +343,95 @@ describe('CardElementManager', () => {
|
||||
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 { AutomationsManager } from '../src/card-controller/automations-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 { CardController } from '../src/card-controller/controller';
|
||||
import { DefaultManager } from '../src/card-controller/default-manager';
|
||||
@@ -626,6 +629,13 @@ export const createParent = (options?: { children?: HTMLElement[] }): HTMLElemen
|
||||
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 => {
|
||||
const element = document.createElement('div') as unknown as LitElement;
|
||||
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