package theme
import (
"math"
"strings"
"testing"
)
// Every theme holds the floor quarry's own palette holds: each text
// colour at 4.5:1 on each ground it is drawn on.
func TestEveryThemeReadsOnItsGrounds(t *testing.T) {
text := []string{"--fg", "--add", "--del", "--syn-kw", "--syn-str", "--syn-num",
"--syn-fn", "--syn-type", "--syn-com", "--syn-builtin", "--syn-attr"}
grounds := []string{"--code-block", "--add-row", "--del-row", "--add-mark", "--del-mark"}
for _, th := range All {
for _, g := range grounds {
for _, c := range text {
fg, bg := th.Value(c), th.Value(g)
if r := Contrast(fg, bg); r < 4.5 {
t.Errorf("FAIL: %s: %s %s on %s %s is %.2f:1", th.ID, c, fg, g, bg, r)
}
}
}
for _, c := range []string{"--fg", "--muted", "--accent"} {
if r := Contrast(th.Value(c), th.Value("--bg")); r < 4.5 {
t.Errorf("FAIL: %s: %s on the page is %.2f:1", th.ID, c, r)
}
}
}
}
// What is not text still has to be seen: a line on the page and on a
// code block, a code pill on the page (and what is written on it), a
// highlight on the code it marks. Material and Rosé Pine once drew
// their lines in the page's own colour. A line stays a line, too: no
// heavier than 3:1 unless the theme is a high-contrast one.
func TestEveryThemeDrawsWhatIsNotText(t *testing.T) {
for _, th := range All {
v := th.Value
for _, g := range []string{"--bg", "--code-block"} {
if r := Contrast(v("--rule"), v(g)); r < 1.45 {
t.Errorf("FAIL: %s: --rule on %s is %.2f:1", th.ID, g, r)
}
}
if r := Contrast(v("--rule"), v("--bg")); r > 3 && !strings.Contains(th.ID, "high-contrast") {
t.Errorf("FAIL: %s: --rule on the page is %.2f:1", th.ID, r)
}
if r := Contrast(v("--code-bg"), v("--bg")); r < 1.12 || r > 1.35 {
t.Errorf("FAIL: %s: --code-bg on the page is %.2f:1", th.ID, r)
}
for _, c := range []string{"--fg", "--muted", "--accent", "--add", "--del"} {
for _, g := range []string{"--bg", "--code-bg"} {
if r := Contrast(v(c), v(g)); r < 4.5 {
t.Errorf("FAIL: %s: %s on %s is %.2f:1", th.ID, c, g, r)
}
}
}
if d := math.Abs(luminance(v("--mark")) - luminance(v("--code-block"))); d < 0.06 {
t.Errorf("FAIL: %s: --mark differs from the code by %.3f luminance", th.ID, d)
}
if r := Contrast(v("--fg"), v("--mark")); r < 4.5 {
t.Errorf("FAIL: %s: --fg on --mark is %.2f:1", th.ID, r)
}
}
}
// A theme is chosen by id from a cookie and served at its own URL, so
// both must be unique and every theme must carry every token a sheet
// writes.
func TestThemesAreWholeAndDistinct(t *testing.T) {
if len(All) < 60 {
t.Fatalf("FAIL: %d themes, want hunk's full set", len(All))
}
ids, urls := map[string]bool{}, map[string]bool{}
for _, th := range All {
if ids[th.ID] {
t.Errorf("FAIL: duplicate id %s", th.ID)
}
ids[th.ID] = true
for _, u := range []string{SiteURL(th), CodeURL(th)} {
if urls[u] {
t.Errorf("FAIL: duplicate url %s", u)
}
urls[u] = true
}
for _, name := range CodeTokens {
if th.Value(name) == "" {
t.Errorf("FAIL: %s has no %s", th.ID, name)
}
}
}
if len(Assets()) != 2*(len(All)+len(Families)) {
t.Errorf("FAIL: %d assets for %d themes and %d families", len(Assets()), len(All), len(Families))
}
if _, ok := ByID("gruvbox-dark-hard"); !ok {
t.Error("FAIL: no gruvbox-dark-hard")
}
}
// The sheets are what the browser reads: the site one on the root,
// above paletteCSS's own selectors; the code one on what draws code,
// with only the code tokens.
func TestSheetsTargetTheRightElements(t *testing.T) {
th, _ := ByID("catppuccin-mocha")
site, code := SiteCSS(th), CodeCSS(th)
if !strings.HasPrefix(site, ":root:root{") || !strings.Contains(site, "--bg:") || !strings.Contains(site, "color-scheme:dark") {
t.Errorf("FAIL: site sheet: %s", site)
}
if !strings.HasPrefix(code, codeSelector+"{") || strings.Contains(code, "--bg:") || strings.Contains(code, "--accent:") {
t.Errorf("FAIL: code sheet: %s", code)
}
if !strings.Contains(code, "--syn-kw:") || !strings.Contains(code, "--add-row:") {
t.Errorf("FAIL: code sheet lacks syntax or diff tokens: %s", code)
}
}