// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package hbrt import "testing" func TestClassCreateAndInstantiate(t *testing.T) { cls := NewClassDef("Person"). AddData("CNAME", MakeString("")). AddData("NAGE", MakeInt(0)). Register() obj := NewObject(cls) if !obj.IsObject() { t.Fatal("should be object") } arr := obj.AsArray() if arr.Class != cls { t.Errorf("class = %d, want %d", arr.Class, cls) } if len(arr.Items) != 2 { t.Fatalf("fields = %d, want 2", len(arr.Items)) } // Default values if arr.Items[0].AsString() != "" { t.Errorf("cName default = %q, want empty", arr.Items[0].AsString()) } if arr.Items[1].AsInt() != 0 { t.Errorf("nAge default = %d, want 0", arr.Items[1].AsInt()) } } func TestClassMethodDispatch(t *testing.T) { cls := NewClassDef("Person") cls.AddData("CNAME", MakeString("")) cls.AddData("NAGE", MakeInt(0)) // METHOD New(cName, nAge) cls.AddMethod("NEW", func(th *Thread) { th.Frame(2, 0) defer th.EndProc() // ::cName := param1 th.PushValue(th.Local(1)) th.SetSelfField("CNAME") // ::nAge := param2 th.PushValue(th.Local(2)) th.SetSelfField("NAGE") // RETURN Self th.PushSelf() th.RetValue() }) // METHOD Greet() → "Hello, I'm " + ::cName cls.AddMethod("GREET", func(th *Thread) { th.Frame(0, 0) defer th.EndProc() th.PushString("Hello, I'm ") th.PushSelfField("CNAME") th.Plus() th.RetValue() }) cls.Register() // Create instance vm := NewVM() th := vm.NewThread() th.Frame(0, 0) // obj := Person():New("Kim", 30) clsID := FindClass("Person").ID obj := NewObject(clsID) th.push(obj) th.PushString("Kim") th.PushInt(30) th.Send("NEW", 2) resultObj := th.pop() // Verify fields were set arr := resultObj.AsArray() if arr.Items[0].AsString() != "Kim" { t.Errorf("cName = %q, want Kim", arr.Items[0].AsString()) } if arr.Items[1].AsInt() != 30 { t.Errorf("nAge = %d, want 30", arr.Items[1].AsInt()) } // Call Greet th.push(resultObj) th.Send("GREET", 0) greeting := th.pop() if greeting.AsString() != "Hello, I'm Kim" { t.Errorf("greet = %q, want %q", greeting.AsString(), "Hello, I'm Kim") } th.EndProc() } func TestClassFieldGetterSetter(t *testing.T) { cls := NewClassDef("Point") cls.AddData("X", MakeInt(0)) cls.AddData("Y", MakeInt(0)) cls.Register() vm := NewVM() th := vm.NewThread() th.Frame(0, 0) obj := NewObject(FindClass("Point").ID) // Getter: obj:X th.push(obj) th.Send("X", 0) if th.pop().AsInt() != 0 { t.Error("X default should be 0") } // Setter: obj:_X := 10 (convention: _FIELDNAME for setter) th.push(obj) th.PushInt(10) th.Send("_X", 1) th.pop() // discard setter result // Verify th.push(obj) th.Send("X", 0) if th.pop().AsInt() != 10 { t.Error("X should be 10 after setter") } th.EndProc() } func TestClassInheritance(t *testing.T) { // Parent: Animal animal := NewClassDef("Animal") animal.AddData("CNAME", MakeString("")) animal.AddMethod("SPEAK", func(th *Thread) { th.Frame(0, 0) defer th.EndProc() th.PushString("...") th.RetValue() }) animal.Register() // Child: Dog INHERIT FROM Animal dog := NewClassDef("Dog") dog.InheritFrom("Animal") // Override SPEAK dog.AddMethod("SPEAK", func(th *Thread) { th.Frame(0, 0) defer th.EndProc() th.PushString("Woof!") th.RetValue() }) // Add new method dog.AddMethod("FETCH", func(th *Thread) { th.Frame(0, 0) defer th.EndProc() th.PushSelfField("CNAME") th.PushString(" fetches the ball!") th.Plus() th.RetValue() }) dog.Register() vm := NewVM() th := vm.NewThread() th.Frame(0, 0) // Create Dog obj := NewObject(FindClass("Dog").ID) arr := obj.AsArray() arr.Items[0] = MakeString("Rex") // set cName // Dog:Speak → "Woof!" (overridden) th.push(obj) th.Send("SPEAK", 0) if th.pop().AsString() != "Woof!" { t.Error("Dog:Speak should be 'Woof!'") } // Dog:Fetch → "Rex fetches the ball!" (new method using inherited field) th.push(obj) th.Send("FETCH", 0) result := th.pop().AsString() if result != "Rex fetches the ball!" { t.Errorf("Dog:Fetch = %q", result) } // Dog has inherited cName from Animal th.push(obj) th.Send("CNAME", 0) if th.pop().AsString() != "Rex" { t.Error("inherited CNAME should be Rex") } th.EndProc() } func TestClassFindAndRegistry(t *testing.T) { // These classes were registered in previous tests if FindClass("Person") == nil { t.Error("Person class should be registered") } if FindClass("PERSON") == nil { t.Error("case-insensitive lookup should work") } if FindClass("NonExistent") != nil { t.Error("non-existent class should return nil") } }