import { describe, expect, it } from "vitest"; import { buildHighlightConfig, evaluateHighlight, getHighlightClass } from "./highlights"; describe("utils/highlights", () => { it("returns null when there are no levels and no fields to evaluate", () => { const cfg = buildHighlightConfig( null, { levels: { good: null, warn: null, danger: null }, }, "x", ); expect(cfg).toBeNull(); }); it("buildHighlightConfig merges levels and namespaces unqualified field keys", () => { const cfg = buildHighlightConfig( { levels: { warn: "global-warn" } }, { levels: { warn: "widget-warn", custom: "widget-custom" }, cpu: { numeric: { when: "gt", value: 80, level: "warn" } }, }, "resources", ); expect(cfg).not.toBeNull(); expect(cfg.levels.warn).toBe("widget-warn"); expect(cfg.levels.custom).toBe("widget-custom"); // Field keys get normalized + namespaced. expect(cfg.fields.cpu).toBeTruthy(); expect(cfg.fields["resources.cpu"]).toBeTruthy(); }); it("normalizes field keys by trimming and skipping blank/null entries", () => { const cfg = buildHighlightConfig( null, { levels: { good: null, warn: null, danger: null, custom: "x" }, " cpu ": { numeric: { when: "gt", value: 1, level: "custom" } }, "": { numeric: { when: "gt", value: 1, level: "danger" } }, empty: null, }, "resources", ); expect(cfg.fields.cpu).toBeTruthy(); expect(cfg.fields["resources.cpu"]).toBeTruthy(); expect(cfg.fields.empty).toBeUndefined(); }); it("evaluateHighlight returns matching numeric rule with valueOnly metadata", () => { const cfg = buildHighlightConfig( null, { // valueOnly should propagate through the result so Block can apply styling. cpu: { valueOnly: true, numeric: { when: "gte", value: 90, level: "danger" } }, }, "resources", ); const hit = evaluateHighlight("resources.cpu", " 90 ", cfg); expect(hit).toMatchObject({ level: "danger", source: "numeric", valueOnly: true }); }); it("evaluateHighlight stringifies booleans and applies case-sensitive string rules", () => { const cfg = buildHighlightConfig(null, { enabled: { string: { when: "equals", value: "true", caseSensitive: true, level: "good" } }, suffix: { string: { when: "endsWith", value: "World", caseSensitive: true, level: "warn" } }, }); expect(evaluateHighlight("enabled", true, cfg)).toMatchObject({ level: "good", source: "string" }); expect(evaluateHighlight("suffix", "HelloWorld", cfg)).toMatchObject({ level: "warn", source: "string" }); expect(evaluateHighlight("suffix", "helloworld", cfg)).toBeNull(); }); it("evaluateHighlight supports string rules (case-insensitive includes)", () => { const cfg = buildHighlightConfig(null, { status: { string: { when: "includes", value: "down", level: "warn" } } }); const hit = evaluateHighlight("status", "Service DOWN", cfg); expect(hit).toMatchObject({ level: "warn", source: "string" }); }); it("getHighlightClass returns configured class for a level", () => { const cfg = buildHighlightConfig({ levels: { danger: "danger-class" } }, {}, "x"); expect(getHighlightClass("danger", cfg)).toBe("danger-class"); expect(getHighlightClass("missing", cfg)).toBeUndefined(); }); it("supports localized numeric parsing and between/outside operators (with negate)", () => { const cfg = buildHighlightConfig(null, { temp: { numeric: [ { when: "between", min: 1000.5, max: 1500.5, level: "warn" }, { when: "outside", value: { min: 1234.5, max: 2234.5 }, level: "danger", negate: true }, ], }, }); // "1.234,56" should parse as 1234.56 and hit the between rule. expect(evaluateHighlight("temp", "1.234,56", cfg)).toMatchObject({ level: "warn", source: "numeric" }); // Negated outside => inside the range should match. expect(evaluateHighlight("temp", "2.000,00", cfg)).toMatchObject({ level: "danger", source: "numeric" }); }); it("supports numeric parsing for dot/comma thousands formats", () => { const cfg = buildHighlightConfig(null, { num: { numeric: { when: "eq", value: 1234.56, level: "good" } }, grouped: { numeric: { when: "eq", value: 1234567, level: "warn" } }, }); expect(evaluateHighlight("num", "1,234.56", cfg)).toMatchObject({ level: "good", source: "numeric" }); expect(evaluateHighlight("grouped", "1.234.567", cfg)).toMatchObject({ level: "warn", source: "numeric" }); }); it("supports regex string rules, including invalid regex patterns (ignored)", () => { const cfg = buildHighlightConfig(null, { status: { string: [ { when: "regex", value: "^up$", level: "good" }, { when: "regex", value: "(", level: "danger" }, // invalid; should be ignored { when: "equals", value: "DOWN", level: "warn", caseSensitive: true }, ], }, }); expect(evaluateHighlight("status", "Up", cfg)).toMatchObject({ level: "good", source: "string" }); expect(evaluateHighlight("status", "DOWN", cfg)).toMatchObject({ level: "warn", source: "string" }); expect(evaluateHighlight("status", "Down", cfg)).toBeNull(); }); it("parses numeric strings with commas/dots/spaces and supports stringified numeric rule values", () => { const cfg = buildHighlightConfig(null, { // string numeric rule values go through toNumber() gt: { numeric: { when: "gt", value: "5", level: "warn" } }, withUnitSuffix: { numeric: { when: "gt", value: 5, level: "warn" } }, withUnitPrefix: { numeric: { when: "gt", value: 5, level: "warn" } }, localizedUnitSuffix: { numeric: { when: "gt", value: 0.5, level: "warn" } }, commaGrouped: { numeric: { when: "eq", value: 1234, level: "good" } }, commaDecimal: { numeric: { when: "eq", value: 12.34, level: "good" } }, dotDecimal: { numeric: { when: "eq", value: 12.34, level: "good" } }, spaceGrouped: { numeric: { when: "eq", value: 1234, level: "good" } }, }); expect(evaluateHighlight("gt", "6", cfg)).toMatchObject({ level: "warn", source: "numeric" }); expect(evaluateHighlight("withUnitSuffix", "5.2 ms", cfg)).toMatchObject({ level: "warn", source: "numeric" }); expect(evaluateHighlight("withUnitPrefix", "ms 5.2", cfg)).toMatchObject({ level: "warn", source: "numeric" }); expect(evaluateHighlight("localizedUnitSuffix", "0,71\u202Fms", cfg)).toMatchObject({ level: "warn", source: "numeric", }); expect(evaluateHighlight("commaGrouped", "1,234", cfg)).toMatchObject({ level: "good", source: "numeric" }); expect(evaluateHighlight("commaDecimal", "12,34", cfg)).toMatchObject({ level: "good", source: "numeric" }); // Include a space so Number(trimmed) fails and we exercise the dot parsing branch. expect(evaluateHighlight("dotDecimal", "12 .34", cfg)).toMatchObject({ level: "good", source: "numeric" }); expect(evaluateHighlight("spaceGrouped", "1 234", cfg)).toMatchObject({ level: "good", source: "numeric" }); }); it("treats unparseable numeric formats as non-numeric", () => { const cfg = buildHighlightConfig(null, { num: { numeric: { when: "gt", value: 0, level: "warn" } }, }); // Invalid comma grouping should not be treated as numeric. expect(evaluateHighlight("num", "1,2,3", cfg)).toBeNull(); // "1.2.3" is not a valid grouped or decimal number for our parser. expect(evaluateHighlight("num", "1.2.3", cfg)).toBeNull(); // Multiple numbers in one string should not be treated as a single numeric value. expect(evaluateHighlight("num", "5/10 ms", cfg)).toBeNull(); // JSX-ish values should not be treated as numeric. expect(evaluateHighlight("num", { props: { children: "x" } }, cfg)).toBeNull(); }); it("falls through numeric evaluation when numeric rules do not match", () => { const cfg = buildHighlightConfig(null, { status: { numeric: { when: "gte", value: 100, level: "danger" }, string: { when: "includes", value: "ok", level: "good" }, }, }); // Numeric rule doesn't match, string rule does. expect(evaluateHighlight("status", "ok", cfg)).toMatchObject({ level: "good", source: "string" }); }); it("stringifies numbers/bigints for string evaluation and ignores unknown numeric operators", () => { const cfg = buildHighlightConfig(null, { // unknown numeric operator should not match weird: { numeric: { when: "nope", value: 1, level: "warn" } }, // bigint should stringify to match a string rule big: { string: { when: "equals", value: "9007199254740993", level: "good", caseSensitive: true } }, }); expect(evaluateHighlight("weird", "10", cfg)).toBeNull(); expect(evaluateHighlight("big", 9007199254740993n, cfg)).toMatchObject({ level: "good", source: "string" }); }); });