current commit
260111.9 · chore: fix license boilerplate
// Copyright 2025 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package color

import (
	"fmt"
	ic "image/color"
	"testing"
)

// TestColorValues test variety of color edge cases.
func TestColorValues(t *testing.T) {
	var values = []struct {
		color Color
		hex   int32
	}{
		{Red, 0xFF0000},
		{Green, 0x008000},
		{Lime, 0x00FF00},
		{Blue, 0x0000FF},
		{Black, 0x000000},
		{White, 0xFFFFFF},
		{Silver, 0xC0C0C0},
		{Navy, 0x000080},
		{None, -1},
		{Reset, -1},
		{Color(300) | IsValid, -1}, // beyond the palette, marked valid
	}

	for _, tc := range values {
		if tc.color.Hex() != tc.hex {
			t.Errorf("Color: %x != %x", tc.color.Hex(), tc.hex)
		}

		if tc.color.TrueColor().Hex() != tc.hex {
			t.Errorf("TrueColor %x != %x", tc.color.TrueColor().Hex(), tc.hex)
		}
	}
}

// TestColorFitting tests color matching.
func TestColorFitting(t *testing.T) {
	var pal []Color
	for i := range 255 {
		pal = append(pal, PaletteColor(i))
	}

	// Exact color fitting on ANSI colors
	for i := range 7 {
		if Find(PaletteColor(i), pal[:8]) != PaletteColor(i) {
			t.Errorf("Color ANSI fit fail at %d", i)
		}
	}
	// Grey is closest to Silver
	if Find(PaletteColor(8), pal[:8]) != PaletteColor(7) {
		t.Errorf("Grey does not fit to silver")
	}
	// Color fitting of upper 8 colors.
	for i := 9; i < 16; i++ {
		if Find(PaletteColor(i), pal[:8]) != PaletteColor(i%8) {
			t.Errorf("Color fit fail at %d", i)
		}
	}
	// Imperfect fit
	if Find(OrangeRed, pal[:16]) != Red ||
		Find(AliceBlue, pal[:16]) != White ||
		Find(Pink, pal) != XTerm217 ||
		Find(Sienna, pal) != XTerm173 ||
		Find(GetColor("#00FD00"), pal) != Lime {
		t.Errorf("Imperfect color fit")
	}

}

// TestColorNameLookup tests looking up colors by a string name.
func TestColorNameLookup(t *testing.T) {
	var values = []struct {
		name  string
		color Color
		rgb   bool
	}{
		{"#FF0000", Red, true},
		{"black", Black, false},
		{"orange", Orange, true},
		{"door", Default, false},
	}
	for _, v := range values {
		c := GetColor(v.name)
		if c.Hex() != v.color.Hex() {
			t.Errorf("Wrong color for %v: %v", v.name, c.Hex())
		}
		if v.rgb {
			if !c.IsRGB() {
				t.Errorf("Color should have RGB: %v", v.name)
			}
		} else {
			if c.IsRGB() {
				t.Errorf("Named color should not be RGB: %v", v.name)
			}
		}

		if c.TrueColor().Hex() != v.color.Hex() {
			t.Errorf("TrueColor did not match")
		}
	}

	// these colors only have strings (for debugging), you cannot use them to create a color
	if None.String() != "none" {
		t.Errorf("ColorNone did not match")
	}
	if Reset.String() != "reset" {
		t.Errorf("Reset did not match")
	}
	if Default.String() != "default" {
		t.Errorf("Default did not match")
	}
}

// TestColorRGB tests the Color.RGB API.
func TestColorRGB(t *testing.T) {
	r, g, b := GetColor("#112233").RGB()
	if r != 0x11 || g != 0x22 || b != 0x33 {
		t.Errorf("RGB wrong (%x, %x, %x)", r, g, b)
	}
	r, g, b = None.RGB()
	if r != -1 || g != -1 || b != -1 {
		t.Errorf("None should not give valid RGB")
	}
}

// TestFromImageColor tests converting from image.Color to Color.
func TestFromImageColor(t *testing.T) {
	red := ic.RGBA{0xFF, 0x00, 0x00, 0x01}
	white := ic.Gray{0xFF}
	cyan := ic.CMYK{0xFF, 0x00, 0x00, 0x00}
	transparent := ic.RGBA{0x01, 0x02, 0x03, 0x00}

	if hex := FromImageColor(red).Hex(); hex != 0xFF0000 {
		t.Errorf("%v is not 0xFF0000", hex)
	}
	if hex := FromImageColor(white).Hex(); hex != 0xFFFFFF {
		t.Errorf("%v is not 0xFFFFFF", hex)
	}
	if hex := FromImageColor(cyan).Hex(); hex != 0x00FFFF {
		t.Errorf("%v is not 0x00FFFF", hex)
	}
	if c := FromImageColor(transparent); c != Default {
		t.Errorf("transparent should be default")
	}
}

// TestColorRGBA tests the Color.RGBA API.
func TestColorRGBA(t *testing.T) {
	r, g, b, a := Red.RGBA()
	if r != 0xffff || g != 0 || b != 0 || a != 0xffff {
		t.Errorf("Wrong RGBA for red: %x %x %x %x", r, g, b, a)
	}
	r, g, b, a = Red.TrueColor().RGBA()
	if r != 0xffff || g != 0 || b != 0 || a != 0xffff {
		t.Errorf("Wrong RGBA for red.TrueColor: %x %x %x %x", r, g, b, a)
	}

	r, g, b, a = Default.RGBA()
	if r != 0 || g != 0 || b != 0 || a != 0 {
		t.Errorf("Non-zero RGBA for default")
	}

	r, g, b, a = None.RGBA()
	if r != 0 || g != 0 || b != 0 || a != 0 {
		t.Errorf("Non-zero RGBA for none")
	}
	r, g, b, a = Reset.RGBA()
	if r != 0 || g != 0 || b != 0 || a != 0 {
		t.Errorf("Non-zero RGBA for reset")
	}
}

// TestColorNames tests the color.Name() API.
func TestColorNames(t *testing.T) {
	cases := []struct {
		c    Color
		name string
		css  string
	}{
		{Red, "red", "#FF0000"},
		{Pink, "pink", "#FFC0CB"},
		{Black, "black", "#000000"},
		{Black.TrueColor(), "", "#000000"},
		{XTerm100, "", "#878700"},
		{Color(1), "", ""}, // invalid color
	}
	for i, cs := range cases {
		t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
			if cs.c.CSS() != cs.css {
				t.Errorf("case %d: color css %q != %q", i, cs.c.CSS(), cs.css)
			}
			if cs.c.Name() != cs.name {
				t.Errorf("case %d: color name %q != %q", i, cs.c.Name(), cs.name)
			}
			exp := cs.c.Name()
			if exp == "" {
				exp = cs.c.CSS()
			}
			if cs.c.Name(true) != exp { // test css
				t.Errorf("case %d: color name(true) %q != %q", i, cs.c.Name(true), exp)
			}
		})
	}
}

// TestColorString tests the color.String() API.
func TestColorString(t *testing.T) {
	if s := Color(0).String(); s != "default" {
		t.Errorf("zero color not default: %q", s)
	}
	if s := Color(10).String(); s != "" {
		t.Errorf("invalid non-zero color did not yield empty string: %q", s)
	}
	if s := Red.String(); s != "red" {
		t.Errorf("wrong string for red: %q", s)
	}
}