- float64 decimal math + classic entry buffer (in-progress digits visible) - CycleBase() with IsInteger() (epsilon) + ErrConversionNotPossible (CERR) - FormatForDisplay() for DEC/HEX/BIN/OCT (integer formatting for non-DEC) - Basic ops: + - * / and MOD (math.Mod) - Equals, SetOperator, EnterDigit/DecimalPoint, ClearEntry, AllClear, ChangeSign, Backspace - Comprehensive tests covering the critical CERR path (23/6, 1/3 + BASE) plus arithmetic and MOD This completes the pure engine. The engine is now usable by the upcoming TUI spike. Paper trail updated.
133 lines
2.7 KiB
Go
133 lines
2.7 KiB
Go
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.
|