const { test } = require("node:test"); const assert = require("node:assert"); const { colorContrast, shade, tint, toHex, parseHexColor } = require("../lib/bootstrapColor"); const { solidButtonRule, outlineButtonRule, emitButtonRules } = require("../lib/deepOverlay"); // Ground truth extracted from the vendored Bootstrap 5.3.3 compiled CSS: // each variant -> { bg, color, hoverBg, activeBorder }. const ORACLES = { primary: { bg: "#0d6efd", color: "#ffffff", hoverBg: "#0b5ed7", activeBorder: "#0a53be" }, secondary: { bg: "#6c757d", color: "#ffffff", hoverBg: "#5c636a", activeBorder: "#51585e" }, success: { bg: "#198754", color: "#ffffff", hoverBg: "#157347", activeBorder: "#13653f" }, info: { bg: "#0dcaf0", color: "#000000", hoverBg: "#31d2f2", activeBorder: "#25cff2" }, warning: { bg: "#ffc107", color: "#000000", hoverBg: "#ffca2c", activeBorder: "#ffc720" }, danger: { bg: "#dc3545", color: "#ffffff", hoverBg: "#bb2d3b", activeBorder: "#a52834" }, light: { bg: "#f8f9fa", color: "#000000", hoverBg: "#d3d4d5", activeBorder: "#babbbc" }, dark: { bg: "#212529", color: "#ffffff", hoverBg: "#424649", activeBorder: "#373b3e" }, }; test("colorContrast matches Bootstrap's --bs-btn-color for all 8 variants", () => { for (const [name, o] of Object.entries(ORACLES)) { assert.equal(colorContrast(o.bg), o.color, `${name} (${o.bg})`); } }); test("solid .btn- rules match Bootstrap's compiled hover/active colors", () => { for (const [name, o] of Object.entries(ORACLES)) { const rule = solidButtonRule(name, o.bg); assert.ok(rule.includes(`--bs-btn-hover-bg:${o.hoverBg};`), `${name} hover-bg -> ${o.hoverBg}\n${rule}`); assert.ok(rule.includes(`--bs-btn-active-border-color:${o.activeBorder};`), `${name} active-border -> ${o.activeBorder}`); assert.ok(rule.includes(`--bs-btn-bg:${o.bg};`), `${name} bg`); } }); test("primary focus-shadow-rgb matches Bootstrap (49, 132, 253)", () => { assert.ok(solidButtonRule("primary", "#0d6efd").includes("--bs-btn-focus-shadow-rgb:49, 132, 253;")); assert.ok(solidButtonRule("warning", "#ffc107").includes("--bs-btn-focus-shadow-rgb:217, 164, 6;")); }); test("shade/tint match Bootstrap's Sass math", () => { assert.equal(toHex(shade(parseHexColor("#0d6efd"), 15)), "#0b5ed7"); assert.equal(toHex(tint(parseHexColor("#ffc107"), 15)), "#ffca2c"); assert.equal(toHex(shade(parseHexColor("#f8f9fa"), 25)), "#babbbc"); // light force-shade assert.equal(toHex(tint(parseHexColor("#212529"), 10)), "#373b3e"); // dark force-tint }); test("outline button inverts to the variant color", () => { const r = outlineButtonRule("primary", "#0d6efd"); assert.ok(r.includes("--bs-btn-color:#0d6efd;")); assert.ok(r.includes("--bs-btn-hover-bg:#0d6efd;")); assert.ok(r.includes("--bs-btn-disabled-bg:transparent;")); }); test("a recolored primary produces a recolored button (the whole point)", () => { const css = emitButtonRules({ primary: "#e83e8c" }); assert.match(css, /\.btn-primary\{[^}]*--bs-btn-bg:#e83e8c;/); // #e83e8c gets BLACK text via color-contrast -> tint mode -> hover LIGHTENS: assert.match(css, /--bs-btn-hover-bg:#eb5b9d;/); // tint(#e83e8c,15%) assert.match(css, /--bs-btn-color:#000000;/); // contrast picks dark text }); test("non-hex variant colors are skipped (no throw)", () => { const css = emitButtonRules({ primary: "var(--x)", success: "#198754" }); assert.doesNotMatch(css, /\.btn-primary\{/); assert.match(css, /\.btn-success\{/); });