package goja import ( "fmt" "reflect" "strings" "testing" ) func TestDefineProperty(t *testing.T) { r := New() o := r.NewObject() err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE) if err != nil { t.Fatal(err) } err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int { return 1 }), nil, FLAG_TRUE, FLAG_TRUE) if err != nil { t.Fatal(err) } err = o.DefineAccessorProperty("accessor_rw", r.ToValue(func(call FunctionCall) Value { return o.Get("__hidden") }), r.ToValue(func(call FunctionCall) (ret Value) { o.Set("__hidden", call.Argument(0)) return }), FLAG_TRUE, FLAG_TRUE) if err != nil { t.Fatal(err) } if v := o.Get("accessor_ro"); v.ToInteger() != 1 { t.Fatalf("Unexpected accessor value: %v", v) } err = o.Set("accessor_ro", r.ToValue(2)) if err == nil { t.Fatal("Expected an error") } if ex, ok := err.(*Exception); ok { if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" { t.Fatalf("Unexpected error: '%s'", msg) } } else { t.Fatalf("Unexected error type: %T", err) } err = o.Set("accessor_rw", 42) if err != nil { t.Fatal(err) } if v := o.Get("accessor_rw"); v.ToInteger() != 42 { t.Fatalf("Unexpected value: %v", v) } } func TestPropertyOrder(t *testing.T) { const SCRIPT = ` var o = {}; var sym1 = Symbol(1); var sym2 = Symbol(2); o[sym2] = 1; o[4294967294] = 1; o[2] = 1; o[1] = 1; o[0] = 1; o["02"] = 1; o[4294967295] = 1; o["01"] = 1; o["00"] = 1; o[sym1] = 1; var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1]; var actual = Reflect.ownKeys(o); if (actual.length !== expected.length) { throw new Error("Unexpected length: "+actual.length); } for (var i = 0; i < actual.length; i++) { if (actual[i] !== expected[i]) { throw new Error("Unexpected list: " + actual); } } ` testScript(SCRIPT, _undefined, t) } func TestDefinePropertiesSymbol(t *testing.T) { const SCRIPT = ` var desc = {}; desc[Symbol.toStringTag] = {value: "Test"}; var o = {}; Object.defineProperties(o, desc); o[Symbol.toStringTag] === "Test"; ` testScript(SCRIPT, valueTrue, t) } func TestObjectShorthandProperties(t *testing.T) { const SCRIPT = ` var b = 1; var a = {b, get() {return "c"}}; assert.sameValue(a.b, b, "#1"); assert.sameValue(a.get(), "c", "#2"); var obj = { w\u0069th() { return 42; } }; assert.sameValue(obj['with'](), 42, 'property exists'); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestObjectAssign(t *testing.T) { const SCRIPT = ` assert.sameValue(Object.assign({ b: 1 }, { get a() { Object.defineProperty(this, "b", { value: 3, enumerable: false }); }, b: 2 }).b, 1, "#1"); assert.sameValue(Object.assign({ b: 1 }, { get a() { delete this.b; }, b: 2 }).b, 1, "#2"); ` testScriptWithTestLib(SCRIPT, _undefined, t) } func TestExportCircular(t *testing.T) { vm := New() o := vm.NewObject() o.Set("o", o) v := o.Export() if m, ok := v.(map[string]interface{}); ok { if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() { t.Fatal("Unexpected value") } } else { t.Fatal("Unexpected type") } res, err := vm.RunString(`var a = []; a[0] = a;`) if err != nil { t.Fatal(err) } v = res.Export() if a, ok := v.([]interface{}); ok { if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() { t.Fatal("Unexpected value") } } else { t.Fatal("Unexpected type") } } type test_s struct { S *test_s1 } type test_s1 struct { S *test_s } func TestExportToCircular(t *testing.T) { vm := New() o := vm.NewObject() o.Set("o", o) var m map[string]interface{} err := vm.ExportTo(o, &m) if err != nil { t.Fatal(err) } type K string type T map[K]T var m1 T err = vm.ExportTo(o, &m1) if err != nil { t.Fatal(err) } type A []A var a A res, err := vm.RunString("var a = []; a[0] = a;") if err != nil { t.Fatal(err) } err = vm.ExportTo(res, &a) if err != nil { t.Fatal(err) } if &a[0] != &a[0][0] { t.Fatal("values do not match") } o = vm.NewObject() o.Set("S", o) var s test_s err = vm.ExportTo(o, &s) if err != nil { t.Fatal(err) } if s.S.S != &s { t.Fatalf("values do not match: %v, %v", s.S.S, &s) } type test_s2 struct { S interface{} S1 *test_s2 } var s2 test_s2 o.Set("S1", o) err = vm.ExportTo(o, &s2) if err != nil { t.Fatal(err) } if m, ok := s2.S.(map[string]interface{}); ok { if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() { t.Fatal("Unexpected m.S") } } else { t.Fatalf("Unexpected s2.S type: %T", s2.S) } if s2.S1 != &s2 { t.Fatal("Unexpected s2.S1") } o1 := vm.NewObject() o1.Set("S", o) o1.Set("S1", o) err = vm.ExportTo(o1, &s2) if err != nil { t.Fatal(err) } if s2.S1.S1 != s2.S1 { t.Fatal("Unexpected s2.S1.S1") } } func TestExportWrappedMap(t *testing.T) { vm := New() m := map[string]interface{}{ "test": "failed", } exported := vm.ToValue(m).Export() if exportedMap, ok := exported.(map[string]interface{}); ok { exportedMap["test"] = "passed" if v := m["test"]; v != "passed" { t.Fatalf("Unexpected m[\"test\"]: %v", v) } } else { t.Fatalf("Unexpected export type: %T", exported) } } func TestExportToWrappedMap(t *testing.T) { vm := New() m := map[string]interface{}{ "test": "failed", } var exported map[string]interface{} err := vm.ExportTo(vm.ToValue(m), &exported) if err != nil { t.Fatal(err) } exported["test"] = "passed" if v := m["test"]; v != "passed" { t.Fatalf("Unexpected m[\"test\"]: %v", v) } } func TestExportToWrappedMapCustom(t *testing.T) { type CustomMap map[string]bool vm := New() m := CustomMap{} var exported CustomMap err := vm.ExportTo(vm.ToValue(m), &exported) if err != nil { t.Fatal(err) } exported["test"] = true if v := m["test"]; v != true { t.Fatalf("Unexpected m[\"test\"]: %v", v) } } func TestExportToSliceNonIterable(t *testing.T) { vm := New() o := vm.NewObject() var a []interface{} err := vm.ExportTo(o, &a) if err == nil { t.Fatal("Expected an error") } if len(a) != 0 { t.Fatalf("a: %v", a) } if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" { t.Fatalf("Unexpected error: %v", err) } } func ExampleRuntime_ExportTo_iterableToSlice() { vm := New() v, err := vm.RunString(` function reverseIterator() { const arr = this; let idx = arr.length; return { next: () => idx > 0 ? {value: arr[--idx]} : {done: true} } } const arr = [1,2,3]; arr[Symbol.iterator] = reverseIterator; arr; `) if err != nil { panic(err) } var arr []int err = vm.ExportTo(v, &arr) if err != nil { panic(err) } fmt.Println(arr) // Output: [3 2 1] } func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) { vm := New() v, err := vm.RunString(` function reverseIterator() { const arr = this; let idx = arr.length; return { next: () => idx > 0 ? {value: arr[--idx]} : {done: true} } } const arr = [1,2,3]; arr[Symbol.iterator] = reverseIterator; new Proxy(arr, {}); `) if err != nil { t.Fatal(err) } var arr []int err = vm.ExportTo(v, &arr) if err != nil { t.Fatal(err) } if out := fmt.Sprint(arr); out != "[3 2 1]" { t.Fatal(out) } } func ExampleRuntime_ExportTo_arrayLikeToSlice() { vm := New() v, err := vm.RunString(` ({ length: 3, 0: 1, 1: 2, 2: 3 }); `) if err != nil { panic(err) } var arr []int err = vm.ExportTo(v, &arr) if err != nil { panic(err) } fmt.Println(arr) // Output: [1 2 3] } func TestExportArrayToArrayMismatchedLengths(t *testing.T) { vm := New() a := vm.NewArray(1, 2) var a1 [3]int err := vm.ExportTo(a, &a1) if err == nil { t.Fatal("expected error") } if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { t.Fatalf("unexpected error: %v", err) } } func TestExportIterableToArrayMismatchedLengths(t *testing.T) { vm := New() a, err := vm.RunString(` new Map([[1, true], [2, true]]); `) if err != nil { t.Fatal(err) } var a1 [3]interface{} err = vm.ExportTo(a, &a1) if err == nil { t.Fatal("expected error") } if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { t.Fatalf("unexpected error: %v", err) } } func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) { vm := New() a, err := vm.RunString(` ({ length: 2, 0: true, 1: true }); `) if err != nil { t.Fatal(err) } var a1 [3]interface{} err = vm.ExportTo(a, &a1) if err == nil { t.Fatal("expected error") } if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { t.Fatalf("unexpected error: %v", err) } } func TestSetForeignReturnValue(t *testing.T) { const SCRIPT = ` var array = [1, 2, 3]; var arrayTarget = new Proxy(array, {}); Object.preventExtensions(array); !Reflect.set(arrayTarget, "foo", 2); ` testScript(SCRIPT, valueTrue, t) } func TestDefinePropertiesUndefinedVal(t *testing.T) { const SCRIPT = ` var target = {}; var sym = Symbol(); target[sym] = 1; target.foo = 2; target[0] = 3; var getOwnKeys = []; var proxy = new Proxy(target, { getOwnPropertyDescriptor: function(_target, key) { getOwnKeys.push(key); }, }); Object.defineProperties({}, proxy); true; ` testScript(SCRIPT, valueTrue, t) } func ExampleObject_Delete() { vm := New() obj := vm.NewObject() _ = obj.Set("test", true) before := obj.Get("test") _ = obj.Delete("test") after := obj.Get("test") fmt.Printf("before: %v, after: %v", before, after) // Output: before: true, after: } func ExampleObject_GetOwnPropertyNames() { vm := New() obj := vm.NewObject() obj.DefineDataProperty("test", vm.ToValue(true), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE) // define a non-enumerable property that Keys() would not return fmt.Print(obj.GetOwnPropertyNames()) // Output: [test] } func TestObjectEquality(t *testing.T) { type CustomInt int type S struct { F CustomInt } vm := New() vm.Set("s", S{}) // indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively), // but for objects they must behave the same. De-referencing s.F creates a new wrapper each time. vm.testScriptWithTestLib(` assert.sameValue([s.F].indexOf(s.F), 0, "indexOf"); assert([s.F].includes(s.F)); `, _undefined, t) } func BenchmarkPut(b *testing.B) { v := &Object{} o := &baseObject{ val: v, extensible: true, } v.self = o o.init() var key Value = asciiString("test") var val Value = valueInt(123) for i := 0; i < b.N; i++ { v.setOwn(key, val, false) } } func BenchmarkPutStr(b *testing.B) { v := &Object{} o := &baseObject{ val: v, extensible: true, } o.init() v.self = o var val Value = valueInt(123) for i := 0; i < b.N; i++ { o.setOwnStr("test", val, false) } } func BenchmarkGet(b *testing.B) { v := &Object{} o := &baseObject{ val: v, extensible: true, } o.init() v.self = o var n Value = asciiString("test") for i := 0; i < b.N; i++ { v.get(n, nil) } } func BenchmarkGetStr(b *testing.B) { v := &Object{} o := &baseObject{ val: v, extensible: true, } v.self = o o.init() for i := 0; i < b.N; i++ { o.getStr("test", nil) } } func __toString(v Value) string { switch v := v.(type) { case asciiString: return string(v) default: return "" } } func BenchmarkToString1(b *testing.B) { v := asciiString("test") for i := 0; i < b.N; i++ { v.toString() } } func BenchmarkToString2(b *testing.B) { v := asciiString("test") for i := 0; i < b.N; i++ { __toString(v) } } func BenchmarkConv(b *testing.B) { count := int64(0) for i := 0; i < b.N; i++ { count += valueInt(123).ToInteger() } if count == 0 { b.Fatal("zero") } } func BenchmarkToUTF8String(b *testing.B) { var s String = asciiString("test") for i := 0; i < b.N; i++ { _ = s.String() } } func BenchmarkAdd(b *testing.B) { var x, y Value x = valueInt(2) y = valueInt(2) for i := 0; i < b.N; i++ { if xi, ok := x.(valueInt); ok { if yi, ok := y.(valueInt); ok { x = xi + yi } } } } func BenchmarkAddString(b *testing.B) { var x, y Value tst := asciiString("22") x = asciiString("2") y = asciiString("2") for i := 0; i < b.N; i++ { var z Value if xi, ok := x.(String); ok { if yi, ok := y.(String); ok { z = xi.Concat(yi) } } if !z.StrictEquals(tst) { b.Fatalf("Unexpected result %v", x) } } }