package tokens import ( "strings" "testing" "amadan.net/rastrillo/fiesta" ) // A shadow is the interesting negative. It CONTAINS a colour and is not // one, it gets a different preview (a card wearing it rather than a // chip painted with it), and a pattern loose enough to match one would // silently reclassify all four depth tokens. func TestColourValueRecognisesADerivedColour(t *testing.T) { for _, v := range []string{ "#fff", "#ffffff", "rgb(0, 0, 0)", "rgba(0, 0, 0, 0.45)", "var(--rst-line)", "color-mix(in oklab, var(--rst-accent) 18%, var(--rst-line))", "color-mix(in oklab, var(--rst-accent) 45%, var(--rst-line))", // day's accent derivations: a hue taken from the accent, a mix // whose source carries a fallback, and a bare var() with a // light-dark() fallback. "oklch(from var(--rst-accent, #1f56d6) 0.513 0.21 h)", "color-mix(in srgb, var(--rst-accent, #1f56d6) 4.5%, var(--rst-surface))", "var(--rst-accent, light-dark(#1f56d6, #7aa2ff))", } { if !colourValue.MatchString(v) { t.Errorf("colourValue does not match %q, so the Tokens page would draw its row with no swatch", v) } } for _, v := range []string{ "0 8px 24px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.5))", "0 1px 2px rgba(0, 0, 0, 0.3)", "8px", "system-ui, -apple-system, sans-serif", "999px", } { if colourValue.MatchString(v) { t.Errorf("colourValue matches %q, which is not a colour on its own; a chip painted with it would preview nothing and a shadow would lose the preview it has", v) } } } // lightHalf takes the first half of a whole-value light-dark() and // leaves everything else alone, a shadow carrying one inside it // included. func TestLightHalfSplitsOnlyAWholeValue(t *testing.T) { for in, want := range map[string]string{ "light-dark(#fff, #000)": "#fff", "light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.5))": "rgba(0, 0, 0, 0.12)", "light-dark(color-mix(in oklab, #fff 10%, #000), #000)": "color-mix(in oklab, #fff 10%, #000)", "0 8px 24px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.5))": "0 8px 24px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.5))", "#1f56d6": "#1f56d6", "light-dark(#fff": "light-dark(#fff", } { if got := lightHalf(in); got != want { t.Errorf("lightHalf(%q) = %q, want %q", in, got, want) } } } // Every theme yields the groups the page draws, every colour row in // them a chip, and nothing lands in "other": a token that does is a // token nobody filed under a heading. func TestEveryThemeHasAPaletteAndNothingIsOther(t *testing.T) { structure, err := Structural() if err != nil { t.Fatal(err) } keys := map[string]int{} for _, g := range structure { keys[g.Key] = len(g.Rows) } if keys["type-scale"] == 0 || keys["spacing"] == 0 { t.Errorf("Structural: type-scale has %d rows and spacing %d; tokens.css's :root block holds both scales", keys["type-scale"], keys["spacing"]) } for _, theme := range fiesta.ThemeNames() { groups, err := Palette(theme) if err != nil { t.Fatalf("%s: %v", theme, err) } seen := map[string]bool{} for _, g := range groups { if seen[g.Key] { t.Errorf("%s: the %s group appears twice", theme, g.Key) } seen[g.Key] = true if len(g.Rows) == 0 { t.Errorf("%s: the %s group has no rows", theme, g.Key) } for _, r := range g.Rows { if r.Colour && r.Preview == "" { t.Errorf("%s: %s is a colour with no preview", theme, r.Name) } if strings.HasPrefix(r.Value, "light-dark(") && g.Kind == "colour" { t.Errorf("%s: %s still reads %q; the palette shows the light half", theme, r.Name, r.Value) } } if g.Key == "other" { var names []string for _, r := range g.Rows { names = append(names, r.Name) } t.Errorf("%s: %d tokens fell through to \"other\": %v", theme, len(g.Rows), names) } } for _, want := range []string{"surfaces-and-lines", "text", "accent", "status-tones", "depth", "controls", "radius", "type-family"} { if !seen[want] { t.Errorf("%s: no %s group", theme, want) } } } }