test: improve coverage to 98.2%, add staticcheck to CI (#7)

* test: improve coverage to 98.2%, add staticcheck to CI

- Fix comment typos (Intensty → Intensity)
- Add tests for createStringerPalette modes (background fill, smart mode)
- Add tests for ColorString variants, BytesToColor determinism
- Add tests for GetBackgroundColor mid-tone edge case
- Add tests for GenerateOKLCHPalette negative/large inputs
- Add Go 1.25 to CI matrix for compatibility testing
- Add staticcheck lint step to CI
- Fix goimports formatting in hash_test.go

* fix(ci): remove Go 1.25 from matrix (go.mod requires 1.26)
This commit is contained in:
2026-03-26 12:18:53 -04:00
committed by GitHub
parent e46914dabb
commit b653c3a59b
4 changed files with 131 additions and 5 deletions

View File

@@ -21,6 +21,11 @@ jobs:
with: with:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
- run: go vet ./... - run: go vet ./...
- name: staticcheck
if: matrix.go-version == '1.26'
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
- run: go test -race -coverprofile=coverage.txt ./... - run: go test -race -coverprofile=coverage.txt ./...
- name: Upload coverage - name: Upload coverage
if: matrix.go-version == '1.26' if: matrix.go-version == '1.26'

View File

@@ -52,7 +52,7 @@ var (
OnCyan = ColorString("\033[46m%s\033[0m") OnCyan = ColorString("\033[46m%s\033[0m")
OnWhite = ColorString("\033[47m%s\033[0m") OnWhite = ColorString("\033[47m%s\033[0m")
// High Intensty // High Intensity
IBlack = ColorString("\033[0;90m%s\033[0m") IBlack = ColorString("\033[0;90m%s\033[0m")
IRed = ColorString("\033[0;91m%s\033[0m") IRed = ColorString("\033[0;91m%s\033[0m")
IGreen = ColorString("\033[0;92m%s\033[0m") IGreen = ColorString("\033[0;92m%s\033[0m")
@@ -62,7 +62,7 @@ var (
ICyan = ColorString("\033[0;96m%s\033[0m") ICyan = ColorString("\033[0;96m%s\033[0m")
IWhite = ColorString("\033[0;97m%s\033[0m") IWhite = ColorString("\033[0;97m%s\033[0m")
// Bold High Intensty // Bold High Intensity
BIBlack = ColorString("\033[1;90m%s\033[0m") BIBlack = ColorString("\033[1;90m%s\033[0m")
BIRed = ColorString("\033[1;91m%s\033[0m") BIRed = ColorString("\033[1;91m%s\033[0m")
BIGreen = ColorString("\033[1;92m%s\033[0m") BIGreen = ColorString("\033[1;92m%s\033[0m")
@@ -72,7 +72,7 @@ var (
BICyan = ColorString("\033[1;96m%s\033[0m") BICyan = ColorString("\033[1;96m%s\033[0m")
BIWhite = ColorString("\033[1;97m%s\033[0m") BIWhite = ColorString("\033[1;97m%s\033[0m")
// High Intensty backgrounds // High Intensity backgrounds
OnIBlack = ColorString("\033[0;100m%s\033[0m") OnIBlack = ColorString("\033[0;100m%s\033[0m")
OnIRed = ColorString("\033[0;101m%s\033[0m") OnIRed = ColorString("\033[0;101m%s\033[0m")
OnIGreen = ColorString("\033[0;102m%s\033[0m") OnIGreen = ColorString("\033[0;102m%s\033[0m")

View File

@@ -12,8 +12,8 @@ import (
type testPalette []color.Color type testPalette []color.Color
func (p testPalette) ToPalette() color.Palette { return color.Palette(p) } func (p testPalette) ToPalette() color.Palette { return color.Palette(p) }
func (p testPalette) Get(i int) color.Color { return p[i] } func (p testPalette) Get(i int) color.Color { return p[i] }
func (p testPalette) Len() int { return len(p) } func (p testPalette) Len() int { return len(p) }
func newTestPalette() testPalette { func newTestPalette() testPalette {
return testPalette{ return testPalette{
@@ -153,6 +153,106 @@ func TestStringerPalette(t *testing.T) {
} }
} }
func TestCreateStringerPaletteBackgroundFill(t *testing.T) {
palette := newTestPalette()
sp := createStringerPalette(true, false, palette)
if len(sp) != len(palette) {
t.Fatalf("expected %d entries, got %d", len(palette), len(sp))
}
result := sp.GetString("test-bg")
if result == "" {
t.Fatal("GetString with background fill returned empty string")
}
if result == "test-bg" {
t.Fatal("GetString with background fill did not wrap with escape codes")
}
}
func TestCreateStringerPaletteDisableSmart(t *testing.T) {
palette := newTestPalette()
sp := createStringerPalette(false, true, palette)
if len(sp) != len(palette) {
t.Fatalf("expected %d entries, got %d", len(palette), len(sp))
}
result := sp.GetString("test-nosmart")
if result == "" {
t.Fatal("GetString with smart mode disabled returned empty string")
}
}
func TestCreateStringerPaletteMultipleSets(t *testing.T) {
p1 := newTestPalette()
p2 := testPalette{
simplecolor.FromRGBA(100, 100, 100, 255),
simplecolor.FromRGBA(200, 200, 200, 255),
}
sp := createStringerPalette(false, false, p1, p2)
if len(sp) != len(p1)+len(p2) {
t.Fatalf("expected %d entries, got %d", len(p1)+len(p2), len(sp))
}
}
func TestGetBackgroundColorMidTone(t *testing.T) {
// A mid-tone color to exercise the luminance threshold
mid := simplecolor.FromRGBA(128, 128, 128, 255)
bg := GetBackgroundColor(mid)
// Should return a valid color (either black or white)
r, g, b, _ := bg.RGBA()
isBlack := r == 0 && g == 0 && b == 0
isWhite := r == 255 && g == 255 && b == 255
if !isBlack && !isWhite {
t.Errorf("expected black or white background, got (%d,%d,%d)", r, g, b)
}
}
func TestColorStringVariants(t *testing.T) {
variants := []struct {
name string
fn ColorStringer
}{
{"Green", Green},
{"Yellow", Yellow},
{"Purple", Purple},
{"Magenta", Magenta},
{"BBlue", BBlue},
{"UCyan", UCyan},
{"OnRed", OnRed},
{"IBlue", IBlue},
{"BICyan", BICyan},
{"OnIGreen", OnIGreen},
}
for _, v := range variants {
t.Run(v.name, func(t *testing.T) {
result := v.fn("test")
if result == "" {
t.Fatalf("%s returned empty string", v.name)
}
if result == "test" {
t.Fatalf("%s did not apply escape codes", v.name)
}
})
}
}
func TestHashBytesEmpty(t *testing.T) {
h := HashBytes(bytes.NewReader([]byte{}))
if h < 0 {
t.Errorf("HashBytes with empty input returned negative: %d", h)
}
}
func TestBytesToColorDeterministic(t *testing.T) {
palette := newTestPalette()
input := []byte("consistent-bytes")
c1 := BytesToColor(palette, bytes.NewReader(input))
c2 := BytesToColor(palette, bytes.NewReader(input))
r1, g1, b1, a1 := c1.RGBA()
r2, g2, b2, a2 := c2.RGBA()
if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 {
t.Error("BytesToColor is not deterministic")
}
}
func TestDifferentInputsDifferentColors(t *testing.T) { func TestDifferentInputsDifferentColors(t *testing.T) {
palette := newTestPalette() palette := newTestPalette()
type rgba struct{ r, g, b, a uint32 } type rgba struct{ r, g, b, a uint32 }

View File

@@ -55,6 +55,27 @@ func TestGenerateOKLCHPaletteSingle(t *testing.T) {
} }
} }
func TestGenerateOKLCHPaletteNegative(t *testing.T) {
palette := GenerateOKLCHPalette(-5, 0.7, 0.15)
if len(palette) != 0 {
t.Errorf("expected empty palette for negative n, got %d colors", len(palette))
}
}
func TestGenerateOKLCHPaletteLarge(t *testing.T) {
palette := GenerateOKLCHPalette(360, 0.7, 0.15)
if len(palette) != 360 {
t.Fatalf("expected 360 colors, got %d", len(palette))
}
// Hue step should be ~1° for 360 colors
c0 := palette[0].ToOKLCH()
c1 := palette[1].ToOKLCH()
step := c1.H - c0.H
if math.Abs(step-1.0) > 1.0 {
t.Errorf("expected ~1° hue step, got %f°", step)
}
}
func TestGenerateOKLCHPaletteDistinct(t *testing.T) { func TestGenerateOKLCHPaletteDistinct(t *testing.T) {
palette := GenerateOKLCHPalette(6, 0.7, 0.15) palette := GenerateOKLCHPalette(6, 0.7, 0.15)
// All colors should be distinct // All colors should be distinct