mirror of
https://github.com/home-assistant/frontend.git
synced 2026-07-02 12:05:39 +01:00
133a9171bc
* Add deterministic fixtures and characterization tests for chart data processing * Extract statistics chart data processing into a pure function * Extract state history line chart data processing into a pure function * Add benchmark suite for chart data processing * Add chart data optimization playbook * Point agent instructions at the chart optimization playbook
197 lines
5.5 KiB
TypeScript
197 lines
5.5 KiB
TypeScript
/**
|
|
* Characterization tests pinning the exact output of the statistics chart
|
|
* data transform. Do NOT update these snapshots to make an optimization
|
|
* pass — see test/benchmarks/README.md.
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
import { generateStatisticsChartData } from "../../../src/components/chart/statistics-chart-data";
|
|
import { StatisticMeanType } from "../../../src/data/recorder";
|
|
import type { StatisticsMetaData } from "../../../src/data/recorder";
|
|
import { createMockComputedStyle } from "../../fixtures/computed-style";
|
|
import { digestResult } from "../../fixtures/digest";
|
|
import { createMockEntityState, createMockHass } from "../../fixtures/hass";
|
|
import { FIXED_EPOCH_MS } from "../../fixtures/history-states";
|
|
import { generateStatistics } from "../../fixtures/statistics";
|
|
|
|
const computedStyle = createMockComputedStyle();
|
|
const dayMs = 24 * 60 * 60 * 1000;
|
|
const now = new Date(FIXED_EPOCH_MS + 7 * dayMs);
|
|
|
|
const buildMetadata = (
|
|
ids: string[],
|
|
unit = "°C",
|
|
hasSum = false
|
|
): Record<string, StatisticsMetaData> =>
|
|
Object.fromEntries(
|
|
ids.map((id) => [
|
|
id,
|
|
{
|
|
statistic_id: id,
|
|
statistics_unit_of_measurement: unit,
|
|
source: "recorder",
|
|
name: null,
|
|
has_sum: hasSum,
|
|
mean_type: hasSum
|
|
? StatisticMeanType.NONE
|
|
: StatisticMeanType.ARITHMETIC,
|
|
unit_class: hasSum ? "energy" : "temperature",
|
|
},
|
|
])
|
|
);
|
|
|
|
describe("generateStatisticsChartData", () => {
|
|
const meanIds = ["sensor.temp_indoor", "sensor.temp_outdoor"];
|
|
const sumIds = ["sensor.energy_a", "sensor.energy_b"];
|
|
|
|
const baseParams = {
|
|
hass: createMockHass(),
|
|
computedStyle,
|
|
now,
|
|
hiddenStats: new Set<string>(),
|
|
hideLegend: false,
|
|
} as const;
|
|
|
|
it("returns undefined for empty statistics", () => {
|
|
expect(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
statisticsData: {},
|
|
statisticsMetaData: {},
|
|
statTypes: ["mean"],
|
|
chartType: "line",
|
|
})
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it("matches snapshot for a line chart with min/mean/max bands", () => {
|
|
expect(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
statisticsData: generateStatistics(1, {
|
|
ids: meanIds,
|
|
period: "hour",
|
|
days: 1,
|
|
}),
|
|
statisticsMetaData: buildMetadata(meanIds),
|
|
statTypes: ["mean", "min", "max"],
|
|
chartType: "line",
|
|
period: "hour",
|
|
})
|
|
).toMatchSnapshot();
|
|
});
|
|
|
|
it("matches snapshot for a bar chart with sum statistics", () => {
|
|
expect(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
statisticsData: generateStatistics(2, {
|
|
ids: sumIds,
|
|
period: "hour",
|
|
days: 1,
|
|
sumStatistics: true,
|
|
}),
|
|
statisticsMetaData: buildMetadata(sumIds, "kWh", true),
|
|
statTypes: ["sum"],
|
|
chartType: "bar",
|
|
period: "hour",
|
|
})
|
|
).toMatchSnapshot();
|
|
});
|
|
|
|
it("matches snapshot for a stacked bar chart with change statistics", () => {
|
|
expect(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
statisticsData: generateStatistics(3, {
|
|
ids: sumIds,
|
|
period: "day",
|
|
days: 7,
|
|
sumStatistics: true,
|
|
}),
|
|
statisticsMetaData: buildMetadata(sumIds, "kWh", true),
|
|
statTypes: ["change"],
|
|
chartType: "bar-stack",
|
|
period: "day",
|
|
})
|
|
).toMatchSnapshot();
|
|
});
|
|
|
|
it("matches snapshot with a hidden statistic", () => {
|
|
expect(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
hiddenStats: new Set([meanIds[0]]),
|
|
statisticsData: generateStatistics(4, {
|
|
ids: meanIds,
|
|
period: "hour",
|
|
days: 1,
|
|
}),
|
|
statisticsMetaData: buildMetadata(meanIds),
|
|
statTypes: ["mean", "min", "max"],
|
|
chartType: "line",
|
|
period: "hour",
|
|
})
|
|
).toMatchSnapshot();
|
|
});
|
|
|
|
it("appends current state for recent data", () => {
|
|
const id = "sensor.temp_indoor";
|
|
const recentNow = new Date(FIXED_EPOCH_MS + dayMs + 5 * 60 * 1000);
|
|
expect(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
hass: createMockHass({
|
|
[id]: createMockEntityState(id, "21.5", {
|
|
unit_of_measurement: "°C",
|
|
device_class: "temperature",
|
|
}),
|
|
}),
|
|
now: recentNow,
|
|
statisticsData: generateStatistics(5, {
|
|
ids: [id],
|
|
period: "hour",
|
|
days: 1,
|
|
}),
|
|
statisticsMetaData: buildMetadata([id]),
|
|
statTypes: ["mean"],
|
|
chartType: "line",
|
|
period: "hour",
|
|
})
|
|
).toMatchSnapshot();
|
|
});
|
|
|
|
it("infers the chart unit from metadata", () => {
|
|
const result = generateStatisticsChartData({
|
|
...baseParams,
|
|
statisticsData: generateStatistics(6, {
|
|
ids: meanIds,
|
|
period: "hour",
|
|
days: 1,
|
|
}),
|
|
statisticsMetaData: buildMetadata(meanIds),
|
|
statTypes: ["mean"],
|
|
chartType: "line",
|
|
});
|
|
expect(result?.unit).toBe("°C");
|
|
});
|
|
|
|
it("large dataset digest is stable", () => {
|
|
expect(
|
|
digestResult(
|
|
generateStatisticsChartData({
|
|
...baseParams,
|
|
statisticsData: generateStatistics(7, {
|
|
ids: meanIds,
|
|
period: "5minute",
|
|
days: 31,
|
|
}),
|
|
statisticsMetaData: buildMetadata(meanIds),
|
|
statTypes: ["mean", "min", "max"],
|
|
chartType: "line",
|
|
period: "5minute",
|
|
})
|
|
)
|
|
).toMatchSnapshot();
|
|
});
|
|
});
|