168 lines
4.2 KiB
Go
168 lines
4.2 KiB
Go
|
|
package ui
|
||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
tea "github.com/charmbracelet/bubbletea"
|
||
|
|
|
||
|
|
"github.com/gmgauthier/gralculator/internal/calc"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestNewApp(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
if a == nil {
|
||
|
|
t.Fatal("NewApp returned nil")
|
||
|
|
}
|
||
|
|
if a.engine == nil {
|
||
|
|
t.Error("engine not initialized")
|
||
|
|
}
|
||
|
|
if a.engine.CurrentBase() != calc.BaseDEC {
|
||
|
|
t.Error("default base should be DEC")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_Quit(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
_, cmd := a.Update(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||
|
|
if cmd == nil {
|
||
|
|
t.Error("expected quit cmd for ctrl+c")
|
||
|
|
}
|
||
|
|
// Simulate the quit msg? But for now, check it returns quit-like
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_DigitsAndDisplay(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
a.height = 24
|
||
|
|
|
||
|
|
// Type 1 2 3
|
||
|
|
for _, d := range "123" {
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{d}})
|
||
|
|
}
|
||
|
|
|
||
|
|
view := a.View()
|
||
|
|
if !contains(view, "123") {
|
||
|
|
t.Errorf("expected 123 in view, got: %s", view)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_Operators(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
|
||
|
|
// 2 + 3 =
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'+'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'3'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyEnter})
|
||
|
|
|
||
|
|
view := a.View()
|
||
|
|
if !contains(view, "5") {
|
||
|
|
t.Errorf("expected result 5, got: %s", view)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_Mod(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
|
||
|
|
// 23 mod 6
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'3'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'m'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'6'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyEnter})
|
||
|
|
|
||
|
|
view := a.View()
|
||
|
|
if !contains(view, "5") {
|
||
|
|
t.Errorf("expected 23 mod 6 =5, got: %s", view)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_BaseCycleAndCERR(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
|
||
|
|
// 1 / 3 = (fraction)
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'1'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'/'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'3'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyEnter})
|
||
|
|
|
||
|
|
// Tab should CERR
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyTab})
|
||
|
|
|
||
|
|
view := a.View()
|
||
|
|
if !contains(view, "CERR") {
|
||
|
|
t.Errorf("expected CERR flash on fractional base switch, got: %s", view)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_HEXEntry(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
|
||
|
|
// Tab to HEX
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyTab})
|
||
|
|
|
||
|
|
// Type 1 A F (hex entry)
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'1'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'f'}})
|
||
|
|
|
||
|
|
view := a.View()
|
||
|
|
if !contains(view, "1AF") {
|
||
|
|
t.Errorf("expected raw HEX entry 1AF in display, got: %s", view)
|
||
|
|
}
|
||
|
|
if !contains(view, "[HEX]") {
|
||
|
|
t.Error("expected HEX base indicator")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_PressedFlash(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'5'}})
|
||
|
|
|
||
|
|
// After key, flash should be set (we can check internal or view for style, but simple state check)
|
||
|
|
if a.flash == "" && a.pressedKey == "" {
|
||
|
|
t.Error("expected flash or pressedKey after digit")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Simulate time tick clear
|
||
|
|
a.Update(clearFlashMsg{which: "key"})
|
||
|
|
if a.flash != "" || a.pressedKey != "" {
|
||
|
|
t.Error("flash should clear after msg")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApp_Update_ClearKeys(t *testing.T) {
|
||
|
|
a := NewApp()
|
||
|
|
a.width = 80
|
||
|
|
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'1'}})
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}})
|
||
|
|
|
||
|
|
// Backspace as C (clear entry)
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyBackspace})
|
||
|
|
view := a.View()
|
||
|
|
if !contains(view, "0") || contains(view, "12") {
|
||
|
|
t.Errorf("backspace/C should clear entry to 0, got: %s", view)
|
||
|
|
}
|
||
|
|
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'9'}})
|
||
|
|
// Del as AC
|
||
|
|
a.Update(tea.KeyMsg{Type: tea.KeyDelete})
|
||
|
|
if a.engine.CurrentBase() != calc.BaseDEC {
|
||
|
|
t.Error("Del/AC should reset base")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func contains(s, substr string) bool {
|
||
|
|
return strings.Contains(s, substr)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Note: clearFlashMsg is defined in ui.go (same package), so we can send it directly in tests.
|
||
|
|
// The tick func is also package-private but we test via direct messages where possible.
|