import { describe, it, expect } from "vitest"; import colors, { dimColor, lighterColor, fullColor, darken, resolveColors, deviceColorMap, } from "./colors"; import type { DeviceColors } from "./types/evcc"; describe("setAlpha helpers", () => { it("appends alpha to 6-digit hex", () => { expect(dimColor("#abcdef")).toBe("#abcdef20"); expect(lighterColor("#abcdef")).toBe("#abcdefaa"); expect(fullColor("#abcdef")).toBe("#abcdefff"); }); it("replaces alpha on 8-digit hex", () => { expect(dimColor("#abcdefff")).toBe("#abcdef20"); expect(lighterColor("#abcdef12")).toBe("#abcdefaa"); expect(fullColor("#abcdef00")).toBe("#abcdefff"); }); it("handles null/undefined", () => { expect(dimColor(null)).toBeUndefined(); expect(lighterColor(null)).toBeUndefined(); expect(fullColor(null)).toBeUndefined(); }); it("returns input unchanged for unexpected length", () => { expect(dimColor("rgb(1,2,3)")).toBe("rgb(1,2,3)"); }); }); describe("darken", () => { it("scales rgb toward black by factor, staying opaque", () => { expect(darken("#ffffff", 0.5)).toBe("#808080"); expect(darken("#0fde41", 1)).toBe("#0fde41"); expect(darken("#0fde41", 0)).toBe("#000000"); }); it("clamps factor and ignores non-6-digit hex", () => { expect(darken("#abcdef", 2)).toBe("#abcdef"); expect(darken("rgb(1,2,3)", 0.5)).toBe("rgb(1,2,3)"); }); }); describe("resolveColors", () => { const palette = colors.palette; it("returns empty map for empty ids", () => { expect(resolveColors([], {})).toEqual({}); }); it("autoassigns palette in order without overrides", () => { const ids = ["a", "b", "c"]; expect(resolveColors(ids, {})).toEqual({ a: palette[0], b: palette[1], c: palette[2], }); }); it("respects explicit override", () => { const res = resolveColors(["a", "b"], { a: "#123456" }); expect(res["a"]).toBe("#123456"); expect(res["b"]).toBe(palette[0]); }); it("skips overridden palette entry on autoassign", () => { // override "a" with first palette color → others should not reuse it const res = resolveColors(["a", "b", "c"], { a: palette[0] }); expect(res["a"]).toBe(palette[0]); expect(res["b"]).toBe(palette[1]); expect(res["c"]).toBe(palette[2]); }); it("empty-string override falls back to autoassign", () => { const res = resolveColors(["a", "b"], { a: "" }); expect(res["a"]).toBe(palette[0]); expect(res["b"]).toBe(palette[1]); }); it("normalizes case when matching taken colors", () => { // override uses lowercase; should still skip the matching palette entry const lower = palette[0].toLowerCase(); const res = resolveColors(["a", "b"], { a: lower }); expect(res["a"]).toBe(lower); expect(res["b"]).toBe(palette[1]); }); it("wraps around once palette exhausted", () => { const ids = Array.from({ length: palette.length + 2 }, (_, i) => `id${i}`); const res = resolveColors(ids, {}); expect(res[ids[0]]).toBe(palette[0]); expect(res[ids[palette.length]]).toBe(palette[0]); expect(res[ids[palette.length + 1]]).toBe(palette[1]); }); it("with every palette entry overridden, autoassign wraps palette", () => { const overrides: DeviceColors = {}; palette.forEach((c, i) => (overrides[`o${i}`] = c)); const ids = [...Object.keys(overrides), "extra"]; const res = resolveColors(ids, overrides); palette.forEach((c, i) => expect(res[`o${i}`]).toBe(c)); // free list empty → falls back to wrap-around on palette expect(res["extra"]).toBe(palette[0]); }); }); describe("deviceColorMap", () => { it("returns empty map for undefined", () => { expect(deviceColorMap(undefined)).toEqual({}); }); it("returns empty map for empty list", () => { expect(deviceColorMap([])).toEqual({}); }); it("converts list of entries to title→color map", () => { expect( deviceColorMap([ { title: "WP-SG+", color: "#2563EB" }, { title: "Heizung", color: "#DC2626" }, ]) ).toEqual({ "WP-SG+": "#2563EB", Heizung: "#DC2626" }); }); });