mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-20 02:38:53 +00:00
* Add time-based conditional visibility for cards * Move clearTimeout outside of scheduleUpdate * Add time string validation * Add time string validation * Remove runtime validation as config shouldnt allow bad values * Fix for midnight crossing * Cap timeout to 32-bit signed integer * Add listener tests * Additional tests * Format
321 lines
10 KiB
TypeScript
321 lines
10 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { calculateNextTimeUpdate } from "../../../src/common/condition/time-calculator";
|
|
import type { HomeAssistant } from "../../../src/types";
|
|
import {
|
|
NumberFormat,
|
|
TimeFormat,
|
|
FirstWeekday,
|
|
DateFormat,
|
|
TimeZone,
|
|
} from "../../../src/data/translation";
|
|
|
|
describe("calculateNextTimeUpdate", () => {
|
|
let mockHass: HomeAssistant;
|
|
|
|
beforeEach(() => {
|
|
mockHass = {
|
|
locale: {
|
|
language: "en-US",
|
|
number_format: NumberFormat.language,
|
|
time_format: TimeFormat.language,
|
|
date_format: DateFormat.language,
|
|
time_zone: TimeZone.local,
|
|
first_weekday: FirstWeekday.language,
|
|
},
|
|
config: {
|
|
time_zone: "America/Los_Angeles",
|
|
},
|
|
} as HomeAssistant;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe("after time calculation", () => {
|
|
it("should calculate time until after time today when it hasn't passed", () => {
|
|
// Set time to 7:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00" });
|
|
|
|
// Should be ~1 hour + 1 minute buffer = 61 minutes
|
|
expect(result).toBeGreaterThan(60 * 60 * 1000); // > 60 minutes
|
|
expect(result).toBeLessThan(62 * 60 * 1000); // < 62 minutes
|
|
});
|
|
|
|
it("should calculate time until after time tomorrow when it has passed", () => {
|
|
// Set time to 9:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 9, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00" });
|
|
|
|
// Should be ~23 hours + 1 minute buffer
|
|
expect(result).toBeGreaterThan(23 * 60 * 60 * 1000);
|
|
expect(result).toBeLessThan(24 * 60 * 60 * 1000);
|
|
});
|
|
|
|
it("should handle after time exactly at current time", () => {
|
|
// Set time to 8:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 8, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00" });
|
|
|
|
// Should be scheduled for tomorrow
|
|
expect(result).toBeGreaterThan(23 * 60 * 60 * 1000);
|
|
});
|
|
|
|
it("should handle after time with seconds", () => {
|
|
// Set time to 7:59:30 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 59, 30));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00:00" });
|
|
|
|
// Should be ~30 seconds + 1 minute buffer
|
|
expect(result).toBeGreaterThan(30 * 1000);
|
|
expect(result).toBeLessThan(2 * 60 * 1000);
|
|
});
|
|
});
|
|
|
|
describe("before time calculation", () => {
|
|
it("should calculate time until before time today when it hasn't passed", () => {
|
|
// Set time to 4:00 PM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 16, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { before: "17:00" });
|
|
|
|
// Should be ~1 hour + 1 minute buffer
|
|
expect(result).toBeGreaterThan(60 * 60 * 1000);
|
|
expect(result).toBeLessThan(62 * 60 * 1000);
|
|
});
|
|
|
|
it("should calculate time until before time tomorrow when it has passed", () => {
|
|
// Set time to 6:00 PM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 18, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { before: "17:00" });
|
|
|
|
// Should be ~23 hours + 1 minute buffer
|
|
expect(result).toBeGreaterThan(23 * 60 * 60 * 1000);
|
|
expect(result).toBeLessThan(24 * 60 * 60 * 1000);
|
|
});
|
|
});
|
|
|
|
describe("combined after and before", () => {
|
|
it("should return the soonest boundary when both are in the future", () => {
|
|
// Set time to 7:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
after: "08:00",
|
|
before: "17:00",
|
|
});
|
|
|
|
// Should return time until 8:00 AM (soonest)
|
|
expect(result).toBeGreaterThan(60 * 60 * 1000); // > 60 minutes
|
|
expect(result).toBeLessThan(62 * 60 * 1000); // < 62 minutes
|
|
});
|
|
|
|
it("should return the soonest boundary when within the range", () => {
|
|
// Set time to 10:00 AM (within 08:00-17:00 range)
|
|
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
after: "08:00",
|
|
before: "17:00",
|
|
});
|
|
|
|
// Should return time until 5:00 PM (next boundary)
|
|
expect(result).toBeGreaterThan(7 * 60 * 60 * 1000); // > 7 hours
|
|
expect(result).toBeLessThan(8 * 60 * 60 * 1000); // < 8 hours
|
|
});
|
|
|
|
it("should handle midnight crossing range", () => {
|
|
// Set time to 11:00 PM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 23, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
after: "22:00",
|
|
before: "06:00",
|
|
});
|
|
|
|
// Should return time until 6:00 AM (next boundary)
|
|
expect(result).toBeGreaterThan(7 * 60 * 60 * 1000); // > 7 hours
|
|
expect(result).toBeLessThan(8 * 60 * 60 * 1000); // < 8 hours
|
|
});
|
|
});
|
|
|
|
describe("weekday boundaries", () => {
|
|
it("should schedule for midnight when weekdays are specified (not all 7)", () => {
|
|
// Set time to Monday 10:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
weekdays: ["mon", "wed", "fri"],
|
|
});
|
|
|
|
// Should be scheduled for midnight (Tuesday)
|
|
expect(result).toBeGreaterThan(14 * 60 * 60 * 1000); // > 14 hours
|
|
expect(result).toBeLessThan(15 * 60 * 60 * 1000); // < 15 hours
|
|
});
|
|
|
|
it("should not schedule midnight when all 7 weekdays specified", () => {
|
|
// Set time to Monday 10:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
weekdays: ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
|
|
});
|
|
|
|
// Should return undefined (no boundaries)
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("should combine weekday midnight with after time", () => {
|
|
// Set time to Monday 7:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
after: "08:00",
|
|
weekdays: ["mon", "wed", "fri"],
|
|
});
|
|
|
|
// Should return the soonest (8:00 AM is sooner than midnight)
|
|
expect(result).toBeGreaterThan(60 * 60 * 1000); // > 60 minutes
|
|
expect(result).toBeLessThan(62 * 60 * 1000); // < 62 minutes
|
|
});
|
|
|
|
it("should prefer midnight over later time boundary", () => {
|
|
// Set time to Monday 11:00 PM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 23, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
after: "08:00",
|
|
weekdays: ["mon", "wed", "fri"],
|
|
});
|
|
|
|
// Should return midnight (sooner than 8:00 AM)
|
|
expect(result).toBeGreaterThan(60 * 60 * 1000); // > 1 hour
|
|
expect(result).toBeLessThan(2 * 60 * 60 * 1000); // < 2 hours
|
|
});
|
|
});
|
|
|
|
describe("no boundaries", () => {
|
|
it("should return undefined when no conditions specified", () => {
|
|
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {});
|
|
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("should return undefined when only all weekdays specified", () => {
|
|
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
weekdays: ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
|
|
});
|
|
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("should return undefined when empty weekdays array", () => {
|
|
vi.setSystemTime(new Date(2024, 0, 15, 10, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, {
|
|
weekdays: [],
|
|
});
|
|
|
|
expect(result).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("buffer addition", () => {
|
|
it("should add 1 minute buffer to next update time", () => {
|
|
// Set time to 7:59 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 59, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00" });
|
|
|
|
// Should be ~1 minute for the boundary + 1 minute buffer = ~2 minutes
|
|
expect(result).toBeGreaterThan(60 * 1000); // > 1 minute
|
|
expect(result).toBeLessThan(3 * 60 * 1000); // < 3 minutes
|
|
});
|
|
});
|
|
|
|
describe("timezone handling", () => {
|
|
it("should use server timezone when configured", () => {
|
|
mockHass.locale.time_zone = TimeZone.server;
|
|
mockHass.config.time_zone = "America/New_York";
|
|
|
|
// Set time to 7:00 AM local time
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00" });
|
|
|
|
// Should calculate based on server timezone
|
|
expect(result).toBeDefined();
|
|
expect(result).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should use local timezone when configured", () => {
|
|
mockHass.locale.time_zone = TimeZone.local;
|
|
|
|
// Set time to 7:00 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00" });
|
|
|
|
// Should calculate based on local timezone
|
|
expect(result).toBeDefined();
|
|
expect(result).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe("edge cases", () => {
|
|
it("should handle midnight (00:00) as after time", () => {
|
|
// Set time to 11:00 PM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 23, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "00:00" });
|
|
|
|
// Should be ~1 hour + 1 minute buffer until midnight
|
|
expect(result).toBeGreaterThan(60 * 60 * 1000);
|
|
expect(result).toBeLessThan(62 * 60 * 1000);
|
|
});
|
|
|
|
it("should handle 23:59 as before time", () => {
|
|
// Set time to 11:00 PM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 23, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { before: "23:59" });
|
|
|
|
// Should be ~59 minutes + 1 minute buffer
|
|
expect(result).toBeGreaterThan(59 * 60 * 1000);
|
|
expect(result).toBeLessThan(61 * 60 * 1000);
|
|
});
|
|
|
|
it("should handle very close boundary (seconds away)", () => {
|
|
// Set time to 7:59:50 AM
|
|
vi.setSystemTime(new Date(2024, 0, 15, 7, 59, 50));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "08:00:00" });
|
|
|
|
// Should be ~10 seconds + 1 minute buffer
|
|
expect(result).toBeGreaterThan(10 * 1000);
|
|
expect(result).toBeLessThan(2 * 60 * 1000);
|
|
});
|
|
|
|
it("should handle DST transition correctly", () => {
|
|
// March 10, 2024 at 1:00 AM PST - before spring forward
|
|
vi.setSystemTime(new Date(2024, 2, 10, 1, 0, 0));
|
|
|
|
const result = calculateNextTimeUpdate(mockHass, { after: "03:00" });
|
|
|
|
// Should handle the transition where 2:00 AM doesn't exist
|
|
expect(result).toBeDefined();
|
|
expect(result).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|