diff --git a/internal/testing/htmltest/script_context_helper.go b/internal/testing/htmltest/script_context_helper.go
new file mode 100644
index 00000000..1f4c7fc6
--- /dev/null
+++ b/internal/testing/htmltest/script_context_helper.go
@@ -0,0 +1,25 @@
+package htmltest
+
+import (
+ "testing"
+
+ "github.com/gost-dom/browser/html"
+)
+
+type ScriptContextHelper struct {
+ html.ScriptContext
+ t testing.TB
+}
+
+func NewScriptContextHelper(t testing.TB, ctx html.ScriptContext) ScriptContextHelper {
+ return ScriptContextHelper{ctx, t}
+}
+
+func (h ScriptContextHelper) MustEval(script string) any {
+ h.t.Helper()
+ res, err := h.Eval(script)
+ if err != nil {
+ h.t.Fatalf("Script error: %v", err)
+ }
+ return res
+}
diff --git a/scripting/internal/js/arguments.go b/scripting/internal/js/arguments.go
index 95198790..a7a11369 100644
--- a/scripting/internal/js/arguments.go
+++ b/scripting/internal/js/arguments.go
@@ -48,8 +48,6 @@ func ConsumeArgument[T, U any](
}
}
-func IsUndefined[T any](v Value[T]) bool { return v == nil || v.IsUndefined() }
-
func ConsumeRestArguments[T, U any](
args CallbackContext[U],
name string,
diff --git a/scripting/internal/js/array.go b/scripting/internal/js/array.go
new file mode 100644
index 00000000..a855f983
--- /dev/null
+++ b/scripting/internal/js/array.go
@@ -0,0 +1,6 @@
+package js
+
+type Array[T any] interface {
+ Value[T]
+ Push(Value[T]) error
+}
diff --git a/scripting/internal/js/callback_context.go b/scripting/internal/js/callback_context.go
index baf140fb..a15ca9f9 100644
--- a/scripting/internal/js/callback_context.go
+++ b/scripting/internal/js/callback_context.go
@@ -169,6 +169,7 @@ type ValueFactory[T any] interface {
NewPromise() Promise[T]
NewString(string) Value[T]
+ NewNumber(float64) Value[T]
NewBoolean(bool) Value[T]
NewObject() Object[T]
NewFunction(Callback[T]) Function[T]
@@ -179,7 +180,7 @@ type ValueFactory[T any] interface {
// NewArray creates a JavaScript array containing the values. If any value
// is nil, it will become undefined in the resulting array.
- NewArray(...Value[T]) Value[T]
+ NewArray(...Value[T]) Array[T]
// NewIterator returns an object implementing the [Iterator protocol]
//
// [Iterator protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol
diff --git a/scripting/internal/js/value.go b/scripting/internal/js/value.go
index a5d54a6a..7790d2e3 100644
--- a/scripting/internal/js/value.go
+++ b/scripting/internal/js/value.go
@@ -1,5 +1,12 @@
package js
+import (
+ "errors"
+ "fmt"
+
+ "github.com/gost-dom/browser/internal/constants"
+)
+
// Value represents a value in JavaScript. Referential equality cannot be used
// to check if to Value instances represent the same value in JavaScript. Use
// StrictEquals to check if two values are equal.
@@ -15,6 +22,7 @@ type Value[T any] interface {
Self() T
String() string
+ Number() float64
Int32() int32
Uint32() uint32
Boolean() bool
@@ -23,9 +31,11 @@ type Value[T any] interface {
IsNull() bool
IsSymbol() bool
IsString() bool
+ IsNumber() bool
IsObject() bool
IsBoolean() bool
IsFunction() bool
+ IsArray() bool
AsFunction() (Function[T], bool)
AsObject() (Object[T], bool)
@@ -33,6 +43,12 @@ type Value[T any] interface {
StrictEquals(Value[T]) bool
}
+// IsNullish returns whether a JavaScript value is null or undefined.
+func IsNullish[T any](v Value[T]) bool { return v == nil || v.IsNull() || v.IsUndefined() }
+
+func IsUndefined[T any](v Value[T]) bool { return v == nil || v.IsUndefined() }
+func IsBoolean[T any](v Value[T]) bool { return v != nil && v.IsBoolean() }
+
type Function[T any] interface {
Value[T]
@@ -100,14 +116,108 @@ type Promise[T any] interface {
Reject(Value[T])
}
-// IsNullish returns whether a JavaScript value is null or undefined.
-func IsNullish[T any](v Value[T]) bool { return v == nil || v.IsNull() || v.IsUndefined() }
-
-func IsBoolean[T any](v Value[T]) bool { return v != nil && v.IsBoolean() }
-
func AsFunction[T any](v Value[T]) (Function[T], bool) {
if IsNullish(v) {
return nil, false
}
return v.AsFunction()
}
+
+func Clone[T any](v Value[T], s Scope[T]) (Value[T], error) {
+ var objects [][2]Value[T]
+ return clone(v, s, &objects)
+}
+
+func clone[T any](v Value[T], s Scope[T], objects *[][2]Value[T]) (Value[T], error) {
+ switch {
+ case v == nil || v.IsUndefined():
+ return s.Undefined(), nil
+ case v.IsNull():
+ return s.Null(), nil
+ case v.IsUndefined():
+ return s.Undefined(), nil
+ case v.IsString():
+ return s.NewString(v.String()), nil
+ case v.IsNumber():
+ return s.NewNumber(v.Number()), nil
+ case v.IsBoolean():
+ return s.NewBoolean(v.Boolean()), nil
+ case v.IsArray():
+ return cloneArray(v, s, objects)
+ case v.IsFunction():
+ //TODO: Use correct error
+ return nil, errors.New("Serialize function")
+ }
+ if o, ok := v.AsObject(); ok {
+ return cloneObject(o, s, objects)
+ }
+ return nil, fmt.Errorf("Unable to clone value: %v", v)
+}
+
+func findKnownValue[T any](o Value[T], knownObjects *[][2]Value[T]) (Value[T], bool) {
+ for _, pair := range *knownObjects {
+ known := pair[0]
+ res := pair[1]
+ if o.StrictEquals(known) {
+ return res, true
+ }
+ }
+ return nil, false
+}
+
+func cloneArray[T any](
+ v Value[T],
+ s Scope[T],
+ knownObjects *[][2]Value[T],
+) (Value[T], error) {
+ if existing, ok := findKnownValue(v, knownObjects); ok {
+ return existing, nil
+ }
+ o, ok := v.AsObject()
+ if !ok {
+ return nil, fmt.Errorf(
+ "Object was an array, but not convertible to object. %w",
+ constants.ErrGostDomBug,
+ )
+ }
+ res := s.NewArray()
+ *knownObjects = append(*knownObjects, [2]Value[T]{v, res})
+
+ for v, err := range Iterate(o) {
+ if err != nil {
+ return nil, err
+ }
+ cloned, err := clone(v, s, knownObjects)
+ if err != nil {
+ return nil, err
+ }
+ res.Push(cloned)
+ }
+ // TODO: Potential bug here, if the array references itself recursively,
+ // this would lead to a stack overflow error.
+ return res, nil
+}
+
+func cloneObject[T any](o Object[T], s Scope[T], knownObjects *[][2]Value[T]) (Value[T], error) {
+ if existing, ok := findKnownValue(o, knownObjects); ok {
+ return existing, nil
+ }
+ res := s.NewObject()
+ *knownObjects = append(*knownObjects, [2]Value[T]{o, res})
+ keys, err := o.Keys()
+ if err != nil {
+ return nil, err
+ }
+ for _, k := range keys {
+ oldV, err := o.Get(k)
+ if err != nil {
+ return nil, err
+ }
+ newV, err := clone(oldV, s, knownObjects)
+ if err != nil {
+ return nil, err
+ }
+ res.Set(k, newV)
+ }
+ return res, nil
+}
diff --git a/scripting/internal/scripttests/engine_suites.go b/scripting/internal/scripttests/engine_suites.go
new file mode 100644
index 00000000..c3d5f688
--- /dev/null
+++ b/scripting/internal/scripttests/engine_suites.go
@@ -0,0 +1,96 @@
+package scripttests
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/gost-dom/browser/html"
+ "github.com/gost-dom/browser/internal/entity"
+ "github.com/gost-dom/browser/internal/testing/htmltest"
+ "github.com/gost-dom/browser/scripting/internal/js"
+ "github.com/stretchr/testify/assert"
+)
+
+// RunScriptEngineSuites runs test suites of the script engine without a
+// predefined global scope. In contrast to [RunSuites] that expect an engine
+// configured for the global scope in the Window realm.
+func RunScriptEngineSuites[T any](t *testing.T, f ScriptEngineFactory[T]) {
+ type Global = entity.Entity
+
+ e := f(js.ConfigurerFunc[T](func(e js.ScriptEngine[T]) {
+ global := e.ConfigureGlobalScope("Global", nil)
+ global.CreateOperation("store", func(ctx js.CallbackContext[T]) (js.Value[T], error) {
+ v, ok := ctx.ConsumeArg()
+ if !ok {
+ return nil, ctx.NewTypeError("Missing argument")
+ }
+ c, err := js.As[entity.Components](ctx.GlobalThis().NativeValue(), nil)
+ if err != nil {
+ return nil, err
+ }
+ entity.SetComponentType(c, v)
+ return nil, nil
+ })
+
+ global.CreateOperation("get", func(ctx js.CallbackContext[T]) (js.Value[T], error) {
+ c, err := js.As[entity.Components](ctx.GlobalThis().NativeValue(), nil)
+ if err != nil {
+ return nil, err
+ }
+ val, ok := entity.ComponentType[js.Value[T]](c)
+ if !ok {
+ return nil, fmt.Errorf("Value missing")
+ }
+
+ res, err := js.Clone(val, ctx)
+ if err != nil {
+ t.Errorf("Clone error: %v", err)
+ }
+ return res, err
+ })
+ }))
+
+ global1 := new(Global)
+ global2 := new(Global)
+
+ c1 := htmltest.NewScriptContextHelper(
+ t,
+ e.NewHost(html.ScriptEngineOptions{}).NewContext(dummyContext{global1, t.Context()}),
+ )
+ c2 := htmltest.NewScriptContextHelper(
+ t,
+ e.NewHost(html.ScriptEngineOptions{}).NewContext(dummyContext{global2, t.Context()}),
+ )
+
+ assert.NoError(t, c1.Run(`
+ const b = {
+ id: "b",
+ }
+ const arr = [1,2,3]
+ const recursiveArray = [1,2,3]
+ recursiveArray.push(recursiveArray)
+ const a = {
+ stringVal: "hello",
+ numberVal: 42.5,
+ trueVal: true,
+ falseVal: false,
+ b1: b,
+ b2: b,
+ arr1: arr,
+ arr2: arr,
+ recursiveArray,
+ }
+ globalThis.store(a)
+ `))
+ val, ok := entity.ComponentType[js.Value[T]](global1)
+ assert.True(t, ok)
+ entity.SetComponentType(global2, val)
+
+ assert.NoError(t, c2.Run("const cloned = globalThis.get()"))
+ assert.Equal(t, "hello", c2.MustEval("cloned.stringVal"))
+ assert.Equal(t, 42.5, c2.MustEval("cloned.numberVal"))
+ assert.True(t, c2.MustEval("cloned.trueVal").(bool))
+ assert.False(t, c2.MustEval("cloned.falseVal").(bool))
+ assert.True(t, c2.MustEval("cloned.b1 === cloned.b2").(bool))
+ assert.Equal(t, "1,2,3", c2.MustEval("cloned.arr1.join(',')"))
+}
diff --git a/scripting/internal/scripttests/suites.go b/scripting/internal/scripttests/suites.go
index ea3e5c99..1667518c 100644
--- a/scripting/internal/scripttests/suites.go
+++ b/scripting/internal/scripttests/suites.go
@@ -1,12 +1,17 @@
package scripttests
import (
+ "context"
+ "log/slog"
+ "net/http"
"testing"
"github.com/gost-dom/browser/html"
+ "github.com/gost-dom/browser/internal/entity"
"github.com/gost-dom/browser/internal/testing/browsertest"
"github.com/gost-dom/browser/scripting/internal/dom/domsuite"
"github.com/gost-dom/browser/scripting/internal/html/htmlsuite"
+ "github.com/gost-dom/browser/scripting/internal/js"
"github.com/gost-dom/browser/scripting/internal/uievents/uieventssuite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
@@ -28,9 +33,11 @@ func RunBasicSuite(t *testing.T, e html.ScriptEngine) {
assert.True(t, w.MustEval("window === globalThis").(bool))
}
+type ScriptEngineFactory[T any] = func(js.Configurer[T]) html.ScriptEngine
+
func RunSuites(t *testing.T, e html.ScriptEngine) {
t.Run("ScriptEngineBehaviour", func(t *testing.T) { testScriptEngineBehaviour(t, e) })
- t.Run("SharowRoot", runSuite(NewShadowRootSuite(e)))
+ t.Run("ShadowRoot", runSuite(NewShadowRootSuite(e)))
t.Run("DocumentFragment", runSuite(NewDocumentFragmentSuite(e)))
t.Run("XMLHttpRequest", runSuite(NewXMLHttpRequestSuite(e)))
t.Run("Location", runSuite(NewLocationSuite(e)))
@@ -58,3 +65,13 @@ func RunSuites(t *testing.T, e html.ScriptEngine) {
t.Run("html", func(t *testing.T) { htmlsuite.RunHtmlSuite(t, e) })
t.Run("dom", func(t *testing.T) { domsuite.RunDomSuite(t, e) })
}
+
+type dummyContext struct {
+ *entity.Entity
+ ctx context.Context
+}
+
+func (c dummyContext) Context() context.Context { return c.ctx }
+func (c dummyContext) HTTPClient() http.Client { return *http.DefaultClient }
+func (c dummyContext) LocationHREF() string { return "http://example.com" }
+func (c dummyContext) Logger() *slog.Logger { return nil }
diff --git a/scripting/sobekengine/array.go b/scripting/sobekengine/array.go
new file mode 100644
index 00000000..4a249547
--- /dev/null
+++ b/scripting/sobekengine/array.go
@@ -0,0 +1,31 @@
+package sobekengine
+
+import (
+ "errors"
+
+ "github.com/grafana/sobek"
+)
+
+type array struct {
+ value
+ obj *sobek.Object
+}
+
+func newArray(c *scriptContext, o *sobek.Object) jsArray {
+ return array{value{c, o}, o}
+}
+
+func (a array) Push(v jsValue) error {
+ push := a.obj.Get("push")
+ if push == nil {
+ return errors.New("gost-dom/sobekengine: array.push: Underlying object doesn't have a push")
+ }
+ p, ok := sobek.AssertFunction(push)
+ if !ok {
+ return errors.New(
+ "gost-dom/sobekengine: array.push: Underlying object's push is not a function",
+ )
+ }
+ _, err := p(a.obj, v.Self().value)
+ return err
+}
diff --git a/scripting/sobekengine/scope.go b/scripting/sobekengine/scope.go
index e19c04d0..0f750ef9 100644
--- a/scripting/sobekengine/scope.go
+++ b/scripting/sobekengine/scope.go
@@ -64,12 +64,12 @@ func (f scope) JSONStringify(v js.Value[jsTypeParam]) string {
panic(fmt.Sprintf("gost-dom/sobekhost: JSONStringify only supports objects. Got: %v", v))
}
-func (f scope) NewArray(v ...js.Value[jsTypeParam]) js.Value[jsTypeParam] {
+func (f scope) NewArray(v ...js.Value[jsTypeParam]) jsArray {
arr := make([]any, len(v))
for i, val := range v {
arr[i] = unwrapValue(val)
}
- return newObject(f.scriptContext, f.vm.NewArray(arr...))
+ return newArray(f.scriptContext, f.vm.NewArray(arr...))
}
func (f scope) NewBoolean(v bool) js.Value[jsTypeParam] {
@@ -100,6 +100,10 @@ func (f scope) NewString(v string) js.Value[jsTypeParam] {
return newValue(f.scriptContext, f.vm.ToValue(v))
}
+func (f scope) NewNumber(v float64) js.Value[jsTypeParam] {
+ return newValue(f.scriptContext, f.vm.ToValue(v))
+}
+
// NewTypeError implements [js.ValueFactory].
func (c scope) NewTypeError(v string) js.Error[jsTypeParam] {
sobekErrVal := c.vm.NewTypeError(v)
diff --git a/scripting/sobekengine/script_context.go b/scripting/sobekengine/script_context.go
index 87a80bd6..1f5038a9 100644
--- a/scripting/sobekengine/script_context.go
+++ b/scripting/sobekengine/script_context.go
@@ -371,7 +371,17 @@ func (c *scriptContext) typeOf(v value) string {
panic(err)
}
return res.String()
+}
+func (c *scriptContext) isArray(v value) bool {
+ vm := c.vm
+ fn, _ := vm.RunString("x => Array.isArray(x)")
+ fnn, _ := sobek.AssertFunction(fn)
+ res, err := fnn(vm.GlobalObject(), v.value)
+ if err != nil {
+ panic(err)
+ }
+ return res.ToBoolean()
}
/* -------- script -------- */
diff --git a/scripting/sobekengine/sobek_test.go b/scripting/sobekengine/sobek_test.go
index 8e19f6ae..b4e414dd 100644
--- a/scripting/sobekengine/sobek_test.go
+++ b/scripting/sobekengine/sobek_test.go
@@ -3,7 +3,9 @@ package sobekengine
import (
"testing"
+ "github.com/gost-dom/browser/html"
"github.com/gost-dom/browser/scripting/internal"
+ "github.com/gost-dom/browser/scripting/internal/js"
"github.com/gost-dom/browser/scripting/internal/scripttests"
"github.com/gost-dom/browser/scripting/internal/testing/jsassert"
)
@@ -35,6 +37,12 @@ func TestBasics(t *testing.T) {
scripttests.RunBasicSuite(t, assertEngine)
}
+func TestSobekEngine(t *testing.T) {
+ scripttests.RunScriptEngineSuites(t,
+ func(c js.Configurer[jsTypeParam]) html.ScriptEngine { return newEngine(c) },
+ )
+}
+
func init() {
configurer := internal.CreateWindowsConfigurer[jsTypeParam]()
configurer.AddConfigurerFunc(jsassert.Configure)
diff --git a/scripting/sobekengine/value.go b/scripting/sobekengine/value.go
index 41752b2f..97086a68 100644
--- a/scripting/sobekengine/value.go
+++ b/scripting/sobekengine/value.go
@@ -8,6 +8,7 @@ import (
type jsTypeParam = value
type jsValue = js.Value[jsTypeParam]
type jsObject = js.Object[jsTypeParam]
+type jsArray = js.Array[jsTypeParam]
type jsFunction = js.Function[jsTypeParam]
type jsError = js.Error[jsTypeParam]
@@ -40,16 +41,19 @@ func (v value) AsFunction() (js.Function[jsTypeParam], bool) {
}
func (v value) AsObject() (jsObject, bool) {
- if o := v.value.ToObject(v.ctx.vm); o != nil {
- return newObject(v.ctx, o), true
+ if !v.IsObject() {
+ return nil, false
}
- return nil, false
+ o := v.value.ToObject(v.ctx.vm)
+ return newObject(v.ctx, o), true
}
func (v value) IsNull() bool { return sobek.IsNull(v.value) }
func (v value) IsUndefined() bool { return sobek.IsUndefined(v.value) }
func (v value) IsString() bool { return sobek.IsString(v.value) }
+func (v value) IsNumber() bool { return sobek.IsNumber(v.value) }
+func (v value) IsArray() bool { return v.ctx.isArray(v) }
func (v value) IsBoolean() bool {
// Sobek doesn't expose an IsBoolean function, so resort to calling 'typeof'
@@ -80,6 +84,9 @@ func (v value) IsFunction() bool {
}
func (v value) String() string { return v.value.String() }
+
+func (v value) Number() float64 { return v.value.ToFloat() }
+
func (v value) Boolean() bool { return v.value.ToBoolean() }
func (v value) Int32() int32 { return int32(v.value.ToInteger()) }
func (v value) Uint32() uint32 { return uint32(v.value.ToInteger()) }
diff --git a/scripting/v8engine/array.go b/scripting/v8engine/array.go
new file mode 100644
index 00000000..18ae7697
--- /dev/null
+++ b/scripting/v8engine/array.go
@@ -0,0 +1,25 @@
+package v8engine
+
+import "github.com/gost-dom/v8go"
+
+type v8Array struct {
+ v8Value
+ Object *v8go.Object
+}
+
+func newV8Array(ctx *V8ScriptContext, o *v8go.Object) jsArray {
+ return &v8Array{v8Value{ctx, o.Value}, o}
+}
+
+func (a *v8Array) Push(v jsValue) error {
+ push, err := a.Object.Get("push")
+ if err != nil {
+ return err
+ }
+ f, err := push.AsFunction()
+ if err != nil {
+ return err
+ }
+ _, err = f.Call(toV8Value(&a.v8Value), toV8Value(v))
+ return err
+}
diff --git a/scripting/v8engine/callback_context.go b/scripting/v8engine/callback_context.go
index b3307b72..028e0126 100644
--- a/scripting/v8engine/callback_context.go
+++ b/scripting/v8engine/callback_context.go
@@ -116,7 +116,8 @@ func (f v8Scope) iso() *v8go.Isolate { return f.host.iso }
func (f v8Scope) Undefined() jsValue { return f.toJSValue(v8go.Undefined(f.iso())) }
func (f v8Scope) Null() jsValue { return f.toJSValue(v8go.Null(f.iso())) }
-func (f v8Scope) NewString(val string) jsValue { return f.newV8Value(val) }
+func (f v8Scope) NewString(val string) jsValue { return f.newV8Value(val) }
+func (f v8Scope) NewNumber(val float64) jsValue { return f.newV8Value(val) }
func (f v8Scope) NewObject() jsObject {
val, err := f.V8ScriptContext.v8ctx.RunScript("({})", "gost-dom/object")
@@ -196,7 +197,7 @@ func (f v8Scope) JSONParse(val string) (jsValue, error) {
}
-func (f v8Scope) NewArray(values ...jsValue) jsValue {
+func (f v8Scope) NewArray(values ...jsValue) jsArray {
// Total hack, v8go doesn't expose Array values, so we polyfill the engine
var err error
arrayOf, err := f.v8ctx.RunScript("Array.of", "gost-polyfills-array")
@@ -209,7 +210,11 @@ func (f v8Scope) NewArray(values ...jsValue) jsValue {
if err != nil {
panic(err)
}
- return res
+ obj, ok := res.AsObject()
+ if !ok {
+ panic("not ok")
+ }
+ return newV8Array(f.V8ScriptContext, obj.(*v8Object).Object)
} else {
panic("Array.of is not a function")
}
diff --git a/scripting/v8engine/script.go b/scripting/v8engine/script.go
index d029a274..7429b3c7 100644
--- a/scripting/v8engine/script.go
+++ b/scripting/v8engine/script.go
@@ -26,22 +26,20 @@ func (s V8Script) Eval() (any, error) {
}
func v8ValueToGoValue(result *v8go.Value) (any, error) {
- if result == nil {
+ switch {
+ case result == nil:
return nil, nil
- }
- if result.IsBoolean() {
+ case result.IsBoolean():
return result.Boolean(), nil
- }
- if result.IsInt32() {
+ case result.IsInt32():
return result.Int32(), nil
- }
- if result.IsString() {
+ case result.IsNumber():
+ return result.Number(), nil
+ case result.IsString():
return result.String(), nil
- }
- if result.IsNull() {
+ case result.IsNull():
return nil, nil
- }
- if result.IsUndefined() {
+ case result.IsUndefined():
return nil, nil
}
if o, err := result.AsObject(); err == nil {
diff --git a/scripting/v8engine/script_host_test.go b/scripting/v8engine/script_host_test.go
index b41072e8..430c722b 100644
--- a/scripting/v8engine/script_host_test.go
+++ b/scripting/v8engine/script_host_test.go
@@ -1,10 +1,16 @@
package v8engine
import (
+ "context"
+ "log/slog"
+ "net/http"
"testing"
+ "github.com/gost-dom/browser/html"
+ "github.com/gost-dom/browser/internal/entity"
"github.com/gost-dom/browser/internal/testing/browsertest"
. "github.com/gost-dom/browser/internal/testing/gomega-matchers"
+ "github.com/gost-dom/browser/scripting/internal/js"
"github.com/gost-dom/browser/scripting/internal/scripttests"
"github.com/onsi/gomega"
)
@@ -27,3 +33,19 @@ func TestScriptHostDocumentScriptLoading(t *testing.T) {
func TestBasics(t *testing.T) {
scripttests.RunBasicSuite(t, assertEngine)
}
+
+type dummyContext struct {
+ *entity.Entity
+ ctx context.Context
+}
+
+func (c dummyContext) Context() context.Context { return c.ctx }
+func (c dummyContext) HTTPClient() http.Client { return *http.DefaultClient }
+func (c dummyContext) LocationHREF() string { return "http://example.com" }
+func (c dummyContext) Logger() *slog.Logger { return nil }
+
+func TestV8Engine(t *testing.T) {
+ scripttests.RunScriptEngineSuites(t,
+ func(c js.Configurer[jsTypeParam]) html.ScriptEngine { return newEngine(c) },
+ )
+}
diff --git a/scripting/v8engine/value.go b/scripting/v8engine/value.go
index b9278b0a..cf6d50e6 100644
--- a/scripting/v8engine/value.go
+++ b/scripting/v8engine/value.go
@@ -14,6 +14,7 @@ type jsValue = js.Value[*v8Value]
type jsClass = js.Class[*v8Value]
type jsFunction = js.Function[*v8Value]
type jsObject = js.Object[*v8Value]
+type jsArray = js.Array[*v8Value]
type jsError = js.Error[*v8Value]
func toV8Value(v jsValue) *v8go.Value {
@@ -54,17 +55,20 @@ func (v *v8Value) v8Value() *v8go.Value {
return v.Value
}
-func (v v8Value) String() string { return v.Value.String() }
-func (v v8Value) Int32() int32 { return v.Value.Int32() }
-func (v v8Value) Uint32() uint32 { return v.Value.Uint32() }
-func (v v8Value) Boolean() bool { return v.Value.Boolean() }
+func (v v8Value) String() string { return v.Value.String() }
+func (v v8Value) Number() float64 { return v.Value.Number() }
+func (v v8Value) Int32() int32 { return v.Value.Int32() }
+func (v v8Value) Uint32() uint32 { return v.Value.Uint32() }
+func (v v8Value) Boolean() bool { return v.Value.Boolean() }
func (v v8Value) IsUndefined() bool { return v.Value == nil || v.Value.IsUndefined() }
func (v v8Value) IsNull() bool { return v.Value.IsNull() }
func (v v8Value) IsBoolean() bool { return v.Value.IsBoolean() }
func (v v8Value) IsString() bool { return v.Value.IsString() }
+func (v v8Value) IsNumber() bool { return v.Value.IsNumber() }
func (v v8Value) IsSymbol() bool { return v.Value.IsSymbol() }
func (v v8Value) IsObject() bool { return v.Value.IsObject() }
+func (v v8Value) IsArray() bool { return v.Value.IsArray() }
func (v v8Value) IsFunction() bool { return v.Value.IsFunction() }
func (v v8Value) StrictEquals(