package uv import ( "image/color" "testing" "github.com/charmbracelet/x/ansi" xwindows "github.com/charmbracelet/x/windows" "github.com/lucasb-eyer/go-colorful" ) // TestLegacyKeyEncodingMethods tests all the LegacyKeyEncoding flag methods func TestLegacyKeyEncodingMethods(t *testing.T) { tests := []struct { name string method func(LegacyKeyEncoding) LegacyKeyEncoding flag uint32 }{ { name: "CtrlAt true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlAt(true) }, flag: flagCtrlAt, }, { name: "CtrlAt false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlAt(false) }, flag: 0, }, { name: "CtrlI true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlI(true) }, flag: flagCtrlI, }, { name: "CtrlI false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlI(false) }, flag: 0, }, { name: "CtrlM true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlM(true) }, flag: flagCtrlM, }, { name: "CtrlM false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlM(false) }, flag: 0, }, { name: "CtrlOpenBracket true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlOpenBracket(true) }, flag: flagCtrlOpenBracket, }, { name: "CtrlOpenBracket false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.CtrlOpenBracket(false) }, flag: 0, }, { name: "Backspace true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.Backspace(true) }, flag: flagBackspace, }, { name: "Backspace false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.Backspace(false) }, flag: 0, }, { name: "Find true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.Find(true) }, flag: flagFind, }, { name: "Find false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.Find(false) }, flag: 0, }, { name: "Select true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.Select(true) }, flag: flagSelect, }, { name: "Select false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.Select(false) }, flag: 0, }, { name: "FKeys true", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.FKeys(true) }, flag: flagFKeys, }, { name: "FKeys false", method: func(l LegacyKeyEncoding) LegacyKeyEncoding { return l.FKeys(false) }, flag: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Start with all flags set if testing false, or no flags if testing true var initial LegacyKeyEncoding if tt.flag == 0 { initial = LegacyKeyEncoding(0xFFFFFFFF) } result := tt.method(initial) if tt.flag != 0 { // Testing true - flag should be set if uint32(result)&tt.flag == 0 { t.Errorf("expected flag %x to be set, got %x", tt.flag, uint32(result)) } } // else: Testing false - specific flag should be cleared. We can't check the exact value since we don't know which flag is being cleared. Just verify the method runs without panic. }) } } // TestDeviceAttributesParsing tests device attributes parsing functions func TestDeviceAttributesParsing(t *testing.T) { // Test parsePrimaryDevAttrs t.Run("parsePrimaryDevAttrs", func(t *testing.T) { params := ansi.Params{62, 1, 2, 6, 9} event := parsePrimaryDevAttrs(params) if pda, ok := event.(PrimaryDeviceAttributesEvent); ok { expected := []int{62, 1, 2, 6, 9} if len(pda) != len(expected) { t.Errorf("expected %d attrs, got %d", len(expected), len(pda)) } for i, v := range expected { if i < len(pda) && pda[i] != v { t.Errorf("attr[%d]: expected %d, got %d", i, v, pda[i]) } } } else { t.Errorf("expected PrimaryDeviceAttributesEvent, got %T", event) } }) // Test parseSecondaryDevAttrs t.Run("parseSecondaryDevAttrs", func(t *testing.T) { params := ansi.Params{1, 2, 3} event := parseSecondaryDevAttrs(params) if sda, ok := event.(SecondaryDeviceAttributesEvent); ok { expected := []int{1, 2, 3} if len(sda) != len(expected) { t.Errorf("expected %d attrs, got %d", len(expected), len(sda)) } for i, v := range expected { if i < len(sda) && sda[i] != v { t.Errorf("attr[%d]: expected %d, got %d", i, v, sda[i]) } } } else { t.Errorf("expected SecondaryDeviceAttributesEvent, got %T", event) } }) // Test parseTertiaryDevAttrs t.Run("parseTertiaryDevAttrs", func(t *testing.T) { data := []byte("4368726d") event := parseTertiaryDevAttrs(data) if tda, ok := event.(TertiaryDeviceAttributesEvent); ok { if expected := "Chrm"; string(tda) != expected { t.Errorf("expected '%s', got '%s'", expected, string(tda)) } } else { t.Errorf("expected TertiaryDeviceAttributesEvent, got %T", event) } }) } // TestHelperFunctions tests various helper functions func TestHelperFunctions(t *testing.T) { // Test shift t.Run("shift", func(t *testing.T) { if shift(uint(0)) != 0 { t.Errorf("shift(0) = %d, want 0", shift(uint(0))) } if shift(uint(1)) != 1 { t.Errorf("shift(1) = %d, want 1", shift(uint(1))) } if shift(uint(0x100)) != 1 { t.Errorf("shift(0x100) = %d, want 1", shift(uint(0x100))) } if shift(uint32(0x1000)) != 0x10 { t.Errorf("shift(0x1000) = %d, want 0x10", shift(uint32(0x1000))) } }) // Test colorToHex t.Run("colorToHex", func(t *testing.T) { tests := []struct { color color.Color want string }{ {color.RGBA{R: 255, G: 128, B: 64, A: 255}, "#ff8040"}, {color.RGBA{R: 0, G: 0, B: 0, A: 255}, "#000000"}, {color.RGBA{R: 255, G: 255, B: 255, A: 255}, "#ffffff"}, {nil, ""}, } for _, tt := range tests { got := colorToHex(tt.color) if got != tt.want { t.Errorf("colorToHex(%v) = %s, want %s", tt.color, got, tt.want) } } }) // Test getMaxMin t.Run("getMaxMin", func(t *testing.T) { tests := []struct { r, g, b float64 max, min float64 }{ {0.5, 0.3, 0.8, 0.8, 0.3}, {1.0, 1.0, 1.0, 1.0, 1.0}, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.2, 0.5, 0.3, 0.5, 0.2}, } for _, tt := range tests { max, min := getMaxMin(tt.r, tt.g, tt.b) if max != tt.max || min != tt.min { t.Errorf("getMaxMin(%f, %f, %f) = (%f, %f), want (%f, %f)", tt.r, tt.g, tt.b, max, min, tt.max, tt.min) } } }) // Test rgbToHSL t.Run("rgbToHSL", func(t *testing.T) { tests := []struct { r, g, b uint8 h, s, l float64 }{ {255, 0, 0, 0, 1.0, 0.5}, // Red {0, 255, 0, 120, 1.0, 0.5}, // Green {0, 0, 255, 240, 1.0, 0.5}, // Blue {128, 128, 128, 0, 0.0, 0.5}, // Gray {255, 255, 255, 0, 0.0, 1.0}, // White {0, 0, 0, 0, 0.0, 0.0}, // Black } for _, tt := range tests { h, s, l := rgbToHSL(tt.r, tt.g, tt.b) // Allow small floating point differences const epsilon = 0.01 if (h-tt.h) > epsilon || (s-tt.s) > epsilon || (l-tt.l) > epsilon { if !(tt.s == 0 && s == 0) { // When saturation is 0, hue is undefined t.Errorf("rgbToHSL(%d, %d, %d) = (%f, %f, %f), want (%f, %f, %f)", tt.r, tt.g, tt.b, h, s, l, tt.h, tt.s, tt.l) } } } }) // Test isDarkColor t.Run("isDarkColor", func(t *testing.T) { tests := []struct { hex string dark bool }{ {"#ffffff", false}, // White {"#000000", true}, // Black {"#808080", false}, // Medium gray {"#404040", true}, // Dark gray {"#c0c0c0", false}, // Light gray {"#ff0000", false}, // Red {"#800000", true}, // Dark red } for _, tt := range tests { c, _ := colorful.Hex(tt.hex) result := isDarkColor(c) if result != tt.dark { t.Errorf("isDarkColor(%s) = %v, want %v", tt.hex, result, tt.dark) } } }) } // TestParseTermcap tests termcap parsing func TestParseTermcap(t *testing.T) { tests := []struct { name string input []byte expectEvent CapabilityEvent }{ { name: "RGB capability", input: []byte("524742"), expectEvent: CapabilityEvent{"RGB"}, }, { name: "Co capability", input: []byte("436F=323536"), expectEvent: CapabilityEvent{"Co=256"}, }, { name: "Empty input", input: []byte(""), expectEvent: CapabilityEvent{""}, }, { name: "Invalid hex", input: []byte("GGGG"), expectEvent: CapabilityEvent{""}, }, { name: "Odd length hex", input: []byte("52474"), expectEvent: CapabilityEvent{""}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { event := parseTermcap(tt.input) if event != tt.expectEvent { t.Errorf("parseTermcap(%q) = %q, want %q", tt.input, event, tt.expectEvent) } }) } } // TestParseUtf8 tests UTF-8 parsing func TestParseUtf8(t *testing.T) { var p EventDecoder tests := []struct { name string input []byte wantN int wantEvent Event }{ { name: "empty input", input: []byte{}, wantN: 0, wantEvent: nil, }, { name: "control character", input: []byte{0x01}, // SOH wantN: 1, wantEvent: KeyPressEvent{Code: 'a', Mod: ModCtrl}, }, { name: "ASCII printable", input: []byte{'a'}, wantN: 1, wantEvent: KeyPressEvent{Code: 'a', Text: "a"}, }, { name: "uppercase letter", input: []byte{'A'}, wantN: 1, wantEvent: KeyPressEvent{Code: 'a', ShiftedCode: 'A', Text: "A", Mod: ModShift}, }, { name: "DEL character", input: []byte{0x7F}, // DEL wantN: 1, wantEvent: KeyPressEvent{Code: KeyBackspace}, }, { name: "UTF-8 multi-byte", input: []byte("€"), // Euro sign wantN: 3, wantEvent: KeyPressEvent{Code: '€', Text: "€"}, }, { name: "invalid UTF-8", input: []byte{0xFF}, wantN: 1, wantEvent: UnknownEvent("\u00ff"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { n, event := p.parseUtf8(tt.input) if n != tt.wantN { t.Errorf("parseUtf8() n = %d, want %d", n, tt.wantN) } if tt.wantEvent == nil { if event != nil { t.Errorf("parseUtf8() event = %v, want nil", event) } } else { switch want := tt.wantEvent.(type) { case KeyPressEvent: if got, ok := event.(KeyPressEvent); ok { if got.Code != want.Code || got.Text != want.Text || got.Mod != want.Mod || got.ShiftedCode != want.ShiftedCode { t.Errorf("parseUtf8() = %+v, want %+v", got, want) } } else { t.Errorf("parseUtf8() = %T, want KeyPressEvent", event) } case UnknownEvent: if got, ok := event.(UnknownEvent); ok { if got != want { t.Errorf("parseUtf8() = %v, want %v", got, want) } } else { t.Errorf("parseUtf8() = %T, want UnknownEvent", event) } } } }) } } // TestParseControl tests control character parsing func TestParseControl(t *testing.T) { var p EventDecoder tests := []struct { name string input byte legacy LegacyKeyEncoding wantEvent Event }{ { name: "NUL with CtrlAt flag", input: ansi.NUL, legacy: LegacyKeyEncoding(flagCtrlAt), wantEvent: KeyPressEvent{Code: '@', Mod: ModCtrl}, }, { name: "NUL without CtrlAt flag", input: ansi.NUL, legacy: 0, wantEvent: KeyPressEvent{Code: KeySpace, Mod: ModCtrl}, }, { name: "BS (backspace)", input: ansi.BS, legacy: 0, wantEvent: KeyPressEvent{Code: 'h', Mod: ModCtrl}, }, { name: "HT with CtrlI flag", input: ansi.HT, legacy: LegacyKeyEncoding(flagCtrlI), wantEvent: KeyPressEvent{Code: 'i', Mod: ModCtrl}, }, { name: "HT without CtrlI flag", input: ansi.HT, legacy: 0, wantEvent: KeyPressEvent{Code: KeyTab}, }, { name: "CR with CtrlM flag", input: ansi.CR, legacy: LegacyKeyEncoding(flagCtrlM), wantEvent: KeyPressEvent{Code: 'm', Mod: ModCtrl}, }, { name: "CR without CtrlM flag", input: ansi.CR, legacy: 0, wantEvent: KeyPressEvent{Code: KeyEnter}, }, { name: "ESC with CtrlOpenBracket flag", input: ansi.ESC, legacy: LegacyKeyEncoding(flagCtrlOpenBracket), wantEvent: KeyPressEvent{Code: '[', Mod: ModCtrl}, }, { name: "ESC without CtrlOpenBracket flag", input: ansi.ESC, legacy: 0, wantEvent: KeyPressEvent{Code: KeyEscape}, }, { name: "DEL with Backspace flag", input: ansi.DEL, legacy: LegacyKeyEncoding(flagBackspace), wantEvent: KeyPressEvent{Code: KeyDelete}, }, { name: "DEL without Backspace flag", input: ansi.DEL, legacy: 0, wantEvent: KeyPressEvent{Code: KeyBackspace}, }, { name: "Space", input: ansi.SP, legacy: 0, wantEvent: KeyPressEvent{Code: KeySpace, Text: " "}, }, { name: "Control-A (SOH)", input: ansi.SOH, legacy: 0, wantEvent: KeyPressEvent{Code: 'a', Mod: ModCtrl}, }, { name: "Control-Z (SUB)", input: ansi.SUB, legacy: 0, wantEvent: KeyPressEvent{Code: 'z', Mod: ModCtrl}, }, { name: "FS (File Separator)", input: ansi.FS, legacy: 0, wantEvent: KeyPressEvent{Code: '\\', Mod: ModCtrl}, }, { name: "US (Unit Separator)", input: ansi.US, legacy: 0, wantEvent: KeyPressEvent{Code: '_', Mod: ModCtrl}, }, { name: "Unknown control", input: 0x80, legacy: 0, wantEvent: UnknownEvent("\u0080"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p.Legacy = tt.legacy event := p.parseControl(tt.input) switch want := tt.wantEvent.(type) { case KeyPressEvent: if got, ok := event.(KeyPressEvent); ok { if got.Code != want.Code || got.Text != want.Text || got.Mod != want.Mod { t.Errorf("parseControl() = %+v, want %+v", got, want) } } else { t.Errorf("parseControl() = %T, want KeyPressEvent", event) } case UnknownEvent: if got, ok := event.(UnknownEvent); ok { if got != want { t.Errorf("parseControl() = %v, want %v", got, want) } } else { t.Errorf("parseControl() = %T, want UnknownEvent", event) } } }) } } // TestWin32Functions tests Win32-related functions func TestWin32Functions(t *testing.T) { // Test ensureKeyCase t.Run("ensureKeyCase", func(t *testing.T) { tests := []struct { name string key Key flags uint32 wantCode rune wantShifted rune wantText string }{ { name: "uppercase with shift", key: Key{Code: 'a', Text: "A", Mod: ModShift}, flags: xwindows.SHIFT_PRESSED, wantCode: 'a', wantShifted: 'A', wantText: "A", }, { name: "lowercase without shift", key: Key{Code: 'a', Text: "a", Mod: 0}, flags: 0, wantCode: 'a', wantText: "a", }, { name: "uppercase without shift", key: Key{Code: 'A', Text: "A", Mod: 0}, flags: 0, wantCode: 'A', wantShifted: 'a', wantText: "a", }, { name: "non-letter", key: Key{Code: '1', Text: "1", Mod: 0}, flags: 0, wantCode: '1', wantText: "1", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ensureKeyCase(tt.key, tt.flags) if result.Text != tt.wantText { t.Errorf("ensureKeyCase(%+v, %d).Text = %q, want %q", tt.key, tt.flags, result.Text, tt.wantText) } if result.Code != tt.wantCode { t.Errorf("ensureKeyCase(%+v, %d).Code = %c, want %c", tt.key, tt.flags, result.Code, tt.wantCode) } if result.ShiftedCode != tt.wantShifted { t.Errorf("ensureKeyCase(%+v, %d).ShiftedCode = %c, want %c", tt.key, tt.flags, result.ShiftedCode, tt.wantShifted) } }) } }) // Test translateControlKeyState t.Run("translateControlKeyState", func(t *testing.T) { tests := []struct { state uint32 want KeyMod }{ {xwindows.RIGHT_ALT_PRESSED, ModAlt}, // RIGHT_ALT_PRESSED {xwindows.LEFT_ALT_PRESSED, ModAlt}, // LEFT_ALT_PRESSED {xwindows.RIGHT_CTRL_PRESSED, ModCtrl}, // RIGHT_CTRL_PRESSED {xwindows.LEFT_CTRL_PRESSED, ModCtrl}, // LEFT_CTRL_PRESSED {xwindows.SHIFT_PRESSED, ModShift}, // SHIFT_PRESSED {xwindows.NUMLOCK_ON, ModNumLock}, // NUMLOCK_ON {xwindows.SCROLLLOCK_ON, ModScrollLock}, // SCROLLLOCK_ON {xwindows.CAPSLOCK_ON, ModCapsLock}, // CAPSLOCK_ON {xwindows.ENHANCED_KEY, 0}, // ENHANCED_KEY {xwindows.RIGHT_ALT_PRESSED | xwindows.RIGHT_CTRL_PRESSED | xwindows.SHIFT_PRESSED, ModAlt | ModCtrl | ModShift}, // Combination } for _, tt := range tests { got := translateControlKeyState(tt.state) if got != tt.want { t.Errorf("translateControlKeyState(0x%04x) = %d, want %d", tt.state, got, tt.want) } } }) // Test parseWin32InputKeyEvent t.Run("parseWin32InputKeyEvent", func(t *testing.T) { var p EventDecoder tests := []struct { name string vk uint16 sc uint16 uc rune kd bool cs uint32 rc uint16 want Event }{ { name: "simple key press", vk: 0x41, // VK_A sc: 0, uc: 'a', kd: true, cs: 0, rc: 1, want: KeyPressEvent{Code: 'a', Text: "a"}, }, { name: "key release", vk: 0x41, // VK_A sc: 0, uc: 'a', kd: false, cs: 0, rc: 1, want: KeyReleaseEvent{Code: 'a', Text: "a"}, }, { name: "function key", vk: 0x70, // VK_F1 sc: 0, uc: 0, kd: true, cs: 0, rc: 1, want: KeyPressEvent{Code: KeyF1}, }, { name: "enter key", vk: 0x0D, // VK_RETURN sc: 0, uc: '\r', kd: true, cs: 0, rc: 1, want: KeyPressEvent{Code: KeyEnter}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := p.parseWin32InputKeyEvent(tt.vk, tt.sc, tt.uc, tt.kd, tt.cs, tt.rc) if got == nil { t.Fatal("parseWin32InputKeyEvent returned nil") } // Basic type check switch tt.want.(type) { case KeyPressEvent: if _, ok := got.(KeyPressEvent); !ok { t.Errorf("expected KeyPressEvent, got %T", got) } case KeyReleaseEvent: if _, ok := got.(KeyReleaseEvent); !ok { t.Errorf("expected KeyReleaseEvent, got %T", got) } } }) } }) }