diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8fc637..de373f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,11 @@ jobs: with: go-version: ${{ matrix.go-version }} - 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 ./... - name: Upload coverage if: matrix.go-version == '1.26' diff --git a/colors.go b/colors.go index c50b685..519b991 100644 --- a/colors.go +++ b/colors.go @@ -52,7 +52,7 @@ var ( OnCyan = ColorString("\033[46m%s\033[0m") OnWhite = ColorString("\033[47m%s\033[0m") - // High Intensty + // High Intensity IBlack = ColorString("\033[0;90m%s\033[0m") IRed = ColorString("\033[0;91m%s\033[0m") IGreen = ColorString("\033[0;92m%s\033[0m") @@ -62,7 +62,7 @@ var ( ICyan = ColorString("\033[0;96m%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") BIRed = ColorString("\033[1;91m%s\033[0m") BIGreen = ColorString("\033[1;92m%s\033[0m") @@ -72,7 +72,7 @@ var ( BICyan = ColorString("\033[1;96m%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") OnIRed = ColorString("\033[0;101m%s\033[0m") OnIGreen = ColorString("\033[0;102m%s\033[0m") diff --git a/hash_test.go b/hash_test.go index cc269d0..8944ab3 100644 --- a/hash_test.go +++ b/hash_test.go @@ -12,8 +12,8 @@ import ( type testPalette []color.Color func (p testPalette) ToPalette() color.Palette { return color.Palette(p) } -func (p testPalette) Get(i int) color.Color { return p[i] } -func (p testPalette) Len() int { return len(p) } +func (p testPalette) Get(i int) color.Color { return p[i] } +func (p testPalette) Len() int { return len(p) } func newTestPalette() 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) { palette := newTestPalette() type rgba struct{ r, g, b, a uint32 } diff --git a/oklch_test.go b/oklch_test.go index 1162031..59aaa42 100644 --- a/oklch_test.go +++ b/oklch_test.go @@ -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) { palette := GenerateOKLCHPalette(6, 0.7, 0.15) // All colors should be distinct