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.