203 lines
8.8 KiB
JavaScript
203 lines
8.8 KiB
JavaScript
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" });
|
|
});
|
|
});
|