From 16ca8d79d281b85daf74c16133c0d8432b883e1e Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 14 Mar 2026 14:38:04 -0700 Subject: [PATCH] fix: Log problems to the console as warnings (once) (#2414) --- src/card-controller/problems/manager.ts | 13 ++++ src/localize/languages/en.json | 6 +- .../card-controller/problems/manager.test.ts | 66 +++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/card-controller/problems/manager.ts b/src/card-controller/problems/manager.ts index e07c5625..e5f71e70 100644 --- a/src/card-controller/problems/manager.ts +++ b/src/card-controller/problems/manager.ts @@ -16,6 +16,7 @@ import { export class ProblemManager { private _api: CardProblemAPI; private _problems = new Map(); + private _loggedKeys = new Set(); constructor(api: CardProblemAPI) { this._api = api; @@ -46,6 +47,7 @@ export class ProblemManager { public async detectStatic(hass: HomeAssistant): Promise { for (const problem of this._problems.values()) { await problem.detectStatic?.(hass); + this._logIfNew(problem); } this._api.getCardElementManager().update(); } @@ -122,9 +124,20 @@ export class ProblemManager { const hadResult = problem.hasResult(); problem.detectDynamic?.(context); stateChanged ||= problem.hasResult() !== hadResult; + this._logIfNew(problem); } if (stateChanged) { this._api.getCardElementManager().update(); } } + + private _logIfNew(problem: Problem): void { + if (problem.hasResult() && !this._loggedKeys.has(problem.key)) { + this._loggedKeys.add(problem.key); + const text = problem.getResult()?.notification.text; + if (text) { + console.warn(`Advanced Camera Card: ${text}`); + } + } + } } diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 4fc589a5..4771dc2e 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -826,12 +826,12 @@ "troubleshooting_guide": "Troubleshooting guide", "config_upgrade": { "heading": "Configuration upgrade available", - "text": "A configuration upgrade is available. To upgrade, edit this card (Dashboard pencil icon \u2192 Three-dot menu \u2192 Edit) and click the 'Automatic Upgrade' button in the card editor." + "text": "A configuration upgrade is available. To upgrade, edit this card (Dashboard pencil icon \u2192 Three-dot menu \u2192 Edit) and click the 'Automatic Upgrade' button in the card editor" }, "legacy_resource": { "heading": "Legacy dashboard resource detected", - "text_both": "The legacy 'frigate-hass-card.js' resource is still registered, please either manually remove it or click the delete icon to automatically remove it. It will be removed in a future release.", - "text_only_legacy": "The legacy 'frigate-hass-card.js' resource must be replaced with 'advanced-camera-card.js'. It will be removed in a future release.", + "text_both": "The legacy 'frigate-hass-card.js' resource is still registered, please either manually remove it or click the delete icon to automatically remove it. It will be removed in a future release", + "text_only_legacy": "The legacy 'frigate-hass-card.js' resource must be replaced with 'advanced-camera-card.js'. It will be removed in a future release", "remove": "Remove legacy resource" }, "stream_not_loading": { diff --git a/tests/card-controller/problems/manager.test.ts b/tests/card-controller/problems/manager.test.ts index cbee5e75..aa04e526 100644 --- a/tests/card-controller/problems/manager.test.ts +++ b/tests/card-controller/problems/manager.test.ts @@ -274,6 +274,72 @@ describe('ProblemManager', () => { }); }); + describe('logging', () => { + it('should log on static detection when problem is active', async () => { + const spy = vi.spyOn(console, 'warn').mockReturnValue(); + const api = createCardAPI(); + const result = createProblemResult({ notification: { text: 'Legacy problem' } }); + vi.mocked(mockLegacyResource.hasResult).mockReturnValue(true); + vi.mocked(mockLegacyResource.getResult).mockReturnValue(result); + + const manager = new ProblemManager(api); + await manager.detectStatic(createHASS()); + + expect(spy).toBeCalledWith('Advanced Camera Card: Legacy problem'); + spy.mockRestore(); + }); + + it('should log on dynamic detection when problem becomes active', () => { + const spy = vi.spyOn(console, 'warn').mockReturnValue(); + const api = createCardAPI(); + const stateManager = new ConditionStateManager(); + vi.mocked(api.getConditionStateManager).mockReturnValue(stateManager); + + const result = createProblemResult({ + notification: { text: 'Stream problem' }, + }); + vi.mocked(mockStreamNotLoading.hasResult) + .mockReturnValueOnce(false) + .mockReturnValue(true); + vi.mocked(mockStreamNotLoading.getResult).mockReturnValue(result); + + const manager = new ProblemManager(api); + manager.initialize(); + + stateManager.setState({ view: 'live' }); + + expect(spy).toBeCalledWith('Advanced Camera Card: Stream problem'); + spy.mockRestore(); + }); + + it('should only log once per problem key', async () => { + const spy = vi.spyOn(console, 'warn').mockReturnValue(); + const api = createCardAPI(); + const result = createProblemResult({ notification: { text: 'Repeated' } }); + vi.mocked(mockLegacyResource.hasResult).mockReturnValue(true); + vi.mocked(mockLegacyResource.getResult).mockReturnValue(result); + + const manager = new ProblemManager(api); + await manager.detectStatic(createHASS()); + await manager.detectStatic(createHASS()); + + expect(spy).toBeCalledTimes(1); + spy.mockRestore(); + }); + + it('should not log when problem has no result', async () => { + const spy = vi.spyOn(console, 'warn').mockReturnValue(); + const api = createCardAPI(); + vi.mocked(mockLegacyResource.hasResult).mockReturnValue(false); + + const manager = new ProblemManager(api); + await manager.detectStatic(createHASS()); + + expect(spy).not.toBeCalled(); + spy.mockRestore(); + }); + }); + describe('destroy', () => { it('should destroy all problems and clear', () => { const api = createCardAPI();