Skip to content

Commit 8cf9503

Browse files
committed
Optimize update entities computation in hui-updates-card
1 parent 09b221c commit 8cf9503

2 files changed

Lines changed: 87 additions & 4 deletions

File tree

src/panels/lovelace/cards/hui-updates-card.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export class HuiUpdatesCard extends LitElement implements LovelaceCard {
2929

3030
@state() private _config?: UpdatesCardConfig;
3131

32+
private _updateCount = 0;
33+
3234
public setConfig(config: UpdatesCardConfig): void {
3335
this._config = config;
3436
}
@@ -88,12 +90,12 @@ export class HuiUpdatesCard extends LitElement implements LovelaceCard {
8890
return;
8991
}
9092

91-
const updateEntities = this._getUpdateEntities();
93+
this._updateCount = this._getUpdateEntities().length;
9294

9395
// Update visibility based on admin status and updates count
9496
const shouldBeHidden = Boolean(
9597
!this.hass.user?.is_admin ||
96-
(this._config.hide_empty && updateEntities.length === 0)
98+
(this._config.hide_empty && this._updateCount === 0)
9799
);
98100

99101
if (shouldBeHidden !== this.hidden) {
@@ -108,8 +110,7 @@ export class HuiUpdatesCard extends LitElement implements LovelaceCard {
108110
return nothing;
109111
}
110112

111-
const updateEntities = this._getUpdateEntities();
112-
const count = updateEntities.length;
113+
const count = this._updateCount;
113114

114115
const label = this.hass.localize("ui.card.updates.title");
115116
const secondary =
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
import "../../../../src/panels/lovelace/cards/hui-updates-card";
3+
import type { HuiUpdatesCard } from "../../../../src/panels/lovelace/cards/hui-updates-card";
4+
import type { UpdatesCardConfig } from "../../../../src/panels/lovelace/cards/types";
5+
import type { HomeAssistant } from "../../../../src/types";
6+
7+
const { filterUpdateEntities } = vi.hoisted(() => ({
8+
filterUpdateEntities: vi.fn(() => []),
9+
}));
10+
11+
// Bundler-defined globals the card's import graph reads at eval time.
12+
vi.hoisted(() => {
13+
Object.assign(globalThis, {
14+
__STATIC_PATH__: "/",
15+
__HASS_URL__: "",
16+
__BUILD__: "modern",
17+
__VERSION__: "test",
18+
__BACKWARDS_COMPAT__: false,
19+
__SUPERVISOR__: false,
20+
__NAMESPACE__: "frontend",
21+
});
22+
});
23+
24+
vi.mock("../../../../src/data/update", () => ({
25+
filterUpdateEntities,
26+
updateCanInstall: vi.fn(() => true),
27+
}));
28+
29+
const createHass = (): HomeAssistant =>
30+
({
31+
states: {},
32+
locale: {
33+
language: "en",
34+
},
35+
user: {
36+
is_admin: true,
37+
},
38+
localize: vi.fn((key: string) => key),
39+
}) as unknown as HomeAssistant;
40+
41+
let elements: HuiUpdatesCard[] = [];
42+
43+
const mount = async (): Promise<HuiUpdatesCard> => {
44+
const el = document.createElement("hui-updates-card") as HuiUpdatesCard;
45+
el.hass = createHass();
46+
el.setConfig({
47+
type: "updates",
48+
hide_empty: false,
49+
} as UpdatesCardConfig);
50+
51+
document.body.appendChild(el);
52+
elements.push(el);
53+
await el.updateComplete;
54+
55+
return el;
56+
};
57+
58+
afterEach(() => {
59+
elements.forEach((el) => el.remove());
60+
elements = [];
61+
vi.restoreAllMocks();
62+
});
63+
64+
describe("hui-updates-card", () => {
65+
// willUpdate() and render() previously each called _getUpdateEntities()
66+
// independently; this pins the count to one call per update cycle so a
67+
// future edit can't silently reintroduce the duplicate.
68+
it("computes update entities once per update cycle", async () => {
69+
const element = await mount();
70+
71+
filterUpdateEntities.mockClear();
72+
73+
element.hass = {
74+
...element.hass!,
75+
states: { ...element.hass!.states },
76+
};
77+
78+
await element.updateComplete;
79+
80+
expect(filterUpdateEntities).toHaveBeenCalledTimes(1);
81+
});
82+
});

0 commit comments

Comments
 (0)