|
| 1 | +import { describe, it, expect, beforeEach, afterEach } from "vitest" |
| 2 | +import { applyTenantTheme, resetTheme, updateFavicon } from "../theme-utils" |
| 3 | + |
| 4 | +describe("theme-utils", () => { |
| 5 | + beforeEach(() => { |
| 6 | + // Clean slate for each test |
| 7 | + document.documentElement.removeAttribute("data-tenant-theme") |
| 8 | + document.documentElement.style.cssText = "" |
| 9 | + // Remove any favicon links added during tests |
| 10 | + document.querySelectorAll("link[rel~='icon']").forEach((el) => el.remove()) |
| 11 | + }) |
| 12 | + |
| 13 | + afterEach(() => { |
| 14 | + resetTheme() |
| 15 | + }) |
| 16 | + |
| 17 | + describe("applyTenantTheme", () => { |
| 18 | + it("applies primaryColor as --primary CSS variable", () => { |
| 19 | + applyTenantTheme({ primaryColor: "#007bff" }) |
| 20 | + expect(document.documentElement.style.getPropertyValue("--primary")).toBe("#007bff") |
| 21 | + }) |
| 22 | + |
| 23 | + it("applies fontFamily as --font-family CSS variable", () => { |
| 24 | + applyTenantTheme({ primaryColor: "#000", fontFamily: "Inter" }) |
| 25 | + expect(document.documentElement.style.getPropertyValue("--font-family")).toBe("Inter") |
| 26 | + }) |
| 27 | + |
| 28 | + it("does not set --font-family when fontFamily is absent", () => { |
| 29 | + applyTenantTheme({ primaryColor: "#000" }) |
| 30 | + expect(document.documentElement.style.getPropertyValue("--font-family")).toBe("") |
| 31 | + }) |
| 32 | + |
| 33 | + it("records applied properties in data attribute", () => { |
| 34 | + applyTenantTheme({ primaryColor: "#ff0000", fontFamily: "Roboto" }) |
| 35 | + const attr = document.documentElement.getAttribute("data-tenant-theme") |
| 36 | + expect(attr).toContain("--primary") |
| 37 | + expect(attr).toContain("--font-family") |
| 38 | + }) |
| 39 | + |
| 40 | + it("sets favicon when faviconUrl is provided", () => { |
| 41 | + applyTenantTheme({ primaryColor: "#000", faviconUrl: "https://example.com/favicon.ico" }) |
| 42 | + const link = document.querySelector<HTMLLinkElement>("link[rel~='icon']") |
| 43 | + expect(link?.href).toBe("https://example.com/favicon.ico") |
| 44 | + }) |
| 45 | + |
| 46 | + it("does not create favicon element when faviconUrl is absent", () => { |
| 47 | + applyTenantTheme({ primaryColor: "#000" }) |
| 48 | + const link = document.querySelector("link[rel~='icon']") |
| 49 | + expect(link).toBeNull() |
| 50 | + }) |
| 51 | + |
| 52 | + it("overwrites a previous theme application", () => { |
| 53 | + applyTenantTheme({ primaryColor: "#ff0000" }) |
| 54 | + applyTenantTheme({ primaryColor: "#00ff00" }) |
| 55 | + expect(document.documentElement.style.getPropertyValue("--primary")).toBe("#00ff00") |
| 56 | + }) |
| 57 | + |
| 58 | + it("clears stale properties when switching to a theme with fewer properties", () => { |
| 59 | + applyTenantTheme({ primaryColor: "#ff0000", fontFamily: "Inter" }) |
| 60 | + expect(document.documentElement.style.getPropertyValue("--font-family")).toBe("Inter") |
| 61 | + |
| 62 | + // Switch to a theme without fontFamily — stale --font-family should be removed |
| 63 | + applyTenantTheme({ primaryColor: "#00ff00" }) |
| 64 | + expect(document.documentElement.style.getPropertyValue("--primary")).toBe("#00ff00") |
| 65 | + expect(document.documentElement.style.getPropertyValue("--font-family")).toBe("") |
| 66 | + }) |
| 67 | + }) |
| 68 | + |
| 69 | + describe("resetTheme", () => { |
| 70 | + it("removes --primary CSS variable", () => { |
| 71 | + applyTenantTheme({ primaryColor: "#007bff" }) |
| 72 | + resetTheme() |
| 73 | + expect(document.documentElement.style.getPropertyValue("--primary")).toBe("") |
| 74 | + }) |
| 75 | + |
| 76 | + it("removes --font-family CSS variable", () => { |
| 77 | + applyTenantTheme({ primaryColor: "#000", fontFamily: "Inter" }) |
| 78 | + resetTheme() |
| 79 | + expect(document.documentElement.style.getPropertyValue("--font-family")).toBe("") |
| 80 | + }) |
| 81 | + |
| 82 | + it("removes the data-tenant-theme attribute", () => { |
| 83 | + applyTenantTheme({ primaryColor: "#007bff" }) |
| 84 | + resetTheme() |
| 85 | + expect(document.documentElement.getAttribute("data-tenant-theme")).toBeNull() |
| 86 | + }) |
| 87 | + |
| 88 | + it("is a no-op when no theme was applied", () => { |
| 89 | + expect(() => resetTheme()).not.toThrow() |
| 90 | + expect(document.documentElement.getAttribute("data-tenant-theme")).toBeNull() |
| 91 | + }) |
| 92 | + |
| 93 | + it("restores the original favicon href", () => { |
| 94 | + // Set up a starting favicon |
| 95 | + const link = document.createElement("link") |
| 96 | + link.rel = "icon" |
| 97 | + link.href = "https://example.com/original.ico" |
| 98 | + document.head.appendChild(link) |
| 99 | + |
| 100 | + applyTenantTheme({ primaryColor: "#000", faviconUrl: "https://example.com/tenant.ico" }) |
| 101 | + expect(link.href).toBe("https://example.com/tenant.ico") |
| 102 | + |
| 103 | + resetTheme() |
| 104 | + expect(link.href).toBe("https://example.com/original.ico") |
| 105 | + }) |
| 106 | + }) |
| 107 | + |
| 108 | + describe("updateFavicon", () => { |
| 109 | + it("creates a favicon link element if none exists", () => { |
| 110 | + updateFavicon("https://example.com/favicon.ico") |
| 111 | + const link = document.querySelector<HTMLLinkElement>("link[rel~='icon']") |
| 112 | + expect(link).not.toBeNull() |
| 113 | + expect(link?.href).toBe("https://example.com/favicon.ico") |
| 114 | + }) |
| 115 | + |
| 116 | + it("updates an existing favicon link element", () => { |
| 117 | + const link = document.createElement("link") |
| 118 | + link.rel = "icon" |
| 119 | + link.href = "https://example.com/old.ico" |
| 120 | + document.head.appendChild(link) |
| 121 | + |
| 122 | + updateFavicon("https://example.com/new.ico") |
| 123 | + expect(link.href).toBe("https://example.com/new.ico") |
| 124 | + }) |
| 125 | + |
| 126 | + it("records the original href in data-default-href", () => { |
| 127 | + const link = document.createElement("link") |
| 128 | + link.rel = "icon" |
| 129 | + link.href = "https://example.com/original.ico" |
| 130 | + document.head.appendChild(link) |
| 131 | + |
| 132 | + updateFavicon("https://example.com/new.ico") |
| 133 | + expect(link.dataset.defaultHref).toBe("https://example.com/original.ico") |
| 134 | + }) |
| 135 | + |
| 136 | + it("does not overwrite data-default-href on subsequent calls", () => { |
| 137 | + const link = document.createElement("link") |
| 138 | + link.rel = "icon" |
| 139 | + link.href = "https://example.com/original.ico" |
| 140 | + document.head.appendChild(link) |
| 141 | + |
| 142 | + updateFavicon("https://example.com/first.ico") |
| 143 | + updateFavicon("https://example.com/second.ico") |
| 144 | + |
| 145 | + // defaultHref should still point to the original |
| 146 | + expect(link.dataset.defaultHref).toBe("https://example.com/original.ico") |
| 147 | + expect(link.href).toBe("https://example.com/second.ico") |
| 148 | + }) |
| 149 | + }) |
| 150 | +}) |
0 commit comments