From ee8f9e3fd19be3e74ea501a2d4b36292466a3bf6 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Thu, 5 Oct 2023 19:54:52 -0700 Subject: [PATCH] Handle (invalid) null hass objects. --- src/card-controller/hass-manager.ts | 26 ++++++----- src/card-controller/message-manager.ts | 8 +++- src/utils/ha/index.ts | 5 +-- tests/card-controller/hass-manager.test.ts | 24 +++++++++- tests/card-controller/message-manager.test.ts | 16 +++++++ tests/utils/ha/index.test.ts | 45 +++++++++++++++++++ 6 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 tests/utils/ha/index.test.ts diff --git a/src/card-controller/hass-manager.ts b/src/card-controller/hass-manager.ts index b449be06..35134171 100644 --- a/src/card-controller/hass-manager.ts +++ b/src/card-controller/hass-manager.ts @@ -16,7 +16,7 @@ export class HASSManager { return this._hass; } - public setHASS(hass: ExtendedHomeAssistant): void { + public setHASS(hass?: ExtendedHomeAssistant | null): void { const getSelectedCameraConfig = (): CameraConfig | null => { const view = this._api.getViewManager().getView(); const cameraManager = this._api.getCameraManager(); @@ -26,13 +26,8 @@ export class HASSManager { : null; }; - const oldHass = this._hass; - this._hass = hass; - - const selectedCamera = getSelectedCameraConfig(); - - if (hasHAConnectionStateChanged(oldHass, hass)) { - if (!this._hass?.connected) { + if (hasHAConnectionStateChanged(this._hass, hass)) { + if (!hass?.connected) { this._api.getMessageManager().setMessageIfHigherPriority({ message: localize('error.reconnecting'), icon: 'mdi:lan-disconnect', @@ -40,9 +35,18 @@ export class HASSManager { dotdotdot: true, }); } else { - this._api.getViewManager().setViewDefault(); + this._api.getMessageManager().resetType('connection'); } - } else if ( + } + + if (!hass) { + return; + } + + const oldHass = this._hass; + this._hass = hass; + + if ( // Home Assistant pumps a lot of updates through. Re-rendering the card is // necessary at times (e.g. to update the 'clip' view as new clips // arrive), but also is a jarring experience for the user (e.g. if they @@ -52,7 +56,7 @@ export class HASSManager { this._isAutomatedViewUpdateAllowed() && isHassDifferent(this._hass, oldHass, [ ...(this._api.getConfigManager().getConfig()?.view.update_entities ?? []), - ...(selectedCamera?.triggers.entities ?? []), + ...(getSelectedCameraConfig()?.triggers.entities ?? []), ]) ) { // If entities being monitored have changed then reset the view to the diff --git a/src/card-controller/message-manager.ts b/src/card-controller/message-manager.ts index fdc50335..17933e6c 100644 --- a/src/card-controller/message-manager.ts +++ b/src/card-controller/message-manager.ts @@ -1,4 +1,4 @@ -import { FrigateCardError, MESSAGE_TYPE_PRIORITIES, Message } from '../types'; +import { FrigateCardError, MESSAGE_TYPE_PRIORITIES, Message, MessageType } from '../types'; import { errorToConsole } from '../utils/basic'; import { CardMessageAPI } from './types'; @@ -31,6 +31,12 @@ export class MessageManager { } } + public resetType(type: MessageType): void { + if (this._message?.type === type) { + this.reset(); + } + } + public setErrorIfHigherPriority(error: unknown): void { // This object should accept unknown objects to be able to seamlessly // process arguments to catch() which can only be unknown/any. diff --git a/src/utils/ha/index.ts b/src/utils/ha/index.ts index 5c671932..5f8e4e75 100644 --- a/src/utils/ha/index.ts +++ b/src/utils/ha/index.ts @@ -375,8 +375,5 @@ export const hasHAConnectionStateChanged = ( oldHass: HomeAssistant | undefined | null, newHass: HomeAssistant | undefined | null, ): boolean => { - return ( - (!oldHass && !newHass?.connected) || - (!!oldHass && oldHass.connected !== !!newHass?.connected) - ); + return oldHass?.connected !== newHass?.connected; }; diff --git a/tests/card-controller/hass-manager.test.ts b/tests/card-controller/hass-manager.test.ts index a79812fa..cb10b2ae 100644 --- a/tests/card-controller/hass-manager.test.ts +++ b/tests/card-controller/hass-manager.test.ts @@ -130,7 +130,29 @@ describe('HASSManager', () => { const reconnectedHASS = createHASS(); manager.setHASS(reconnectedHASS); - expect(api.getViewManager().setViewDefault).toBeCalled(); + expect(api.getMessageManager().resetType).toBeCalled(); + }); + + it('hass is null', () => { + const api = createAPIWithoutMediaPlayers(); + const manager = new HASSManager(api); + const connectedHASS = createHASS(); + connectedHASS.connected = true; + + manager.setHASS(connectedHASS); + manager.setHASS(null); + + expect(api.getMessageManager().setMessageIfHigherPriority).toBeCalledWith( + expect.objectContaining({ + message: 'Reconnecting', + icon: 'mdi:lan-disconnect', + type: 'connection', + dotdotdot: true, + }), + ); + + manager.setHASS(connectedHASS); + expect(api.getMessageManager().resetType).toBeCalled(); }); }); diff --git a/tests/card-controller/message-manager.test.ts b/tests/card-controller/message-manager.test.ts index da2db8e1..658943d7 100644 --- a/tests/card-controller/message-manager.test.ts +++ b/tests/card-controller/message-manager.test.ts @@ -71,6 +71,22 @@ describe('MessageManager', () => { expect(api.getCardElementManager().update).toBeCalled(); }); + it('should reset message that matches type', () => { + const api = createCardAPI(); + const manager = new MessageManager(api); + + const message = createMessage({ type: 'connection' }); + manager.setMessageIfHigherPriority(message); + expect(manager.getMessage()).toBe(message); + + manager.resetType('error'); + expect(manager.getMessage()).toBe(message); + + manager.resetType('connection'); + expect(manager.getMessage()).toBeNull(); + expect(manager.hasMessage()).toBeFalsy(); + }); + it('should respect priority', () => { const api = createCardAPI(); const manager = new MessageManager(api); diff --git a/tests/utils/ha/index.test.ts b/tests/utils/ha/index.test.ts new file mode 100644 index 00000000..15f5675a --- /dev/null +++ b/tests/utils/ha/index.test.ts @@ -0,0 +1,45 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { describe, expect, it } from 'vitest'; +import { hasHAConnectionStateChanged } 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(); + }); +});