gralculator/internal/calc/calc_test.go

210 lines
4.9 KiB
Go
Raw Normal View History

package calc
import (
"errors"
"testing"
)
func TestNewEngine_Defaults(t *testing.T) {
e := NewEngine()
if e.CurrentBase() != BaseDEC {
t.Errorf("expected DEC, got %s", e.CurrentBase())
}
if got := e.FormatForDisplay(); got != "0" {
t.Errorf("initial display: want 0, got %s", got)
}
}
func TestCycleBase_IntegerOK(t *testing.T) {
e := NewEngine()
e.accumulator = 42
e.entry = ""
if err := e.CycleBase(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if e.CurrentBase() != BaseHEX {
t.Errorf("expected HEX, got %s", e.CurrentBase())
}
if got := e.FormatForDisplay(); got != "2A" {
t.Errorf("hex of 42: want 2A, got %s", got)
}
}
func TestCycleBase_FractionalCERR(t *testing.T) {
e := NewEngine()
// 23/6 = 3.8333...
e.accumulator = 23.0 / 6.0
e.entry = ""
err := e.CycleBase()
if !errors.Is(err, ErrConversionNotPossible) {
t.Fatalf("expected ErrConversionNotPossible, got %v", err)
}
if e.CurrentBase() != BaseDEC {
t.Error("base must remain DEC after CERR")
}
}
func TestBasicArithmetic(t *testing.T) {
e := NewEngine()
// 2 + 3 =
e.EnterDigit('2')
e.SetOperator("+")
e.EnterDigit('3')
e.Equals()
if got := e.FormatForDisplay(); got != "5" {
t.Errorf("2+3: want 5, got %s", got)
}
// * 4 =
e.SetOperator("*")
e.EnterDigit('4')
e.Equals()
if got := e.FormatForDisplay(); got != "20" {
t.Errorf("5*4: want 20, got %s", got)
}
}
func TestMOD(t *testing.T) {
e := NewEngine()
e.EnterDigit('2')
e.EnterDigit('3')
e.SetOperator("mod")
e.EnterDigit('6')
e.Equals()
if got := e.FormatForDisplay(); got != "5" {
t.Errorf("23 mod 6: want 5, got %s", got)
}
}
func TestFractionalThenBASE_CERR(t *testing.T) {
e := NewEngine()
// 1 / 3 = 0.333...
e.EnterDigit('1')
e.SetOperator("/")
e.EnterDigit('3')
e.Equals()
if e.IsInteger() {
t.Error("1/3 should not be integer")
}
err := e.CycleBase()
if !errors.Is(err, ErrConversionNotPossible) {
t.Fatalf("expected CERR on fractional BASE, got %v", err)
}
}
func TestClearAndBackspace(t *testing.T) {
e := NewEngine()
e.EnterDigit('1')
e.EnterDigit('2')
e.EnterDigit('3')
e.Backspace()
if got := e.FormatForDisplay(); got != "12" {
t.Errorf("backspace: want 12, got %s", got)
}
e.ClearEntry()
if got := e.FormatForDisplay(); got != "0" {
t.Errorf("clear entry: want 0, got %s", got)
}
e.AllClear()
if e.CurrentBase() != BaseDEC {
t.Error("all clear should reset base to DEC for MVP")
}
}
func TestChangeSign(t *testing.T) {
e := NewEngine()
e.EnterDigit('5')
e.ChangeSign()
if got := e.FormatForDisplay(); got != "-5" {
t.Errorf("sign: want -5, got %s", got)
}
}
// More tests (entry buffer, multi-op, decimal point, etc.) will be added as the
// TUI spike exercises real usage. The CERR path is the critical one for BASE.
// === Tight-scope HEX entry tests ===
func TestEnterDigit_HEX(t *testing.T) {
e := NewEngine()
e.base = BaseHEX
e.EnterDigit('1')
e.EnterDigit('a')
e.EnterDigit('F')
if got := e.FormatForDisplay(); got != "1AF" {
t.Errorf("HEX entry display: want 1AF, got %s", got)
}
// Commit and check value (1AF hex = 431 decimal)
e.SetOperator("+")
e.EnterDigit('1') // simple commit
e.Equals()
if got := e.FormatForDisplay(); got != "432" {
t.Errorf("after hex commit +1: want 432, got %s", got)
}
}
func TestEnterDigit_HEX_IgnoresDecimal(t *testing.T) {
e := NewEngine()
e.base = BaseHEX
e.EnterDigit('1')
e.EnterDecimalPoint() // should be ignored
e.EnterDigit('A')
if got := e.FormatForDisplay(); got != "1A" {
t.Errorf("HEX should ignore dot: want 1A, got %s", got)
}
}
func TestEnterDigit_HEX_OnlyInHEXMode(t *testing.T) {
e := NewEngine() // starts in DEC
e.EnterDigit('1')
e.EnterDigit('A') // 'A' ignored in DEC (now validated)
if got := e.FormatForDisplay(); got != "1" {
t.Errorf("A ignored in DEC: want 1, got %s", got)
}
e.ClearEntry()
e.base = BaseHEX
e.EnterDigit('A')
if got := e.FormatForDisplay(); got != "A" {
t.Errorf("A accepted in HEX: want A, got %s", got)
}
}
func TestCycleBase_DuringHEXEntry(t *testing.T) {
e := NewEngine()
e.base = BaseHEX
e.EnterDigit('1')
e.EnterDigit('0') // 16 decimal
// Tab while in entry (from HEX): commit the hex value (16) then switch to next (BIN in this case)
err := e.CycleBase()
if err != nil {
t.Fatalf("unexpected CERR during integer hex entry: %v", err)
}
if e.CurrentBase() != BaseBIN {
t.Errorf("expected BIN after one cycle from HEX, got %s", e.CurrentBase())
}
// Display shows the committed value in the new base
if got := e.FormatForDisplay(); got != "10000" {
t.Errorf("committed hex value (16) shown in BIN: want 10000, got %s", got)
}
// Verify the *numeric* value was correctly parsed from hex entry (16 decimal)
// by doing arithmetic and checking (even though display is in BIN)
e.SetOperator("+")
e.EnterDigit('1')
e.Equals()
// 16 + 1 = 17. In BIN that's 10001
if got := e.FormatForDisplay(); got != "10001" {
t.Errorf("after hex-committed +1 (display in BIN): want 10001, got %s", got)
}
}